Spaces:
Paused
Paused
Update web/pages/procurement.py
Browse files- web/pages/procurement.py +483 -498
web/pages/procurement.py
CHANGED
@@ -1,536 +1,521 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
import numpy as np
|
|
|
|
|
4 |
import plotly.express as px
|
5 |
import plotly.graph_objects as go
|
6 |
-
from datetime import datetime
|
|
|
|
|
7 |
|
8 |
-
|
9 |
-
|
10 |
-
عرض صفحة إدارة المشتريات والعقود
|
11 |
-
"""
|
12 |
-
st.subheader("إدارة المشتريات والعقود")
|
13 |
-
|
14 |
-
# الخيارات الفرعية
|
15 |
-
tabs = st.tabs(["العقود النشطة", "أوامر الشراء", "المناقصات الداخلية", "تقييم الموردين"])
|
16 |
-
|
17 |
-
# تبويب العقود النشطة
|
18 |
-
with tabs[0]:
|
19 |
-
show_active_contracts()
|
20 |
-
|
21 |
-
# تبويب أوامر الشراء
|
22 |
-
with tabs[1]:
|
23 |
-
show_purchase_orders()
|
24 |
-
|
25 |
-
# تبويب المناقصات الداخلية
|
26 |
-
with tabs[2]:
|
27 |
-
show_internal_tenders()
|
28 |
-
|
29 |
-
# تبويب تقييم الموردين
|
30 |
-
with tabs[3]:
|
31 |
-
show_vendor_evaluation()
|
32 |
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
"رقم العقد": ["C-2025-1001", "C-2025-1042", "C-2024-0987", "C-2024-0912", "C-2025-1123",
|
44 |
-
"C-2024-0875", "C-2025-1088", "C-2025-1156", "C-2024-0932", "C-2025-1201"],
|
45 |
-
"المورد": ["شركة الصناعات السعودية", "مؤسسة الخليج للمقاولات", "شركة الرياض للإنشاءات",
|
46 |
-
"الشركة العربية للمعدات", "مصنع المنتجات الإسمنتية", "شركة تقنيات البناء",
|
47 |
-
"مؤسسة المدار للتوريدات", "شركة البنية التحتية المتكاملة", "مصنع الصلب السعودي",
|
48 |
-
"شركة الأنابيب الوطنية"],
|
49 |
-
"نوع العقد": ["توريد مواد", "مقاولات", "خدمات هندسية", "تأجير معدات", "توريد مواد",
|
50 |
-
"خدمات فنية", "توريد مواد", "مقاولات", "توريد مواد", "توريد مواد"],
|
51 |
-
"تاريخ البدء": [
|
52 |
-
current_date - timedelta(days=120),
|
53 |
-
current_date - timedelta(days=90),
|
54 |
-
current_date - timedelta(days=210),
|
55 |
-
current_date - timedelta(days=180),
|
56 |
-
current_date - timedelta(days=60),
|
57 |
-
current_date - timedelta(days=240),
|
58 |
-
current_date - timedelta(days=45),
|
59 |
-
current_date - timedelta(days=30),
|
60 |
-
current_date - timedelta(days=150),
|
61 |
-
current_date - timedelta(days=15)
|
62 |
-
],
|
63 |
-
"تاريخ الانتهاء": [
|
64 |
-
current_date + timedelta(days=245),
|
65 |
-
current_date + timedelta(days=270),
|
66 |
-
current_date + timedelta(days=155),
|
67 |
-
current_date + timedelta(days=185),
|
68 |
-
current_date + timedelta(days=305),
|
69 |
-
current_date + timedelta(days=125),
|
70 |
-
current_date + timedelta(days=320),
|
71 |
-
current_date + timedelta(days=335),
|
72 |
-
current_date + timedelta(days=215),
|
73 |
-
current_date + timedelta(days=350)
|
74 |
-
],
|
75 |
-
"القيمة (مليون ريال)": [12.5, 28.7, 8.3, 6.2, 9.1, 5.4, 7.8, 15.6, 11.2, 10.9],
|
76 |
-
"نسبة الإنجاز (%)": [45, 30, 75, 65, 20, 80, 15, 10, 60, 5]
|
77 |
-
}
|
78 |
-
|
79 |
-
# إنشاء DataFrame
|
80 |
-
contracts_df = pd.DataFrame(contracts_data)
|
81 |
-
|
82 |
-
# إضافة المدة المتبقية
|
83 |
-
contracts_df["المدة المتبقية (يوم)"] = (contracts_df["تاريخ الانتهاء"] - current_date).dt.days
|
84 |
-
|
85 |
-
# تصنيف الحالة
|
86 |
-
conditions = [
|
87 |
-
(contracts_df["المدة المتبقية (يوم)"] < 30),
|
88 |
-
(contracts_df["المدة المتبقية (يوم)"] < 90),
|
89 |
-
(contracts_df["المدة المتبقية (يوم)"] >= 90)
|
90 |
-
]
|
91 |
-
values = ["على وشك الانتهاء", "متوسطة", "طويلة الأجل"]
|
92 |
-
colors = ["#D32F2F", "#FFC107", "#4CAF50"]
|
93 |
-
|
94 |
-
contracts_df["حالة العقد"] = np.select(conditions, values)
|
95 |
-
|
96 |
-
# عرض فلاتر البحث
|
97 |
-
col1, col2, col3 = st.columns(3)
|
98 |
-
|
99 |
-
with col1:
|
100 |
-
contract_type_filter = st.selectbox(
|
101 |
-
"نوع العقد",
|
102 |
-
["الكل"] + sorted(contracts_df["نوع العقد"].unique().tolist())
|
103 |
-
)
|
104 |
-
|
105 |
-
with col2:
|
106 |
-
status_filter = st.selectbox(
|
107 |
-
"حالة العقد",
|
108 |
-
["الكل"] + sorted(contracts_df["حالة العقد"].unique().tolist())
|
109 |
-
)
|
110 |
-
|
111 |
-
with col3:
|
112 |
-
min_value = st.number_input("الحد الأدنى للقيمة (مليون ريال)", 0.0, 50.0, 0.0)
|
113 |
-
|
114 |
-
# تطبيق الفلاتر
|
115 |
-
filtered_df = contracts_df.copy()
|
116 |
-
|
117 |
-
if contract_type_filter != "الكل":
|
118 |
-
filtered_df = filtered_df[filtered_df["نوع العقد"] == contract_type_filter]
|
119 |
-
|
120 |
-
if status_filter != "الكل":
|
121 |
-
filtered_df = filtered_df[filtered_df["حالة العقد"] == status_filter]
|
122 |
-
|
123 |
-
if min_value > 0:
|
124 |
-
filtered_df = filtered_df[filtered_df["القيمة (مليون ريال)"] >= min_value]
|
125 |
-
|
126 |
-
# عرض العقود المصفاة
|
127 |
-
st.dataframe(filtered_df, use_container_width=True)
|
128 |
-
|
129 |
-
# تحليلات العقود
|
130 |
-
st.markdown("### تحليلات العقود")
|
131 |
-
|
132 |
-
col1, col2 = st.columns(2)
|
133 |
-
|
134 |
-
with col1:
|
135 |
-
# توزيع العقود حسب النوع
|
136 |
-
type_distribution = contracts_df.groupby("نوع العقد")["القيمة (مليون ريال)"].sum().reset_index()
|
137 |
-
|
138 |
-
fig1 = px.pie(
|
139 |
-
type_distribution,
|
140 |
-
values="القيمة (مليون ريال)",
|
141 |
-
names="نوع العقد",
|
142 |
-
title="توزيع قيمة العقود حسب النوع",
|
143 |
-
color_discrete_sequence=px.colors.qualitative.Bold
|
144 |
-
)
|
145 |
-
|
146 |
-
fig1.update_traces(textposition="inside", textinfo="percent+label")
|
147 |
-
|
148 |
-
st.plotly_chart(fig1, use_container_width=True)
|
149 |
-
|
150 |
-
with col2:
|
151 |
-
# توزيع العقود حسب الحالة
|
152 |
-
status_distribution = contracts_df.groupby("حالة العقد").agg({
|
153 |
-
"رقم العقد": "count",
|
154 |
-
"القيمة (مليون ريال)": "sum"
|
155 |
-
}).reset_index()
|
156 |
-
|
157 |
-
status_distribution.columns = ["الحالة", "عدد العقود", "إجمالي القيمة (مليون ريال)"]
|
158 |
-
|
159 |
-
# ترتيب الحالات
|
160 |
-
status_order = {"على وشك الانتهاء": 1, "متوسطة": 2, "طويلة الأجل": 3}
|
161 |
-
status_distribution["الترتيب"] = status_distribution["الحالة"].map(status_order)
|
162 |
-
status_distribution = status_distribution.sort_values("الترتيب")
|
163 |
-
|
164 |
-
# اختيار الألوان حسب الحالة
|
165 |
-
status_colors = {"على وشك الانتهاء": "#D32F2F", "متوسطة": "#FFC107", "طويلة الأجل": "#4CAF50"}
|
166 |
-
|
167 |
-
fig2 = px.bar(
|
168 |
-
status_distribution,
|
169 |
-
x="الحالة",
|
170 |
-
y="إجمالي القيمة (مليون ريال)",
|
171 |
-
color="الحالة",
|
172 |
-
text="عدد العقود",
|
173 |
-
title="توزيع العقود حسب المدة المتبقية",
|
174 |
-
color_discrete_map=status_colors
|
175 |
-
)
|
176 |
-
|
177 |
-
fig2.update_traces(texttemplate="%{text} عقد", textposition="outside")
|
178 |
-
|
179 |
-
st.plotly_chart(fig2, use_container_width=True)
|
180 |
-
|
181 |
-
# العقود القريبة من الانتهاء
|
182 |
-
st.markdown("### العقود على وشك الانتهاء")
|
183 |
-
|
184 |
-
expiring_contracts = contracts_df[contracts_df["المدة المتبقية (يوم)"] < 30].sort_values("المدة المتبقية (يوم)")
|
185 |
-
|
186 |
-
if not expiring_contracts.empty:
|
187 |
-
for _, contract in expiring_contracts.iterrows():
|
188 |
-
st.markdown(f"""
|
189 |
-
**{contract['رقم العقد']} - {contract['المورد']}**
|
190 |
-
**نوع العقد:** {contract['نوع العقد']}
|
191 |
-
**المدة المتبقية:** {contract['المدة المتبقية (يوم)']} يوم
|
192 |
-
**نسبة الإنجاز:** {contract['نسبة الإنجاز (%)']}%
|
193 |
-
**القيمة:** {contract['القيمة (مليون ريال)']} مليون ريال
|
194 |
-
""")
|
195 |
-
|
196 |
-
# شريط التقدم
|
197 |
-
st.progress(contract['نسبة الإنجاز (%)'] / 100)
|
198 |
-
st.markdown("---")
|
199 |
-
else:
|
200 |
-
st.info("لا توجد عقود على وشك الانتهاء خلال الشهر القادم")
|
201 |
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
""
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
"مؤسسة المدار للتوريدات", "شركة البنية التحتية المتكاملة", "مصنع الصلب السعودي",
|
217 |
-
"شركة الأنابيب الوطنية"
|
218 |
-
],
|
219 |
-
"المشروع": [
|
220 |
-
"مشروع توسعة شبكة الطرق", "بناء المدارس", "تطوير البنية التحتية",
|
221 |
-
"تحديث شبكة المياه", "بناء المستشفى التخصصي", "إنشاء مركز البيانات",
|
222 |
-
"توسعة المطار", "تطوير الحدائق العامة", "بناء المجمع السكني",
|
223 |
-
"تطوير شبكة الصرف الصحي"
|
224 |
-
],
|
225 |
-
"تاريخ الطلب": [
|
226 |
-
current_date - timedelta(days=np.random.randint(5, 60)) for _ in range(10)
|
227 |
-
],
|
228 |
-
"تاريخ التسليم المتوقع": [
|
229 |
-
current_date + timedelta(days=np.random.randint(5, 45)) for _ in range(10)
|
230 |
-
],
|
231 |
-
"القيمة (ريال)": [
|
232 |
-
np.random.randint(50000, 5000000) for _ in range(10)
|
233 |
-
],
|
234 |
-
"الحالة": np.random.choice(
|
235 |
-
["جديد", "قيد المعالجة", "تم الشحن", "تم الاستلام", "مغلق"],
|
236 |
-
size=10,
|
237 |
-
p=[0.2, 0.3, 0.2, 0.2, 0.1]
|
238 |
-
)
|
239 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
240 |
|
241 |
-
|
242 |
-
po_df = pd.DataFrame(po_data)
|
243 |
-
|
244 |
-
# عرض فلاتر البحث
|
245 |
-
col1, col2 = st.columns(2)
|
246 |
-
|
247 |
-
with col1:
|
248 |
-
status_filter = st.selectbox(
|
249 |
-
"حالة أمر الشراء",
|
250 |
-
["الكل"] + sorted(po_df["الحالة"].unique().tolist())
|
251 |
-
)
|
252 |
-
|
253 |
-
with col2:
|
254 |
-
vendor_filter = st.selectbox(
|
255 |
-
"المورد",
|
256 |
-
["الكل"] + sorted(po_df["المورد"].unique().tolist())
|
257 |
-
)
|
258 |
|
259 |
-
|
260 |
-
|
261 |
|
262 |
-
|
263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
264 |
|
265 |
-
|
266 |
-
|
|
|
|
|
|
|
|
|
267 |
|
268 |
-
|
269 |
-
st.dataframe(filtered_po, use_container_width=True)
|
270 |
|
271 |
-
|
272 |
-
|
|
|
273 |
|
274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
275 |
|
276 |
-
|
277 |
-
|
278 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
color_discrete_sequence=px.colors.qualitative.Bold
|
286 |
)
|
287 |
|
288 |
-
|
|
|
|
|
|
|
|
|
289 |
|
290 |
-
st.
|
291 |
-
|
292 |
-
with col2:
|
293 |
-
# قيمة أوامر الشراء حسب المورد
|
294 |
-
vendor_values = po_df.groupby("المورد")["القيمة (ريال)"].sum().reset_index()
|
295 |
-
vendor_values = vendor_values.sort_values("القيمة (ريال)", ascending=False).head(5)
|
296 |
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
|
301 |
-
|
302 |
-
|
303 |
-
|
304 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
|
306 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
307 |
|
308 |
-
st.plotly_chart(fig2, use_container_width=True)
|
309 |
-
|
310 |
-
# إضافة أمر شراء جديد
|
311 |
-
st.markdown("### إضافة أمر شراء جديد")
|
312 |
-
|
313 |
-
with st.expander("إضافة أمر شراء جديد"):
|
314 |
col1, col2 = st.columns(2)
|
315 |
|
316 |
with col1:
|
317 |
-
|
318 |
-
|
319 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
320 |
|
321 |
with col2:
|
322 |
-
|
323 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
عرض المناقصات الداخلية
|
331 |
-
"""
|
332 |
-
st.markdown("## المناقصات الداخلية")
|
333 |
-
|
334 |
-
# إنشاء بيانات توضيحية للمناقصات الداخلية
|
335 |
-
current_date = datetime.now().date()
|
336 |
-
|
337 |
-
tenders_data = {
|
338 |
-
"رقم المناقصة": [f"IT-{2025}-{i:04d}" for i in range(1001, 1009)],
|
339 |
-
"العنوان": [
|
340 |
-
"توريد معدات بناء ثقيلة",
|
341 |
-
"شراء مواد إنشائية",
|
342 |
-
"خدمات نقل وشحن",
|
343 |
-
"توريد أنظمة تكييف",
|
344 |
-
"خدمات تركيب كهربائية",
|
345 |
-
"توريد محولات كهربائية",
|
346 |
-
"خدمات أمن وسلامة",
|
347 |
-
"توريد أنظمة مراقبة"
|
348 |
-
],
|
349 |
-
"المشروع": [
|
350 |
-
"مشروع توسعة شبكة الطرق", "بناء المدارس", "تطوير البنية التحتية",
|
351 |
-
"تحديث شبكة المياه", "بناء المستشفى التخصصي", "إنشاء مركز البيانات",
|
352 |
-
"توسعة المطار", "تطوير الحدائق العامة"
|
353 |
-
],
|
354 |
-
"تاريخ النشر": [current_date - timedelta(days=np.random.randint(5, 30)) for _ in range(8)],
|
355 |
-
"الموعد النهائي": [current_date + timedelta(days=np.random.randint(10, 45)) for _ in range(8)],
|
356 |
-
"القيمة التقديرية (ريال)": [
|
357 |
-
np.random.randint(200000, 10000000) for _ in range(8)
|
358 |
-
],
|
359 |
-
"عدد العروض المستلمة": [np.random.randint(0, 10) for _ in range(8)],
|
360 |
-
"الحالة": np.random.choice(
|
361 |
-
["مفتوحة", "مغلقة", "قيد التقييم", "تم الترسية", "ملغاة"],
|
362 |
-
size=8,
|
363 |
-
p=[0.4, 0.1, 0.2, 0.2, 0.1]
|
364 |
-
)
|
365 |
-
}
|
366 |
-
|
367 |
-
# إنشاء DataFrame
|
368 |
-
tenders_df = pd.DataFrame(tenders_data)
|
369 |
-
|
370 |
-
# عرض فلاتر البحث
|
371 |
-
col1, col2 = st.columns(2)
|
372 |
-
|
373 |
-
with col1:
|
374 |
-
status_filter = st.selectbox(
|
375 |
-
"حالة المناقصة",
|
376 |
-
["الكل"] + sorted(tenders_df["الحالة"].unique().tolist())
|
377 |
-
)
|
378 |
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
["الكل"] + sorted(tenders_df["المشروع"].unique().tolist())
|
383 |
-
)
|
384 |
-
|
385 |
-
# تطبيق الفلاتر
|
386 |
-
filtered_tenders = tenders_df.copy()
|
387 |
-
|
388 |
-
if status_filter != "الكل":
|
389 |
-
filtered_tenders = filtered_tenders[filtered_tenders["الحالة"] == status_filter]
|
390 |
-
|
391 |
-
if project_filter != "الكل":
|
392 |
-
filtered_tenders = filtered_tenders[filtered_tenders["المشروع"] == project_filter]
|
393 |
-
|
394 |
-
# عرض المناقصات المصفاة
|
395 |
-
st.dataframe(filtered_tenders, use_container_width=True)
|
396 |
-
|
397 |
-
# تحليلات المناقصات
|
398 |
-
st.markdown("### تحليلات المناقصات الداخلية")
|
399 |
-
|
400 |
-
col1, col2 = st.columns(2)
|
401 |
-
|
402 |
-
with col1:
|
403 |
-
# توزيع المناقصات حسب الحالة
|
404 |
-
status_counts = tenders_df.groupby("الحالة").size().reset_index(name="العدد")
|
405 |
-
|
406 |
-
fig1 = px.pie(
|
407 |
-
status_counts,
|
408 |
-
values="العدد",
|
409 |
-
names="الحالة",
|
410 |
-
title="توزيع المناقصات حسب الحالة",
|
411 |
-
color_discrete_sequence=px.colors.qualitative.Bold
|
412 |
-
)
|
413 |
-
|
414 |
-
fig1.update_traces(textposition="inside", textinfo="percent+label")
|
415 |
|
416 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
lambda x: "توريد" if "توريد" in x else "خدمات" if "خدمات" in x else "أخرى"
|
422 |
-
)
|
423 |
|
424 |
-
|
425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
435 |
|
436 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
437 |
|
438 |
-
st.
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
459 |
-
|
460 |
-
|
461 |
-
|
462 |
-
|
463 |
-
|
464 |
-
|
465 |
-
|
466 |
-
|
467 |
-
|
468 |
-
|
469 |
-
|
470 |
-
|
471 |
-
vendors_eval_data = {
|
472 |
-
"المورد": [
|
473 |
-
"شركة الصناعات السعودية", "مؤسسة الخليج للمقاولات", "شركة الرياض للإنشاءات",
|
474 |
-
"الشركة العربية للمعدات", "مصنع المنتجات الإسمنتية", "شركة تقنيات البناء",
|
475 |
-
"مؤسسة المدار للتوريدات", "شركة البنية التحتية المتكاملة", "مصنع الصلب السعودي",
|
476 |
-
"شركة الأنابيب الوطنية"
|
477 |
-
],
|
478 |
-
"الفئة": [
|
479 |
-
"مواد بناء", "مقاولات", "خدمات هندسية", "معدات", "مواد خام",
|
480 |
-
"تقنيات", "مواد متنوعة", "بنية تحتية", "صناعات معدنية", "أنابيب"
|
481 |
-
],
|
482 |
-
"جودة المنتجات (5)": [4.5, 3.8, 4.2, 3.2, 4.7, 3.5, 3.9, 4.3, 4.6, 4.1],
|
483 |
-
"الالتزام بالمواعيد (5)": [4.2, 3.5, 4.0, 2.8, 4.5, 3.7, 3.6, 4.4, 4.3, 3.9],
|
484 |
-
"التنافسية السعرية (5)": [3.8, 4.2, 3.5, 4.6, 3.7, 4.1, 4.4, 3.8, 3.6, 4.0],
|
485 |
-
"الاستجابة والتواصل (5)": [4.3, 3.9, 4.5, 3.5, 4.2, 3.6, 3.8, 4.1, 4.4, 4.0],
|
486 |
-
"نسبة المحتوى المحلي (%)": [85, 92, 78, 65, 100, 70, 88, 75, 95, 82],
|
487 |
-
"عدد المشاريع المنفذة": [12, 8, 10, 5, 7, 6, 4, 9, 11, 8]
|
488 |
-
}
|
489 |
-
|
490 |
-
# إنشاء DataFrame
|
491 |
-
vendors_eval_df = pd.DataFrame(vendors_eval_data)
|
492 |
-
|
493 |
-
# حساب التقييم العام
|
494 |
-
eval_weights = {
|
495 |
-
"جودة المنتجات (5)": 0.35,
|
496 |
-
"الالتزام بالمواعيد (5)": 0.25,
|
497 |
-
"التنافسية السعرية (5)": 0.2,
|
498 |
-
"الاستجابة والتواصل (5)": 0.2
|
499 |
-
}
|
500 |
-
|
501 |
-
# حساب التقييم المرجح
|
502 |
-
for col, weight in eval_weights.items():
|
503 |
-
vendors_eval_df[f"{col} (مرجح)"] = vendors_eval_df[col] * weight
|
504 |
-
|
505 |
-
vendors_eval_df["التقييم العام"] = vendors_eval_df[[f"{col} (مرجح)" for col in eval_weights.keys()]].sum(axis=1)
|
506 |
-
|
507 |
-
# تصنيف الموردين
|
508 |
-
conditions = [
|
509 |
-
(vendors_eval_df["التقييم العام"] >= 4.5),
|
510 |
-
(vendors_eval_df["التقييم العام"] >= 4.0),
|
511 |
-
(vendors_eval_df["التقييم العام"] >= 3.5),
|
512 |
-
(vendors_eval_df["التقييم العام"] >= 3.0),
|
513 |
-
(vendors_eval_df["التقييم العام"] < 3.0)
|
514 |
-
]
|
515 |
-
values = ["ممتاز", "جيد جداً", "جيد", "مقبول", "ضعيف"]
|
516 |
-
vendors_eval_df["التصنيف"] = np.select(conditions, values)
|
517 |
-
|
518 |
-
# عرض مقارنة الموردين
|
519 |
-
st.markdown("### مقارنة تقييمات الموردين")
|
520 |
-
|
521 |
-
selected_vendors = st.multiselect(
|
522 |
-
"اختر الموردين للمقارنة",
|
523 |
-
vendors_eval_df["المورد"].tolist(),
|
524 |
-
default=vendors_eval_df["المورد"].tolist()[:5]
|
525 |
-
)
|
526 |
-
|
527 |
-
if selected_vendors:
|
528 |
-
# تصفية البيانات
|
529 |
-
selected_df = vendors_eval_df[vendors_eval_df["المورد"].isin(selected_vendors)]
|
530 |
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
import numpy as np
|
4 |
+
import matplotlib.pyplot as plt
|
5 |
+
import seaborn as sns
|
6 |
import plotly.express as px
|
7 |
import plotly.graph_objects as go
|
8 |
+
from datetime import datetime
|
9 |
+
import os
|
10 |
+
import sys
|
11 |
|
12 |
+
# إضافة المسار الرئيسي للمشروع إلى PATH
|
13 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
|
15 |
+
# استيراد الوحدات الخاصة بالمشروع
|
16 |
+
from modules.document_processor import DocumentProcessor
|
17 |
+
from modules.requirement_analyzer import RequirementAnalyzer
|
18 |
+
from modules.cost_risk_analyzer import CostRiskAnalyzer
|
19 |
+
from modules.schedule_analyzer import ScheduleAnalyzer
|
20 |
+
from modules.local_content import LocalContentCalculator
|
21 |
+
from modules.supply_chain import SupplyChainAnalyzer
|
22 |
+
from modules.ai_models import LLMProcessor, ArabicBERTModel
|
23 |
+
from utils.database import VectorDBConnector, TemplateLoader
|
24 |
+
from utils.api_integrations import MunafasatAPI, EtimadAPI, BaladyAPI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
25 |
|
26 |
+
# تكوين الصفحة
|
27 |
+
st.set_page_config(
|
28 |
+
page_title="تحليل المناقصات والعقود",
|
29 |
+
page_icon="📋",
|
30 |
+
layout="wide",
|
31 |
+
initial_sidebar_state="expanded"
|
32 |
+
)
|
33 |
+
|
34 |
+
# تحديد النمط والتصميم
|
35 |
+
st.markdown("""
|
36 |
+
<style>
|
37 |
+
.main {
|
38 |
+
direction: rtl;
|
39 |
+
text-align: right;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
}
|
41 |
+
.stTabs [data-baseweb="tab-list"] {
|
42 |
+
gap: 24px;
|
43 |
+
}
|
44 |
+
.stTabs [data-baseweb="tab"] {
|
45 |
+
height: 50px;
|
46 |
+
white-space: pre-wrap;
|
47 |
+
background-color: #F0F2F6;
|
48 |
+
border-radius: 4px 4px 0px 0px;
|
49 |
+
gap: 1px;
|
50 |
+
padding-top: 10px;
|
51 |
+
padding-bottom: 10px;
|
52 |
+
}
|
53 |
+
.css-12oz5g7 {
|
54 |
+
flex-direction: row-reverse;
|
55 |
+
}
|
56 |
+
.css-1v3fvcr {
|
57 |
+
direction: rtl;
|
58 |
+
}
|
59 |
+
.stMarkdown {
|
60 |
+
direction: rtl;
|
61 |
+
}
|
62 |
+
</style>
|
63 |
+
""", unsafe_allow_html=True)
|
64 |
+
|
65 |
+
# ------------------------------------------------------------------------
|
66 |
+
# الدوال المساعدة
|
67 |
+
# ------------------------------------------------------------------------
|
68 |
+
@st.cache_data(ttl=3600)
|
69 |
+
def load_tender_templates():
|
70 |
+
"""تحميل قوالب المناقصات من قاعدة البيانات"""
|
71 |
+
template_loader = TemplateLoader()
|
72 |
+
return template_loader.load_tender_templates()
|
73 |
+
|
74 |
+
@st.cache_data(ttl=3600)
|
75 |
+
def load_supplier_database():
|
76 |
+
"""تحميل قاعدة بيانات الموردين"""
|
77 |
+
try:
|
78 |
+
supply_chain = SupplyChainAnalyzer()
|
79 |
+
return supply_chain.get_suppliers_database()
|
80 |
+
except Exception as e:
|
81 |
+
st.error(f"خطأ في تحميل قاعدة بيانات الموردين: {e}")
|
82 |
+
return pd.DataFrame()
|
83 |
+
|
84 |
+
def process_uploaded_documents(uploaded_files):
|
85 |
+
"""معالجة المستندات المرفوعة"""
|
86 |
+
if not uploaded_files:
|
87 |
+
return None, None
|
88 |
|
89 |
+
document_processor = DocumentProcessor()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
+
extracted_data = {}
|
92 |
+
file_contents = {}
|
93 |
|
94 |
+
for file in uploaded_files:
|
95 |
+
try:
|
96 |
+
file_content = file.read()
|
97 |
+
file_extension = file.name.split(".")[-1].lower()
|
98 |
+
|
99 |
+
file_contents[file.name] = file_content
|
100 |
+
processed_data = document_processor.process_document(
|
101 |
+
file_content,
|
102 |
+
file_extension,
|
103 |
+
file.name
|
104 |
+
)
|
105 |
+
|
106 |
+
extracted_data[file.name] = processed_data
|
107 |
+
|
108 |
+
except Exception as e:
|
109 |
+
st.error(f"خطأ في معالجة الملف {file.name}: {e}")
|
110 |
|
111 |
+
return extracted_data, file_contents
|
112 |
+
|
113 |
+
def analyze_requirements(extracted_data):
|
114 |
+
"""تحليل متطلبات المناقصة"""
|
115 |
+
if not extracted_data:
|
116 |
+
return None
|
117 |
|
118 |
+
requirement_analyzer = RequirementAnalyzer()
|
|
|
119 |
|
120 |
+
analyzed_results = {}
|
121 |
+
for file_name, data in extracted_data.items():
|
122 |
+
analyzed_results[file_name] = requirement_analyzer.analyze(data)
|
123 |
|
124 |
+
return analyzed_results
|
125 |
+
|
126 |
+
def analyze_local_content(extracted_data, project_data):
|
127 |
+
"""تحليل المحتوى المحلي للمناقصة"""
|
128 |
+
if not extracted_data or not project_data:
|
129 |
+
return None
|
130 |
+
|
131 |
+
local_content_calculator = LocalContentCalculator()
|
132 |
+
|
133 |
+
# استخراج بيانات المشروع ذات الصلة
|
134 |
+
project_type = project_data.get("project_type", "")
|
135 |
+
budget = project_data.get("budget", 0)
|
136 |
+
location = project_data.get("location", "")
|
137 |
+
duration = project_data.get("duration", 0)
|
138 |
+
|
139 |
+
# تحليل المحتوى المحلي
|
140 |
+
local_content_results = local_content_calculator.calculate(
|
141 |
+
extracted_data,
|
142 |
+
project_type=project_type,
|
143 |
+
budget=budget,
|
144 |
+
location=location,
|
145 |
+
duration=duration
|
146 |
+
)
|
147 |
|
148 |
+
return local_content_results
|
149 |
+
|
150 |
+
# ------------------------------------------------------------------------
|
151 |
+
# واجهة المستخدم الرئيسية
|
152 |
+
# ------------------------------------------------------------------------
|
153 |
+
def main():
|
154 |
+
st.title("نظام تحليل المناقصات والعقود")
|
155 |
+
|
156 |
+
# الشريط الجانبي
|
157 |
+
with st.sidebar:
|
158 |
+
st.header("الإعدادات والخيارات")
|
159 |
|
160 |
+
st.subheader("رفع المستندات")
|
161 |
+
uploaded_files = st.file_uploader(
|
162 |
+
"رفع وثائق المناقصة، العقد، أو الملفات ذات الصلة",
|
163 |
+
accept_multiple_files=True,
|
164 |
+
type=["pdf", "docx", "xlsx", "csv", "txt"]
|
|
|
165 |
)
|
166 |
|
167 |
+
st.subheader("ضبط التحليل")
|
168 |
+
analysis_mode = st.radio(
|
169 |
+
"اختر نمط التحليل:",
|
170 |
+
["سريع", "متوسط", "متقدم"]
|
171 |
+
)
|
172 |
|
173 |
+
use_ai = st.checkbox("استخدام الذكاء الاصطناعي للتحليل المتقدم", value=True)
|
|
|
|
|
|
|
|
|
|
|
174 |
|
175 |
+
# إعدادات متقدمة
|
176 |
+
with st.expander("إعدادات متقدمة"):
|
177 |
+
ai_model = st.selectbox(
|
178 |
+
"نموذج الذكاء الاصطناعي",
|
179 |
+
["Claude (Anthropic)", "AraGPT", "BERT عربي مخصص"]
|
180 |
+
)
|
181 |
+
|
182 |
+
similarity_threshold = st.slider(
|
183 |
+
"عتبة التشابه للتوصيات",
|
184 |
+
min_value=0.5,
|
185 |
+
max_value=0.95,
|
186 |
+
value=0.75,
|
187 |
+
step=0.05
|
188 |
+
)
|
189 |
+
|
190 |
+
supply_chain_depth = st.slider(
|
191 |
+
"عمق تحليل سلسلة الإمداد",
|
192 |
+
min_value=1,
|
193 |
+
max_value=5,
|
194 |
+
value=2,
|
195 |
+
step=1
|
196 |
+
)
|
197 |
|
198 |
+
# زر تحليل
|
199 |
+
analysis_btn = st.button("بدء التحليل", type="primary")
|
200 |
+
|
201 |
+
# الأقسام الرئيسية للتطبيق
|
202 |
+
tabs = st.tabs([
|
203 |
+
"نظرة عامة",
|
204 |
+
"تحليل المتطلبات",
|
205 |
+
"تحليل التكاليف والمخاطر",
|
206 |
+
"المحتوى المحلي",
|
207 |
+
"سلسلة الإمداد",
|
208 |
+
"الجدول الزمني",
|
209 |
+
"التوصيات والملخص"
|
210 |
+
])
|
211 |
+
|
212 |
+
# حالة التطبيق
|
213 |
+
if "project_data" not in st.session_state:
|
214 |
+
st.session_state.project_data = {
|
215 |
+
"project_title": "",
|
216 |
+
"project_type": "",
|
217 |
+
"project_number": "",
|
218 |
+
"budget": 0,
|
219 |
+
"location": "",
|
220 |
+
"duration": 0,
|
221 |
+
"start_date": None,
|
222 |
+
"end_date": None
|
223 |
+
}
|
224 |
+
|
225 |
+
if "analysis_results" not in st.session_state:
|
226 |
+
st.session_state.analysis_results = None
|
227 |
+
|
228 |
+
if "extracted_data" not in st.session_state:
|
229 |
+
st.session_state.extracted_data = None
|
230 |
+
|
231 |
+
# نظرة عامة - القسم الأول
|
232 |
+
with tabs[0]:
|
233 |
+
st.header("معلومات المشروع / المناقصة")
|
234 |
|
|
|
|
|
|
|
|
|
|
|
|
|
235 |
col1, col2 = st.columns(2)
|
236 |
|
237 |
with col1:
|
238 |
+
st.session_state.project_data["project_title"] = st.text_input(
|
239 |
+
"عنوان المشروع / المناقصة",
|
240 |
+
value=st.session_state.project_data.get("project_title", "")
|
241 |
+
)
|
242 |
+
|
243 |
+
st.session_state.project_data["project_type"] = st.selectbox(
|
244 |
+
"نوع المشروع",
|
245 |
+
[
|
246 |
+
"", "إنشاءات", "تقنية معلومات", "استشارات",
|
247 |
+
"توريد معدات", "خدمات", "أخرى"
|
248 |
+
],
|
249 |
+
index=0
|
250 |
+
)
|
251 |
+
|
252 |
+
st.session_state.project_data["project_number"] = st.text_input(
|
253 |
+
"رقم المشروع / المناقصة",
|
254 |
+
value=st.session_state.project_data.get("project_number", "")
|
255 |
+
)
|
256 |
+
|
257 |
+
st.session_state.project_data["budget"] = st.number_input(
|
258 |
+
"الميزانية التقديرية (ريال سعودي)",
|
259 |
+
min_value=0,
|
260 |
+
value=int(st.session_state.project_data.get("budget", 0))
|
261 |
+
)
|
262 |
|
263 |
with col2:
|
264 |
+
st.session_state.project_data["location"] = st.text_input(
|
265 |
+
"الموقع",
|
266 |
+
value=st.session_state.project_data.get("location", "")
|
267 |
+
)
|
268 |
+
|
269 |
+
st.session_state.project_data["duration"] = st.number_input(
|
270 |
+
"المدة (بالأشهر)",
|
271 |
+
min_value=0,
|
272 |
+
value=int(st.session_state.project_data.get("duration", 0))
|
273 |
+
)
|
274 |
+
|
275 |
+
start_date = st.date_input(
|
276 |
+
"تاريخ البدء المتوقع",
|
277 |
+
value=st.session_state.project_data.get("start_date", datetime.now().date())
|
278 |
+
)
|
279 |
+
st.session_state.project_data["start_date"] = start_date
|
280 |
+
|
281 |
+
end_date = st.date_input(
|
282 |
+
"تاريخ الانتهاء المتوقع",
|
283 |
+
value=st.session_state.project_data.get("end_date", None)
|
284 |
+
)
|
285 |
+
st.session_state.project_data["end_date"] = end_date
|
286 |
|
287 |
+
# عرض المستندات المرفوعة
|
288 |
+
if uploaded_files:
|
289 |
+
st.subheader("المستندات المرفوعة")
|
290 |
+
file_list = ", ".join([file.name for file in uploaded_files])
|
291 |
+
st.info(f"تم رفع {len(uploaded_files)} ملفات: {file_list}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
292 |
|
293 |
+
# تحليل المتطلبات - القسم الثاني
|
294 |
+
with tabs[1]:
|
295 |
+
st.header("تحليل المتطلبات")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
296 |
|
297 |
+
if st.session_state.extracted_data is not None:
|
298 |
+
# عرض المتطلبات المستخرجة
|
299 |
+
st.subheader("المتطلبات الرئيسية")
|
300 |
+
|
301 |
+
# هنا سنفترض أن البيانات المستخرجة تحتوي على مفتاح 'requirements'
|
302 |
+
requirements_found = False
|
303 |
+
|
304 |
+
for file_name, data in st.session_state.extracted_data.items():
|
305 |
+
if 'requirements' in data:
|
306 |
+
requirements_found = True
|
307 |
+
st.write(f"المتطلبات من الملف: {file_name}")
|
308 |
+
|
309 |
+
for i, req in enumerate(data['requirements']):
|
310 |
+
with st.expander(f"المتطلب {i+1}: {req.get('title', 'متطلب')}"):
|
311 |
+
st.write(f"**الوصف:** {req.get('description', 'لا يوجد وصف')}")
|
312 |
+
st.write(f"**الأهمية:** {req.get('importance', 'عادية')}")
|
313 |
+
st.write(f"**الفئة:** {req.get('category', 'عامة')}")
|
314 |
+
|
315 |
+
if 'compliance' in req:
|
316 |
+
st.write(f"**الامتثال:** {req['compliance']}")
|
317 |
+
|
318 |
+
if not requirements_found:
|
319 |
+
st.info("لم يتم استخراج متطلبات محددة من المستندات. يرجى رفع وثائق المناقصة أو العقد.")
|
320 |
+
else:
|
321 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض المتطلبات.")
|
322 |
|
323 |
+
# تحليل التكاليف والمخاطر - القسم الثالث
|
324 |
+
with tabs[2]:
|
325 |
+
st.header("تحليل التكاليف والمخاطر")
|
|
|
|
|
326 |
|
327 |
+
if st.session_state.analysis_results is not None:
|
328 |
+
cost_tab, risk_tab = st.tabs(["تحليل التكاليف", "تحليل المخاطر"])
|
329 |
+
|
330 |
+
with cost_tab:
|
331 |
+
st.subheader("هيكل التكاليف")
|
332 |
+
# هنا يمكن إضافة رسوم بيانية وتحليلات للتكاليف
|
333 |
+
|
334 |
+
# مثال لرسم بياني افتراضي
|
335 |
+
if 'cost_breakdown' in st.session_state.analysis_results:
|
336 |
+
cost_data = st.session_state.analysis_results['cost_breakdown']
|
337 |
+
fig = px.pie(
|
338 |
+
values=list(cost_data.values()),
|
339 |
+
names=list(cost_data.keys()),
|
340 |
+
title="توزيع التكاليف"
|
341 |
+
)
|
342 |
+
st.plotly_chart(fig)
|
343 |
+
else:
|
344 |
+
st.write("لم يتم العثور على بيانات التكاليف")
|
345 |
+
|
346 |
+
with risk_tab:
|
347 |
+
st.subheader("تقييم المخاطر")
|
348 |
+
# هنا يمكن إضافة تحليل المخاطر
|
349 |
+
|
350 |
+
# مثال لعرض جدول المخاطر
|
351 |
+
if 'risks' in st.session_state.analysis_results:
|
352 |
+
risks_df = pd.DataFrame(st.session_state.analysis_results['risks'])
|
353 |
+
st.dataframe(risks_df)
|
354 |
+
else:
|
355 |
+
st.write("لم يتم العثور على بيانات المخاطر")
|
356 |
+
else:
|
357 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض تحليل التكاليف والمخاطر.")
|
358 |
+
|
359 |
+
# المحتوى المحلي - القسم الرابع
|
360 |
+
with tabs[3]:
|
361 |
+
st.header("تحليل المحتوى المحلي")
|
362 |
|
363 |
+
if st.session_state.analysis_results is not None and 'local_content' in st.session_state.analysis_results:
|
364 |
+
local_content = st.session_state.analysis_results['local_content']
|
365 |
+
|
366 |
+
# عرض النسبة الإجمالية للمحتوى المحلي
|
367 |
+
if 'overall_percentage' in local_content:
|
368 |
+
st.metric(
|
369 |
+
label="نسبة المحتوى المحلي الإجمالية",
|
370 |
+
value=f"{local_content['overall_percentage']:.2f}%"
|
371 |
+
)
|
372 |
+
|
373 |
+
# عرض تفاصيل المحتوى المحلي
|
374 |
+
if 'breakdown' in local_content:
|
375 |
+
st.subheader("تفاصيل المحتوى المحلي")
|
376 |
+
|
377 |
+
breakdown = local_content['breakdown']
|
378 |
+
fig = px.bar(
|
379 |
+
x=list(breakdown.keys()),
|
380 |
+
y=list(breakdown.values()),
|
381 |
+
title="تحليل المحتوى المحلي حسب الفئة"
|
382 |
+
)
|
383 |
+
fig.update_layout(
|
384 |
+
xaxis_title="الفئة",
|
385 |
+
yaxis_title="النسبة المئوية"
|
386 |
+
)
|
387 |
+
st.plotly_chart(fig)
|
388 |
+
|
389 |
+
# توصيات لتحسين نسبة المحتوى المحلي
|
390 |
+
if 'recommendations' in local_content:
|
391 |
+
st.subheader("توصيات لتحسين المحتوى المحلي")
|
392 |
+
|
393 |
+
for i, rec in enumerate(local_content['recommendations']):
|
394 |
+
st.write(f"{i+1}. {rec}")
|
395 |
+
else:
|
396 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض تحليل المحتوى المحلي.")
|
397 |
+
|
398 |
+
# سلسلة الإمداد - القسم الخامس
|
399 |
+
with tabs[4]:
|
400 |
+
st.header("تحليل سلسلة الإمداد")
|
401 |
|
402 |
+
if st.session_state.analysis_results is not None and 'supply_chain' in st.session_state.analysis_results:
|
403 |
+
supply_chain = st.session_state.analysis_results['supply_chain']
|
404 |
+
|
405 |
+
# عرض الموردين المحتملين
|
406 |
+
if 'potential_suppliers' in supply_chain:
|
407 |
+
st.subheader("الموردين المحتملين")
|
408 |
+
suppliers_df = pd.DataFrame(supply_chain['potential_suppliers'])
|
409 |
+
st.dataframe(suppliers_df)
|
410 |
+
|
411 |
+
# عرض المخاطر المتعلقة بسلسلة الإمداد
|
412 |
+
if 'risks' in supply_chain:
|
413 |
+
st.subheader("مخاطر سلسلة الإمداد")
|
414 |
+
|
415 |
+
for i, risk in enumerate(supply_chain['risks']):
|
416 |
+
with st.expander(f"المخاطرة {i+1}: {risk['title']}"):
|
417 |
+
st.write(f"**الوصف:** {risk['description']}")
|
418 |
+
st.write(f"**الاحتمالية:** {risk['probability']}")
|
419 |
+
st.write(f"**التأثير:** {risk['impact']}")
|
420 |
+
st.write(f"**استراتيجيات التخفيف:** {risk['mitigation']}")
|
421 |
+
|
422 |
+
# عرض توصيات تحسين سلسلة الإمداد
|
423 |
+
if 'optimization' in supply_chain:
|
424 |
+
st.subheader("توصيات تحسين سلسلة الإمداد")
|
425 |
+
|
426 |
+
for i, opt in enumerate(supply_chain['optimization']):
|
427 |
+
st.write(f"{i+1}. {opt}")
|
428 |
+
else:
|
429 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض تحليل سلسلة الإمداد.")
|
430 |
+
|
431 |
+
# الجدول الزمني - القسم السادس
|
432 |
+
with tabs[5]:
|
433 |
+
st.header("تحليل الجدول الزمني")
|
434 |
|
435 |
+
if st.session_state.analysis_results is not None and 'schedule' in st.session_state.analysis_results:
|
436 |
+
schedule = st.session_state.analysis_results['schedule']
|
437 |
+
|
438 |
+
# عرض المراحل الرئيسية للمشروع
|
439 |
+
if 'phases' in schedule:
|
440 |
+
st.subheader("المراحل الرئيسية للمشروع")
|
441 |
+
|
442 |
+
for i, phase in enumerate(schedule['phases']):
|
443 |
+
with st.expander(f"المرحلة {i+1}: {phase['name']}"):
|
444 |
+
st.write(f"**البداية:** {phase['start_date']}")
|
445 |
+
st.write(f"**النهاية:** {phase['end_date']}")
|
446 |
+
st.write(f"**المدة:** {phase['duration']} أيام")
|
447 |
+
st.write(f"**الأنشطة:** {', '.join(phase['activities'])}")
|
448 |
+
|
449 |
+
# عرض المسار الحرج
|
450 |
+
if 'critical_path' in schedule:
|
451 |
+
st.subheader("المسار الحرج")
|
452 |
+
|
453 |
+
for i, activity in enumerate(schedule['critical_path']):
|
454 |
+
st.write(f"{i+1}. {activity}")
|
455 |
+
|
456 |
+
# عرض توصيات لتحسين الجدول الزمني
|
457 |
+
if 'optimization' in schedule:
|
458 |
+
st.subheader("توصيات تحسين الجدول الزمني")
|
459 |
+
|
460 |
+
for i, opt in enumerate(schedule['optimization']):
|
461 |
+
st.write(f"{i+1}. {opt}")
|
462 |
+
else:
|
463 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض تحليل الجدول الزمني.")
|
464 |
+
|
465 |
+
# التوصيات والملخص - القسم السابع
|
466 |
+
with tabs[6]:
|
467 |
+
st.header("التوصيات والملخص")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
468 |
|
469 |
+
if st.session_state.analysis_results is not None and 'recommendations' in st.session_state.analysis_results:
|
470 |
+
recommendations = st.session_state.analysis_results['recommendations']
|
471 |
+
|
472 |
+
# عرض التوصيات الرئيسية
|
473 |
+
st.subheader("التوصيات الرئيسية")
|
474 |
+
|
475 |
+
for i, rec in enumerate(recommendations):
|
476 |
+
with st.expander(f"التوصية {i+1}: {rec['title']}"):
|
477 |
+
st.write(f"**الوصف:** {rec['description']}")
|
478 |
+
st.write(f"**الأولوية:** {rec['priority']}")
|
479 |
+
st.write(f"**الفوائد:** {rec['benefits']}")
|
480 |
+
|
481 |
+
if 'implementation' in rec:
|
482 |
+
st.write(f"**خطوات التنفيذ:**")
|
483 |
+
for j, step in enumerate(rec['implementation']):
|
484 |
+
st.write(f" {j+1}. {step}")
|
485 |
+
|
486 |
+
# عرض الملخص التنفيذي
|
487 |
+
if 'executive_summary' in st.session_state.analysis_results:
|
488 |
+
st.subheader("الملخص التنفيذي")
|
489 |
+
st.write(st.session_state.analysis_results['executive_summary'])
|
490 |
+
else:
|
491 |
+
st.info("يرجى رفع المستندات وبدء التحليل لعرض التوصيات والملخص.")
|
492 |
+
|
493 |
+
# معالجة زر التحليل
|
494 |
+
if analysis_btn and uploaded_files:
|
495 |
+
with st.spinner("جارٍ تحليل المستندات..."):
|
496 |
+
# معالجة المستندات المرفوعة
|
497 |
+
extracted_data, file_contents = process_uploaded_documents(uploaded_files)
|
498 |
+
st.session_state.extracted_data = extracted_data
|
499 |
+
|
500 |
+
# تحليل المتطلبات
|
501 |
+
requirement_results = analyze_requirements(extracted_data)
|
502 |
+
|
503 |
+
# تحليل المحتوى المحلي
|
504 |
+
local_content_results = analyze_local_content(
|
505 |
+
extracted_data,
|
506 |
+
st.session_state.project_data
|
507 |
+
)
|
508 |
+
|
509 |
+
# إنشاء نتائج التحليل الشاملة
|
510 |
+
st.session_state.analysis_results = {
|
511 |
+
"requirements": requirement_results,
|
512 |
+
"local_content": local_content_results,
|
513 |
+
# هنا ستضاف نتائج التحليلات الأخرى
|
514 |
+
}
|
515 |
+
|
516 |
+
st.success("تم الانتهاء من التحليل!")
|
517 |
+
elif analysis_btn and not uploaded_files:
|
518 |
+
st.error("يرجى رفع المستندات المطلوبة أولاً.")
|
519 |
+
|
520 |
+
if __name__ == "__main__":
|
521 |
+
main()
|