EGYADMIN commited on
Commit
1bec93c
·
verified ·
1 Parent(s): 93cd151

Create modules/supply_chain.py

Browse files
Files changed (1) hide show
  1. modules/supply_chain.py +715 -0
modules/supply_chain.py ADDED
@@ -0,0 +1,715 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import pandas as pd
4
+ import numpy as np
5
+ from typing import Dict, List, Any, Union, Tuple, Optional
6
+ from datetime import datetime
7
+
8
+ class SupplyChainAnalyzer:
9
+ """
10
+ فئة لتحليل سلسلة الإمداد في المناقصات
11
+ """
12
+
13
+ def __init__(self):
14
+ """
15
+ تهيئة محلل سلسلة الإمداد
16
+ """
17
+ # تحميل قاعدة بيانات الموردين
18
+ self.suppliers_db = self._load_suppliers_database()
19
+
20
+ # تحميل قاعدة بيانات المواد
21
+ self.materials_db = self._load_materials_database()
22
+
23
+ # تحميل قاعدة بيانات المخاطر
24
+ self.risks_db = self._load_risks_database()
25
+
26
+ def _load_suppliers_database(self) -> Dict[str, Dict[str, Any]]:
27
+ """
28
+ تحميل قاعدة بيانات الموردين
29
+ """
30
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
31
+ return {
32
+ "supplier1": {
33
+ "id": "S001",
34
+ "name": "شركة المواد الإنشائية السعودية",
35
+ "sector": "الإنشاءات",
36
+ "location": "الرياض",
37
+ "categories": ["مواد بناء", "حديد", "أسمنت", "خرسانة"],
38
+ "local_content_percentage": 85,
39
+ "rating": 4.5,
40
+ "contact": {
41
+ "email": "[email protected]",
42
+ "phone": "+966-11-XXXXXXX",
43
+ "website": "www.saudiconstruction.com"
44
+ },
45
+ "certifications": ["ISO 9001", "شهادة المحتوى المحلي"],
46
+ "historical_performance": {
47
+ "on_time_delivery": 92,
48
+ "quality": 90,
49
+ "cost": 85
50
+ }
51
+ },
52
+ "supplier2": {
53
+ "id": "S002",
54
+ "name": "شركة التقنية السعودية",
55
+ "sector": "تقنية المعلومات",
56
+ "location": "جدة",
57
+ "categories": ["أجهزة حاسب", "برمجيات", "شبكات", "أمن معلومات"],
58
+ "local_content_percentage": 65,
59
+ "rating": 4.2,
60
+ "contact": {
61
+ "email": "[email protected]",
62
+ "phone": "+966-12-XXXXXXX",
63
+ "website": "www.sauditech.com"
64
+ },
65
+ "certifications": ["ISO 27001", "شهادة المحتوى المحلي"],
66
+ "historical_performance": {
67
+ "on_time_delivery": 88,
68
+ "quality": 92,
69
+ "cost": 80
70
+ }
71
+ },
72
+ "supplier3": {
73
+ "id": "S003",
74
+ "name": "مصنع المعادن السعودي",
75
+ "sector": "الصناعة",
76
+ "location": "الدمام",
77
+ "categories": ["معادن", "ألمنيوم", "نحاس", "فولاذ"],
78
+ "local_content_percentage": 92,
79
+ "rating": 4.3,
80
+ "contact": {
81
+ "email": "[email protected]",
82
+ "phone": "+966-13-XXXXXXX",
83
+ "website": "www.saudimetal.com"
84
+ },
85
+ "certifications": ["ISO 9001", "ISO 14001", "شهادة المحتوى المحلي"],
86
+ "historical_performance": {
87
+ "on_time_delivery": 90,
88
+ "quality": 91,
89
+ "cost": 82
90
+ }
91
+ }
92
+ }
93
+
94
+ def _load_materials_database(self) -> Dict[str, Dict[str, Any]]:
95
+ """
96
+ تحميل قاعدة بيانات المواد
97
+ """
98
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
99
+ return {
100
+ "material1": {
101
+ "id": "M001",
102
+ "name": "حديد تسليح",
103
+ "category": "مواد بناء",
104
+ "local_manufacturers": ["مصنع المعادن السعودي", "شركة حديد الراجحي"],
105
+ "average_price": 3000, # ريال سعودي للطن
106
+ "lead_time": 14, # بالأيام
107
+ "risk_level": "متوسط",
108
+ "local_content_percentage": 90
109
+ },
110
+ "material2": {
111
+ "id": "M002",
112
+ "name": "أسمنت بورتلاندي",
113
+ "category": "مواد بناء",
114
+ "local_manufacturers": ["شركة أسمنت اليمامة", "شركة أسمنت السعودية"],
115
+ "average_price": 15, # ريال سعودي للكيس
116
+ "lead_time": 7, # بالأيام
117
+ "risk_level": "منخفض",
118
+ "local_content_percentage": 100
119
+ },
120
+ "material3": {
121
+ "id": "M003",
122
+ "name": "خادم حاسوبي",
123
+ "category": "تقنية معلومات",
124
+ "local_manufacturers": ["شركة التقنية السعودية"],
125
+ "average_price": 15000, # ريال سعودي للوحدة
126
+ "lead_time": 30, # بالأيام
127
+ "risk_level": "عالي",
128
+ "local_content_percentage": 60
129
+ }
130
+ }
131
+
132
+ def _load_risks_database(self) -> Dict[str, Dict[str, Any]]:
133
+ """
134
+ تحميل قاعدة بيانات المخاطر
135
+ """
136
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
137
+ return {
138
+ "risk1": {
139
+ "id": "R001",
140
+ "title": "انقطاع سلسلة التوريد",
141
+ "description": "عدم قدرة الموردين على توفير المواد في الوقت المحدد",
142
+ "probability": "متوسط",
143
+ "impact": "عالي",
144
+ "mitigation": [
145
+ "وجود موردين بدلاء",
146
+ "الاحتفاظ بمخزون استراتيجي",
147
+ "التعاقد طويل الأمد مع الموردين الرئيسيين"
148
+ ],
149
+ "sector": "عام"
150
+ },
151
+ "risk2": {
152
+ "id": "R002",
153
+ "title": "تقلبات الأسعار",
154
+ "description": "تغيرات كبيرة في أسعار المواد الخام",
155
+ "probability": "عالي",
156
+ "impact": "متوسط",
157
+ "mitigation": [
158
+ "تثبيت الأسعار في العقود",
159
+ "استخدام آليات التحوط",
160
+ "تنويع مصادر التوريد"
161
+ ],
162
+ "sector": "عام"
163
+ },
164
+ "risk3": {
165
+ "id": "R003",
166
+ "title": "عدم الامتثال للمحتوى المحلي",
167
+ "description": "عدم قدرة الموردين على تلبية متطلبات المحتوى المحلي",
168
+ "probability": "متوسط",
169
+ "impact": "عالي",
170
+ "mitigation": [
171
+ "اختيار موردين مؤهلين بنسبة محتوى محلي عالية",
172
+ "دعم الموردين المحليين لزيادة قدراتهم",
173
+ "تطوير برامج تأهيل للموردين المحليين"
174
+ ],
175
+ "sector": "عام"
176
+ }
177
+ }
178
+
179
+ def get_suppliers_database(self) -> pd.DataFrame:
180
+ """
181
+ الحصول على قاعدة بيانات الموردين كـ DataFrame
182
+ """
183
+ suppliers_list = []
184
+
185
+ for supplier_id, supplier_data in self.suppliers_db.items():
186
+ supplier_info = {
187
+ "id": supplier_data["id"],
188
+ "name": supplier_data["name"],
189
+ "sector": supplier_data["sector"],
190
+ "location": supplier_data["location"],
191
+ "categories": ", ".join(supplier_data["categories"]),
192
+ "local_content_percentage": supplier_data["local_content_percentage"],
193
+ "rating": supplier_data["rating"]
194
+ }
195
+ suppliers_list.append(supplier_info)
196
+
197
+ return pd.DataFrame(suppliers_list)
198
+
199
+ def analyze(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
200
+ """
201
+ تحليل سلسلة الإمداد بناءً على البيانات المستخرجة
202
+
203
+ المعاملات:
204
+ ----------
205
+ extracted_data : Dict[str, Any]
206
+ البيانات المستخرجة من المستندات
207
+ **kwargs : Dict[str, Any]
208
+ معاملات إضافية مثل نوع المشروع، الميزانية، الموقع، المدة
209
+
210
+ المخرجات:
211
+ --------
212
+ Dict[str, Any]
213
+ نتائج تحليل سلسلة الإمداد
214
+ """
215
+ supply_chain_results = {
216
+ "potential_suppliers": [],
217
+ "needed_materials": [],
218
+ "risks": [],
219
+ "optimization": [],
220
+ "local_content_impact": {}
221
+ }
222
+
223
+ # تحديد نوع المشروع والقطاع
224
+ project_type = kwargs.get("project_type", "")
225
+ sector = self._determine_sector(project_type, extracted_data)
226
+
227
+ # استخراج معلومات سلسلة الإمداد من البيانات
228
+ supply_chain_info = self._extract_supply_chain_info(extracted_data)
229
+
230
+ # تحديد المواد المطلوبة
231
+ needed_materials = self._identify_needed_materials(extracted_data, supply_chain_info, sector)
232
+ supply_chain_results["needed_materials"] = needed_materials
233
+
234
+ # تحديد الموردين المحتملين
235
+ potential_suppliers = self._identify_potential_suppliers(needed_materials, sector)
236
+ supply_chain_results["potential_suppliers"] = potential_suppliers
237
+
238
+ # تحليل المخاطر
239
+ risks = self._analyze_risks(needed_materials, potential_suppliers, sector)
240
+ supply_chain_results["risks"] = risks
241
+
242
+ # اقتراحات لتحسين سلسلة الإمداد
243
+ optimization = self._generate_optimization_suggestions(
244
+ needed_materials, potential_suppliers, risks, sector
245
+ )
246
+ supply_chain_results["optimization"] = optimization
247
+
248
+ # تحليل تأثير سلسلة الإمداد على المحتوى المحلي
249
+ local_content_impact = self._analyze_local_content_impact(
250
+ needed_materials, potential_suppliers
251
+ )
252
+ supply_chain_results["local_content_impact"] = local_content_impact
253
+
254
+ return supply_chain_results
255
+
256
+ def _determine_sector(self, project_type: str, extracted_data: Dict[str, Any]) -> str:
257
+ """
258
+ تحديد القطاع بناءً على نوع المشروع والبيانات المستخرجة
259
+ """
260
+ # قاموس لتحويل أنواع المشاريع الشائعة إلى قطاعات
261
+ project_to_sector = {
262
+ "إنشاءات": "الإنشاءات",
263
+ "مباني": "الإنشاءات",
264
+ "طرق": "الإنشاءات",
265
+ "جسور": "الإنشاءات",
266
+ "تقنية معلومات": "تقنية المعلومات",
267
+ "برمجيات": "تقنية المعلومات",
268
+ "تطبيقات": "تقنية المعلومات",
269
+ "صناعة": "الصناعة",
270
+ "مصانع": "الصناعة",
271
+ "تجارة": "التجارة",
272
+ "أسواق": "التجارة"
273
+ }
274
+
275
+ # محاولة تحديد القطاع من نوع المشروع
276
+ if project_type:
277
+ for key, value in project_to_sector.items():
278
+ if key in project_type.lower():
279
+ return value
280
+
281
+ # إذا لم يتم تحديد القطاع من نوع المشروع، نحاول تحديده من البيانات المستخرجة
282
+ if "text" in extracted_data:
283
+ text = extracted_data["text"].lower()
284
+ sector_scores = {}
285
+
286
+ for key, value in project_to_sector.items():
287
+ score = text.count(key.lower())
288
+ if value not in sector_scores:
289
+ sector_scores[value] = 0
290
+ sector_scores[value] += score
291
+
292
+ # اختيار القطاع الأكثر ذكراً
293
+ if sector_scores:
294
+ max_sector = max(sector_scores, key=sector_scores.get)
295
+ if sector_scores[max_sector] > 0:
296
+ return max_sector
297
+
298
+ # القطاع الافتراضي إذا لم نتمكن من تحديده
299
+ return "عام"
300
+
301
+ def _extract_supply_chain_info(self, extracted_data: Dict[str, Any]) -> Dict[str, Any]:
302
+ """
303
+ استخراج معلومات سلسلة الإمداد من البيانات المستخرجة
304
+ """
305
+ supply_chain_info = {
306
+ "mentioned_suppliers": [],
307
+ "mentioned_materials": [],
308
+ "supply_chain_requirements": []
309
+ }
310
+
311
+ # استخراج الموردين المذكورين
312
+ if "supply_chain" in extracted_data and "suppliers" in extracted_data["supply_chain"]:
313
+ supply_chain_info["mentioned_suppliers"] = extracted_data["supply_chain"]["suppliers"]
314
+
315
+ # استخراج المواد المذكورة
316
+ if "supply_chain" in extracted_data and "materials" in extracted_data["supply_chain"]:
317
+ supply_chain_info["mentioned_materials"] = extracted_data["supply_chain"]["materials"]
318
+
319
+ # استخراج متطلبات سلسلة الإمداد
320
+ if "requirements" in extracted_data:
321
+ for req in extracted_data["requirements"]:
322
+ if any(keyword in req.get("title", "").lower() for keyword in ["سلسلة", "إمداد", "توريد", "مورد"]):
323
+ supply_chain_info["supply_chain_requirements"].append(req)
324
+ elif any(keyword in req.get("description", "").lower() for keyword in ["سلسلة", "إمداد", "توريد", "مورد"]):
325
+ supply_chain_info["supply_chain_requirements"].append(req)
326
+
327
+ return supply_chain_info
328
+
329
+ def _identify_needed_materials(self, extracted_data: Dict[str, Any],
330
+ supply_chain_info: Dict[str, Any], sector: str) -> List[Dict[str, Any]]:
331
+ """
332
+ تحديد المواد المطلوبة للمش��وع
333
+ """
334
+ needed_materials = []
335
+
336
+ # إضافة المواد المذكورة صراحةً
337
+ for material in supply_chain_info["mentioned_materials"]:
338
+ material_info = {
339
+ "name": material["name"],
340
+ "source": "مذكور صراحةً",
341
+ "context": material.get("context", ""),
342
+ "quantity": "غير محدد",
343
+ "local_availability": "غير معروف"
344
+ }
345
+
346
+ # البحث عن المادة في قاعدة البيانات
347
+ for db_material_id, db_material in self.materials_db.items():
348
+ if material["name"].lower() in db_material["name"].lower() or db_material["name"].lower() in material["name"].lower():
349
+ material_info.update({
350
+ "id": db_material["id"],
351
+ "category": db_material["category"],
352
+ "local_manufacturers": db_material["local_manufacturers"],
353
+ "average_price": db_material["average_price"],
354
+ "lead_time": db_material["lead_time"],
355
+ "risk_level": db_material["risk_level"],
356
+ "local_content_percentage": db_material["local_content_percentage"],
357
+ "local_availability": "متوفر محلياً" if db_material["local_manufacturers"] else "غير متوفر محلياً"
358
+ })
359
+ break
360
+
361
+ needed_materials.append(material_info)
362
+
363
+ # إضافة مواد إضافية بناءً على القطاع
364
+ if sector == "الإنشاءات" and not any(material["name"].lower() == "حديد تسليح" for material in needed_materials):
365
+ if "material1" in self.materials_db:
366
+ material = self.materials_db["material1"]
367
+ needed_materials.append({
368
+ "id": material["id"],
369
+ "name": material["name"],
370
+ "category": material["category"],
371
+ "source": "مقترح بناءً على القطاع",
372
+ "local_manufacturers": material["local_manufacturers"],
373
+ "average_price": material["average_price"],
374
+ "lead_time": material["lead_time"],
375
+ "risk_level": material["risk_level"],
376
+ "local_content_percentage": material["local_content_percentage"],
377
+ "quantity": "غير محدد",
378
+ "local_availability": "متوفر محلياً" if material["local_manufacturers"] else "غير متوفر محلياً"
379
+ })
380
+
381
+ if sector == "الإنشاءات" and not any(material["name"].lower() == "أسمنت" for material in needed_materials):
382
+ if "material2" in self.materials_db:
383
+ material = self.materials_db["material2"]
384
+ needed_materials.append({
385
+ "id": material["id"],
386
+ "name": material["name"],
387
+ "category": material["category"],
388
+ "source": "مقترح بناءً على القطاع",
389
+ "local_manufacturers": material["local_manufacturers"],
390
+ "average_price": material["average_price"],
391
+ "lead_time": material["lead_time"],
392
+ "risk_level": material["risk_level"],
393
+ "local_content_percentage": material["local_content_percentage"],
394
+ "quantity": "غير محدد",
395
+ "local_availability": "متوفر محلياً" if material["local_manufacturers"] else "غير متوفر محلياً"
396
+ })
397
+
398
+ if sector == "تقنية المعلومات" and not any(material["name"].lower() == "خادم" for material in needed_materials):
399
+ if "material3" in self.materials_db:
400
+ material = self.materials_db["material3"]
401
+ needed_materials.append({
402
+ "id": material["id"],
403
+ "name": material["name"],
404
+ "category": material["category"],
405
+ "source": "مقترح بناءً على القطاع",
406
+ "local_manufacturers": material["local_manufacturers"],
407
+ "average_price": material["average_price"],
408
+ "lead_time": material["lead_time"],
409
+ "risk_level": material["risk_level"],
410
+ "local_content_percentage": material["local_content_percentage"],
411
+ "quantity": "غير محدد",
412
+ "local_availability": "متوفر محلياً" if material["local_manufacturers"] else "غير متوفر محلياً"
413
+ })
414
+
415
+ return needed_materials
416
+
417
+ def _identify_potential_suppliers(self, needed_materials: List[Dict[str, Any]],
418
+ sector: str) -> List[Dict[str, Any]]:
419
+ """
420
+ تحديد الموردين المحتملين
421
+ """
422
+ potential_suppliers = []
423
+ material_categories = set()
424
+
425
+ # جمع فئات المواد المطلوبة
426
+ for material in needed_materials:
427
+ if "category" in material:
428
+ material_categories.add(material["category"])
429
+
430
+ # تحديد الموردين المحتملين بناءً على المواد المطلوبة والقطاع
431
+ for supplier_id, supplier_data in self.suppliers_db.items():
432
+ # التحقق من القطاع
433
+ if supplier_data["sector"] == sector or supplier_data["sector"] == "عام":
434
+ # التحقق من فئات المواد
435
+ supplier_categories = set(supplier_data["categories"])
436
+
437
+ if material_categories.intersection(supplier_categories) or not material_categories:
438
+ supplier_info = {
439
+ "id": supplier_data["id"],
440
+ "name": supplier_data["name"],
441
+ "sector": supplier_data["sector"],
442
+ "location": supplier_data["location"],
443
+ "categories": supplier_data["categories"],
444
+ "local_content_percentage": supplier_data["local_content_percentage"],
445
+ "rating": supplier_data["rating"],
446
+ "contact": supplier_data["contact"],
447
+ "certifications": supplier_data["certifications"],
448
+ "historical_performance": supplier_data["historical_performance"],
449
+ "match_score": self._calculate_supplier_match_score(supplier_data, needed_materials, sector)
450
+ }
451
+
452
+ potential_suppliers.append(supplier_info)
453
+
454
+ # ترتيب الموردين المحتملين بناءً على درجة التطابق
455
+ potential_suppliers = sorted(potential_suppliers, key=lambda x: x["match_score"], reverse=True)
456
+
457
+ return potential_suppliers
458
+
459
+ def _calculate_supplier_match_score(self, supplier_data: Dict[str, Any],
460
+ needed_materials: List[Dict[str, Any]], sector: str) -> float:
461
+ """
462
+ حساب درجة تطابق المورد مع متطلبات المشروع
463
+ """
464
+ match_score = 0.0
465
+
466
+ # درجة تطابق القطاع
467
+ if supplier_data["sector"] == sector:
468
+ match_score += 0.3
469
+ elif supplier_data["sector"] == "عام":
470
+ match_score += 0.1
471
+
472
+ # درجة تطابق فئات المواد
473
+ material_categories = set()
474
+ for material in needed_materials:
475
+ if "category" in material:
476
+ material_categories.add(material["category"])
477
+
478
+ supplier_categories = set(supplier_data["categories"])
479
+ category_match_ratio = 0.0
480
+
481
+ if material_categories and supplier_categories:
482
+ common_categories = material_categories.intersection(supplier_categories)
483
+ category_match_ratio = len(common_categories) / len(material_categories)
484
+
485
+ match_score += 0.3 * category_match_ratio
486
+
487
+ # درجة المحتوى المحلي
488
+ local_content_score = min(supplier_data["local_content_percentage"] / 100, 1.0)
489
+ match_score += 0.2 * local_content_score
490
+
491
+ # درجة التقييم
492
+ rating_score = min(supplier_data["rating"] / 5, 1.0)
493
+ match_score += 0.1 * rating_score
494
+
495
+ # درجة الأداء السابق
496
+ if "historical_performance" in supplier_data:
497
+ performance = supplier_data["historical_performance"]
498
+ performance_score = (
499
+ performance.get("on_time_delivery", 0) +
500
+ performance.get("quality", 0) +
501
+ performance.get("cost", 0)
502
+ ) / 300 # تقسيم على 300 لتحويلها إلى نسبة مئوية (ثلاثة معايير بحد أقصى 100 لكل منهم)
503
+
504
+ match_score += 0.1 * performance_score
505
+
506
+ return round(match_score, 2)
507
+
508
+ def _analyze_risks(self, needed_materials: List[Dict[str, Any]],
509
+ potential_suppliers: List[Dict[str, Any]], sector: str) -> List[Dict[str, Any]]:
510
+ """
511
+ تحليل مخاطر سلسلة الإمداد
512
+ """
513
+ risks = []
514
+
515
+ # إضافة المخاطر العامة
516
+ for risk_id, risk_data in self.risks_db.items():
517
+ if risk_data["sector"] == "عام" or risk_data["sector"] == sector:
518
+ risks.append(risk_data)
519
+
520
+ # تحليل مخاطر المواد
521
+ high_risk_materials = [material for material in needed_materials if material.get("risk_level") == "عالي"]
522
+ if high_risk_materials:
523
+ material_names = [material["name"] for material in high_risk_materials]
524
+ risks.append({
525
+ "id": "R101",
526
+ "title": "مواد عالية المخاطر",
527
+ "description": f"المشروع يتطلب مواد عالية المخاطر: {', '.join(material_names)}",
528
+ "probability": "عالي",
529
+ "impact": "عالي",
530
+ "mitigation": [
531
+ "تأمين بدائل محلية للمواد عالية المخاطر",
532
+ "التعاقد المسبق مع الموردين",
533
+ "الاحتفاظ بمخزون استراتيجي"
534
+ ],
535
+ "sector": sector
536
+ })
537
+
538
+ # تحليل مخاطر المواد غير المتوفرة محلياً
539
+ non_local_materials = [material for material in needed_materials if material.get("local_availability") == "غير متوفر محلياً"]
540
+ if non_local_materials:
541
+ material_names = [material["name"] for material in non_local_materials]
542
+ risks.append({
543
+ "id": "R102",
544
+ "title": "مواد غير متوفرة محلياً",
545
+ "description": f"المشروع يتطلب مواد غير متوفرة محلياً: {', '.join(material_names)}",
546
+ "probability": "متوسط",
547
+ "impact": "عالي",
548
+ "mitigation": [
549
+ "البحث عن بدائل محلية",
550
+ "تطوير قدرات مصنِّعين محليين",
551
+ "التخطيط المسبق للاستيراد"
552
+ ],
553
+ "sector": sector
554
+ })
555
+
556
+ # تحليل مخاطر الموردين
557
+ if not potential_suppliers:
558
+ risks.append({
559
+ "id": "R103",
560
+ "title": "عدم توفر موردين مناسبين",
561
+ "description": "لم يتم العثور على موردين مناسبين لتلبية متطلبات المشروع",
562
+ "probability": "عالي",
563
+ "impact": "عالي",
564
+ "mitigation": [
565
+ "توسيع نطاق البحث عن موردين",
566
+ "تعديل متطلبات المشروع",
567
+ "تطوير قدرات موردين محليين"
568
+ ],
569
+ "sector": sector
570
+ })
571
+ elif len(potential_suppliers) < 3:
572
+ risks.append({
573
+ "id": "R104",
574
+ "title": "عدد محدود من الموردين",
575
+ "description": f"عدد محدود من الموردين المناسبين: {len(potential_suppliers)}",
576
+ "probability": "متوسط",
577
+ "impact": "متوسط",
578
+ "mitigation": [
579
+ "توسيع نطاق البحث عن موردين إضافيين",
580
+ "تطوير استراتيجية للتعامل مع الموردين المتاحين",
581
+ "النظر في خيارات بديلة"
582
+ ],
583
+ "sector": sector
584
+ })
585
+
586
+ # تحليل مخاطر المحتوى المحلي
587
+ local_content_percentages = [material.get("local_content_percentage", 0) for material in needed_materials if "local_content_percentage" in material]
588
+ if local_content_percentages:
589
+ avg_local_content = sum(local_content_percentages) / len(local_content_percentages)
590
+
591
+ if avg_local_content < 50:
592
+ risks.append({
593
+ "id": "R105",
594
+ "title": "انخفاض نسبة المحتوى المحلي",
595
+ "description": f"متوسط نسبة المحتوى المحلي للمواد المطلوبة منخفض: {avg_local_content:.1f}%",
596
+ "probability": "عالي",
597
+ "impact": "عالي",
598
+ "mitigation": [
599
+ "زيادة الاعتماد على الموردين المحليين",
600
+ "تطوير قدرات المصنِّعين المحليين",
601
+ "البحث عن بدائل محلية للمواد المستوردة"
602
+ ],
603
+ "sector": sector
604
+ })
605
+
606
+ return risks
607
+
608
+ def _generate_optimization_suggestions(self, needed_materials: List[Dict[str, Any]],
609
+ potential_suppliers: List[Dict[str, Any]],
610
+ risks: List[Dict[str, Any]], sector: str) -> List[str]:
611
+ """
612
+ إعداد اقتراحات لتحسين سلسلة الإمداد
613
+ """
614
+ optimization_suggestions = []
615
+
616
+ # اقتراحات لتحسين موثوقية سلسلة الإمداد
617
+ optimization_suggestions.append(
618
+ "تعزيز موثوقية سلسلة الإمداد من خلال توفي�� مصادر بديلة للمواد الحرجة"
619
+ )
620
+
621
+ # اقتراحات لزيادة المحتوى المحلي
622
+ local_content_percentages = [material.get("local_content_percentage", 0) for material in needed_materials if "local_content_percentage" in material]
623
+ if local_content_percentages:
624
+ avg_local_content = sum(local_content_percentages) / len(local_content_percentages)
625
+
626
+ if avg_local_content < 70:
627
+ optimization_suggestions.append(
628
+ f"زيادة نسبة المحتوى المحلي من {avg_local_content:.1f}% إلى 70% على الأقل من خلال التعاون مع موردين محليين"
629
+ )
630
+
631
+ # اقتراحات لخفض التكاليف
632
+ optimization_suggestions.append(
633
+ "خفض تكاليف سلسلة الإمداد من خلال التعاقد طويل الأمد مع الموردين الرئيسيين"
634
+ )
635
+
636
+ # اقتراحات لتقليل المخاطر العالية
637
+ high_risks = [risk for risk in risks if risk.get("impact") == "عالي"]
638
+ if high_risks:
639
+ optimization_suggestions.append(
640
+ f"تطوير استراتيجية للتخفيف من المخاطر العالية في سلسلة الإمداد ({len(high_risks)} مخاطر)"
641
+ )
642
+
643
+ # اقتراحات لتحسين الجودة
644
+ optimization_suggestions.append(
645
+ "تحسين جودة المواد من خلال اختيار موردين ذوي تقييمات عالية ومراقبة الجودة بشكل مستمر"
646
+ )
647
+
648
+ # اقتراحات خاصة بالقطاع
649
+ if sector == "الإنشاءات":
650
+ optimization_suggestions.append(
651
+ "تطوير علاقات استراتيجية مع موردي مواد البناء الرئيسية (الحديد، الأسمنت، الخرسانة) لضمان استمرارية التوريد"
652
+ )
653
+ elif sector == "تقنية المعلومات":
654
+ optimization_suggestions.append(
655
+ "بناء شراكات مع الشركات التقنية المحلية لتطوير حلول مخصصة تلبي احتياجات المشروع وتزيد المحتوى المحلي"
656
+ )
657
+
658
+ # اقتراحات عامة
659
+ optimization_suggestions.extend([
660
+ "تطبيق نظام إدارة المخزون الأمثل (Just-in-Time) لتقليل تكاليف التخزين وزيادة الكفاءة",
661
+ "تطوير منظومة تتبع رقمية لمراقبة سلسلة الإمداد بشكل فعال",
662
+ "تدريب وتأهيل الموردين المحليين لتلبية متطلبات الجودة العالمية"
663
+ ])
664
+
665
+ return optimization_suggestions
666
+
667
+ def _analyze_local_content_impact(self, needed_materials: List[Dict[str, Any]],
668
+ potential_suppliers: List[Dict[str, Any]]) -> Dict[str, Any]:
669
+ """
670
+ تحليل تأثير سلسلة الإمداد على المحتوى المحلي
671
+ """
672
+ local_content_impact = {
673
+ "overall_percentage": 0.0,
674
+ "materials_local_content": 0.0,
675
+ "suppliers_local_content": 0.0,
676
+ "recommendations": []
677
+ }
678
+
679
+ # حساب متوسط نسبة المحتوى المحلي للمواد
680
+ local_content_percentages = [material.get("local_content_percentage", 0) for material in needed_materials if "local_content_percentage" in material]
681
+ if local_content_percentages:
682
+ materials_local_content = sum(local_content_percentages) / len(local_content_percentages)
683
+ local_content_impact["materials_local_content"] = round(materials_local_content, 2)
684
+
685
+ # حساب متوسط نسبة المحتوى المحلي للموردين
686
+ supplier_local_content_percentages = [supplier.get("local_content_percentage", 0) for supplier in potential_suppliers]
687
+ if supplier_local_content_percentages:
688
+ suppliers_local_content = sum(supplier_local_content_percentages) / len(supplier_local_content_percentages)
689
+ local_content_impact["suppliers_local_content"] = round(suppliers_local_content, 2)
690
+
691
+ # حساب النسبة الإجمالية للمحتوى المحلي
692
+ overall_percentage = (
693
+ local_content_impact["materials_local_content"] * 0.7 +
694
+ local_content_impact["suppliers_local_content"] * 0.3
695
+ )
696
+ local_content_impact["overall_percentage"] = round(overall_percentage, 2)
697
+
698
+ # إعداد توصيات لتحسين نسبة المحتوى المحلي
699
+ if overall_percentage < 50:
700
+ local_content_impact["recommendations"].append(
701
+ "زيادة الاعتماد على الموردين المحليين ذوي نسبة محتوى محلي عالية"
702
+ )
703
+
704
+ if local_content_impact["materials_local_content"] < 50:
705
+ local_content_impact["recommendations"].append(
706
+ "البحث عن بدائل محلية للمواد المستوردة أو ذات نسبة محتوى محلي منخفضة"
707
+ )
708
+
709
+ local_content_impact["recommendations"].extend([
710
+ "تطوير قدرات المصنِّعين المحليين لزيادة نسبة المحتوى المحلي",
711
+ "الاستفادة من برامج دعم المحتوى المحلي المقدمة من هيئة المحتوى المحلي والمشتريات الحكومية",
712
+ "تطبيق معايير تفضيل المنتجات المحلية في المناقصات والمشتريات"
713
+ ])
714
+
715
+ return local_content_impact