EGYADMIN commited on
Commit
90c4f34
·
verified ·
1 Parent(s): 82f942c

Upload 107 files

Browse files
README.md CHANGED
@@ -1,11 +1,11 @@
1
  ---
2
  license: mit
3
- title: نظام تحليل العقود والمناقصات بالذكاء الاصطناعي WAHBI- AI
4
  sdk: streamlit
5
- emoji: 📉
6
- colorFrom: red
7
  colorTo: green
8
- sdk_version: 1.44.0
9
  ---
10
  # نظام تحليل العقود والمناقصات | WAHBi-AI v2
11
  ## شركة شبه الجزيرة للمقاولات
 
1
  ---
2
  license: mit
3
+ title: نظام تحليل العقود والمناقصات بالذكاء الاصطناعي
4
  sdk: streamlit
5
+ emoji: 📊
6
+ colorFrom: green
7
  colorTo: green
8
+ sdk_version: 1.43.2
9
  ---
10
  # نظام تحليل العقود والمناقصات | WAHBi-AI v2
11
  ## شركة شبه الجزيرة للمقاولات
app.py CHANGED
@@ -36,18 +36,64 @@ def initialize_nltk_resources():
36
  # تهيئة موارد NLTK عند بدء التطبيق
37
  initialize_nltk_resources()
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  # إعداد إعدادات الصفحة
40
- st.set_page_config(
41
- page_title="نظام WAHBi للذكاء الاصطناعي | التعاقدات والمناقصات",
42
- page_icon="📊",
43
- layout="wide",
44
- initial_sidebar_state="expanded"
45
- )
 
 
 
 
46
 
47
  # استيراد ملف CSS للإصلاحات RTL
