|
""" |
|
وحدة تحليل المستندات - التطبيق الرئيسي |
|
""" |
|
|
|
|
|
import os |
|
import sys |
|
import logging |
|
import base64 |
|
import json |
|
import time |
|
from io import BytesIO |
|
from pathlib import Path |
|
from urllib.parse import urlparse |
|
from tempfile import NamedTemporaryFile |
|
|
|
|
|
import streamlit as st |
|
|
|
|
|
import pandas as pd |
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import plotly.express as px |
|
import plotly.graph_objects as go |
|
import requests |
|
from PIL import Image |
|
|
|
|
|
try: |
|
from .services.text_extractor import TextExtractor |
|
from .services.item_extractor import ItemExtractor |
|
from .services.document_parser import DocumentParser |
|
except ImportError: |
|
try: |
|
from modules.document_analysis.services.text_extractor import TextExtractor |
|
from modules.document_analysis.services.item_extractor import ItemExtractor |
|
from modules.document_analysis.services.document_parser import DocumentParser |
|
except ImportError: |
|
|
|
class TextExtractor: |
|
def __init__(self, config=None): |
|
self.config = config or {} |
|
|
|
def extract_from_pdf(self, file_path): |
|
return "نص مستخرج مؤقت من PDF" |
|
|
|
def extract_from_docx(self, file_path): |
|
return "نص مستخرج مؤقت من DOCX" |
|
|
|
def extract_from_image(self, file_path): |
|
return "نص مستخرج مؤقت من صورة" |
|
|
|
def extract(self, file_path): |
|
_, ext = os.path.splitext(file_path) |
|
ext = ext.lower() |
|
|
|
if ext == '.pdf': |
|
return self.extract_from_pdf(file_path) |
|
elif ext in ('.doc', '.docx'): |
|
return self.extract_from_docx(file_path) |
|
elif ext in ('.jpg', '.jpeg', '.png'): |
|
return self.extract_from_image(file_path) |
|
else: |
|
return "نوع ملف غير مدعوم" |
|
|
|
class ItemExtractor: |
|
def __init__(self, config=None): |
|
self.config = config or {} |
|
|
|
def extract_tables(self, document): |
|
return [{"عنوان": "جدول مؤقت", "بيانات": []}] |
|
|
|
def extract_items(self, document): |
|
return [ |
|
{"رقم البند": "A1", "وصف البند": "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", "الوحدة": "م3", "الكمية": 250.0}, |
|
{"رقم البند": "A2", "وصف البند": "توريد وتركيب حديد التسليح للأساسات", "الوحدة": "طن", "الكمية": 25.0}, |
|
{"رقم البند": "A3", "وصف البند": "أعمال العزل المائي للأساسات", "الوحدة": "م2", "الكمية": 500.0} |
|
] |
|
|
|
class DocumentParser: |
|
def __init__(self, config=None): |
|
self.config = config or {} |
|
|
|
def parse_document(self, file_path): |
|
return { |
|
"metadata": { |
|
"title": "مستند مؤقت", |
|
"author": "غير معروف", |
|
"date": "2024-01-01", |
|
"pages": 10 |
|
}, |
|
"content": "محتوى مؤقت للمستند", |
|
"tables": [], |
|
"items": [] |
|
} |
|
|
|
def extract_metadata(self, file_path): |
|
return { |
|
"title": "مستند مؤقت", |
|
"author": "غير معروف", |
|
"date": "2024-01-01", |
|
"pages": 10 |
|
} |
|
|
|
|
|
class DocumentAnalysisApp: |
|
"""وحدة تحليل المستندات""" |
|
|
|
def __init__(self): |
|
"""تهيئة وحدة تحليل المستندات""" |
|
|
|
|
|
self.text_extractor = TextExtractor() |
|
self.item_extractor = ItemExtractor() |
|
self.document_parser = DocumentParser() |
|
|
|
|
|
if 'analyzed_documents' not in st.session_state: |
|
st.session_state.analyzed_documents = [] |
|
|
|
if 'extracted_items' not in st.session_state: |
|
st.session_state.extracted_items = [] |
|
|
|
|
|
self.temp_dir = Path("temp_documents") |
|
self.temp_dir.mkdir(exist_ok=True) |
|
|
|
def render(self): |
|
"""عرض واجهة وحدة تحليل المستندات""" |
|
|
|
st.markdown("<h1 class='module-title'>وحدة تحليل المستندات</h1>", unsafe_allow_html=True) |
|
|
|
tabs = st.tabs([ |
|
"تحليل المستندات", |
|
"استخراج البنود والكميات", |
|
"تحليل الصور والمخططات", |
|
"مكتبة المستندات", |
|
"الإعدادات" |
|
]) |
|
|
|
with tabs[0]: |
|
self._render_document_analysis_tab() |
|
|
|
with tabs[1]: |
|
self._render_item_extraction_tab() |
|
|
|
with tabs[2]: |
|
self._render_image_analysis_tab() |
|
|
|
with tabs[3]: |
|
self._render_document_library_tab() |
|
|
|
with tabs[4]: |
|
self._render_settings_tab() |
|
|
|
def _render_document_analysis_tab(self): |
|
"""عرض تبويب تحليل المستندات""" |
|
|
|
st.markdown("### تحليل المستندات") |
|
|
|
|
|
uploaded_file = st.file_uploader("رفع مستند للتحليل", type=["pdf", "docx", "txt", "jpg", "jpeg", "png"], key="document_upload") |
|
|
|
if uploaded_file is not None: |
|
|
|
file_path = self._save_uploaded_file(uploaded_file) |
|
|
|
if file_path: |
|
st.success(f"تم رفع الملف: {uploaded_file.name}") |
|
|
|
|
|
file_info = self._get_file_info(file_path) |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.metric("نوع الملف", file_info["type"]) |
|
|
|
with col2: |
|
st.metric("حجم الملف", file_info["size"]) |
|
|
|
with col3: |
|
if "pages" in file_info: |
|
st.metric("عدد الصفحات", file_info["pages"]) |
|
|
|
|
|
analysis_options = st.multiselect( |
|
"اختر خيارات التحليل", |
|
[ |
|
"استخراج النص", |
|
"استخراج الجداول", |
|
"استخراج البنود والكميات", |
|
"استخراج المعلومات الرئيسية", |
|
"تحليل هيكل المستند" |
|
], |
|
default=["استخراج النص", "استخراج البنود والكميات"], |
|
key="analysis_options" |
|
) |
|
|
|
|
|
if st.button("بدء التحليل", key="start_analysis_button"): |
|
with st.spinner("جاري تحليل المستند..."): |
|
|
|
time.sleep(2) |
|
|
|
|
|
analysis_results = {} |
|
|
|
if "استخراج النص" in analysis_options: |
|
analysis_results["text"] = self.text_extractor.extract(file_path) |
|
|
|
if "استخراج الجداول" in analysis_options: |
|
|
|
tables = self.item_extractor.extract_tables(file_path) |
|
analysis_results["tables"] = tables |
|
|
|
if "استخراج البنود والكميات" in analysis_options: |
|
|
|
items = self.item_extractor.extract_items(file_path) |
|
analysis_results["items"] = items |
|
|
|
|
|
st.session_state.extracted_items = items |
|
|
|
if "استخراج المعلومات الرئيسية" in analysis_options: |
|
|
|
metadata = self.document_parser.extract_metadata(file_path) |
|
analysis_results["metadata"] = metadata |
|
|
|
if "تحليل هيكل المستند" in analysis_options: |
|
|
|
structure = { |
|
"sections": [ |
|
{"title": "مقدمة", "level": 1, "page": 1}, |
|
{"title": "نطاق العمل", "level": 1, "page": 2}, |
|
{"title": "المواصفات الفنية", "level": 1, "page": 3}, |
|
{"title": "جدول الكميات", "level": 1, "page": 5}, |
|
{"title": "الشروط الخاصة", "level": 1, "page": 7} |
|
] |
|
} |
|
analysis_results["structure"] = structure |
|
|
|
|
|
st.session_state.analyzed_documents.append({ |
|
"file_name": uploaded_file.name, |
|
"file_path": str(file_path), |
|
"analysis_options": analysis_options, |
|
"results": analysis_results, |
|
"timestamp": time.strftime("%Y-%m-%d %H:%M:%S") |
|
}) |
|
|
|
st.success("تم الانتهاء من تحليل المستند!") |
|
|
|
|
|
self._display_analysis_results(analysis_results) |
|
|
|
|
|
if st.session_state.analyzed_documents: |
|
st.markdown("### سجل التحليلات السابقة") |
|
|
|
for i, doc in enumerate(reversed(st.session_state.analyzed_documents)): |
|
with st.expander(f"{doc['file_name']} ({doc['timestamp']})"): |
|
st.markdown(f"**خيارات التحليل:** {', '.join(doc['analysis_options'])}") |
|
|
|
|
|
self._display_analysis_results(doc['results']) |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
if st.button("إرسال إلى وحدة التسعير", key=f"send_to_pricing_{i}"): |
|
st.success("تم إرسال البيانات إلى وحدة التسعير بنجاح!") |
|
|
|
with col2: |
|
if st.button("تصدير النتائج", key=f"export_results_{i}"): |
|
st.success("تم تصدير النتائج بنجاح!") |
|
|
|
def _render_item_extraction_tab(self): |
|
"""عرض تبويب استخراج البنود والكميات""" |
|
|
|
st.markdown("### استخراج البنود والكميات") |
|
|
|
|
|
if not st.session_state.extracted_items: |
|
st.warning("لا توجد بنود مستخرجة. يرجى تحليل مستند أولاً.") |
|
|
|
|
|
st.markdown("### مثال توضيحي") |
|
|
|
|
|
sample_items = [ |
|
{"رقم البند": "A1", "وصف البند": "توريد وتركيب أعمال الخرسانة المسلحة للأساسات", "الوحدة": "م3", "الكمية": 250.0}, |
|
{"رقم البند": "A2", "وصف البند": "توريد وتركيب حديد التسليح للأساسات", "الوحدة": "طن", "الكمية": 25.0}, |
|
{"رقم البند": "A3", "وصف البند": "أعمال العزل المائي للأساسات", "الوحدة": "م2", "الكمية": 500.0}, |
|
{"رقم البند": "A4", "وصف البند": "أعمال الردم والدك للأساسات", "الوحدة": "م3", "الكمية": 300.0}, |
|
{"رقم البند": "A5", "وصف البند": "توريد وتركيب أعمال الخرسانة المسلحة للأعمدة", "الوحدة": "م3", "الكمية": 120.0} |
|
] |
|
|
|
|
|
items_df = pd.DataFrame(sample_items) |
|
st.dataframe(items_df, use_container_width=True, hide_index=True) |
|
|
|
|
|
if st.button("استخدام البيانات التوضيحية", key="use_sample_data_button"): |
|
st.session_state.extracted_items = sample_items |
|
st.success("تم استخدام البيانات التوضيحية!") |
|
st.rerun() |
|
else: |
|
|
|
items_df = pd.DataFrame(st.session_state.extracted_items) |
|
|
|
|
|
if "سعر الوحدة" not in items_df.columns: |
|
items_df["سعر الوحدة"] = 0.0 |
|
|
|
if "الإجمالي" not in items_df.columns: |
|
items_df["الإجمالي"] = 0.0 |
|
|
|
|
|
st.markdown("### البنود المستخرجة") |
|
edited_df = st.data_editor(items_df, use_container_width=True, hide_index=True, key="items_editor") |
|
|
|
|
|
st.session_state.extracted_items = edited_df.to_dict('records') |
|
|
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
if st.button("إرسال إلى وحدة التسعير", key="send_to_pricing_button"): |
|
|
|
if 'current_pricing' not in st.session_state: |
|
st.session_state.current_pricing = { |
|
'name': "مناقصة جديدة", |
|
'number': "T-" + time.strftime("%Y-%m-%d"), |
|
'client': "", |
|
'location': "", |
|
'method': "التسعير القياسي", |
|
'submission_date': None, |
|
'items': edited_df, |
|
'status': 'جديد', |
|
'created_at': time.strftime("%Y-%m-%d %H:%M:%S") |
|
} |
|
else: |
|
st.session_state.current_pricing['items'] = edited_df |
|
|
|
st.success("تم إرسال البنود إلى وحدة التسعير بنجاح!") |
|
|
|
with col2: |
|
if st.button("تصدير إلى Excel", key="export_to_excel_button"): |
|
st.success("تم تصدير البنود إلى Excel بنجاح!") |
|
|
|
with col3: |
|
if st.button("مسح البنود", key="clear_items_button"): |
|
st.session_state.extracted_items = [] |
|
st.warning("تم مسح البنود!") |
|
st.rerun() |
|
|
|
|
|
st.markdown("### إحصائيات البنود") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.metric("عدد البنود", len(edited_df)) |
|
|
|
with col2: |
|
units_count = edited_df['الوحدة'].value_counts() |
|
most_common_unit = units_count.index[0] if not units_count.empty else "غير متوفر" |
|
st.metric("الوحدة الأكثر استخداماً", most_common_unit) |
|
|
|
with col3: |
|
total_quantity = edited_df['الكمية'].sum() |
|
st.metric("إجمالي الكميات", f"{total_quantity:,.2f}") |
|
|
|
|
|
st.markdown("### توزيع البنود حسب الوحدة") |
|
|
|
units_df = pd.DataFrame(units_count).reset_index() |
|
units_df.columns = ['الوحدة', 'العدد'] |
|
|
|
fig = px.pie( |
|
units_df, |
|
values='العدد', |
|
names='الوحدة', |
|
title='توزيع البنود حسب الوحدة', |
|
hole=0.4 |
|
) |
|
|
|
st.plotly_chart(fig, use_container_width=True) |
|
|
|
def _render_image_analysis_tab(self): |
|
"""عرض تبويب تحليل الصور والمخططات""" |
|
|
|
st.markdown("### تحليل الصور والمخططات") |
|
|
|
|
|
uploaded_image = st.file_uploader("رفع صورة أو مخطط للتحليل", type=["jpg", "jpeg", "png", "tif", "tiff"], key="image_upload") |
|
|
|
if uploaded_image is not None: |
|
|
|
image = Image.open(uploaded_image) |
|
st.image(image, caption=uploaded_image.name, use_column_width=True) |
|
|
|
|
|
analysis_type = st.selectbox( |
|
"نوع التحليل", |
|
[ |
|
"استخراج النص من الصورة", |
|
"تحليل المخططات الهندسية", |
|
"قياس المساحات والأبعاد", |
|
"تحليل مخصص" |
|
], |
|
key="image_analysis_type" |
|
) |
|
|
|
|
|
if st.button("بدء التحليل", key="start_image_analysis_button"): |
|
with st.spinner("جاري تحليل الصورة..."): |
|
|
|
time.sleep(2) |
|
|
|
if analysis_type == "استخراج النص من الصورة": |
|
|
|
extracted_text = "نص مستخرج من الصورة (محاكاة):\n\n" |
|
extracted_text += "مواصفات المشروع:\n" |
|
extracted_text += "- مساحة الأرض: 1000 م2\n" |
|
extracted_text += "- عدد الطوابق: 3\n" |
|
extracted_text += "- ارتفاع المبنى: 12 م\n" |
|
|
|
st.markdown("### النص المستخرج من الصورة") |
|
st.text_area("النص المستخرج", extracted_text, height=200) |
|
|
|
elif analysis_type == "تحليل المخططات الهندسية": |
|
|
|
st.markdown("### نتائج تحليل المخطط الهندسي") |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 6)) |
|
ax.imshow(image) |
|
|
|
|
|
ax.annotate('غرفة المعيشة', xy=(100, 100), xytext=(150, 50), |
|
arrowprops=dict(facecolor='red', shrink=0.05)) |
|
|
|
ax.annotate('المطبخ', xy=(300, 150), xytext=(350, 100), |
|
arrowprops=dict(facecolor='blue', shrink=0.05)) |
|
|
|
ax.annotate('غرفة النوم', xy=(200, 300), xytext=(250, 350), |
|
arrowprops=dict(facecolor='green', shrink=0.05)) |
|
|
|
st.pyplot(fig) |
|
|
|
|
|
st.markdown("### معلومات المخطط") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
st.metric("المساحة الإجمالية", "150 م2") |
|
|
|
with col2: |
|
st.metric("عدد الغرف", "3") |
|
|
|
with col3: |
|
st.metric("عدد الحمامات", "2") |
|
|
|
elif analysis_type == "قياس المساحات والأبعاد": |
|
|
|
st.markdown("### نتائج قياس المساحات والأبعاد") |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(10, 6)) |
|
ax.imshow(image) |
|
|
|
|
|
ax.plot([50, 250], [50, 50], 'r-', linewidth=2) |
|
ax.text(150, 40, '10 م', color='red', fontsize=12, ha='center') |
|
|
|
ax.plot([50, 50], [50, 250], 'b-', linewidth=2) |
|
ax.text(40, 150, '8 م', color='blue', fontsize=12, va='center', rotation=90) |
|
|
|
st.pyplot(fig) |
|
|
|
|
|
measurements = pd.DataFrame({ |
|
'العنصر': ['الطول', 'العرض', 'المساحة', 'المحيط'], |
|
'القيمة': ['10 م', '8 م', '80 م2', '36 م'] |
|
}) |
|
|
|
st.dataframe(measurements, use_container_width=True, hide_index=True) |
|
|
|
else: |
|
st.markdown("### نتائج التحليل المخصص") |
|
st.info("تم تحليل الصورة بنجاح. يمكنك تخصيص التحليل حسب احتياجاتك.") |
|
|
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
if st.button("تصدير نتائج التحليل", key="export_image_analysis_button"): |
|
st.success("تم تصدير نتائج التحليل بنجاح!") |
|
|
|
with col2: |
|
if st.button("إرسال إلى وحدة التسعير", key="send_image_to_pricing_button"): |
|
st.success("تم إرسال نتائج التحليل إلى وحدة التسعير بنجاح!") |
|
|
|
def _render_document_library_tab(self): |
|
"""عرض تبويب مكتبة المستندات""" |
|
|
|
st.markdown("### مكتبة المستندات") |
|
|
|
|
|
if 'document_library' not in st.session_state: |
|
st.session_state.document_library = [ |
|
{ |
|
"id": 1, |
|
"name": "كراسة شروط مشروع توسعة مستشفى الملك فهد", |
|
"type": "PDF", |
|
"size": "5.2 MB", |
|
"pages": 120, |
|
"upload_date": "2024-01-15", |
|
"category": "كراسات الشروط", |
|
"tags": ["صحي", "مستشفى", "توسعة"] |
|
}, |
|
{ |
|
"id": 2, |
|
"name": "جدول كميات صيانة محطات المياه", |
|
"type": "Excel", |
|
"size": "1.8 MB", |
|
"pages": None, |
|
"upload_date": "2024-02-10", |
|
"category": "جداول الكميات", |
|
"tags": ["مياه", "صيانة", "محطات"] |
|
}, |
|
{ |
|
"id": 3, |
|
"name": "مخططات إنشاء مدرسة ثانوية", |
|
"type": "PDF", |
|
"size": "12.5 MB", |
|
"pages": 45, |
|
"upload_date": "2024-02-25", |
|
"category": "مخططات", |
|
"tags": ["تعليم", "مدرسة", "إنشاء"] |
|
}, |
|
{ |
|
"id": 4, |
|
"name": "عقد إنشاء طريق دائري", |
|
"type": "Word", |
|
"size": "0.9 MB", |
|
"pages": 28, |
|
"upload_date": "2024-03-05", |
|
"category": "عقود", |
|
"tags": ["طرق", "إنشاء", "دائري"] |
|
}, |
|
{ |
|
"id": 5, |
|
"name": "تقرير فني لمشروع تطوير شبكة مياه", |
|
"type": "PDF", |
|
"size": "3.7 MB", |
|
"pages": 65, |
|
"upload_date": "2024-03-15", |
|
"category": "تقارير فنية", |
|
"tags": ["مياه", "شبكة", "تطوير"] |
|
} |
|
] |
|
|
|
|
|
search_query = st.text_input("البحث في المكتبة", key="library_search") |
|
|
|
col1, col2, col3 = st.columns(3) |
|
|
|
with col1: |
|
category_filter = st.selectbox( |
|
"تصفية حسب الفئة", |
|
["الكل", "كراسات الشروط", "جداول الكميات", "مخططات", "عقود", "تقارير فنية"], |
|
key="category_filter" |
|
) |
|
|
|
with col2: |
|
type_filter = st.selectbox( |
|
"تصفية حسب النوع", |
|
["الكل", "PDF", "Word", "Excel", "Image"], |
|
key="type_filter" |
|
) |
|
|
|
with col3: |
|
sort_by = st.selectbox( |
|
"ترتيب حسب", |
|
["تاريخ الرفع (الأحدث أولاً)", "تاريخ الرفع (الأقدم أولاً)", "الاسم (أ-ي)", "الاسم (ي-أ)", "الحجم (الأكبر أولاً)", "الحجم (الأصغر أولاً)"], |
|
key="sort_by" |
|
) |
|
|
|
|
|
filtered_documents = st.session_state.document_library.copy() |
|
|
|
|
|
if search_query: |
|
filtered_documents = [doc for doc in filtered_documents if search_query.lower() in doc["name"].lower() or |
|
any(search_query.lower() in tag.lower() for tag in doc["tags"])] |
|
|
|
|
|
if category_filter != "الكل": |
|
filtered_documents = [doc for doc in filtered_documents if doc["category"] == category_filter] |
|
|
|
|
|
if type_filter != "الكل": |
|
filtered_documents = [doc for doc in filtered_documents if doc["type"] == type_filter] |
|
|
|
|
|
if sort_by == "تاريخ الرفع (الأحدث أولاً)": |
|
filtered_documents.sort(key=lambda x: x["upload_date"], reverse=True) |
|
elif sort_by == "تاريخ الرفع (الأقدم أولاً)": |
|
filtered_documents.sort(key=lambda x: x["upload_date"]) |
|
elif sort_by == "الاسم (أ-ي)": |
|
filtered_documents.sort(key=lambda x: x["name"]) |
|
elif sort_by == "الاسم (ي-أ)": |
|
filtered_documents.sort(key=lambda x: x["name"], reverse=True) |
|
elif sort_by == "الحجم (الأكبر أولاً)": |
|
filtered_documents.sort(key=lambda x: float(x["size"].split()[0]), reverse=True) |
|
elif sort_by == "الحجم (الأصغر أولاً)": |
|
filtered_documents.sort(key=lambda x: float(x["size"].split()[0])) |
|
|
|
|
|
st.markdown(f"### المستندات ({len(filtered_documents)})") |
|
|
|
if not filtered_documents: |
|
st.info("لا توجد مستندات تطابق معايير البحث.") |
|
else: |
|
|
|
for i, doc in enumerate(filtered_documents): |
|
with st.container(): |
|
col1, col2, col3 = st.columns([3, 1, 1]) |
|
|
|
with col1: |
|
st.markdown(f"**{doc['name']}**") |
|
st.markdown(f"الفئة: {doc['category']} | النوع: {doc['type']} | الحجم: {doc['size']} | تاريخ الرفع: {doc['upload_date']}") |
|
st.markdown(f"الوسوم: {', '.join(doc['tags'])}") |
|
|
|
with col2: |
|
if st.button("عرض", key=f"view_doc_{i}"): |
|
st.session_state.selected_document = doc |
|
st.success(f"جاري عرض المستند: {doc['name']}") |
|
|
|
with col3: |
|
if st.button("تحليل", key=f"analyze_doc_{i}"): |
|
st.session_state.selected_document = doc |
|
st.success(f"جاري تحليل المستند: {doc['name']}") |
|
|
|
st.markdown("---") |
|
|
|
|
|
st.markdown("### رفع مستند جديد") |
|
|
|
uploaded_file = st.file_uploader("اختر ملفاً للرفع", type=["pdf", "docx", "xlsx", "jpg", "jpeg", "png"], key="library_upload") |
|
|
|
if uploaded_file is not None: |
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
doc_category = st.selectbox( |
|
"فئة المستند", |
|
["كراسات الشروط", "جداول الكميات", "مخططات", "عقود", "تقارير فنية", "أخرى"], |
|
key="doc_category" |
|
) |
|
|
|
with col2: |
|
doc_tags = st.text_input("الوسوم (مفصولة بفواصل)", key="doc_tags") |
|
|
|
if st.button("رفع المستند", key="upload_to_library_button"): |
|
|
|
new_doc = { |
|
"id": len(st.session_state.document_library) + 1, |
|
"name": uploaded_file.name, |
|
"type": uploaded_file.name.split(".")[-1].upper(), |
|
"size": f"{uploaded_file.size / (1024 * 1024):.1f} MB", |
|
"pages": None, |
|
"upload_date": time.strftime("%Y-%m-%d"), |
|
"category": doc_category, |
|
"tags": [tag.strip() for tag in doc_tags.split(",") if tag.strip()] |
|
} |
|
|
|
st.session_state.document_library.append(new_doc) |
|
st.success(f"تم رفع المستند: {uploaded_file.name}") |
|
st.rerun() |
|
|
|
def _render_settings_tab(self): |
|
"""عرض تبويب الإعدادات""" |
|
|
|
st.markdown("### إعدادات تحليل المستندات") |
|
|
|
|
|
with st.expander("إعدادات استخراج النص", expanded=True): |
|
st.markdown("#### إعدادات استخراج النص") |
|
|
|
ocr_engine = st.selectbox( |
|
"محرك التعرف الضوئي على النصوص", |
|
["Tesseract OCR", "Google Cloud Vision", "Amazon Textract", "Microsoft Azure OCR"], |
|
index=0, |
|
key="ocr_engine" |
|
) |
|
|
|
language = st.selectbox( |
|
"لغة المستندات", |
|
["العربية", "الإنجليزية", "العربية والإنجليزية"], |
|
index=0, |
|
key="ocr_language" |
|
) |
|
|
|
dpi = st.slider( |
|
"دقة المسح (DPI)", |
|
min_value=100, |
|
max_value=600, |
|
value=300, |
|
step=50, |
|
key="ocr_dpi" |
|
) |
|
|
|
if st.button("حفظ إعدادات استخراج النص", key="save_ocr_settings"): |
|
st.success("تم حفظ إعدادات استخراج النص بنجاح!") |
|
|
|
|
|
with st.expander("إعدادات استخراج البنود", expanded=True): |
|
st.markdown("#### إعدادات استخراج البنود") |
|
|
|
extraction_method = st.selectbox( |
|
"طريقة استخراج البنود", |
|
["تحليل الجداول", "تحليل النص", "الذكاء الاصطناعي", "مزيج"], |
|
index=3, |
|
key="extraction_method" |
|
) |
|
|
|
auto_detect_units = st.checkbox( |
|
"اكتشاف الوحدات تلقائياً", |
|
value=True, |
|
key="auto_detect_units" |
|
) |
|
|
|
normalize_quantities = st.checkbox( |
|
"توحيد صيغة الكميات", |
|
value=True, |
|
key="normalize_quantities" |
|
) |
|
|
|
if st.button("حفظ إعدادات استخراج البنود", key="save_extraction_settings"): |
|
st.success("تم حفظ إعدادات استخراج البنود بنجاح!") |
|
|
|
|
|
with st.expander("إعدادات تحليل الصور", expanded=True): |
|
st.markdown("#### إعدادات تحليل الصور") |
|
|
|
image_analysis_engine = st.selectbox( |
|
"محرك تحليل الصور", |
|
["OpenCV", "Google Cloud Vision", "Amazon Rekognition", "Microsoft Azure Computer Vision"], |
|
index=0, |
|
key="image_analysis_engine" |
|
) |
|
|
|
image_resolution = st.slider( |
|
"دقة تحليل الصور", |
|
min_value=1, |
|
max_value=10, |
|
value=5, |
|
key="image_resolution" |
|
) |
|
|
|
if st.button("حفظ إعدادات تحليل الصور", key="save_image_analysis_settings"): |
|
st.success("تم حفظ إعدادات تحليل الصور بنجاح!") |
|
|
|
|
|
with st.expander("إعدادات متقدمة", expanded=False): |
|
st.markdown("#### إعدادات متقدمة") |
|
|
|
temp_files_retention = st.slider( |
|
"مدة الاحتفاظ بالملفات المؤقتة (أيام)", |
|
min_value=1, |
|
max_value=30, |
|
value=7, |
|
key="temp_files_retention" |
|
) |
|
|
|
max_file_size = st.slider( |
|
"الحد الأقصى لحجم الملف (ميجابايت)", |
|
min_value=5, |
|
max_value=100, |
|
value=50, |
|
key="max_file_size" |
|
) |
|
|
|
parallel_processing = st.checkbox( |
|
"تفعيل المعالجة المتوازية", |
|
value=True, |
|
key="parallel_processing" |
|
) |
|
|
|
if st.button("حفظ الإعدادات المتقدمة", key="save_advanced_settings"): |
|
st.success("تم حفظ الإعدادات المتقدمة بنجاح!") |
|
|
|
def _save_uploaded_file(self, uploaded_file): |
|
"""حفظ الملف المرفوع في مجلد مؤقت""" |
|
try: |
|
file_path = self.temp_dir / uploaded_file.name |
|
with open(file_path, "wb") as f: |
|
f.write(uploaded_file.getbuffer()) |
|
return file_path |
|
except Exception as e: |
|
st.error(f"حدث خطأ أثناء حفظ الملف: {str(e)}") |
|
return None |
|
|
|
def _get_file_info(self, file_path): |
|
"""الحصول على معلومات الملف""" |
|
file_info = { |
|
"type": file_path.suffix[1:].upper(), |
|
"size": f"{file_path.stat().st_size / (1024 * 1024):.2f} MB" |
|
} |
|
|
|
|
|
if file_path.suffix.lower() == ".pdf": |
|
|
|
file_info["pages"] = 10 |
|
|
|
return file_info |
|
|
|
def _display_analysis_results(self, results): |
|
"""عرض نتائج التحليل""" |
|
|
|
if not results: |
|
st.info("لا توجد نتائج للعرض.") |
|
return |
|
|
|
|
|
if "text" in results: |
|
with st.expander("النص المستخرج", expanded=False): |
|
st.text_area("النص", results["text"], height=200) |
|
|
|
|
|
if "tables" in results and results["tables"]: |
|
with st.expander("الجداول المستخرجة", expanded=True): |
|
for i, table in enumerate(results["tables"]): |
|
st.markdown(f"**جدول {i+1}: {table.get('عنوان', 'بدون عنوان')}**") |
|
|
|
if "بيانات" in table and table["بيانات"]: |
|
|
|
try: |
|
df = pd.DataFrame(table["بيانات"]) |
|
st.dataframe(df, use_container_width=True, hide_index=True) |
|
except Exception: |
|
st.text(str(table["بيانات"])) |
|
else: |
|
st.info("لا توجد بيانات في هذا الجدول.") |
|
|
|
|
|
if "items" in results and results["items"]: |
|
with st.expander("البنود المستخرجة", expanded=True): |
|
items_df = pd.DataFrame(results["items"]) |
|
st.dataframe(items_df, use_container_width=True, hide_index=True) |
|
|
|
|
|
if st.button("إرسال البنود إلى وحدة التسعير", key="send_extracted_items_button"): |
|
st.session_state.extracted_items = results["items"] |
|
st.success("تم إرسال البنود المستخرجة إلى وحدة التسعير!") |
|
|
|
|
|
if "metadata" in results: |
|
with st.expander("المعلومات الرئيسية", expanded=True): |
|
metadata = results["metadata"] |
|
|
|
col1, col2 = st.columns(2) |
|
|
|
with col1: |
|
st.markdown(f"**عنوان المستند:** {metadata.get('title', 'غير متوفر')}") |
|
st.markdown(f"**المؤلف:** {metadata.get('author', 'غير متوفر')}") |
|
|
|
with col2: |
|
st.markdown(f"**التاريخ:** {metadata.get('date', 'غير متوفر')}") |
|
st.markdown(f"**عدد الصفحات:** {metadata.get('pages', 'غير متوفر')}") |
|
|
|
|
|
if "structure" in results and "sections" in results["structure"]: |
|
with st.expander("هيكل المستند", expanded=False): |
|
sections = results["structure"]["sections"] |
|
|
|
for section in sections: |
|
indent = " " * (section["level"] * 4) |
|
st.markdown(f"{indent}• **{section['title']}** (صفحة {section['page']})", unsafe_allow_html=True) |
|
|