diff --git "a/modules/document_comparison/document_comparison_app.py" "b/modules/document_comparison/document_comparison_app.py"
--- "a/modules/document_comparison/document_comparison_app.py"
+++ "b/modules/document_comparison/document_comparison_app.py"
@@ -1,1003 +1,1030 @@
-"""
-وحدة مقارنة المستندات - نظام تحليل المناقصات
-"""
-
-import streamlit as st
-import pandas as pd
-import numpy as np
-import os
-import sys
-from pathlib import Path
-import difflib
-import re
-import datetime
-
-# إضافة مسار المشروع للنظام
-sys.path.append(str(Path(__file__).parent.parent))
-
-# استيراد محسن واجهة المستخدم
-from styling.enhanced_ui import UIEnhancer
-
-class DocumentComparisonApp:
- """تطبيق مقارنة المستندات"""
-
- def __init__(self):
- """تهيئة تطبيق مقارنة المستندات"""
- self.ui = UIEnhancer(page_title="مقارنة المستندات - نظام تحليل المناقصات", page_icon="📄")
- self.ui.apply_theme_colors()
-
- # بيانات المستندات (نموذجية)
- self.documents_data = [
- {
- "id": "DOC001",
- "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
- "type": "كراسة شروط",
- "version": "1.0",
- "date": "2025-01-15",
- "size": 2.4,
- "pages": 45,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/specs_v1.pdf"
- },
- {
- "id": "DOC002",
- "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
- "type": "كراسة شروط",
- "version": "1.1",
- "date": "2025-02-10",
- "size": 2.6,
- "pages": 48,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/specs_v1.1.pdf"
- },
- {
- "id": "DOC003",
- "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
- "type": "كراسة شروط",
- "version": "2.0",
- "date": "2025-03-05",
- "size": 2.8,
- "pages": 52,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/specs_v2.0.pdf"
- },
- {
- "id": "DOC004",
- "name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
- "type": "جدول كميات",
- "version": "1.0",
- "date": "2025-01-15",
- "size": 1.2,
- "pages": 20,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/boq_v1.0.xlsx"
- },
- {
- "id": "DOC005",
- "name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
- "type": "جدول كميات",
- "version": "1.1",
- "date": "2025-02-20",
- "size": 1.3,
- "pages": 22,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/boq_v1.1.xlsx"
- },
- {
- "id": "DOC006",
- "name": "المخططات - مناقصة إنشاء مبنى إداري",
- "type": "مخططات",
- "version": "1.0",
- "date": "2025-01-15",
- "size": 15.6,
- "pages": 30,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/drawings_v1.0.pdf"
- },
- {
- "id": "DOC007",
- "name": "المخططات - مناقصة إنشاء مبنى إداري",
- "type": "مخططات",
- "version": "2.0",
- "date": "2025-03-10",
- "size": 18.2,
- "pages": 35,
- "related_entity": "T-2025-001",
- "path": "/documents/T-2025-001/drawings_v2.0.pdf"
- },
- {
- "id": "DOC008",
- "name": "كراسة الشروط - مناقصة صيانة طرق",
- "type": "كراسة شروط",
- "version": "1.0",
- "date": "2025-02-05",
- "size": 1.8,
- "pages": 32,
- "related_entity": "T-2025-002",
- "path": "/documents/T-2025-002/specs_v1.0.pdf"
- },
- {
- "id": "DOC009",
- "name": "كراسة الشروط - مناقصة صيانة طرق",
- "type": "كراسة شروط",
- "version": "1.1",
- "date": "2025-03-15",
- "size": 1.9,
- "pages": 34,
- "related_entity": "T-2025-002",
- "path": "/documents/T-2025-002/specs_v1.1.pdf"
- },
- {
- "id": "DOC010",
- "name": "جدول الكميات - مناقصة صيانة طرق",
- "type": "جدول كميات",
- "version": "1.0",
- "date": "2025-02-05",
- "size": 0.9,
- "pages": 15,
- "related_entity": "T-2025-002",
- "path": "/documents/T-2025-002/boq_v1.0.xlsx"
- }
- ]
-
- # بيانات نموذجية لمحتوى المستندات (للعرض فقط)
- self.sample_document_content = {
- "DOC001": """
- # كراسة الشروط والمواصفات
- ## مناقصة إنشاء مبنى إداري
-
- ### 1. مقدمة
- تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقدم بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض.
-
- ### 2. نطاق العمل
- يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع، ويشمل ذلك:
- - أعمال الهيكل الإنشائي
- - أعمال التشطيبات الداخلية والخارجية
- - أعمال الكهرباء والميكانيكا
- - أعمال تنسيق الموقع
-
- ### 3. المواصفات الفنية
- #### 3.1 أعمال الخرسانة
- - يجب أن تكون الخرسانة المسلحة بقوة لا تقل عن 30 نيوتن/مم²
- - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
-
- #### 3.2 أعمال التشطيبات
- - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
- - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
-
- ### 4. الشروط العامة
- - مدة التنفيذ: 18 شهراً من تاريخ استلام الموقع
- - غرامة التأخير: 0.1% من قيمة العقد عن كل يوم تأخير
- - ضمان الأعمال: 10 سنوات للهيكل الإنشائي، 5 سنوات للأعمال الميكانيكية والكهربائية
- """,
-
- "DOC002": """
- # كراسة الشروط والمواصفات
- ## مناقصة إنشاء مبنى إداري
-
- ### 1. مقدمة
- تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقدم بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض.
-
- ### 2. نطاق العمل
- يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5500 متر مربع، ويشمل ذلك:
- - أعمال الهيكل الإنشائي
- - أعمال التشطيبات الداخلية والخارجية
- - أعمال الكهرباء والميكانيكا
- - أعمال تنسيق الموقع
- - أعمال أنظمة الأمن والسلامة
-
- ### 3. المواصفات الفنية
- #### 3.1 أعمال الخرسانة
- - يجب أن تكون الخرسانة المسلحة بقوة لا تقل عن 35 نيوتن/مم²
- - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
-
- #### 3.2 أعمال التشطيبات
- - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
- - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
- - يجب استخدام زجاج عاكس للحرارة للواجهات
-
- ### 4. الشروط العامة
- - مدة التنفيذ: 16 شهراً من تاريخ استلام الموقع
- - غرامة التأخير: 0.15% من قيمة العقد عن كل يوم تأخير
- - ضمان الأعمال: 10 سنوات للهيكل الإنشائي، 5 سنوات للأعمال الميكانيكية والكهربائية
- """,
-
- "DOC003": """
- # كراسة الشروط والمواصفات
- ## مناقصة إنشاء مبنى إداري
-
- ### 1. مقدمة
- تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقدم بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض وفقاً للمواصفات المعتمدة من الهيئة السعودية للمواصفات والمقاييس.
-
- ### 2. نطاق العمل
- يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 6 طوابق بمساحة إجمالية 6000 متر مربع، ويشمل ذلك:
- - أعمال الهيكل الإنشائي
- - أعمال التشطيبات الداخلية والخارجية
- - أعمال الكهرباء والميكانيكا
- - أعمال تنسيق الموقع
- - أعمال أنظمة الأمن والسلامة
- - أعمال أنظمة المباني الذكية
-
- ### 3. المواصفات الفنية
- #### 3.1 أعمال الخرسانة
- - يجب أن تكون الخرسانة المسلحة بقوة لا تقل عن 40 نيوتن/مم²
- - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
- - يجب استخدام إضافات للخرسانة لزيادة مقاومتها للعوامل الجوية
-
- #### 3.2 أعمال التشطيبات
- - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
- - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
- - يجب استخدام زجاج عاكس للحرارة للواجهات
- - يجب استخدام مواد صديقة للبيئة
-
- ### 4. الشروط العامة
- - مدة التنفيذ: 15 شهراً من تاريخ استلام الموقع
- - غرامة التأخير: 0.2% من قيمة العقد عن كل يوم تأخير
- - ضمان الأعمال: 15 سنوات للهيكل الإنشائي، 7 سنوات للأعمال الميكانيكية والكهربائية
-
- ### 5. متطلبات الاستدامة
- - يجب أن يحقق المبنى متطلبات الاستدامة وفقاً لمعايير LEED
- - يجب توفير أنظمة لترشيد استهلاك الطاقة والمياه
- """
- }
-
- def run(self):
- """تشغيل تطبيق مقارنة المستندات"""
- # إنشاء قائمة العناصر
- menu_items = [
- {"name": "لوحة المعلومات", "icon": "house"},
- {"name": "المناقصات والعقود", "icon": "file-text"},
- {"name": "تحليل المستندات", "icon": "file-earmark-text"},
- {"name": "نظام التسعير", "icon": "calculator"},
- {"name": "حاسبة تكاليف البناء", "icon": "building"},
- {"name": "الموارد والتكاليف", "icon": "people"},
- {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
- {"name": "إدارة المشاريع", "icon": "kanban"},
- {"name": "الخرائط والمواقع", "icon": "geo-alt"},
- {"name": "الجدول الزمني", "icon": "calendar3"},
- {"name": "الإشعارات", "icon": "bell"},
- {"name": "مقارنة المستندات", "icon": "files"},
- {"name": "المساعد الذكي", "icon": "robot"},
- {"name": "التقارير", "icon": "bar-chart"},
- {"name": "الإعدادات", "icon": "gear"}
- ]
-
- # إنشاء الشريط الجانبي
- selected = self.ui.create_sidebar(menu_items)
-
- # إنشاء ترويسة الصفحة
- self.ui.create_header("مقارنة المستندات", "أدوات متقدمة لمقارنة وتحليل المستندات")
-
- # إنشاء علامات تبويب للوظائف المختلفة
- tabs = st.tabs(["مقارنة الإصدارات", "مقارنة المستندات", "تحليل التغييرات", "سجل التغييرات"])
-
- # علامة تبويب مقارنة الإصدارات
- with tabs[0]:
- self.compare_versions()
-
- # علامة تبويب مقارنة المستندات
- with tabs[1]:
- self.compare_documents()
-
- # علامة تبويب تحليل التغييرات
- with tabs[2]:
- self.analyze_changes()
-
- # علامة تبويب سجل التغييرات
- with tabs[3]:
- self.show_change_history()
-
- def compare_versions(self):
- """مقارنة إصدارات المستندات"""
- st.markdown("### مقارنة إصدارات المستندات")
-
- # اختيار المناقصة
- tender_options = list(set([doc["related_entity"] for doc in self.documents_data]))
- selected_tender = st.selectbox(
- "اختر المناقصة",
- options=tender_options
- )
-
- # فلترة المستندات حسب المناقصة المختارة
- filtered_docs = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender]
-
- # اختيار نوع المستند
- doc_types = list(set([doc["type"] for doc in filtered_docs]))
- selected_type = st.selectbox(
- "اختر نوع المستند",
- options=doc_types
- )
-
- # فلترة المستندات حسب النوع المختار
- type_filtered_docs = [doc for doc in filtered_docs if doc["type"] == selected_type]
-
- # ترتيب المستندات حسب الإصدار
- type_filtered_docs = sorted(type_filtered_docs, key=lambda x: x["version"])
-
- if len(type_filtered_docs) < 2:
- st.warning("يجب توفر إصدارين على الأقل للمقارنة")
- else:
- # اختيار الإصدارات للمقارنة
- col1, col2 = st.columns(2)
-
- with col1:
- version_options = [f"{doc['name']} (الإصدار {doc['version']})" for doc in type_filtered_docs]
- selected_version1_index = st.selectbox(
- "الإصدار الأول",
- options=range(len(version_options)),
- format_func=lambda x: version_options[x]
- )
- selected_doc1 = type_filtered_docs[selected_version1_index]
-
- with col2:
- remaining_indices = [i for i in range(len(type_filtered_docs)) if i != selected_version1_index]
- selected_version2_index = st.selectbox(
- "الإصدار الثاني",
- options=remaining_indices,
- format_func=lambda x: version_options[x]
- )
- selected_doc2 = type_filtered_docs[selected_version2_index]
-
- # زر بدء المقارنة
- if st.button("بدء المقارنة", use_container_width=True):
- # عرض معلومات المستندات المختارة
- st.markdown("### معلومات المستندات المختارة")
-
- col1, col2 = st.columns(2)
-
- with col1:
- st.markdown(f"**الإصدار الأول:** {selected_doc1['version']}")
- st.markdown(f"**التاريخ:** {selected_doc1['date']}")
- st.markdown(f"**عدد الصفحات:** {selected_doc1['pages']}")
- st.markdown(f"**الحجم:** {selected_doc1['size']} ميجابايت")
-
- with col2:
- st.markdown(f"**الإصدار الثاني:** {selected_doc2['version']}")
- st.markdown(f"**التاريخ:** {selected_doc2['date']}")
- st.markdown(f"**عدد الصفحات:** {selected_doc2['pages']}")
- st.markdown(f"**الحجم:** {selected_doc2['size']} ميجابايت")
-
- # الحصول على محتوى المستندات (في تطبيق حقيقي، سيتم استرجاع المحتوى من الملفات الفعلية)
- doc1_content = self.sample_document_content.get(selected_doc1["id"], "محتوى المستند غير متوفر")
- doc2_content = self.sample_document_content.get(selected_doc2["id"], "محتوى المستند غير متوفر")
-
- # إجراء المقارنة
- self.display_comparison(doc1_content, doc2_content)
-
- def display_comparison(self, text1, text2):
- """عرض نتائج المقارنة بين نصين"""
- st.markdown("### نتائج المقارنة")
-
- # تقسيم النصوص إلى أسطر
- lines1 = text1.splitlines()
- lines2 = text2.splitlines()
-
- # إجراء المقارنة باستخدام difflib
- d = difflib.Differ()
- diff = list(d.compare(lines1, lines2))
-
- # عرض ملخص التغييرات
- added = len([line for line in diff if line.startswith('+ ')])
- removed = len([line for line in diff if line.startswith('- ')])
- changed = len([line for line in diff if line.startswith('? ')])
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- self.ui.create_metric_card(
- "الإضافات",
- str(added),
- None,
- self.ui.COLORS['success']
- )
-
- with col2:
- self.ui.create_metric_card(
- "الحذف",
- str(removed),
- None,
- self.ui.COLORS['danger']
- )
-
- with col3:
- self.ui.create_metric_card(
- "التغييرات",
- str(changed // 2), # تقسيم على 2 لأن كل تغيير يظهر مرتين
- None,
- self.ui.COLORS['warning']
- )
-
- # عرض التغييرات بالتفصيل
- st.markdown("### التغييرات بالتفصيل")
-
- # إنشاء عرض HTML للتغييرات
- html_diff = []
- for line in diff:
- if line.startswith('+ '):
- html_diff.append(f'
{line[2:]}
')
- elif line.startswith('- '):
- html_diff.append(f'{line[2:]}
')
- elif line.startswith('? '):
- # تجاهل أسطر التفاصيل
- continue
- else:
- html_diff.append(f'{line[2:]}
')
-
- # عرض التغييرات
- st.markdown(''.join(html_diff), unsafe_allow_html=True)
-
- # خيارات إضافية
- st.markdown("### خيارات إضافية")
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- if st.button("تصدير التغييرات", use_container_width=True):
- st.success("تم تصدير التغييرات بنجاح")
-
- with col2:
- if st.button("إنشاء تقرير", use_container_width=True):
- st.success("تم إنشاء التقرير بنجاح")
-
- with col3:
- if st.button("حفظ المقارنة", use_container_width=True):
- st.success("تم حفظ المقارنة بنجاح")
-
- def compare_documents(self):
- """مقارنة مستندات مختلفة"""
- st.markdown("### مقارنة مستندات مختلفة")
-
- # اختيار المستند الأول
- col1, col2 = st.columns(2)
-
- with col1:
- tender1_options = list(set([doc["related_entity"] for doc in self.documents_data]))
- selected_tender1 = st.selectbox(
- "اختر المناقصة الأولى",
- options=tender1_options,
- key="tender1"
- )
-
- # فلترة المستندات حسب المناقصة المختارة
- filtered_docs1 = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender1]
-
- # اختيار المستند
- doc_options1 = [f"{doc['name']} (الإصدار {doc['version']})" for doc in filtered_docs1]
- selected_doc1_index = st.selectbox(
- "اختر المستند الأول",
- options=range(len(doc_options1)),
- format_func=lambda x: doc_options1[x],
- key="doc1"
- )
- selected_doc1 = filtered_docs1[selected_doc1_index]
-
- with col2:
- tender2_options = list(set([doc["related_entity"] for doc in self.documents_data]))
- selected_tender2 = st.selectbox(
- "اختر المناقصة الثانية",
- options=tender2_options,
- key="tender2"
- )
-
- # فلترة المستندات حسب المناقصة المختارة
- filtered_docs2 = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender2]
-
- # اختيار المستند
- doc_options2 = [f"{doc['name']} (الإصدار {doc['version']})" for doc in filtered_docs2]
- selected_doc2_index = st.selectbox(
- "اختر المستند الثاني",
- options=range(len(doc_options2)),
- format_func=lambda x: doc_options2[x],
- key="doc2"
- )
- selected_doc2 = filtered_docs2[selected_doc2_index]
-
- # خيارات المقارنة
- st.markdown("### خيارات المقارنة")
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- comparison_type = st.radio(
- "نوع المقارنة",
- options=["مقارنة كاملة", "مقارنة الأقسام المتطابقة فقط", "مقارنة الاختلافات فقط"]
- )
-
- with col2:
- ignore_options = st.multiselect(
- "ت��اهل",
- options=["المسافات", "علامات الترقيم", "حالة الأحرف", "الأرقام"],
- default=["المسافات"]
- )
-
- with col3:
- similarity_threshold = st.slider(
- "عتبة التشابه",
- min_value=0.0,
- max_value=1.0,
- value=0.7,
- step=0.05
- )
-
- # زر بدء المقارنة
- if st.button("بدء المقارنة بين المستندات", use_container_width=True):
- # عرض معلومات المستندات المختارة
- st.markdown("### معلومات المستندات المختارة")
-
- col1, col2 = st.columns(2)
-
- with col1:
- st.markdown(f"**المستند الأول:** {selected_doc1['name']}")
- st.markdown(f"**الإصدار:** {selected_doc1['version']}")
- st.markdown(f"**التاريخ:** {selected_doc1['date']}")
- st.markdown(f"**المناقصة:** {selected_doc1['related_entity']}")
-
- with col2:
- st.markdown(f"**المستند الثاني:** {selected_doc2['name']}")
- st.markdown(f"**الإصدار:** {selected_doc2['version']}")
- st.markdown(f"**التاريخ:** {selected_doc2['date']}")
- st.markdown(f"**المناقصة:** {selected_doc2['related_entity']}")
-
- # الحصول على محتوى المستندات (في تطبيق حقيقي، سيتم استرجاع المحتوى من الملفات الفعلية)
- doc1_content = self.sample_document_content.get(selected_doc1["id"], "محتوى المستند غير متوفر")
- doc2_content = self.sample_document_content.get(selected_doc2["id"], "محتوى المستند غير متوفر")
-
- # إجراء المقارنة
- self.display_document_comparison(doc1_content, doc2_content, comparison_type, ignore_options, similarity_threshold)
-
- def display_document_comparison(self, text1, text2, comparison_type, ignore_options, similarity_threshold):
- """عرض نتائج المقارنة بين مستندين"""
- st.markdown("### نتائج المقارنة بين المستندين")
-
- # تقسيم النصوص إلى أقسام (في هذا المثال، نستخدم العناوين كفواصل للأقسام)
- sections1 = self.split_into_sections(text1)
- sections2 = self.split_into_sections(text2)
-
- # حساب نسبة التشابه الإجمالية
- similarity = difflib.SequenceMatcher(None, text1, text2).ratio()
-
- # عرض نسبة التشابه
- st.markdown(f"**نسبة التشابه الإجمالية:** {similarity:.2%}")
-
- # عرض مقارنة الأقسام
- st.markdown("### مقارنة الأقسام")
-
- # إنشاء جدول لمقارنة الأقسام
- section_comparisons = []
-
- for section1_title, section1_content in sections1.items():
- best_match = None
- best_similarity = 0
-
- for section2_title, section2_content in sections2.items():
- # حساب نسبة التشابه بين عناوين الأقسام
- title_similarity = difflib.SequenceMatcher(None, section1_title, section2_title).ratio()
-
- # حساب نسبة التشابه بين محتوى الأقسام
- content_similarity = difflib.SequenceMatcher(None, section1_content, section2_content).ratio()
-
- # حساب متوسط نسبة التشابه
- avg_similarity = (title_similarity + content_similarity) / 2
-
- if avg_similarity > best_similarity:
- best_similarity = avg_similarity
- best_match = {
- "title": section2_title,
- "content": section2_content,
- "similarity": avg_similarity
- }
-
- # إضافة المقارنة إلى القائمة
- if best_match and best_similarity >= similarity_threshold:
- section_comparisons.append({
- "section1_title": section1_title,
- "section2_title": best_match["title"],
- "similarity": best_similarity
- })
- else:
- section_comparisons.append({
- "section1_title": section1_title,
- "section2_title": "غير موجود",
- "similarity": 0
- })
-
- # إضافة الأقسام ��لموجودة في المستند الثاني فقط
- for section2_title, section2_content in sections2.items():
- if not any(comp["section2_title"] == section2_title for comp in section_comparisons):
- section_comparisons.append({
- "section1_title": "غير موجود",
- "section2_title": section2_title,
- "similarity": 0
- })
-
- # عرض جدول المقارنة
- section_df = pd.DataFrame(section_comparisons)
- section_df = section_df.rename(columns={
- "section1_title": "القسم في المستند الأول",
- "section2_title": "القسم في المستند الثاني",
- "similarity": "نسبة التشابه"
- })
-
- # تنسيق نسبة التشابه
- section_df["نسبة التشابه"] = section_df["نسبة التشابه"].apply(lambda x: f"{x:.2%}")
-
- st.dataframe(
- section_df,
- use_container_width=True,
- hide_index=True
- )
-
- # عرض تفاصيل المقارنة
- st.markdown("### تفاصيل المقارنة")
-
- # اختيار قسم للمقارنة التفصيلية
- selected_section = st.selectbox(
- "اختر قسماً للمقارنة التفصيلية",
- options=[comp["section1_title"] for comp in section_comparisons if comp["section1_title"] != "غير موجود"]
- )
-
- # العثور على القسم المقابل في المستند الثاني
- matching_comparison = next((comp for comp in section_comparisons if comp["section1_title"] == selected_section), None)
-
- if matching_comparison and matching_comparison["section2_title"] != "غير موجود":
- # الحصول على محتوى القسمين
- section1_content = sections1[selected_section]
- section2_content = sections2[matching_comparison["section2_title"]]
-
- # عرض المقارنة التفصيلية
- self.display_comparison(section1_content, section2_content)
- else:
- st.warning("القسم المحدد غير موجود في المستند الثاني")
-
- def split_into_sections(self, text):
- """تقسيم النص إلى أقسام باستخدام العناوين"""
- sections = {}
- current_section = None
- current_content = []
-
- for line in text.splitlines():
- # البحث عن العناوين (الأسطر التي تبدأ بـ #)
- if line.strip().startswith('#'):
- # حفظ القسم السابق إذا وجد
- if current_section:
- sections[current_section] = '\n'.join(current_content)
-
- # بدء قسم جديد
- current_section = line.strip()
- current_content = []
- elif current_section:
- # إضافة السطر إلى محتوى القسم الحالي
- current_content.append(line)
-
- # حفظ القسم الأخير
- if current_section:
- sections[current_section] = '\n'.join(current_content)
-
- return sections
-
- def analyze_changes(self):
- """تحليل التغييرات في المستندات"""
- st.markdown("### تحليل التغييرات في المستندات")
-
- # اختيار المناقصة
- tender_options = list(set([doc["related_entity"] for doc in self.documents_data]))
- selected_tender = st.selectbox(
- "اختر المناقصة",
- options=tender_options,
- key="analyze_tender"
- )
-
- # فلترة المستندات حسب المناقصة المختارة
- filtered_docs = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender]
-
- # تجميع المستندات حسب النوع
- doc_types = {}
- for doc in filtered_docs:
- if doc["type"] not in doc_types:
- doc_types[doc["type"]] = []
- doc_types[doc["type"]].append(doc)
-
- # عرض تحليل التغييرات لكل نوع مستند
- for doc_type, docs in doc_types.items():
- if len(docs) > 1:
- with st.expander(f"تحليل التغييرات في {doc_type}"):
- # ترتيب المستندات حسب الإصدار
- sorted_docs = sorted(docs, key=lambda x: x["version"])
-
- # عرض معلومات الإصدارات
- st.markdown(f"**عدد الإصدارات:** {len(sorted_docs)}")
- st.markdown(f"**أول إصدار:** {sorted_docs[0]['version']} ({sorted_docs[0]['date']})")
- st.markdown(f"**آخر إصدار:** {sorted_docs[-1]['version']} ({sorted_docs[-1]['date']})")
-
- # حساب التغييرات بين الإصدارات
- changes = []
- for i in range(1, len(sorted_docs)):
- prev_doc = sorted_docs[i-1]
- curr_doc = sorted_docs[i]
-
- # حساب التغييرات (في تطبيق حقيقي، سيتم تحليل المحتوى الفعلي)
- page_diff = curr_doc["pages"] - prev_doc["pages"]
- size_diff = curr_doc["size"] - prev_doc["size"]
-
- changes.append({
- "from_version": prev_doc["version"],
- "to_version": curr_doc["version"],
- "date": curr_doc["date"],
- "page_diff": page_diff,
- "size_diff": size_diff
- })
-
- # عرض جدول التغييرات
- changes_df = pd.DataFrame(changes)
- changes_df = changes_df.rename(columns={
- "from_version": "من الإصدار",
- "to_version": "إلى الإصدار",
- "date": "تاريخ التغيير",
- "page_diff": "التغيير في عدد الصفحات",
- "size_diff": "التغيير في الحجم (ميجابايت)"
- })
-
- st.dataframe(
- changes_df,
- use_container_width=True,
- hide_index=True
- )
-
- # عرض رسم بياني للتغييرات
- st.markdown("#### تطور حجم المستند عبر الإصدارات")
-
- versions = [doc["version"] for doc in sorted_docs]
- sizes = [doc["size"] for doc in sorted_docs]
-
- chart_data = pd.DataFrame({
- "الإصدار": versions,
- "الحجم (ميجابايت)": sizes
- })
-
- st.line_chart(chart_data.set_index("الإصدار"))
-
- # عرض رسم بياني لعدد الصفحات
- st.markdown("#### تطور عدد الصفحات عبر الإصدارات")
-
- pages = [doc["pages"] for doc in sorted_docs]
-
- chart_data = pd.DataFrame({
- "الإصدار": versions,
- "عدد الصفحات": pages
- })
-
- st.line_chart(chart_data.set_index("الإصدار"))
-
- # تحليل التغييرات الشاملة
- st.markdown("### تحليل التغييرات الشاملة")
-
- # حساب إجمالي التغييرات (في تطبيق حقيقي، سيتم تحليل المحتوى الفعلي)
- total_docs = len(filtered_docs)
- total_versions = sum(len(docs) for docs in doc_types.values())
- avg_versions = total_versions / len(doc_types) if doc_types else 0
-
- col1, col2, col3 = st.columns(3)
-
- with col1:
- self.ui.create_metric_card(
- "إجمالي المستندات",
- str(total_docs),
- None,
- self.ui.COLORS['primary']
- )
-
- with col2:
- self.ui.create_metric_card(
- "إجمالي الإصدارات",
- str(total_versions),
- None,
- self.ui.COLORS['secondary']
- )
-
- with col3:
- self.ui.create_metric_card(
- "متوسط الإصدارات لكل نوع",
- f"{avg_versions:.1f}",
- None,
- self.ui.COLORS['accent']
- )
-
- # عرض توزيع التغييرات حسب النوع
- st.markdown("#### توزيع الإصدارات حسب نوع المستند")
-
- type_counts = {doc_type: len(docs) for doc_type, docs in doc_types.items()}
-
- chart_data = pd.DataFrame({
- "نوع المستند": list(type_counts.keys()),
- "عدد الإصدارات": list(type_counts.values())
- })
-
- st.bar_chart(chart_data.set_index("نوع المستند"))
-
- def show_change_history(self):
- """عرض سجل التغييرات"""
- st.markdown("### سجل التغييرات")
-
- # إنشاء بيانات نموذجية لسجل التغييرات
- change_history = [
- {
- "id": "CH001",
- "document_id": "DOC001",
- "document_name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
- "from_version": "1.0",
- "to_version": "1.1",
- "change_date": "2025-02-10",
- "change_type": "تحديث",
- "changed_by": "أحمد محمد",
- "description": "تحديث المواصفات الفنية وشروط التنفيذ",
- "sections_changed": ["نطاق العمل", "المواصفات الفنية", "الشروط العامة"]
- },
- {
- "id": "CH002",
- "document_id": "DOC002",
- "document_name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
- "from_version": "1.1",
- "to_version": "2.0",
- "change_date": "2025-03-05",
- "change_type": "تحديث رئيسي",
- "changed_by": "سارة عبدالله",
- "description": "إضافة متطلبات الاستدامة وتحديث المواصفات الفنية",
- "sections_changed": ["المواصفات الفنية", "الشروط العامة", "متطلبات الاستدامة"]
- },
- {
- "id": "CH003",
- "document_id": "DOC004",
- "document_name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
- "from_version": "1.0",
- "to_version": "1.1",
- "change_date": "2025-02-20",
- "change_type": "تحديث",
- "changed_by": "خالد عمر",
- "description": "تحديث الكميات وإضافة بنود جديدة",
- "sections_changed": ["أعمال الهيكل الإنشائي", "أعمال التشطيبات", "أعمال الكهرباء"]
- },
- {
- "id": "CH004",
- "document_id": "DOC006",
- "document_name": "المخططات - مناقصة إنشاء مبنى إداري",
- "from_version": "1.0",
- "to_version": "2.0",
- "change_date": "2025-03-10",
- "change_type": "تحديث رئيسي",
- "changed_by": "محمد علي",
- "description": "تحديث المخططات المعمارية والإنشائية",
- "sections_changed": ["المخططات المعمارية", "المخططات الإنشائية", "مخططات الكهرباء"]
- },
- {
- "id": "CH005",
- "document_id": "DOC008",
- "document_name": "كراسة الشروط - مناقصة صيانة طرق",
- "from_version": "1.0",
- "to_version": "1.1",
- "change_date": "2025-03-15",
- "change_type": "تحديث",
- "changed_by": "فاطمة أحمد",
- "description": "تحديث المواصفات الفنية وشروط التنفيذ",
- "sections_changed": ["نطاق العمل", "المواصفات الفنية", "الشروط العامة"]
- }
- ]
-
- # إنشاء فلاتر للسجل
- col1, col2, col3 = st.columns(3)
-
- with col1:
- document_filter = st.selectbox(
- "المستند",
- options=["الكل"] + list(set([ch["document_name"] for ch in change_history]))
- )
-
- with col2:
- change_type_filter = st.selectbox(
- "نوع التغيير",
- options=["الكل"] + list(set([ch["change_type"] for ch in change_history]))
- )
-
- with col3:
- date_range = st.date_input(
- "نطاق التاريخ",
- value=(
- datetime.datetime.strptime("2025-01-01", "%Y-%m-%d").date(),
- datetime.datetime.strptime("2025-12-31", "%Y-%m-%d").date()
- )
- )
-
- # تطبيق الفلاتر
- filtered_history = change_history
-
- if document_filter != "الكل":
- filtered_history = [ch for ch in filtered_history if ch["document_name"] == document_filter]
-
- if change_type_filter != "الكل":
- filtered_history = [ch for ch in filtered_history if ch["change_type"] == change_type_filter]
-
- if len(date_range) == 2:
- start_date, end_date = date_range
- filtered_history = [
- ch for ch in filtered_history
- if start_date <= datetime.datetime.strptime(ch["change_date"], "%Y-%m-%d").date() <= end_date
- ]
-
- # عرض سجل التغييرات
- if not filtered_history:
- st.info("لا توجد تغييرات تطابق الفلاتر المحددة")
- else:
- # تحويل البيانات إلى DataFrame
- history_df = pd.DataFrame(filtered_history)
-
- # إعادة ترتيب الأعمدة وتغيير أسمائها
- display_df = history_df[[
- "id", "document_name", "from_version", "to_version", "change_date", "change_type", "changed_by", "description"
- ]].rename(columns={
- "id": "الرقم",
- "document_name": "اسم المستند",
- "from_version": "من الإصدار",
- "to_version": "إلى الإصدار",
- "change_date": "تاريخ التغيير",
- "change_type": "نوع التغيير",
- "changed_by": "بواسطة",
- "description": "الوصف"
- })
-
- # عرض الجدول
- st.dataframe(
- display_df,
- use_container_width=True,
- hide_index=True
- )
-
- # عرض تفاصيل التغيير المحدد
- st.markdown("### تفاصيل التغيير")
-
- selected_change_id = st.selectbox(
- "اختر تغييراً لعرض التفاصيل",
- options=[ch["id"] for ch in filtered_history],
- format_func=lambda x: next((f"{ch['id']} - {ch['document_name']} ({ch['from_version']} إلى {ch['to_version']})" for ch in filtered_history if ch["id"] == x), "")
- )
-
- # العثور على التغيير المحدد
- selected_change = next((ch for ch in filtered_history if ch["id"] == selected_change_id), None)
-
- if selected_change:
- col1, col2 = st.columns(2)
-
- with col1:
- st.markdown(f"**المستند:** {selected_change['document_name']}")
- st.markdown(f"**من الإصدار:** {selected_change['from_version']}")
- st.markdown(f"**إلى الإصدار:** {selected_change['to_version']}")
- st.markdown(f"**تاريخ التغيير:** {selected_change['change_date']}")
-
- with col2:
- st.markdown(f"**نوع التغيير:** {selected_change['change_type']}")
- st.markdown(f"**بواسطة:** {selected_change['changed_by']}")
- st.markdown(f"**الوصف:** {selected_change['description']}")
-
- # عرض الأقسام التي تم تغييرها
- st.markdown("#### الأقسام التي تم تغييرها")
-
- for section in selected_change["sections_changed"]:
- st.markdown(f"- {section}")
-
- # أزرار الإجراءات
- col1, col2, col3 = st.columns(3)
-
- with col1:
- if st.button("عرض التغييرات بالتفصيل", use_container_width=True):
- st.success("تم فتح التغييرات بالتفصيل")
-
- with col2:
- if st.button("إنشاء تقرير", use_container_width=True):
- st.success("تم إنشاء التقرير بنجاح")
-
- with col3:
- if st.button("تصدير التغييرات", use_container_width=True):
- st.success("تم تصدير التغييرات بنجاح")
-
-# تشغيل التطبيق
-if __name__ == "__main__":
- doc_comparison_app = DocumentComparisonApp()
- doc_comparison_app.run()
+"""
+وحدة مقارنة المستندات - نظام تحليل المناقصات
+"""
+
+import streamlit as st
+import pandas as pd
+import numpy as np
+import os
+import sys
+from pathlib import Path
+import difflib
+import re
+import datetime
+
+# إضافة مسار المشروع للنظام
+sys.path.append(str(Path(__file__).parent.parent))
+
+# استيراد محسن واجهة المستخدم
+from styling.enhanced_ui import UIEnhancer
+
+class DocumentComparisonApp:
+ """تطبيق مقارنة المستندات"""
+
+ def __init__(self):
+ """تهيئة تطبيق مقارنة المستندات"""
+ self.ui = UIEnhancer(page_title="مقارنة المستندات - نظام تحليل المناقصات", page_icon="📄")
+ self.ui.apply_theme_colors()
+
+ # بيانات المستندات (نموذجية)
+ self.documents_data = [
+ {
+ "id": "DOC001",
+ "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
+ "type": "كراسة شروط",
+ "version": "1.0",
+ "date": "2025-01-15",
+ "size": 2.4,
+ "pages": 45,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/specs_v1.pdf"
+ },
+ {
+ "id": "DOC002",
+ "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
+ "type": "كراسة شروط",
+ "version": "1.1",
+ "date": "2025-02-10",
+ "size": 2.6,
+ "pages": 48,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/specs_v1.1.pdf"
+ },
+ {
+ "id": "DOC003",
+ "name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
+ "type": "كراسة شروط",
+ "version": "2.0",
+ "date": "2025-03-05",
+ "size": 2.8,
+ "pages": 52,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/specs_v2.0.pdf"
+ },
+ {
+ "id": "DOC004",
+ "name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
+ "type": "جدول كميات",
+ "version": "1.0",
+ "date": "2025-01-15",
+ "size": 1.2,
+ "pages": 20,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/boq_v1.0.xlsx"
+ },
+ {
+ "id": "DOC005",
+ "name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
+ "type": "جدول كميات",
+ "version": "1.1",
+ "date": "2025-02-20",
+ "size": 1.3,
+ "pages": 22,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/boq_v1.1.xlsx"
+ },
+ {
+ "id": "DOC006",
+ "name": "المخططات - مناقصة إنشاء مبنى إداري",
+ "type": "مخططات",
+ "version": "1.0",
+ "date": "2025-01-15",
+ "size": 15.6,
+ "pages": 30,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/drawings_v1.0.pdf"
+ },
+ {
+ "id": "DOC007",
+ "name": "المخططات - مناقصة إنشاء مبنى إداري",
+ "type": "مخططات",
+ "version": "2.0",
+ "date": "2025-03-10",
+ "size": 18.2,
+ "pages": 35,
+ "related_entity": "T-2025-001",
+ "path": "/documents/T-2025-001/drawings_v2.0.pdf"
+ },
+ {
+ "id": "DOC008",
+ "name": "كراسة الشروط - مناقصة صيانة طرق",
+ "type": "كراسة شروط",
+ "version": "1.0",
+ "date": "2025-02-05",
+ "size": 1.8,
+ "pages": 32,
+ "related_entity": "T-2025-002",
+ "path": "/documents/T-2025-002/specs_v1.0.pdf"
+ },
+ {
+ "id": "DOC009",
+ "name": "كراسة الشروط - مناقصة صيانة طرق",
+ "type": "كراسة شروط",
+ "version": "1.1",
+ "date": "2025-03-15",
+ "size": 1.9,
+ "pages": 34,
+ "related_entity": "T-2025-002",
+ "path": "/documents/T-2025-002/specs_v1.1.pdf"
+ },
+ {
+ "id": "DOC010",
+ "name": "جدول الكميات - مناقصة صيانة طرق",
+ "type": "جدول كميات",
+ "version": "1.0",
+ "date": "2025-02-05",
+ "size": 0.9,
+ "pages": 15,
+ "related_entity": "T-2025-002",
+ "path": "/documents/T-2025-002/boq_v1.0.xlsx"
+ }
+ ]
+
+ # بيانات نموذجية لمحتوى المستندات (للعرض فقط)
+ self.sample_document_content = {
+ "DOC001": """
+ # كراسة الشروط والمواصفات
+ ## مناقصة إنشاء مبنى إداري
+
+ ### 1. مقدمة
+ تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقد�� بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض.
+
+ ### 2. نطاق العمل
+ يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5000 متر مربع، ويشمل ذلك:
+ - أعمال الهيكل الإنشائي
+ - أعمال التشطيبات الداخلية والخارجية
+ - أعمال الكهرباء والميكانيكا
+ - أعمال تنسيق الموقع
+
+ ### 3. المواصفات الفنية
+ #### 3.1 أعمال الخرسانة
+ - يجب أن تكون الخرسانة المسلحة بقوة لا تقل عن 30 نيوتن/مم²
+ - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
+
+ #### 3.2 أعمال التشطيبات
+ - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
+ - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
+
+ ### 4. الشروط العامة
+ - مدة التنفيذ: 18 شهراً من تاريخ استلام الموقع
+ - غرامة التأخير: 0.1% من قيمة العقد عن كل يوم تأخير
+ - ضمان الأعمال: 10 سنوات للهيكل الإنشائي، 5 سنوات للأعمال الميكانيكية والكهربائية
+ """,
+
+ "DOC002": """
+ # كراسة الشروط والمواصفات
+ ## مناقصة إنشاء مبنى إداري
+
+ ### 1. مقدمة
+ تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقدم بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض.
+
+ ### 2. نطاق العمل
+ يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 5 طوابق بمساحة إجمالية 5500 متر مربع، ويشمل ذلك:
+ - أعمال الهيكل الإنشائي
+ - أعمال التشطيبات الداخلية والخارجية
+ - أعمال الكهرباء والميكانيكا
+ - أعمال تنسيق الموقع
+ - أعمال أنظمة الأمن والسلامة
+
+ ### 3. المواصفات الفنية
+ #### 3.1 أعمال الخرسانة
+ - يجب أن تكون الخرسانة المسلحة بقوة لا تقل عن 35 نيوتن/مم²
+ - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
+
+ #### 3.2 أعمال التشطيبات
+ - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
+ - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
+ - يجب استخدام زجاج عاكس للحرارة للواجهات
+
+ ### 4. الشروط العامة
+ - مدة التنفيذ: 16 شهراً من تاريخ استلام الموقع
+ - غرامة التأخير: 0.15% من قيمة العقد عن كل يوم تأخير
+ - ضمان الأعمال: 10 سنوات للهيكل الإنشائي، 5 سنوات للأعمال الميكانيكية والكهربائية
+ """,
+
+ "DOC003": """
+ # كراسة الشروط والمواصفات
+ ## مناقصة إنشاء مبنى إداري
+
+ ### 1. مقدمة
+ تدعو شركة شبه الجزيرة للمقاولات الشركات المتخصصة للتقدم بعروضها لتنفيذ مشروع إنشاء مبنى إداري في مدينة الرياض وفقاً للمواصفات المعتمدة من الهيئة السعودية للمواصفات والمقاييس.
+
+ ### 2. نطاق العمل
+ يشمل نطاق العمل تصميم وتنفيذ مبنى إداري مكون من 6 طوابق بمساحة إجمالية 6000 متر مربع، ويشمل ذلك:
+ - أعمال الهيكل الإنشائي
+ - أعمال التشطيبات الداخلية والخارجية
+ - أعمال الكهرباء والميكانيكا
+ - أعمال تنسيق الموقع
+ - أعمال أنظمة الأمن والسلامة
+ - أعمال أنظمة المباني الذكية
+
+ ### 3. المواصفات الفنية
+ #### 3.1 أعمال الخرسانة
+ - يجب أن تكون الخرسانة المسلحة بقوة لا ��قل عن 40 نيوتن/مم²
+ - يجب استخدام حديد تسليح مطابق للمواصفات السعودية
+ - يجب استخدام إضافات للخرسانة لزيادة مقاومتها للعوامل الجوية
+
+ #### 3.2 أعمال التشطيبات
+ - يجب استخدام مواد عالية الجودة للتشطيبات الداخلية
+ - يجب أن تكون الواجهات الخارجية مقاومة للعوامل الجوية
+ - يجب استخدام زجاج عاكس للحرارة للواجهات
+ - يجب استخدام مواد صديقة للبيئة
+
+ ### 4. الشروط العامة
+ - مدة التنفيذ: 15 شهراً من تاريخ استلام الموقع
+ - غرامة التأخير: 0.2% من قيمة العقد عن كل يوم تأخير
+ - ضمان الأعمال: 15 سنوات للهيكل الإنشائي، 7 سنوات للأعمال الميكانيكية والكهربائية
+
+ ### 5. متطلبات الاستدامة
+ - يجب أن يحقق المبنى متطلبات الاستدامة وفقاً لمعايير LEED
+ - يجب توفير أنظمة لترشيد استهلاك الطاقة والمياه
+ """
+ }
+
+ def run(self):
+ """تشغيل تطبيق مقارنة المستندات"""
+ # إنشاء قائمة العناصر
+ menu_items = [
+ {"name": "لوحة المعلومات", "icon": "house"},
+ {"name": "المناقصات والعقود", "icon": "file-text"},
+ {"name": "تحليل المستندات", "icon": "file-earmark-text"},
+ {"name": "نظام التسعير", "icon": "calculator"},
+ {"name": "حاسبة تكاليف البناء", "icon": "building"},
+ {"name": "الموارد والتكاليف", "icon": "people"},
+ {"name": "تحليل المخاطر", "icon": "exclamation-triangle"},
+ {"name": "إدارة المشاريع", "icon": "kanban"},
+ {"name": "الخرائط والمواقع", "icon": "geo-alt"},
+ {"name": "الجدول الزمني", "icon": "calendar3"},
+ {"name": "الإشعارات", "icon": "bell"},
+ {"name": "مقارنة المستندات", "icon": "files"},
+ {"name": "المساعد الذكي", "icon": "robot"},
+ {"name": "التقارير", "icon": "bar-chart"},
+ {"name": "الإعدادات", "icon": "gear"}
+ ]
+
+ # إنشاء الشريط الجانبي
+ selected = self.ui.create_sidebar(menu_items)
+
+ # إنشاء ترويسة الصفحة
+ self.ui.create_header("مقارنة المستندات", "أدوات متقدمة لمقارنة وتحليل المستندات")
+
+ # إنشاء علامات تبويب للوظائف المختلفة
+ tabs = st.tabs(["مقارنة الإصدارات", "مقارنة المستندات", "تحليل التغييرات", "سجل التغييرات"])
+
+ # علامة تبويب مقارنة الإصدارات
+ with tabs[0]:
+ self.compare_versions()
+
+ # علامة تبويب مقارنة المستندات
+ with tabs[1]:
+ self.compare_documents()
+
+ # علامة تبويب تحليل التغييرات
+ with tabs[2]:
+ self.analyze_changes()
+
+ # علامة تبويب سجل التغييرات
+ with tabs[3]:
+ self.show_change_history()
+
+ def compare_versions(self):
+ """مقارنة إصدارات المستندات"""
+ st.markdown("### مقارنة إصدارات المستندات")
+
+ # إضافة خيار تحميل الملفات
+ upload_tab, database_tab = st.tabs(["تحميل ملفات للمقارنة", "مقارنة المستندات المحفوظة"])
+
+ with upload_tab:
+ st.markdown("#### تحميل الملفات للمقارنة")
+ uploaded_file1 = st.file_uploader("الملف الأول", type=["pdf", "docx", "txt"], key="file1")
+ uploaded_file2 = st.file_uploader("الملف الثاني", type=["pdf", "docx", "txt"], key="file2")
+
+ if uploaded_file1 and uploaded_file2:
+ if st.button("مقارنة الملفات المحملة"):
+ try:
+ # قراءة محتوى الملفات
+ content1 = self._read_file_content(uploaded_file1)
+ content2 = self._read_file_content(uploaded_file2)
+
+ # عرض المقارنة
+ self.display_comparison(content1, content2)
+ except Exception as e:
+ st.error(f"حدث خطأ أثناء مقارنة الملفات: {str(e)}")
+
+ with database_tab:
+ # اختيار المناقصة
+ tender_options = list(set([doc["related_entity"] for doc in self.documents_data]))
+ selected_tender = st.selectbox(
+ "اختر المناقصة",
+ options=tender_options,
+ key="tender_select"
+ )
+
+ # فلترة المستندات حسب المناقصة المختارة
+ filtered_docs = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender]
+
+ # اختيار نوع المستند
+ doc_types = list(set([doc["type"] for doc in filtered_docs]))
+ selected_type = st.selectbox(
+ "اختر نوع المستند",
+ options=doc_types
+ )
+
+ # فلترة المستندات حسب النوع المختار
+ type_filtered_docs = [doc for doc in filtered_docs if doc["type"] == selected_type]
+
+ # ترتيب المستندات حسب الإصدار
+ type_filtered_docs = sorted(type_filtered_docs, key=lambda x: x["version"])
+
+ if len(type_filtered_docs) < 2:
+ st.warning("يجب توفر إصدارين على الأقل للمقارنة")
+ else:
+ # اختيار الإصدارات للمقارنة
+ col1, col2 = st.columns(2)
+
+ with col1:
+ version_options = [f"{doc['name']} (الإصدار {doc['version']})" for doc in type_filtered_docs]
+ selected_version1_index = st.selectbox(
+ "الإصدار الأول",
+ options=range(len(version_options)),
+ format_func=lambda x: version_options[x]
+ )
+ selected_doc1 = type_filtered_docs[selected_version1_index]
+
+ with col2:
+ remaining_indices = [i for i in range(len(type_filtered_docs)) if i != selected_version1_index]
+ selected_version2_index = st.selectbox(
+ "الإصدار الثاني",
+ options=remaining_indices,
+ format_func=lambda x: version_options[x]
+ )
+ selected_doc2 = type_filtered_docs[selected_version2_index]
+
+ # زر بدء المقارنة
+ if st.button("بدء المقارنة", use_container_width=True):
+ # عرض معلومات المستندات المختارة
+ st.markdown("### معلومات المستندات المختارة")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown(f"**الإصدار الأول:** {selected_doc1['version']}")
+ st.markdown(f"**التاريخ:** {selected_doc1['date']}")
+ st.markdown(f"**عدد الصفحات:** {selected_doc1['pages']}")
+ st.markdown(f"**الحجم:** {selected_doc1['size']} ميجابايت")
+
+ with col2:
+ st.markdown(f"**الإصدار الثاني:** {selected_doc2['version']}")
+ st.markdown(f"**التاريخ:** {selected_doc2['date']}")
+ st.markdown(f"**عدد الصفحات:** {selected_doc2['pages']}")
+ st.markdown(f"**الحجم:** {selected_doc2['size']} ميجابايت")
+
+ # الحصول على محتوى المستندات (في تطبيق حقيقي، سيتم استرجاع المحتوى من الملفات الفعلية)
+ doc1_content = self.sample_document_content.get(selected_doc1["id"], "محتوى المستند غير متوفر")
+ doc2_content = self.sample_document_content.get(selected_doc2["id"], "محتوى المستند غير متوفر")
+
+ # إجراء المقارنة
+ self.display_comparison(doc1_content, doc2_content)
+
+ def _read_file_content(self, file):
+ if file:
+ return file.read().decode("utf-8") # Assumes UTF-8 encoding, adjust if needed.
+ return ""
+
+ def display_comparison(self, text1, text2):
+ """عرض نتائج المقارنة بين نصين"""
+ st.markdown("### نتائج المقارنة")
+
+ # تقسيم النصوص إلى أسطر
+ lines1 = text1.splitlines()
+ lines2 = text2.splitlines()
+
+ # إجراء المقارنة باستخدام difflib
+ d = difflib.Differ()
+ diff = list(d.compare(lines1, lines2))
+
+ # عرض ملخص التغييرات
+ added = len([line for line in diff if line.startswith('+ ')])
+ removed = len([line for line in diff if line.startswith('- ')])
+ changed = len([line for line in diff if line.startswith('? ')])
+
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ self.ui.create_metric_card(
+ "الإضافات",
+ str(added),
+ None,
+ self.ui.COLORS['success']
+ )
+
+ with col2:
+ self.ui.create_metric_card(
+ "الحذف",
+ str(removed),
+ None,
+ self.ui.COLORS['danger']
+ )
+
+ with col3:
+ self.ui.create_metric_card(
+ "التغييرات",
+ str(changed // 2), # تقسيم على 2 لأن كل تغيير يظهر مرتين
+ None,
+ self.ui.COLORS['warning']
+ )
+
+ # عرض التغييرات بالتفصيل
+ st.markdown("### التغييرات بالتفصيل")
+
+ # إنشاء عرض HTML للتغييرات
+ html_diff = []
+ for line in diff:
+ if line.startswith('+ '):
+ html_diff.append(f'{line[2:]}
')
+ elif line.startswith('- '):
+ html_diff.append(f'{line[2:]}
')
+ elif line.startswith('? '):
+ # تجاهل أسطر التفاصيل
+ continue
+ else:
+ html_diff.append(f'{line[2:]}
')
+
+ # عرض التغييرات
+ st.markdown(''.join(html_diff), unsafe_allow_html=True)
+
+ # خيارات إضافية
+ st.markdown("### خيارات إضافية")
+
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ if st.button("تصدير التغييرات", use_container_width=True):
+ st.success("تم تصدير التغييرات بنجاح")
+
+ with col2:
+ if st.button("إنشاء تقرير", use_container_width=True):
+ st.success("تم إنشاء التقرير بنجاح")
+
+ with col3:
+ if st.button("حفظ المقارنة", use_container_width=True):
+ st.success("تم حفظ المقارنة بنجاح")
+
+ def compare_documents(self):
+ """مقارنة مستندات مختلفة"""
+ st.markdown("### مقارنة مستندات مختلفة")
+
+ # اختيار المستند الأول
+ col1, col2 = st.columns(2)
+
+ with col1:
+ tender1_options = list(set([doc["related_entity"] for doc in self.documents_data]))
+ selected_tender1 = st.selectbox(
+ "اختر المناقصة الأولى",
+ options=tender1_options,
+ key="tender1"
+ )
+
+ # فلترة المستندات حسب المناقصة المختارة
+ filtered_docs1 = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender1]
+
+ # اختيار المستند
+ doc_options1 = [f"{doc['name']} (الإصدار {doc['version']})" for doc in filtered_docs1]
+ selected_doc1_index = st.selectbox(
+ "اختر المستند الأول",
+ options=range(len(doc_options1)),
+ format_func=lambda x: doc_options1[x],
+ key="doc1"
+ )
+ selected_doc1 = filtered_docs1[selected_doc1_index]
+
+ with col2:
+ tender2_options = list(set([doc["related_entity"] for doc in self.documents_data]))
+ selected_tender2 = st.selectbox(
+ "اختر المناقصة الثانية",
+ options=tender2_options,
+ key="tender2"
+ )
+
+ # فلترة المستندات حسب المناقصة المختارة
+ filtered_docs2 = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender2]
+
+ # اختيار المستند
+ doc_options2 = [f"{doc['name']} (الإصدار {doc['version']})" for doc in filtered_docs2]
+ selected_doc2_index = st.selectbox(
+ "اختر المستند الثاني",
+ options=range(len(doc_options2)),
+ format_func=lambda x: doc_options2[x],
+ key="doc2"
+ )
+ selected_doc2 = filtered_docs2[selected_doc2_index]
+
+ # خيارات المقارنة
+ st.markdown("### خيارات المقارنة")
+
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ comparison_type = st.radio(
+ "نوع المقارنة",
+ options=["مقارنة كاملة", "مقارنة الأقسام المتطابقة فقط", "مقارنة الاختلافات فقط"]
+ )
+
+ with col2:
+ ignore_options = st.multiselect(
+ "تجاهل",
+ options=["المسافات", "علامات الترقيم", "حالة الأحرف", "الأرقام"],
+ default=["المسافات"]
+ )
+
+ with col3:
+ similarity_threshold = st.slider(
+ "عتبة التشابه",
+ min_value=0.0,
+ max_value=1.0,
+ value=0.7,
+ step=0.05
+ )
+
+ # زر بدء المقارنة
+ if st.button("بدء المقارنة بين المستندات", use_container_width=True):
+ # عرض معلومات المستندات المختارة
+ st.markdown("### معلومات المستندات المختارة")
+
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown(f"**المستند الأول:** {selected_doc1['name']}")
+ st.markdown(f"**الإصدار:** {selected_doc1['version']}")
+ st.markdown(f"**التاريخ:** {selected_doc1['date']}")
+ st.markdown(f"**المناقصة:** {selected_doc1['related_entity']}")
+
+ with col2:
+ st.markdown(f"**المستند الثاني:** {selected_doc2['name']}")
+ st.markdown(f"**الإصدار:** {selected_doc2['version']}")
+ st.markdown(f"**التاريخ:** {selected_doc2['date']}")
+ st.markdown(f"**المناقصة:** {selected_doc2['related_entity']}")
+
+ # الحصول على محتوى المستندات (في تطبيق حقيقي، سيتم استرجاع المحتوى من الملفات الفعلية)
+ doc1_content = self.sample_document_content.get(selected_doc1["id"], "محتوى المستند غير متوفر")
+ doc2_content = self.sample_document_content.get(selected_doc2["id"], "محتوى المستند غير متوفر")
+
+ # إجراء المقارنة
+ self.display_document_comparison(doc1_content, doc2_content, comparison_type, ignore_options, similarity_threshold)
+
+ def display_document_comparison(self, text1, text2, comparison_type, ignore_options, similarity_threshold):
+ """عرض نتائج المقارنة بين مستندين"""
+ st.markdown("### نتائج المقارنة بين المستندين")
+
+ # تقسيم النصوص إلى أقسام (في هذا المثال، نستخدم العناوين كفواصل للأقسام)
+ sections1 = self.split_into_sections(text1)
+ sections2 = self.split_into_sections(text2)
+
+ # حساب نسبة التشابه الإجمالية
+ similarity = difflib.SequenceMatcher(None, text1, text2).ratio()
+
+ # عرض نسبة التشابه
+ st.markdown(f"**نسبة التشابه الإجمالية:** {similarity:.2%}")
+
+ # عرض مقارنة الأقسام
+ st.markdown("### مقارنة الأقسام")
+
+ # إنشاء جدول لمقارنة الأقسام
+ section_comparisons = []
+
+ for section1_title, section1_content in sections1.items():
+ best_match = None
+ best_similarity = 0
+
+ for section2_title, section2_content in sections2.items():
+ # حساب نسبة التشابه بين عناوين الأقسام
+ title_similarity = difflib.SequenceMatcher(None, section1_title, section2_title).ratio()
+
+ # حساب نسبة التشابه بين محتوى الأقسام
+ content_similarity = difflib.SequenceMatcher(None, section1_content, section2_content).ratio()
+
+ # حساب متوسط نسبة التشابه
+ avg_similarity = (title_similarity + content_similarity) / 2
+
+ if avg_similarity > best_similarity:
+ best_similarity = avg_similarity
+ best_match = {
+ "title": section2_title,
+ "content": section2_content,
+ "similarity": avg_similarity
+ }
+
+ # إضافة المقارنة إلى القائمة
+ if best_match and best_similarity >= similarity_threshold:
+ section_comparisons.append({
+ "section1_title": section1_title,
+ "section2_title": best_match["title"],
+ "similarity": best_similarity
+ })
+ else:
+ section_comparisons.append({
+ "section1_title": section1_title,
+ "section2_title": "غير موجود",
+ "similarity": 0
+ })
+
+ # إضافة الأقسام الموجودة في المستند الثاني فقط
+ for section2_title, section2_content in sections2.items():
+ if not any(comp["section2_title"] == section2_title for comp in section_comparisons):
+ section_comparisons.append({
+ "section1_title": "غير موجود",
+ "section2_title": section2_title,
+ "similarity": 0
+ })
+
+ # عرض جدول المقارنة
+ section_df = pd.DataFrame(section_comparisons)
+ section_df = section_df.rename(columns={
+ "section1_title": "القسم في المستند الأول",
+ "section2_title": "القسم في المستند الثاني",
+ "similarity": "نسبة التشابه"
+ })
+
+ # تنسيق نسبة التشابه
+ section_df["نسبة التشابه"] = section_df["نسبة التشابه"].apply(lambda x: f"{x:.2%}")
+
+ st.dataframe(
+ section_df,
+ use_container_width=True,
+ hide_index=True
+ )
+
+ # عرض تفاصيل المقارنة
+ st.markdown("### تفاصيل المقارنة")
+
+ # اختيار قسم للمقارنة التفصيلية
+ selected_section = st.selectbox(
+ "اختر قسماً للمقارنة التفصيلية",
+ options=[comp["section1_title"] for comp in section_comparisons if comp["section1_title"] != "غير موجود"]
+ )
+
+ # العثور على القسم المقابل في المستند الثاني
+ matching_comparison = next((comp for comp in section_comparisons if comp["section1_title"] == selected_section), None)
+
+ if matching_comparison and matching_comparison["section2_title"] != "غير موجود":
+ # الحصول على محتوى القسمين
+ section1_content = sections1[selected_section]
+ section2_content = sections2[matching_comparison["section2_title"]]
+
+ # عرض المقارنة التفصيلية
+ self.display_comparison(section1_content, section2_content)
+ else:
+ st.warning("القسم المحدد غير موجود في المستند الثاني")
+
+ def split_into_sections(self, text):
+ """تقسيم النص إلى أقسام باستخدام العناوين"""
+ sections = {}
+ current_section = None
+ current_content = []
+
+ for line in text.splitlines():
+ # البحث عن العناوين (الأسطر التي تبدأ بـ #)
+ if line.strip().startswith('#'):
+ # حفظ القسم السابق إذا وجد
+ if current_section:
+ sections[current_section] = '\n'.join(current_content)
+
+ # بدء قسم جديد
+ current_section = line.strip()
+ current_content = []
+ elif current_section:
+ # إضافة السطر إلى محتوى القسم الحالي
+ current_content.append(line)
+
+ # حفظ القسم الأخير
+ if current_section:
+ sections[current_section]
+
+ return sections
+
+ def analyze_changes(self):
+ """تحليل التغييرات في المستندات"""
+ st.markdown("### تحليل التغييرات في المستندات")
+
+ # اختيار المناقصة
+ tender_options = list(set([doc["related_entity"] for doc in self.documents_data]))
+ selected_tender = st.selectbox(
+ "اختر المناقصة",
+ options=tender_options,
+ key="analyze_tender"
+ )
+
+ # فلترة المستندات حسب المناقصة المختارة
+ filtered_docs = [doc for doc in self.documents_data if doc["related_entity"] == selected_tender]
+
+ # تجميع المستندات حسب النوع
+ doc_types = {}
+ for doc in filtered_docs:
+ if doc["type"] not in doc_types:
+ doc_types[doc["type"]] = []
+ doc_types[doc["type"]].append(doc)
+
+ # عرض تحليل التغييرات لكل نوع مستند
+ for doc_type, docs in doc_types.items():
+ if len(docs) > 1:
+ with st.expander(f"تحليل التغييرات في {doc_type}"):
+ # ترتيب المستندات حسب الإصدار
+ sorted_docs = sorted(docs, key=lambda x: x["version"])
+
+ # عرض معلومات الإصدارات
+ st.markdown(f"**عدد الإصدارات:** {len(sorted_docs)}")
+ st.markdown(f"**أول إصدار:** {sorted_docs[0]['version']} ({sorted_docs[0]['date']})")
+ st.markdown(f"**آخر إصدار:** {sorted_docs[-1]['version']} ({sorted_docs[-1]['date']})")
+
+ # حساب التغييرات بين الإصدارات
+ changes = []
+ for i in range(1, len(sorted_docs)):
+ prev_doc = sorted_docs[i-1]
+ curr_doc = sorted_docs[i]
+
+ # حساب التغييرات (في تطبيق حقيقي، سيتم تحليل المحتوى الفعلي)
+ page_diff = curr_doc["pages"] - prev_doc["pages"]
+ size_diff = curr_doc["size"] - prev_doc["size"]
+
+ changes.append({
+ "from_version": prev_doc["version"],
+ "to_version": curr_doc["version"],
+ "date": curr_doc["date"],
+ "page_diff": page_diff,
+ "size_diff": size_diff
+ })
+
+ # عرض جدول التغييرات
+ changes_df = pd.DataFrame(changes)
+ changes_df = changes_df.rename(columns={
+ "from_version": "من الإصدار",
+ "to_version": "إلى الإصدار",
+ "date": "تاريخ التغيير",
+ "page_diff": "التغيير في عدد الصفحات",
+ "size_diff": "التغيير في الحجم (ميجابايت)"
+ })
+
+ st.dataframe(
+ changes_df,
+ use_container_width=True,
+ hide_index=True
+ )
+
+ # عرض رسم بياني للتغييرات
+ st.markdown("#### تطور حجم المستند عبر الإصدارات")
+
+ versions = [doc["version"] for doc in sorted_docs]
+ sizes = [doc["size"] for doc in sorted_docs]
+
+ chart_data = pd.DataFrame({
+ "الإصدار": versions,
+ "الحجم (ميجابايت)": sizes
+ })
+
+ st.line_chart(chart_data.set_index("الإصدار"))
+
+ # عرض رسم بياني لعدد الصفحات
+ st.markdown("#### تطور عدد الصفحات عبر الإصدارات")
+
+ pages = [doc["pages"] for doc in sorted_docs]
+
+ chart_data = pd.DataFrame({
+ "الإصدار": versions,
+ "عدد الصفحات": pages
+ })
+
+ st.line_chart(chart_data.set_index("الإصدار"))
+
+ # تحليل التغييرات الشاملة
+ st.markdown("### تحليل التغييرات الشاملة")
+
+ # حساب إجمالي التغييرات (في تطبيق حقيقي، سيتم تحليل المحتوى الفعلي)
+ total_docs = len(filtered_docs)
+ total_versions = sum(len(docs) for docs in doc_types.values())
+ avg_versions = total_versions / len(doc_types) if doc_types else 0
+
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ self.ui.create_metric_card(
+ "إجمالي المستندات",
+ str(total_docs),
+ None,
+ self.ui.COLORS['primary']
+ )
+
+ with col2:
+ self.ui.create_metric_card(
+ "إجمالي الإصدارات",
+ str(total_versions),
+ None,
+ self.ui.COLORS['secondary']
+ )
+
+ with col3:
+ self.ui.create_metric_card(
+ "متوسط الإصدارات لكل نوع",
+ f"{avg_versions:.1f}",
+ None,
+ self.ui.COLORS['accent']
+ )
+
+ # عرض توزيع التغييرات حسب النوع
+ st.markdown("#### توزيع الإصدارات حسب نوع المستند")
+
+ type_counts = {doc_type: len(docs) for doc_type, docs in doc_types.items()}
+
+ chart_data = pd.DataFrame({
+ "نوع المستند": list(type_counts.keys()),
+ "عدد الإصدارات": list(type_counts.values())
+ })
+
+ st.bar_chart(chart_data.set_index("نوع المستند"))
+
+ def show_change_history(self):
+ """عرض سجل التغييرات"""
+ st.markdown("### سجل التغييرات")
+
+ # إنشاء بيانات نموذجية لسجل التغييرات
+ change_history = [
+ {
+ "id": "CH001",
+ "document_id": "DOC001",
+ "document_name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
+ "from_version": "1.0",
+ "to_version": "1.1",
+ "change_date": "2025-02-10",
+ "change_type": "تحديث",
+ "changed_by": "أحمد محمد",
+ "description": "تحديث المواصفات الفنية وشروط التنفيذ",
+ "sections_changed": ["نطاق العمل", "المواصفات الفنية", "الشروط العامة"]
+ },
+ {
+ "id": "CH002",
+ "document_id": "DOC002",
+ "document_name": "كراسة الشروط - مناقصة إنشاء مبنى إداري",
+ "from_version": "1.1",
+ "to_version": "2.0",
+ "change_date": "2025-03-05",
+ "change_type": "تحديث رئيسي",
+ "changed_by": "سارة عبدالله",
+ "description": "إضافة متطلبات الاستدامة وتحديث المواصفات الفنية",
+ "sections_changed": ["المواصفات الفنية", "الشروط العامة", "متطلبات الاستدامة"]
+ },
+ {
+ "id": "CH003",
+ "document_id": "DOC004",
+ "document_name": "جدول الكميات - مناقصة إنشاء مبنى إداري",
+ "from_version": "1.0",
+ "to_version": "1.1",
+ "change_date": "2025-02-20",
+ "change_type": "تحديث",
+ "changed_by": "خالد عمر",
+ "description": "تحديث الكميات وإضافة بنود جديدة",
+ "sections_changed": ["أعمال الهيكل الإنشائي", "أعمال التشطيبات", "أعمال الكهرباء"]
+ },
+ {
+ "id": "CH004",
+ "document_id": "DOC006",
+ "document_name": "المخططات - مناقصة إنشاء مبنى إداري",
+ "from_version": "1.0",
+ "to_version": "2.0",
+ "change_date": "2025-03-10",
+ "change_type": "تحديث رئيسي",
+ "changed_by": "محمد علي",
+ "description": "تحديث المخططات المعمارية والإنشائية",
+ "sections_changed": ["المخططات المعمارية", "المخططات الإنشائية", "مخططات الكهرباء"]
+ },
+ {
+ "id": "CH005",
+ "document_id": "DOC008",
+ "document_name": "كراسة الشروط - مناقصة صيانة طرق",
+ "from_version": "1.0",
+ "to_version": "1.1",
+ "change_date": "2025-03-15",
+ "change_type": "تحديث",
+ "changed_by": "فاطمة أحمد",
+ "description": "تحديث المواصفات الفنية وشروط التنفيذ",
+ "sections_changed": ["نطاق العمل", "المواصفات الفنية", "الشروط العامة"]
+ }
+ ]
+
+ # إنشاء فلاتر للسجل
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ document_filter = st.selectbox(
+ "المستند",
+ options=["الكل"] + list(set([ch["document_name"] for ch in change_history]))
+ )
+
+ with col2:
+ change_type_filter = st.selectbox(
+ "نوع التغيير",
+ options=["الكل"] + list(set([ch["change_type"] for ch in change_history]))
+ )
+
+ with col3:
+ date_range = st.date_input(
+ "نطاق التاريخ",
+ value=(
+ datetime.datetime.strptime("2025-01-01", "%Y-%m-%d").date(),
+ datetime.datetime.strptime("2025-12-31", "%Y-%m-%d").date()
+ )
+ )
+
+ # تطبيق الفلاتر
+ filtered_history = change_history
+
+ if document_filter != "الكل":
+ filtered_history = [ch for ch in filtered_history if ch["document_name"] == document_filter]
+
+ if change_type_filter != "الكل":
+ filtered_history = [ch for ch in filtered_history if ch["change_type"] == change_type_filter]
+
+ if len(date_range) == 2:
+ start_date, end_date = date_range
+ filtered_history = [
+ ch for ch in filtered_history
+ if start_date <= datetime.datetime.strptime(ch["change_date"], "%Y-%m-%d").date() <= end_date
+ ]
+
+ # عرض سجل التغييرات
+ if not filtered_history:
+ st.info("لا توجد تغييرات تطابق الفلاتر المحددة")
+ else:
+ # تحويل البيانات إلى DataFrame
+ history_df = pd.DataFrame(filtered_history)
+
+ # إعادة ترتيب الأعمدة وتغيير أسمائها
+ display_df = history_df[[
+ "id", "document_name", "from_version", "to_version", "change_date", "change_type", "changed_by", "description"
+ ]].rename(columns={
+ "id": "الرقم",
+ "document_name": "اسم المستند",
+ "from_version": "من الإصدار",
+ "to_version": "إلى الإصدار",
+ "change_date": "تاريخ التغيير",
+ "change_type": "نوع التغيير",
+ "changed_by": "بواسطة",
+ "description": "الوصف"
+ })
+
+ # عرض الجدول
+ st.dataframe(
+ display_df,
+ use_container_width=True,
+ hide_index=True
+ )
+
+ # عرض تفاصيل التغيير المحدد
+ st.markdown("### تفاصيل التغيير")
+
+ selected_change_id = st.selectbox(
+ "اختر تغييراً لعرض التفاصيل",
+ options=[ch["id"] for ch in filtered_history],
+ format_func=lambda x: next((f"{ch['id']} - {ch['document_name']} ({ch['from_version']} إلى {ch['to_version']})" for ch in filtered_history if ch["id"] == x), "")
+ )
+
+ # العثور على التغيير المحدد
+ selected_change = next((ch for ch in filtered_history if ch["id"] == selected_change_id), None)
+
+ if selected_change:
+ col1, col2 = st.columns(2)
+
+ with col1:
+ st.markdown(f"**المستند:** {selected_change['document_name']}")
+ st.markdown(f"**من الإصدار:** {selected_change['from_version']}")
+ st.markdown(f"**إلى الإصدار:** {selected_change['to_version']}")
+ st.markdown(f"**تاريخ التغيير:** {selected_change['change_date']}")
+
+ with col2:
+ st.markdown(f"**نوع التغيير:** {selected_change['change_type']}")
+ st.markdown(f"**بواسطة:** {selected_change['changed_by']}")
+ st.markdown(f"**الوصف:** {selected_change['description']}")
+
+ # عرض الأقسام التي تم تغييرها
+ st.markdown("#### الأقسام التي تم تغييرها")
+
+ for section in selected_change["sections_changed"]:
+ st.markdown(f"- {section}")
+
+ # أزرار الإجراءات
+ col1, col2, col3 = st.columns(3)
+
+ with col1:
+ if st.button("عرض التغييرات بالتفصيل", use_container_width=True):
+ st.success("تم فتح التغييرات بالتفصيل")
+
+ with col2:
+ if st.button("إنشاء تقرير", use_container_width=True):
+ st.success("تم إنشاء التقرير بنجاح")
+
+ with col3:
+ if st.button("تصدير التغييرات", use_container_width=True):
+ st.success("تم تصدير التغييرات بنجاح")
+
+# تشغيل التطبيق
+if __name__ == "__main__":
+ doc_comparison_app = DocumentComparisonApp()
+ doc_comparison_app.run()
\ No newline at end of file