EGYADMIN commited on
Commit
34dc2df
·
verified ·
1 Parent(s): b364a1a

Update web/pages/procurement.py

Browse files
Files changed (1) hide show
  1. 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, timedelta
 
 
7
 
8
- def show_procurement():
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
- def show_active_contracts():
34
- """
35
- عرض بيانات العقود النشطة
36
- """
37
- st.markdown("## العقود النشطة")
38
-
39
- # إنشاء بيانات توضيحية للعقود
40
- current_date = datetime.now().date()
41
-
42
- contracts_data = {
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
- def show_purchase_orders():
203
- """
204
- عرض أوامر الشراء
205
- """
206
- st.markdown("## أوامر الشراء")
207
-
208
- # إنشاء بيانات توضيحية لأوامر الشراء
209
- current_date = datetime.now().date()
210
-
211
- po_data = {
212
- "رقم أمر الشراء": [f"PO-{2025}-{i:04d}" for i in range(1001, 1011)],
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
- # إنشاء DataFrame
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
- filtered_po = po_df.copy()
261
 
262
- if status_filter != "الكل":
263
- filtered_po = filtered_po[filtered_po["الحالة"] == status_filter]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
264
 
265
- if vendor_filter != "الكل":
266
- filtered_po = filtered_po[filtered_po["المورد"] == vendor_filter]
 
 
 
 
267
 
268
- # عرض أوامر الشراء المصفاة
269
- st.dataframe(filtered_po, use_container_width=True)
270
 
271
- # تحليلات أوامر الشراء
272
- st.markdown("### تحليلات أوامر الشراء")
 
273
 
274
- col1, col2 = st.columns(2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
275
 
276
- with col1:
277
- # توزيع أوامر الشراء حسب الحالة
278
- status_counts = po_df.groupby("الحالة").size().reset_index(name="العدد")
 
 
 
 
 
 
 
 
279
 
280
- fig1 = px.pie(
281
- status_counts,
282
- values="العدد",
283
- names="الحالة",
284
- title="توزيع أوامر الشراء حسب الحالة",
285
- color_discrete_sequence=px.colors.qualitative.Bold
286
  )
287
 
288
- fig1.update_traces(textposition="inside", textinfo="percent+label")
 
 
 
 
289
 
290
- st.plotly_chart(fig1, use_container_width=True)
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
- fig2 = px.bar(
298
- vendor_values,
299
- x="المورد",
300
- y="القيمة (ريال)",
301
- title="أعلى 5 موردين حسب قيمة أوامر الشراء",
302
- color="القيمة (ريال)",
303
- color_continuous_scale="Viridis"
304
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
305
 
306
- fig2.update_yaxes(title_text="القيمة (ريال)")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- new_vendor = st.selectbox("المورد", sorted(po_df["المورد"].unique().tolist()))
318
- new_project = st.selectbox("المشروع", sorted(po_df["المشروع"].unique().tolist()))
319
- new_value = st.number_input("القيمة (ريال)", min_value=1000, max_value=10000000, value=100000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
 
321
  with col2:
322
- new_delivery_date = st.date_input("تاريخ التسليم المتوقع", value=current_date + timedelta(days=30))
323
- new_description = st.text_area("وصف الطلب", height=100)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
 
325
- if st.button("إضافة أمر الشراء"):
326
- st.success(f"تم إضافة أمر الشراء بنجاح للمورد {new_vendor} بقيمة {new_value:,} ريال")
327
-
328
- def show_internal_tenders():
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
- with col2:
380
- project_filter = st.selectbox(
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.plotly_chart(fig1, use_container_width=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
 
418
- with col2:
419
- # متوسط عدد العروض حسب نوع المناقصة
420
- tenders_df["نوع المناقصة"] = tenders_df["العنوان"].apply(
421
- lambda x: "توريد" if "توريد" in x else "خدمات" if "خدمات" in x else "أخرى"
422
- )
423
 
424
- avg_offers = tenders_df.groupby("نوع المناقصة")["عدد العروض المستلمة"].mean().reset_index()
425
- avg_offers["عدد العروض المستلمة"] = avg_offers["عدد العروض المستلمة"].round(1)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
426
 
427
- fig2 = px.bar(
428
- avg_offers,
429
- x="نوع المناقصة",
430
- y="عدد العروض المستلمة",
431
- title="متوسط عدد العروض حسب نوع المناقصة",
432
- color="نوع المناقصة",
433
- text="عدد العروض المستلمة"
434
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
435
 
436
- fig2.update_traces(texttemplate="%{text}", textposition="outside")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
437
 
438
- st.plotly_chart(fig2, use_container_width=True)
439
-
440
- # المناقصات القريبة من الإغلاق
441
- st.markdown("### المناقصات القريبة من الموعد النهائي")
442
-
443
- closing_soon = tenders_df[
444
- (tenders_df["الموعد النهائي"] > current_date) &
445
- (tenders_df["الموعد النهائي"] <= current_date + timedelta(days=7)) &
446
- (tenders_df["الحالة"] == "مفتوحة")
447
- ].sort_values("الموعد النهائي")
448
-
449
- if not closing_soon.empty:
450
- for _, tender in closing_soon.iterrows():
451
- days_left = (tender["الموعد النهائي"] - current_date).days
452
-
453
- st.markdown(f"""
454
- **{tender['رقم المناقصة']} - {tender['العنوان']}**
455
- **المشروع:** {tender['المشروع']}
456
- **الموعد النهائي:** {tender['الموعد النهائي'].strftime('%Y/%m/%d')} ({days_left} أيام متبقية)
457
- **القيمة التقديرية:** {tender['القيمة التقديرية (ريال)']:,} ريال
458
- **العروض المستلمة حتى الآن:** {tender['عدد العروض المستلمة']}
459
- """)
460
- st.markdown("---")
461
- else:
462
- st.info("لا توجد مناقصات على وشك الإغلاق خلال الأسبوع القادم")
463
-
464
- def show_vendor_evaluation():
465
- """
466
- عرض تقييم الموردين
467
- """
468
- st.markdown("## تقييم الموردين")
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
- if selected_vendors:
533
- selected_df = vendors_eval_df[vendors_eval_df["المورد"].isin(selected_vendors)]
534
- st.dataframe(selected_df)
535
- else:
536
- st.write("يرجى اختيار مورد لعرض البيانات.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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()