Spaces:
Paused
Paused
Update modules/cost_risk_analyzer.py
Browse files- modules/cost_risk_analyzer.py +296 -1
modules/cost_risk_analyzer.py
CHANGED
@@ -1,4 +1,299 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
"risk_level": risk_level,
|
3 |
"impact": "تأثير على هامش الربح وزيادة التكاليف الإجمالية",
|
4 |
"mitigation": self._generate_overrun_mitigation(category, deviation_percentage)
|
|
|
1 |
+
local_content_percentage = (local_content / total_cost * 100) if total_cost > 0 else 0
|
2 |
+
|
3 |
+
# حساب المحتوى المحلي حسب الفئة
|
4 |
+
categories = {}
|
5 |
+
for item in items:
|
6 |
+
category = item.get("category", "أخرى")
|
7 |
+
cost = item.get("cost", 0)
|
8 |
+
local_contribution = item.get("local_content_contribution", 0)
|
9 |
+
local_amount = cost * local_contribution / 100
|
10 |
+
|
11 |
+
if category in categories:
|
12 |
+
categories[category]["total"] += cost
|
13 |
+
categories[category]["local"] += local_amount
|
14 |
+
else:
|
15 |
+
categories[category] = {
|
16 |
+
"total": cost,
|
17 |
+
"local": local_amount
|
18 |
+
}
|
19 |
+
|
20 |
+
local_content_by_category = {}
|
21 |
+
for category, data in categories.items():
|
22 |
+
local_content_by_category[category] = (data["local"] / data["total"] * 100) if data["total"] > 0 else 0
|
23 |
+
|
24 |
+
# الهدف المرجعي للمحتوى المحلي
|
25 |
+
target_local_content = self.reference_data.get("target_local_content", 40)
|
26 |
+
local_content_gap = target_local_content - local_content_percentage
|
27 |
+
|
28 |
+
# إمكانية التحسين
|
29 |
+
import_items = [item for item in items if item.get("source_type") == "import"]
|
30 |
+
improvement_potential = sum(item.get("cost", 0) for item in import_items) / total_cost * 100 if total_cost > 0 else 0
|
31 |
+
|
32 |
+
return {
|
33 |
+
"local_content_percentage": local_content_percentage,
|
34 |
+
"local_content_by_category": local_content_by_category,
|
35 |
+
"target_local_content": target_local_content,
|
36 |
+
"local_content_gap": local_content_gap,
|
37 |
+
"improvement_potential": improvement_potential
|
38 |
+
}
|
39 |
+
|
40 |
+
def _identify_procurement_improvements(self, procurement_costs: Dict[str, Any],
|
41 |
+
suppliers_analysis: Dict[str, Any],
|
42 |
+
local_content_analysis: Dict[str, Any]) -> List[str]:
|
43 |
+
"""
|
44 |
+
تحديد فرص تحسين المشتريات
|
45 |
+
|
46 |
+
المعاملات:
|
47 |
+
----------
|
48 |
+
procurement_costs : Dict[str, Any]
|
49 |
+
تحليل تكاليف المشتريات
|
50 |
+
suppliers_analysis : Dict[str, Any]
|
51 |
+
تحليل الموردين
|
52 |
+
local_content_analysis : Dict[str, Any]
|
53 |
+
تحليل المحتوى المحلي
|
54 |
+
|
55 |
+
المخرجات:
|
56 |
+
--------
|
57 |
+
List[str]
|
58 |
+
فرص تحسين المشتريات
|
59 |
+
"""
|
60 |
+
improvements = []
|
61 |
+
|
62 |
+
# تحسين تكاليف المشتريات
|
63 |
+
if procurement_costs["cost_efficiency"] < 0:
|
64 |
+
improvements.append("تحسين كفاءة تكاليف المشتريات من خلال إعادة التفاوض مع الموردين وتوحيد الطلبات.")
|
65 |
+
|
66 |
+
# تحسين تنوع الموردين
|
67 |
+
if suppliers_analysis["supplier_diversity"] < 30:
|
68 |
+
improvements.append("زيادة تنوع الموردين لتقليل مخاطر سلسلة الإمداد وتحسين المرونة.")
|
69 |
+
|
70 |
+
# تحسين نسبة الموردين المحليين
|
71 |
+
if suppliers_analysis["local_percentage"] < 50:
|
72 |
+
improvements.append("زيادة نسبة الموردين المحليين لتعزيز المحتوى المحلي وتقليل تكاليف النقل.")
|
73 |
+
|
74 |
+
# تحسين المحتوى المحلي
|
75 |
+
if local_content_analysis["local_content_gap"] > 0:
|
76 |
+
improvements.append(f"سد فجوة المحتوى المحلي البالغة {local_content_analysis['local_content_gap']:.1f}% للوصول إلى الهدف.")
|
77 |
+
|
78 |
+
# تحسين تركيز الموردين
|
79 |
+
high_concentration = [supplier for supplier, percentage in suppliers_analysis["supplier_concentration"].items() if percentage > 30]
|
80 |
+
if high_concentration:
|
81 |
+
improvements.append(f"تقليل الاعتماد على الموردين الرئيسيين ({', '.join(high_concentration)}) لتقليل المخاطر.")
|
82 |
+
|
83 |
+
# إضافة توصيات عامة
|
84 |
+
improvements.extend([
|
85 |
+
"تطبيق نظام تقييم الموردين وتحفيز الأداء المتميز.",
|
86 |
+
"تحسين عملية التخطيط للمشتريات لتقليل الطلبات العاجلة والتكاليف الإضافية.",
|
87 |
+
"تطوير قاعدة بيانات للموردين وتعزيز الشفافية في عملية اختيار الموردين.",
|
88 |
+
"الاستفادة من التكنولوجيا لأتمتة عمليات المشتريات وتحسين الكفاءة."
|
89 |
+
])
|
90 |
+
|
91 |
+
return improvements
|
92 |
+
|
93 |
+
def _generate_procurement_summary(self, procurement_costs: Dict[str, Any],
|
94 |
+
suppliers_analysis: Dict[str, Any],
|
95 |
+
local_content_analysis: Dict[str, Any]) -> str:
|
96 |
+
"""
|
97 |
+
إعداد ملخص لتقرير المشتريات
|
98 |
+
|
99 |
+
المعاملات:
|
100 |
+
----------
|
101 |
+
procurement_costs : Dict[str, Any]
|
102 |
+
تحليل تكاليف المشتريات
|
103 |
+
suppliers_analysis : Dict[str, Any]
|
104 |
+
تحليل الموردين
|
105 |
+
local_content_analysis : Dict[str, Any]
|
106 |
+
تحليل المحتوى المحلي
|
107 |
+
|
108 |
+
المخرجات:
|
109 |
+
--------
|
110 |
+
str
|
111 |
+
ملخص تقرير المشتريات
|
112 |
+
"""
|
113 |
+
summary = "ملخص تقرير المشتريات:\n\n"
|
114 |
+
|
115 |
+
# ملخص التكاليف
|
116 |
+
summary += "1. تكاليف المشتريات:\n"
|
117 |
+
summary += f" - إجمالي تكاليف المشتريات: {procurement_costs['total_procurement_cost']:,.0f} ريال\n"
|
118 |
+
summary += f" - نسبة المشتريات المحلية: {procurement_costs['local_percentage']:.1f}%\n"
|
119 |
+
if procurement_costs['cost_efficiency'] > 0:
|
120 |
+
summary += f" - كفاءة التكلفة: {procurement_costs['cost_efficiency']:.1f}% (وفر عن المتوسط)\n"
|
121 |
+
else:
|
122 |
+
summary += f" - كفاءة التكلفة: {-procurement_costs['cost_efficiency']:.1f}% (تجاوز عن المتوسط)\n"
|
123 |
+
|
124 |
+
# ملخص الموردين
|
125 |
+
summary += "\n2. تحليل الموردين:\n"
|
126 |
+
summary += f" - عدد الموردين: {suppliers_analysis['total_suppliers']}\n"
|
127 |
+
summary += f" - نسبة الموردين المحليين: {suppliers_analysis['local_percentage']:.1f}%\n"
|
128 |
+
summary += f" - تنوع الموردين: {suppliers_analysis['supplier_diversity']:.1f}%\n"
|
129 |
+
|
130 |
+
# ملخص المحتوى المحلي
|
131 |
+
summary += "\n3. تحليل المحتوى المحلي:\n"
|
132 |
+
summary += f" - نسبة المحتوى المحلي الحالية: {local_content_analysis['local_content_percentage']:.1f}%\n"
|
133 |
+
summary += f" - نسبة المحتوى المحلي المستهدفة: {local_content_analysis['target_local_content']:.1f}%\n"
|
134 |
+
summary += f" - فجوة المحتوى المحلي: {local_content_analysis['local_content_gap']:.1f}%\n"
|
135 |
+
summary += f" - إمكانية تحسين المحتوى المحلي: {local_content_analysis['improvement_potential']:.1f}%\n"
|
136 |
+
|
137 |
+
return summary
|
138 |
+
|
139 |
+
def _generate_default_mitigation(self, risk: Dict[str, Any]) -> str:
|
140 |
+
"""
|
141 |
+
إنشاء استراتيجية تخفيف افتراضية للمخاطرة
|
142 |
+
|
143 |
+
المعاملات:
|
144 |
+
----------
|
145 |
+
risk : Dict[str, Any]
|
146 |
+
المخاطرة
|
147 |
+
|
148 |
+
المخرجات:
|
149 |
+
--------
|
150 |
+
str
|
151 |
+
استراتيجية التخفيف
|
152 |
+
"""
|
153 |
+
# استخراج نوع المخاطرة
|
154 |
+
risk_name = risk.get("name", "").lower()
|
155 |
+
risk_type = risk.get("type", "").lower()
|
156 |
+
|
157 |
+
if "أسعار" in risk_name or "تكلفة" in risk_name:
|
158 |
+
return "تأمين عقود توريد بأسعار ثابتة وإعداد ميزانية احتياطية للتغييرات المحتملة في الأسعار."
|
159 |
+
elif "دفعات" in risk_name or "تمويل" in risk_name or "سيولة" in risk_name:
|
160 |
+
return "وضع شروط دفع واضحة في العقد وتأمين تسهيلات ائتمانية احتياطية وإعداد خطة للتدفق النقدي."
|
161 |
+
elif "عملات" in risk_name or "صرف" in risk_name:
|
162 |
+
return "استخدام آليات التحوط والتعاقد بالعملة المحلية حيثما أمكن."
|
163 |
+
elif risk_type == "financial":
|
164 |
+
return "إعداد خطة مالية بديلة وتخصيص احتياطي طوارئ مالي بنسبة مناسبة من قيمة المشروع."
|
165 |
+
else:
|
166 |
+
return "تحديد وتقييم المخاطر بشكل دوري ووضع خطط بديلة للتعامل معها."
|
167 |
+
|
168 |
+
def _generate_overrun_mitigation(self, category: str, deviation_percentage: float) -> str:
|
169 |
+
"""
|
170 |
+
إنشاء استراتيجية تخفيف لتجاوز التكاليف
|
171 |
+
|
172 |
+
المعاملات:
|
173 |
+
----------
|
174 |
+
category : str
|
175 |
+
فئة التكلفة
|
176 |
+
deviation_percentage : float
|
177 |
+
نسبة التجاوز
|
178 |
+
|
179 |
+
المخرجات:
|
180 |
+
--------
|
181 |
+
str
|
182 |
+
استراتيجية التخفيف
|
183 |
+
"""
|
184 |
+
category = category.lower()
|
185 |
+
|
186 |
+
if "مواد" in category:
|
187 |
+
return "البحث عن موردين بديلين وإعادة التفاوض على الأسعار وتحسين إدارة المخزون."
|
188 |
+
elif "عمالة" in category:
|
189 |
+
return "تحسين إنتاجية العمالة وإع��دة توزيع المهام وتقييم إمكانية الاستعانة بمقاولي الباطن."
|
190 |
+
elif "معدات" in category:
|
191 |
+
return "تحسين استغلال المعدات ومراجعة خيارات الشراء مقابل الإيجار وتقليل أوقات التوقف."
|
192 |
+
elif "مقاولين" in category or "باطن" in category:
|
193 |
+
return "مراجعة عقود مقاولي الباطن وإعادة التفاوض على الأسعار وتحسين إدارة العقود."
|
194 |
+
elif "إدارة" in category or "غير مباشرة" in category:
|
195 |
+
return "ترشيد المصاريف الإدارية وتقليل التكاليف غير المباشرة ومراجعة الهيكل التنظيمي."
|
196 |
+
else:
|
197 |
+
return "مراجعة بنود التكلفة وتحديد فرص التوفير وتطبيق إجراءات ضبط التكاليف."
|
198 |
+
|
199 |
+
def _generate_deviation_recommendation(self, category: str, expected_deviation: float) -> str:
|
200 |
+
"""
|
201 |
+
إنشاء توصية للتعامل مع الانحراف المتوقع
|
202 |
+
|
203 |
+
المعاملات:
|
204 |
+
----------
|
205 |
+
category : str
|
206 |
+
فئة التكلفة
|
207 |
+
expected_deviation : float
|
208 |
+
الانحراف المتوقع
|
209 |
+
|
210 |
+
المخرجات:
|
211 |
+
--------
|
212 |
+
str
|
213 |
+
التوصية
|
214 |
+
"""
|
215 |
+
category = category.lower()
|
216 |
+
|
217 |
+
if expected_deviation > 0:
|
218 |
+
# توصيات لتجاوز التكاليف
|
219 |
+
if "مواد" in category:
|
220 |
+
return "مراجعة بدائل المواد وتوحيد المشتريات للاستفادة من وفورات الحجم وتأمين العقود مسبقًا."
|
221 |
+
elif "عمالة" in category:
|
222 |
+
return "تحسين جدولة العمالة وزيادة الإنتاجية وتقييم إمكانية الاستعانة بمقاولي الباطن."
|
223 |
+
elif "معدات" in category:
|
224 |
+
return "تحسين إدارة المعدات وزيادة كفاءة الاستخدام ومراجعة خيارات الإيجار بدلاً من الشراء."
|
225 |
+
else:
|
226 |
+
return "تنفيذ إجراءات ضبط التكاليف ومراقبة الإنفاق بشكل دوري ومراجعة الميزانية."
|
227 |
+
else:
|
228 |
+
# توصيات للوفر في التكاليف
|
229 |
+
if abs(expected_deviation) > 10:
|
230 |
+
return "مراجعة تقديرات التكلفة لضمان عدم المبالغة وإعادة توزيع الموارد للأنشطة الحرجة."
|
231 |
+
else:
|
232 |
+
return "الاستفادة من الوفر المتوقع في تعزيز جودة المشروع أو تغطية تجاوزات محتملة في بنود أخرى."
|
233 |
+
|
234 |
+
def _load_reference_data(self) -> Dict[str, Any]:
|
235 |
+
"""
|
236 |
+
تحميل البيانات المرجعية
|
237 |
+
|
238 |
+
المخرجات:
|
239 |
+
--------
|
240 |
+
Dict[str, Any]
|
241 |
+
البيانات المرجعية
|
242 |
+
"""
|
243 |
+
try:
|
244 |
+
file_path = 'data/templates/cost_reference_data.json'
|
245 |
+
if os.path.exists(file_path):
|
246 |
+
with open(file_path, 'r', encoding='utf-8') as f:
|
247 |
+
return json.load(f)
|
248 |
+
else:
|
249 |
+
logger.warning(f"ملف البيانات المرجعية غير موجود: {file_path}")
|
250 |
+
# إنشاء بيانات مرجعية افتراضية
|
251 |
+
return self._create_default_reference_data()
|
252 |
+
except Exception as e:
|
253 |
+
logger.error(f"فشل في تحميل البيانات المرجعية: {str(e)}")
|
254 |
+
return self._create_default_reference_data()
|
255 |
+
|
256 |
+
def _create_default_reference_data(self) -> Dict[str, Any]:
|
257 |
+
"""
|
258 |
+
إنشاء بيانات مرجعية افتراضية
|
259 |
+
|
260 |
+
المخرجات:
|
261 |
+
--------
|
262 |
+
Dict[str, Any]
|
263 |
+
البيانات المرجعية الافتراضية
|
264 |
+
"""
|
265 |
+
return {
|
266 |
+
"avg_direct_percentage": 75.0,
|
267 |
+
"avg_indirect_percentage": 25.0,
|
268 |
+
"avg_profit_margin": 15.0,
|
269 |
+
"avg_cost_revenue_ratio": 85.0,
|
270 |
+
"avg_roi": 20.0,
|
271 |
+
"avg_procurement_cost": 1000000,
|
272 |
+
"target_local_content": 40.0,
|
273 |
+
"sector_benchmarks": {
|
274 |
+
"construction": {
|
275 |
+
"profit_margin": 12.0,
|
276 |
+
"cost_revenue_ratio": 88.0,
|
277 |
+
"direct_percentage": 80.0
|
278 |
+
},
|
279 |
+
"infrastructure": {
|
280 |
+
"profit_margin": 10.0,
|
281 |
+
"cost_revenue_ratio": 90.0,
|
282 |
+
"direct_percentage": 85.0
|
283 |
+
},
|
284 |
+
"services": {
|
285 |
+
"profit_margin": 20.0,
|
286 |
+
"cost_revenue_ratio": 80.0,
|
287 |
+
"direct_percentage": 65.0
|
288 |
+
}
|
289 |
+
},
|
290 |
+
"cost_overrun_statistics": {
|
291 |
+
"avg_overrun_percentage": 15.0,
|
292 |
+
"materials_overrun": 12.0,
|
293 |
+
"labor_overrun": 18.0,
|
294 |
+
"equipment_overrun": 10.0
|
295 |
+
}
|
296 |
+
} "deviation_percentage": deviation_percentage,
|
297 |
"risk_level": risk_level,
|
298 |
"impact": "تأثير على هامش الربح وزيادة التكاليف الإجمالية",
|
299 |
"mitigation": self._generate_overrun_mitigation(category, deviation_percentage)
|