48
- with open("static/css/rtl-fixes.css", "r") as f:
49
- rtl_css = f.read()
50
- st.markdown(f"<style>{rtl_css}</style>", unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  # إضافة Font Awesome وأي أصول خارجية أخرى
53
  st.markdown("""
@@ -844,115 +890,30 @@ def render_reports_and_analytics():
844
 
845
 
846
  def render_ai_assistant():
847
- """عرض واجهة المساعد الذكي"""
848
- st.markdown("<h1 class='app-title'>المساعد الذكي</h1>", unsafe_allow_html=True)
849
-
850
- st.markdown("""
851
- <div class="section-card">
852
- <p>المساعد الذكي هو واجهة تفاعلية مدعومة بتقنيات الذكاء الاصطناعي لمساعدتك في جميع أنشطة إدارة المشاريع والعقود.
853
- يمكنك طرح أسئلة بلغتك الطبيعية والحصول على إجابات فورية، أو طلب مساعدة في مهام محددة مثل تحليل بنود العقد أو تقدير التكاليف.</p>
854
- </div>
855
- """, unsafe_allow_html=True)
856
-
857
- # واجهة المحادثة
858
- st.markdown("### تحدث مع المساعد الذكي")
859
-
860
- st.markdown("""
861
- <div style="background-color: #f5f5f5; border-radius: 10px; padding: 20px; margin-bottom: 20px;">
862
- <div style="display: flex; margin-bottom: 15px;">
863
- <div style="width: 40px; height: 40px; background-color: #1E88E5; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; margin-left: 15px;">W</div>
864
- <div style="background-color: #e0e0e0; padding: 10px 15px; border-radius: 10px; max-width: 70%;">
865
- <div style="margin-bottom: 5px; font-weight: bold;">مساعد WAHBi</div>
866
- <div>مرحباً! أنا المساعد الذكي الخاص بنظام WAHBi. كيف يمكنني مساعدتك اليوم؟</div>
867
- </div>
868
- </div>
869
 
870
- <div style="display: flex; margin-bottom: 15px; justify-content: flex-end;">
871
- <div style="background-color: #1E88E5; color: white; padding: 10px 15px; border-radius: 10px; max-width: 70%;">
872
- <div style="margin-bottom: 5px; font-weight: bold;">أنت</div>
873
- <div>كيف يمكنني تحليل شروط الدفع في عقد جديد؟</div>
874
- </div>
875
- <div style="width: 40px; height: 40px; background-color: #78909C; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; margin-right: 15px;">أ</div>
876
- </div>
877
 
878
- <div style="display: flex; margin-bottom: 15px;">
879
- <div style="width: 40px; height: 40px; background-color: #1E88E5; border-radius: 50%; display: flex; align-items: center; justify-content: center; color: white; font-weight: bold; margin-left: 15px;">W</div>
880
- <div style="background-color: #e0e0e0; padding: 10px 15px; border-radius: 10px; max-width: 70%;">
881
- <div style="margin-bottom: 5px; font-weight: bold;">مساعد WAHBi</div>
882
- <div>
883
- لتحليل شروط الدفع في عقد جديد، يمكنك اتباع الخطوات التالية:
884
- <ol style="padding-right: 20px; margin-top: 5px;">
885
- <li>انتقل إلى وحدة "تحليل المستندات" من القائمة الجانبية</li>
886
- <li>اختر "تحليل العقد الشامل" أو "تحليل الشروط والأحكام"</li>
887
- <li>قم بتحميل ملف العقد بتنسيق PDF أو Word</li>
888
- <li>حدد خيار "شروط الدفع" في إعدادات التحليل</li>
889
- <li>انقر على زر "تحليل" وانتظر النتائج</li>
890
- </ol>
891
-
892
- سيقوم النظام باستخراج جميع شروط الدفع من العقد، بما في ذلك المبالغ والمواعيد وشروط الاستحقاق والضمانات المطلوبة.
893
-
894
- هل ترغب في البدء في هذا التحليل الآن؟
895
- </div>
896
- </div>
897
- </div>
898
- </div>
899
- """, unsafe_allow_html=True)
900
-
901
- # إدخال أسئلة جديدة
902
- st.markdown("""
903
- <div style="background-color: #f5f5f5; border-radius: 10px; padding: 20px;">
904
- <textarea style="width: 100%; padding: 10px; border-radius: 5px; border: 1px solid #ddd; resize: none; height: 100px;" placeholder="اكتب سؤالك هنا..."></textarea>
905
- <div style="display: flex; justify-content: space-between; margin-top: 10px;">
906
- <div>
907
- <button style="background-color: #78909C; color: white; border: none; border-radius: 5px; padding: 8px 12px; margin-left: 10px; cursor: pointer;">
908
- <i class="fas fa-paperclip"></i> إرفاق ملف
909
- </button>
910
- <button style="background-color: #78909C; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer;">
911
- <i class="fas fa-microphone"></i> تسجيل صوتي
912
- </button>
913
- </div>
914
- <button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 15px; cursor: pointer;">
915
- إرسال <i class="fas fa-paper-plane"></i>
916
- </button>
917
- </div>
918
- </div>
919
- """, unsafe_allow_html=True)
920
-
921
- # اقتراحات المساعد الذكي
922
- st.markdown("### اقتراحات المساعد الذكي")
923
-
924
- col1, col2, col3 = st.columns(3)
925
-
926
- with col1:
927
- st.markdown("""
928
- <div class="card">
929
- <div class="card-title">تحليل عقد جديد</div>
930
- <p>تحليل شامل لمستند عقد جديد لاستخراج البنود والشروط الهامة والمخاطر المحتملة</p>
931
- <button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
932
- بدء التحليل
933
- </button>
934
- </div>
935
- """, unsafe_allow_html=True)
936
-
937
- with col2:
938
  st.markdown("""
939
- <div class="card">
940
- <div class="card-title">تقدير تكاليف مشروع</div>
941
- <p>الحصول على مساعدة في تقدير تكاليف مشروع جديد بناءً على بيانات المشاريع السابقة</p>
942
- <button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
943
- بدء التقدير
944
- </button>
945
  </div>
946
  """, unsafe_allow_html=True)
947
-
948
- with col3:
 
 
 
 
 
949
  st.markdown("""
950
- <div class="card">
951
- <div class="card-title">تحليل المخاطر</div>
952
- <p>تحليل المخاطر المحتملة في مشروع أو عقد وتقديم توصيات للتخفيف منها</p>
953
- <button style="background-color: #1E88E5; color: white; border: none; border-radius: 5px; padding: 8px 12px; cursor: pointer; width: 100%;">
954
- تحليل المخاطر
955
- </button>
956
  </div>
957
  """, unsafe_allow_html=True)
958
 
 
36
  # تهيئة موارد NLTK عند بدء التطبيق
37
  initialize_nltk_resources()
38
 
39
+ # مسار نسبي للملفات الثابتة (للتأكد من العمل في بيئات مختلفة)
40
+ def get_static_path(file_path):
41
+ """مسار ملف ثابت يعمل سواء كان التشغيل من المجلد الرئيسي أو من المجلد الفرعي"""
42
+ # قائمة المسارات المحتملة
43
+ possible_paths = [
44
+ # المسار المباشر (في حالة تشغيل التطبيق من نفس المجلد)
45
+ file_path,
46
+ # المسار النسبي من مجلد التطبيق (tender-analysis-system)
47
+ os.path.join(os.path.dirname(__file__), file_path),
48
+ # المسار النسبي من المجلد الأعلى
49
+ os.path.join(os.path.dirname(os.path.dirname(__file__)), "tender-analysis-system", file_path),
50
+ ]
51
+
52
+ # اختبار كل مسار محتمل
53
+ for path in possible_paths:
54
+ if os.path.exists(path):
55
+ return path
56
+
57
+ # إذا لم يتم العثور على الملف، إعادة المسار الأصلي
58
+ return file_path
59
+
60
  # إعداد إعدادات الصفحة
61
+ try:
62
+ st.set_page_config(
63
+ page_title="نظام WAHBi للذكاء الاصطناعي | التعاقدات والمناقصات",
64
+ page_icon="📊",
65
+ layout="wide",
66
+ initial_sidebar_state="expanded"
67
+ )
68
+ except Exception as e:
69
+ print(f"خطأ في إعداد الصفحة: {e}")
70
+ # يحدث هذا غالبًا عند استخدام st.set_page_config أكثر من مرة
71
 
72
  # استيراد ملف CSS للإصلاحات RTL
73
+ try:
74
+ rtl_css_path = get_static_path("static/css/rtl-fixes.css")
75
+ with open(rtl_css_path, "r", encoding='utf-8') as f:
76
+ rtl_css = f.read()
77
+ st.markdown(f"<style>{rtl_css}</style>", unsafe_allow_html=True)
78
+ except FileNotFoundError:
79
+ st.warning("تعذر العثور على ملف تصحيحات RTL، سيتم استخدام النمط الافتراضي")
80
+ print(f"تعذر العثور على ملف: {rtl_css_path}")
81
+ except Exception as e:
82
+ st.warning(f"حدث خطأ أثناء تحميل تصحيحات RTL: {str(e)}")
83
+ print(f"خطأ في تحميل ملف CSS: {str(e)}")
84
+
85
+ # استيراد ملف CSS المحسن
86
+ try:
87
+ enhanced_css_path = get_static_path("static/css/enhanced-styles.css")
88
+ with open(enhanced_css_path, "r", encoding='utf-8') as f:
89
+ enhanced_css = f.read()
90
+ st.markdown(f"<style>{enhanced_css}</style>", unsafe_allow_html=True)
91
+ except FileNotFoundError:
92
+ st.warning("تعذر العثور على ملف التنسيقات المحسنة، سيتم استخدام النمط الافتراضي")
93
+ print(f"تعذر العثور على ملف: {enhanced_css_path}")
94
+ except Exception as e:
95
+ st.warning(f"حدث خطأ أثناء تحميل التنسيقات المحسنة: {str(e)}")
96
+ print(f"خطأ في تحميل ملف CSS: {str(e)}")
97
 
98
  # إضافة Font Awesome وأي أصول خارجية أخرى
99
  st.markdown("""
 
890
 
891
 
892
  def render_ai_assistant():
893
+ """عرض واجهة المساعد الذكي باستخدام المكون الجديد"""
894
+ try:
895
+ from modules.ai_assistant.assistant_app import AssistantApp
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
896
 
897
+ # عرض العنوان والوصف
898
+ st.markdown("<h1 class='app-title'>المساعد الذكي</h1>", unsafe_allow_html=True)
 
 
 
 
 
899
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
  st.markdown("""
901
+ <div class="section-card">
902
+ <p>المساعد الذكي هو واجهة تفاعلية مدعومة بتقنيات الذكاء الاصطناعي لمساعدتك في جميع أنشطة إدارة المشاريع والعقود.
903
+ يمكنك طرح أسئلة بلغتك الطبيعية والحصول على إجابات فورية، أو طلب مساعدة في مهام محددة مثل تحليل بنود العقد أو تقدير التكاليف.</p>
 
 
 
904
  </div>
905
  """, unsafe_allow_html=True)
906
+
907
+ # استدعاء المساعد الذكي الجديد
908
+ ai_assistant = AssistantApp()
909
+ ai_assistant.render()
910
+
911
+ except Exception as e:
912
+ st.error(f"حدث خطأ في تحميل المساعد الذكي: {str(e)}")
913
  st.markdown("""
914
+ <div class="error-card">
915
+ <h3>😞 عذراً، واجهنا مشكلة في تحميل المساعد الذكي</h3>
916
+ <p>يرجى المحاولة مرة أخرى لاحقاً أو التواصل مع فريق الدعم الفني إذا استمرت المشكلة.</p>
 
 
 
917
  </div>
918
  """, unsafe_allow_html=True)
919
 
modules/document_comparison/document_comparator.py CHANGED
@@ -47,6 +47,19 @@ class DocumentComparator:
47
  def _initialize_nltk(self):
48
  """تهيئة مكتبة NLTK وتنزيل الحزم المطلوبة"""
49
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
50
  # محاولة استخدام sent_tokenize للتحقق من وجود حزمة punkt
51
  from nltk.tokenize import sent_tokenize
52
  sent_tokenize("This is a test sentence.")
 
47
  def _initialize_nltk(self):
48
  """تهيئة مكتبة NLTK وتنزيل الحزم المطلوبة"""
49
  try:
50
+ # استيراد nltk
51
+ import nltk
52
+
53
+ # قائمة بالحزم المطلوبة
54
+ required_packages = ['punkt', 'stopwords', 'wordnet']
55
+ for package in required_packages:
56
+ try:
57
+ # محاولة استخدام الحزمة أولاً، وإذا فشلت يتم تنزيلها
58
+ nltk.data.find(f'tokenizers/{package}')
59
+ except LookupError:
60
+ print(f"تنزيل حزمة NLTK: {package}")
61
+ nltk.download(package, quiet=True)
62
+
63
  # محاولة استخدام sent_tokenize للتحقق من وجود حزمة punkt
64
  from nltk.tokenize import sent_tokenize
65
  sent_tokenize("This is a test sentence.")
modules/pricing/construction_calculator.py CHANGED
@@ -19,6 +19,18 @@ def render_construction_calculator():
19
  """
20
  عرض حاسبة تكاليف البناء المتكاملة
21
  """
 
 
 
 
 
 
 
 
 
 
 
 
22
  st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
23
 
24
  # معلومات المشروع
@@ -639,6 +651,30 @@ def render_final_report(project_name, project_location, project_area, project_ty
639
  """
640
  st.markdown("<h3>التقرير النهائي لتكاليف المشروع</h3>", unsafe_allow_html=True)
641
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
642
  # حساب التكاليف المباشرة والإجمالية
643
  direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
644
  total_costs = direct_costs + st.session_state.admin_cost
@@ -681,23 +717,39 @@ def render_final_report(project_name, project_location, project_area, project_ty
681
  st.markdown("</div>", unsafe_allow_html=True)
682
 
683
  # عرض التفاصيل بالمتر المربع
684
- per_sqm_cost = total_price / project_area
685
-
686
- st.markdown("<div class='card'>", unsafe_allow_html=True)
687
- st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
688
- st.markdown(f"<p>تكلفة المتر المربع الإجمالية: <strong>{per_sqm_cost:,.2f} ريال/م²</strong></p>", unsafe_allow_html=True)
689
- st.markdown("</div>", unsafe_allow_html=True)
 
 
 
 
 
690
 
691
  # رسم بياني لتوزيع التكاليف
692
  st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
693
 
694
- cost_distribution = [
695
- {"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": st.session_state.materials_cost / total_price * 100},
696
- {"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": st.session_state.equipment_cost / total_price * 100},
697
- {"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": st.session_state.labor_cost / total_price * 100},
698
- {"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": st.session_state.admin_cost / total_price * 100},
699
- {"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": profit_amount / total_price * 100}
700
- ]
 
 
 
 
 
 
 
 
 
 
 
701
 
702
  cost_df = pd.DataFrame(cost_distribution)
703
 
 
19
  """
20
  عرض حاسبة تكاليف البناء المتكاملة
21
  """
22
+ # التأكد من وجود المتغيرات في حالة الجلسة
23
+ if 'materials_cost' not in st.session_state:
24
+ st.session_state.materials_cost = 0.0
25
+ if 'equipment_cost' not in st.session_state:
26
+ st.session_state.equipment_cost = 0.0
27
+ if 'labor_cost' not in st.session_state:
28
+ st.session_state.labor_cost = 0.0
29
+ if 'admin_cost' not in st.session_state:
30
+ st.session_state.admin_cost = 0.0
31
+ if 'profit_margin' not in st.session_state:
32
+ st.session_state.profit_margin = 15.0
33
+
34
  st.markdown("<h2 class='module-title'>حاسبة تكاليف البناء المتكاملة</h2>", unsafe_allow_html=True)
35
 
36
  # معلومات المشروع
 
651
  """
652
  st.markdown("<h3>التقرير النهائي لتكاليف المشروع</h3>", unsafe_allow_html=True)
653
 
654
+ # التأكد من وجود المتغيرات المطلوبة في حالة الجلسة وضمان أن لديهم قيم صحيحة
655
+ required_fields = {
656
+ 'materials_cost': 0.0,
657
+ 'equipment_cost': 0.0,
658
+ 'labor_cost': 0.0,
659
+ 'admin_cost': 0.0,
660
+ 'profit_margin': 15.0,
661
+ 'materials': [],
662
+ 'equipment': [],
663
+ 'labor': [],
664
+ 'admin_expenses': []
665
+ }
666
+
667
+ # مرور على كافة الحقول المطلوبة للتأكد من وجودها
668
+ for field, default_value in required_fields.items():
669
+ if field not in st.session_state:
670
+ st.session_state[field] = default_value
671
+
672
+ # التحقق من أن القيم العددية صالحة (غير None وليست NaN)
673
+ if field in ['materials_cost', 'equipment_cost', 'labor_cost', 'admin_cost', 'profit_margin']:
674
+ # إذا كانت القيمة None أو NaN، استخدم القيمة الافتراضية
675
+ if st.session_state[field] is None or pd.isna(st.session_state[field]):
676
+ st.session_state[field] = default_value
677
+
678
  # حساب التكاليف المباشرة والإجمالية
679
  direct_costs = st.session_state.materials_cost + st.session_state.equipment_cost + st.session_state.labor_cost
680
  total_costs = direct_costs + st.session_state.admin_cost
 
717
  st.markdown("</div>", unsafe_allow_html=True)
718
 
719
  # عرض التفاصيل بالمتر المربع
720
+ if project_area > 0:
721
+ per_sqm_cost = total_price / project_area
722
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
723
+ st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
724
+ st.markdown(f"<p>تكلفة المتر المربع الإجمالية: <strong>{per_sqm_cost:,.2f} ريال/م²</strong></p>", unsafe_allow_html=True)
725
+ st.markdown("</div>", unsafe_allow_html=True)
726
+ else:
727
+ st.markdown("<div class='card'>", unsafe_allow_html=True)
728
+ st.markdown("<h4>تكلفة المتر المربع</h4>", unsafe_allow_html=True)
729
+ st.markdown("<p>يرجى إدخال مساحة صحيحة للمشروع لحساب تكلفة المتر المربع</p>", unsafe_allow_html=True)
730
+ st.markdown("</div>", unsafe_allow_html=True)
731
 
732
  # رسم بياني لتوزيع التكاليف
733
  st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
734
 
735
+ # تجنب القسمة على صفر
736
+ if total_price > 0:
737
+ cost_distribution = [
738
+ {"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": st.session_state.materials_cost / total_price * 100},
739
+ {"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": st.session_state.equipment_cost / total_price * 100},
740
+ {"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": st.session_state.labor_cost / total_price * 100},
741
+ {"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": st.session_state.admin_cost / total_price * 100},
742
+ {"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": profit_amount / total_price * 100}
743
+ ]
744
+ else:
745
+ # إذا كان المجموع صفر، اجعل جميع النسب المئوية صفر
746
+ cost_distribution = [
747
+ {"النوع": "المواد", "القيمة": st.session_state.materials_cost, "النسبة": 0},
748
+ {"النوع": "المعدات", "القيمة": st.session_state.equipment_cost, "النسبة": 0},
749
+ {"النوع": "العمالة", "القيمة": st.session_state.labor_cost, "النسبة": 0},
750
+ {"النوع": "المصاريف الإدارية", "القيمة": st.session_state.admin_cost, "النسبة": 0},
751
+ {"النوع": "هامش الربح", "القيمة": profit_amount, "النسبة": 0}
752
+ ]
753
 
754
  cost_df = pd.DataFrame(cost_distribution)
755
 
modules/resources/resources_app.py CHANGED
@@ -461,17 +461,21 @@ class ResourcesApp:
461
  # تحويل البيانات إلى DataFrame
462
  price_history_df = pd.DataFrame(price_history_data)
463
 
464
- # رسم المخطط الخطي
465
- fig = px.line(
466
- price_history_df,
467
- x='التاريخ',
468
- y='السعر',
469
- color='المادة',
470
- title='تطور أسعار المواد الرئيسية خلال العام الماضي',
471
- labels={'price': 'السعر (ريال)', 'date': 'التاريخ'}
472
- )
473
-
474
- st.plotly_chart(fig, use_container_width=True)
 
 
 
 
475
 
476
  def _render_materials_tab(self):
477
  """عرض تبويب المواد"""
@@ -1049,15 +1053,30 @@ class ResourcesApp:
1049
  material_ids = {material['name']: material['id'] for material in st.session_state.materials}
1050
  selected_ids = [material_ids[name] for name in selected_materials if name in material_ids]
1051
 
 
 
 
 
 
1052
  price_history_data = []
1053
  for entry in st.session_state.price_history:
1054
  if entry['material_id'] in selected_ids:
 
1055
  material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "")
1056
- price_history_data.append({
1057
- 'المادة': material_name,
1058
- 'التاريخ': pd.to_datetime(entry['date']),
1059
- 'السعر': entry['price']
1060
- })
 
 
 
 
 
 
 
 
 
1061
 
1062
  if not price_history_data:
1063
  st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
@@ -1066,32 +1085,37 @@ class ResourcesApp:
1066
  # تحويل البيانات إلى DataFrame
1067
  price_history_df = pd.DataFrame(price_history_data)
1068
 
1069
- # عرض المخطط الخطي للأسعار
1070
- fig = px.line(
1071
- price_history_df,
1072
- x='التاريخ',
1073
- y='السعر',
1074
- color='المادة',
1075
- title='تطور أسعار المواد المختارة',
1076
- labels={'السعر': 'السعر (ريال)'}
1077
- )
1078
-
1079
- st.plotly_chart(fig, use_container_width=True)
 
 
 
 
1080
 
1081
  # حساب التغيرات في الأسعار
1082
  materials_price_changes = []
1083
 
1084
  for material_name in selected_materials:
1085
- material_prices = price_history_df[price_history_df['المادة'] == material_name].sort_values('التاريخ')
 
1086
 
1087
  if len(material_prices) >= 2:
1088
- first_price = material_prices.iloc[0]['السعر']
1089
- last_price = material_prices.iloc[-1]['السعر']
1090
  price_change = last_price - first_price
1091
  price_change_percent = (price_change / first_price) * 100
1092
 
1093
  # حساب التقلب (الانحراف المعياري)
1094
- price_volatility = material_prices['السعر'].std()
1095
 
1096
  materials_price_changes.append({
1097
  'المادة': material_name,
@@ -1259,17 +1283,21 @@ class ResourcesApp:
1259
  price_history_data = []
1260
  for entry in st.session_state.price_history:
1261
  if entry['material_id'] == material_id:
1262
- price_history_data.append({
1263
- 'التاريخ': pd.to_datetime(entry['date']),
1264
- 'السعر': entry['price']
1265
- })
 
 
 
 
1266
 
1267
  if not price_history_data:
1268
  st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.")
1269
  return
1270
 
1271
  # تحويل البيانات إلى DataFrame
1272
- price_history_df = pd.DataFrame(price_history_data).sort_values('التاريخ')
1273
 
1274
  # إجراء التوقع
1275
  # في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet
@@ -1278,7 +1306,7 @@ class ResourcesApp:
1278
  # حساب متوسط التغير الشهري
1279
  monthly_changes = []
1280
  for i in range(1, len(price_history_df)):
1281
- monthly_changes.append(price_history_df.iloc[i]['السعر'] - price_history_df.iloc[i-1]['السعر'])
1282
 
1283
  if monthly_changes:
1284
  avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
@@ -1286,8 +1314,8 @@ class ResourcesApp:
1286
  avg_monthly_change = 0
1287
 
1288
  # إنشاء بيانات التوقع
1289
- last_date = price_history_df['التاريخ'].max()
1290
- last_price = price_history_df.loc[price_history_df['التاريخ'] == last_date, 'السعر'].values[0]
1291
 
1292
  forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M')
1293
  forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)]
@@ -1296,25 +1324,25 @@ class ResourcesApp:
1296
  forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
1297
 
1298
  forecast_df = pd.DataFrame({
1299
- 'التاريخ': forecast_dates,
1300
- 'السعر': forecast_prices,
1301
- 'النوع': ['توقع'] * forecast_period
1302
  })
1303
 
1304
  # دمج البيانات التاريخية والتوقع
1305
  historical_df = price_history_df.copy()
1306
- historical_df['النوع'] = ['تاريخي'] * len(historical_df)
1307
 
1308
  combined_df = pd.concat([historical_df, forecast_df], ignore_index=True)
1309
 
1310
  # عرض المخطط
1311
  fig = px.line(
1312
  combined_df,
1313
- x='التاريخ',
1314
- y='السعر',
1315
- color='النوع',
1316
  title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة',
1317
- labels={'السعر': 'السعر (ريال)'},
1318
  color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'}
1319
  )
1320
 
@@ -1349,9 +1377,14 @@ class ResourcesApp:
1349
  st.markdown("#### جدول توقع الأسعار")
1350
 
1351
  forecast_table = forecast_df.copy()
1352
- forecast_table['التاريخ'] = forecast_table['التاريخ'].dt.strftime('%Y-%m')
1353
- forecast_table['السعر'] = forecast_table['السعر'].apply(lambda x: f"{x:,.2f} ريال")
1354
- forecast_table = forecast_table.drop(columns=['النوع'])
 
 
 
 
 
1355
 
1356
  st.dataframe(forecast_table, use_container_width=True, hide_index=True)
1357
 
 
461
  # تحويل البيانات إلى DataFrame
462
  price_history_df = pd.DataFrame(price_history_data)
463
 
464
+ # التحقق من وجود بيانات قبل رسم المخطط
465
+ if len(price_history_data) == 0:
466
+ st.warning("لا توجد بيانات تاريخية للأسعار متاحة لعرضها")
467
+ else:
468
+ # رسم المخطط الخطي
469
+ fig = px.line(
470
+ price_history_df,
471
+ x='التاريخ',
472
+ y='السعر',
473
+ color='المادة',
474
+ title='تطور أسعار المواد الرئيسية خلال العام الماضي',
475
+ labels={'التاريخ': 'التاريخ', 'السعر': 'السعر (ريال)', 'المادة': 'المادة'}
476
+ )
477
+ # عرض المخطط فقط إذا تم إنشاؤه
478
+ st.plotly_chart(fig, use_container_width=True)
479
 
480
  def _render_materials_tab(self):
481
  """عرض تبويب المواد"""
 
1053
  material_ids = {material['name']: material['id'] for material in st.session_state.materials}
1054
  selected_ids = [material_ids[name] for name in selected_materials if name in material_ids]
1055
 
1056
+ # التحقق من وجود بيانات سعرية في session_state.price_history
1057
+ if 'price_history' not in st.session_state or not st.session_state.price_history:
1058
+ st.warning("لا توجد بيانات أسعار متاحة للتحليل.")
1059
+ return
1060
+
1061
  price_history_data = []
1062
  for entry in st.session_state.price_history:
1063
  if entry['material_id'] in selected_ids:
1064
+ # الحصول على اسم المادة من المعرف
1065
  material_name = next((material['name'] for material in st.session_state.materials if material['id'] == entry['material_id']), "")
1066
+
1067
+ # التحقق من وجود المفاتيح المطلوبة
1068
+ if 'date' in entry and 'price' in entry:
1069
+ try:
1070
+ # إضافة البيانات إلى قائمة البيانات مع تحويل التاريخ إلى كائن datetime
1071
+ price_history_data.append({
1072
+ 'material': material_name, # استخدام أسماء إنجليزية للمفاتيح
1073
+ 'date': pd.to_datetime(entry['date']),
1074
+ 'price': float(entry['price']) # التأكد من تحويل السعر إلى رقم
1075
+ })
1076
+ except (ValueError, TypeError) as e:
1077
+ # تسجيل أخطاء تحويل البيانات
1078
+ st.error(f"خطأ في معالجة البيانات: {e}")
1079
+ continue
1080
 
1081
  if not price_history_data:
1082
  st.warning("لا توجد بيانات أسعار متاحة للمواد المختارة.")
 
1085
  # تحويل البيانات إلى DataFrame
1086
  price_history_df = pd.DataFrame(price_history_data)
1087
 
1088
+ # التحقق من وجود بيانات قبل رسم المخطط
1089
+ if len(price_history_df) == 0:
1090
+ st.warning("لا توجد بيانات تاريخية للأسعار متاحة لعرضها")
1091
+ else:
1092
+ # عرض المخطط الخطي للأسعار باستخدام أسماء الأعمدة الإنجليزية
1093
+ fig = px.line(
1094
+ price_history_df,
1095
+ x='date',
1096
+ y='price',
1097
+ color='material',
1098
+ title='تطور أسعار المواد المختارة',
1099
+ labels={'date': 'التاريخ', 'price': 'السعر (ريال)', 'material': 'المادة'}
1100
+ )
1101
+
1102
+ st.plotly_chart(fig, use_container_width=True)
1103
 
1104
  # حساب التغيرات في الأسعار
1105
  materials_price_changes = []
1106
 
1107
  for material_name in selected_materials:
1108
+ # استخدام أسماء الأعمدة الإنجليزية للتصفية والترتيب
1109
+ material_prices = price_history_df[price_history_df['material'] == material_name].sort_values('date')
1110
 
1111
  if len(material_prices) >= 2:
1112
+ first_price = material_prices.iloc[0]['price']
1113
+ last_price = material_prices.iloc[-1]['price']
1114
  price_change = last_price - first_price
1115
  price_change_percent = (price_change / first_price) * 100
1116
 
1117
  # حساب التقلب (الانحراف المعياري)
1118
+ price_volatility = material_prices['price'].std()
1119
 
1120
  materials_price_changes.append({
1121
  'المادة': material_name,
 
1283
  price_history_data = []
1284
  for entry in st.session_state.price_history:
1285
  if entry['material_id'] == material_id:
1286
+ try:
1287
+ price_history_data.append({
1288
+ 'date': pd.to_datetime(entry['date']),
1289
+ 'price': float(entry['price'])
1290
+ })
1291
+ except (ValueError, TypeError) as e:
1292
+ st.error(f"خطأ في معالجة البيانات: {e}")
1293
+ continue
1294
 
1295
  if not price_history_data:
1296
  st.warning("لا توجد بيانات تاريخية كافية للمادة المحددة للقيام بالتوقع.")
1297
  return
1298
 
1299
  # تحويل البيانات إلى DataFrame
1300
+ price_history_df = pd.DataFrame(price_history_data).sort_values('date')
1301
 
1302
  # إجراء التوقع
1303
  # في الواقع، ستستخدم نماذج تعلم آلي مثل ARIMA أو Prophet
 
1306
  # حساب متوسط التغير الشهري
1307
  monthly_changes = []
1308
  for i in range(1, len(price_history_df)):
1309
+ monthly_changes.append(price_history_df.iloc[i]['price'] - price_history_df.iloc[i-1]['price'])
1310
 
1311
  if monthly_changes:
1312
  avg_monthly_change = sum(monthly_changes) / len(monthly_changes)
 
1314
  avg_monthly_change = 0
1315
 
1316
  # إنشاء بيانات التوقع
1317
+ last_date = price_history_df['date'].max()
1318
+ last_price = price_history_df.loc[price_history_df['date'] == last_date, 'price'].values[0]
1319
 
1320
  forecast_dates = pd.date_range(start=last_date + pd.DateOffset(months=1), periods=forecast_period, freq='M')
1321
  forecast_prices = [last_price + (i+1) * avg_monthly_change for i in range(forecast_period)]
 
1324
  forecast_prices = [price + random.uniform(-price*0.05, price*0.05) for price in forecast_prices]
1325
 
1326
  forecast_df = pd.DataFrame({
1327
+ 'date': forecast_dates,
1328
+ 'price': forecast_prices,
1329
+ 'type': ['توقع'] * forecast_period
1330
  })
1331
 
1332
  # دمج البيانات التاريخية والتوقع
1333
  historical_df = price_history_df.copy()
1334
+ historical_df['type'] = ['تاريخي'] * len(historical_df)
1335
 
1336
  combined_df = pd.concat([historical_df, forecast_df], ignore_index=True)
1337
 
1338
  # عرض المخطط
1339
  fig = px.line(
1340
  combined_df,
1341
+ x='date',
1342
+ y='price',
1343
+ color='type',
1344
  title=f'توقع أسعار {selected_material} للـ {forecast_period} أشهر القادمة',
1345
+ labels={'date': 'التاريخ', 'price': 'السعر (ريال)', 'type': 'النوع'},
1346
  color_discrete_map={'تاريخي': 'blue', 'توقع': 'red'}
1347
  )
1348
 
 
1377
  st.markdown("#### جدول توقع الأسعار")
1378
 
1379
  forecast_table = forecast_df.copy()
1380
+ forecast_table['date'] = forecast_table['date'].dt.strftime('%Y-%m')
1381
+ forecast_table['price'] = forecast_table['price'].apply(lambda x: f"{x:,.2f} ريال")
1382
+ # إعادة تسمية الأعمدة إلى العربية لعرض الجدول
1383
+ forecast_table = forecast_table.rename(columns={
1384
+ 'date': 'التاريخ',
1385
+ 'price': 'السعر'
1386
+ })
1387
+ forecast_table = forecast_table.drop(columns=['type'])
1388
 
1389
  st.dataframe(forecast_table, use_container_width=True, hide_index=True)
1390
 
test.py ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ اختبار التغييرات على مشروع البايثون في نظام تحليل المناقصات
3
+ """
4
+
5
+ import os
6
+ import sys
7
+
8
+ print("=== اختبار التغييرات في نظام تحليل المناقصات ===\n")
9
+
10
+ # إضافة المسار الحالي إلى مسار النظام
11
+ sys.path.append('.')
12
+
13
+ # اختبار استيراد وحدة credits
14
+ print("1. اختبار استيراد وحدة credits:")
15
+ try:
16
+ from utils.components.credits import render_credits, display_credits
17
+ print("✅ تم استيراد وحدة credits بنجاح")
18
+ except Exception as e:
19
+ print(f"❌ فشل استيراد وحدة credits: {str(e)}")
20
+
21
+ # اختبار استيراد وحدة التحليل
22
+ print("\n2. اختبار استيراد وحدة الصوت:")
23
+ try:
24
+ from modules.voice_narration.voice_over_system import VoiceOverSystem
25
+ print("✅ تم استيراد وحدة الصوت بنجاح")
26
+ except Exception as e:
27
+ print(f"❌ فشل استيراد وحدة الصوت: {str(e)}")
28
+
29
+ # اختبار استيراد وحدة templates_catalog
30
+ print("\n3. اختبار استيراد وحدة كتالوج القوالب:")
31
+ try:
32
+ from modules.pricing.services.templates_catalog.templates_catalog import TemplatesCatalog
33
+ print("✅ تم استيراد وحدة كتالوج القوالب بنجاح")
34
+ except Exception as e:
35
+ print(f"❌ فشل استيراد وحدة كتالوج القوالب: {str(e)}")
36
+
37
+ # اختبار استيراد وحدة pdf_handler
38
+ print("\n4. اختبار استيراد وحدة معالج PDF:")
39
+ try:
40
+ from utils.pdf_handler import export_pricing_to_pdf
41
+ print("✅ تم استيراد وحدة معالج PDF بنجاح")
42
+ except Exception as e:
43
+ print(f"❌ فشل استيراد وحدة معالج PDF: {str(e)}")
44
+
45
+ print("\n=== النهاية ===")
utils/components/header.py CHANGED
@@ -7,18 +7,26 @@ from datetime import datetime
7
  import config
8
 
9
 
10
- def render_header():
11
  """
12
  عرض ترويسة الصفحة المحسنة
 
 
 
13
  """
14
  # إنشاء مكون الترويسة باستخدام HTML
 
 
 
 
 
15
  header_html = """
16
  <div class="header-container">
17
  <div class="header-title">
18
  <div class="logo">
19
  <span class="logo-text">WAHBi AI</span>
20
  </div>
21
- <h1>نظام تحليل العقود والمناقصات</h1>
22
  <p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
23
  </div>
24
  <div class="header-info">
@@ -44,7 +52,7 @@ def render_header():
44
  year = today.year
45
 
46
  # استبدال القيم في قالب HTML
47
- header_html = header_html.format(day=day, month=month, year=year)
48
 
49
  # عرض الترويسة
50
  st.markdown(header_html, unsafe_allow_html=True)
 
7
  import config
8
 
9
 
10
+ def render_header(page_title=None):
11
  """
12
  عرض ترويسة الصفحة المحسنة
13
+
14
+ الوسيطات:
15
+ page_title: عنوان الصفحة المعروضة (اختياري)
16
  """
17
  # إنشاء مكون الترويسة باستخدام HTML
18
+ title_display = "نظام تحليل العقود والمناقصات"
19
+ # إذا تم تمرير عنوان للصفحة، قم بإضافته للعنوان الرئيسي
20
+ if page_title:
21
+ title_display = f"نظام تحليل العقود والمناقصات: {page_title}"
22
+
23
  header_html = """
24
  <div class="header-container">
25
  <div class="header-title">
26
  <div class="logo">
27
  <span class="logo-text">WAHBi AI</span>
28
  </div>
29
+ <h1>{title}</h1>
30
  <p>الحلول الشاملة للتسعير والتحليل بالذكاء الاصطناعي - شركة شبه الجزيرة للمقاولات</p>
31
  </div>
32
  <div class="header-info">
 
52
  year = today.year
53
 
54
  # استبدال القيم في قالب HTML
55
+ header_html = header_html.format(title=title_display, day=day, month=month, year=year)
56
 
57
  # عرض الترويسة
58
  st.markdown(header_html, unsafe_allow_html=True)
utils/components/sidebar.py CHANGED
@@ -59,8 +59,14 @@ def render_sidebar():
59
  default_index=0,
60
  styles={
61
  "container": {"padding": "5px", "background-color": "#f0f2f6", "direction": "rtl"},
62
- "icon": {"color": "orange", "font-size": "18px"},
63
- "nav-link": {"font-size": "14px", "text-align": "right", "margin": "0px"},
 
 
 
 
 
 
64
  "nav-link-selected": {"background-color": "#ff9a3c"},
65
  }
66
  )
 
59
  default_index=0,
60
  styles={
61
  "container": {"padding": "5px", "background-color": "#f0f2f6", "direction": "rtl"},
62
+ "icon": {"color": "orange", "font-size": "18px", "margin-left": "10px"},
63
+ "nav-link": {
64
+ "font-size": "14px",
65
+ "text-align": "right",
66
+ "margin": "0px",
67
+ "direction": "rtl",
68
+ "justify-content": "flex-end"
69
+ },
70
  "nav-link-selected": {"background-color": "#ff9a3c"},
71
  }
72
  )