Spaces:
Paused
Paused
Create modules/supply_chain.py
Browse files- 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": "info@saudiconstruction.com",
|
| 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": "info@sauditech.com",
|
| 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": "info@saudimetal.com",
|
| 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
|