Upload 107 files
Browse files- README.md +4 -4
- app.py +73 -112
- modules/document_comparison/document_comparator.py +13 -0
- modules/pricing/construction_calculator.py +65 -13
- modules/resources/resources_app.py +83 -50
- test.py +45 -0
- utils/components/header.py +11 -3
- utils/components/sidebar.py +8 -2
README.md
CHANGED
@@ -1,11 +1,11 @@
|
|
1 |
---
|
2 |
license: mit
|
3 |
-
title: نظام تحليل العقود والمناقصات بالذكاء الاصطناعي
|
4 |
sdk: streamlit
|
5 |
-
emoji:
|
6 |
-
colorFrom:
|
7 |
colorTo: green
|
8 |
-
sdk_version: 1.
|
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 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
|
|
|
|
|
|
|
|
46 |
|
47 |
# استيراد ملف CSS للإصلاحات RTL
|
48 |
-
|
49 |
-
|
50 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
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 |
-
|
871 |
-
|
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 |
-
<
|
941 |
-
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
949 |
st.markdown("""
|
950 |
-
<div class="card">
|
951 |
-
<
|
952 |
-
<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 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
|
|
|
|
|
|
|
|
|
|
690 |
|
691 |
# رسم بياني لتوزيع التكاليف
|
692 |
st.markdown("<h4>توزيع التكاليف</h4>", unsafe_allow_html=True)
|
693 |
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
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 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
|
472 |
-
|
473 |
-
|
474 |
-
|
|
|
|
|
|
|
|
|
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 |
-
|
1057 |
-
|
1058 |
-
|
1059 |
-
|
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 |
-
|
1071 |
-
|
1072 |
-
|
1073 |
-
|
1074 |
-
|
1075 |
-
|
1076 |
-
|
1077 |
-
|
1078 |
-
|
1079 |
-
|
|
|
|
|
|
|
|
|
1080 |
|
1081 |
# حساب التغيرات في الأسعار
|
1082 |
materials_price_changes = []
|
1083 |
|
1084 |
for material_name in selected_materials:
|
1085 |
-
|
|
|
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['
|
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 |
-
|
1263 |
-
|
1264 |
-
|
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]['
|
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['
|
1290 |
-
last_price = price_history_df.loc[price_history_df['
|
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 |
-
'
|
1300 |
-
'
|
1301 |
-
'
|
1302 |
})
|
1303 |
|
1304 |
# دمج البيانات التاريخية والتوقع
|
1305 |
historical_df = price_history_df.copy()
|
1306 |
-
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['
|
1353 |
-
forecast_table['
|
1354 |
-
|
|
|
|
|
|
|
|
|
|
|
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
|
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": {
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
)
|