EGYADMIN commited on
Commit
d7a8860
·
verified ·
1 Parent(s): bc4fbcf

Create analysis/cost_estimator.py

Browse files
Files changed (1) hide show
  1. analysis/cost_estimator.py +1442 -0
analysis/cost_estimator.py ADDED
@@ -0,0 +1,1442 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ def _calculate_equipment_costs(self, scope_of_work: List[str], project_duration: int) -> Dict[str, float]:
3
+ """
4
+ حساب تكاليف المعدات
5
+
6
+ المعاملات:
7
+ ----------
8
+ scope_of_work : List[str]
9
+ نطاق العمل
10
+ project_duration : int
11
+ مدة المشروع بالأشهر
12
+
13
+ المخرجات:
14
+ --------
15
+ Dict[str, float]
16
+ تكاليف المعدات
17
+ """
18
+ equipment_costs = {}
19
+
20
+ # تحديد نوع المشروع بناءً على نطاق العمل
21
+ project_type = self._determine_project_type(scope_of_work)
22
+
23
+ # الحصول على المعدات المطلوبة لهذا النوع من المشاريع
24
+ required_equipment = self.cost_db.get("equipment_requirements", {}).get(project_type, {})
25
+
26
+ # حساب تكاليف كل نوع من المعدات
27
+ for equipment_type, count in required_equipment.items():
28
+ # الحصول على التكلفة الشهرية
29
+ monthly_cost = self.equipment_costs.get(equipment_type, {}).get("monthly_cost", 0)
30
+
31
+ # الحصول على نوع التكلفة (شراء أو إيجار)
32
+ cost_type = self.equipment_costs.get(equipment_type, {}).get("cost_type", "إيجار")
33
+
34
+ if cost_type == "شراء":
35
+ # في حالة الشراء، نستخدم التكلفة الإجمالية وليس الشهرية
36
+ total_cost = self.equipment_costs.get(equipment_type, {}).get("purchase_cost", 0) * count
37
+ else:
38
+ # في حالة الإيجار، نحسب التكلفة الشهرية على مدة المشروع
39
+ total_cost = monthly_cost * count * project_duration
40
+
41
+ # إضافة التكلفة إلى القاموس
42
+ equipment_costs[equipment_type] = total_cost
43
+
44
+ return equipment_costs
45
+
46
+ def _calculate_material_costs(self, quantities: List[Dict[str, Any]]) -> Dict[str, float]:
47
+ """
48
+ حساب تكاليف المواد
49
+
50
+ المعاملات:
51
+ ----------
52
+ quantities : List[Dict[str, Any]]
53
+ كميات المواد
54
+
55
+ المخرجات:
56
+ --------
57
+ Dict[str, float]
58
+ تكاليف المواد
59
+ """
60
+ material_costs = {}
61
+
62
+ # إذا لم تكن هناك كميات، استخدم قيم افتراضية
63
+ if not quantities:
64
+ material_costs = {
65
+ "مواد بناء أساسية": 500000,
66
+ "مواد تشطيب": 300000,
67
+ "مواد كهربائية": 200000,
68
+ "مواد سباكة": 150000,
69
+ "مواد أخرى": 100000
70
+ }
71
+ return material_costs
72
+
73
+ # تجميع المواد حسب الفئة
74
+ material_categories = {}
75
+
76
+ for item in quantities:
77
+ description = item.get("description", "")
78
+ quantity = item.get("quantity", 0)
79
+
80
+ # تحديد فئة المادة
81
+ category = self._determine_material_category(description)
82
+
83
+ # إضافة الكمية إلى الفئة
84
+ if category in material_categories:
85
+ material_categories[category].append((description, quantity))
86
+ else:
87
+ material_categories[category] = [(description, quantity)]
88
+
89
+ # حساب تكلفة كل فئة
90
+ for category, items in material_categories.items():
91
+ total_quantity = sum(quantity for _, quantity in items)
92
+ unit_price = self.cost_db.get("material_prices", {}).get(category, 1000)
93
+
94
+ material_costs[category] = total_quantity * unit_price
95
+
96
+ return material_costs
97
+
98
+ def _calculate_indirect_costs(self, labor_costs: Dict[str, float],
99
+ equipment_costs: Dict[str, float],
100
+ material_costs: Dict[str, float],
101
+ project_duration: int) -> Dict[str, float]:
102
+ """
103
+ حساب التكاليف غير المباشرة
104
+
105
+ المعاملات:
106
+ ----------
107
+ labor_costs : Dict[str, float]
108
+ تكاليف العمالة
109
+ equipment_costs : Dict[str, float]
110
+ تكاليف المعدات
111
+ material_costs : Dict[str, float]
112
+ تكاليف المواد
113
+ project_duration : int
114
+ مدة المشروع بالأشهر
115
+
116
+ المخرجات:
117
+ --------
118
+ Dict[str, float]
119
+ التكاليف غير المباشرة
120
+ """
121
+ indirect_costs = {}
122
+
123
+ # إجمالي التكاليف المباشرة
124
+ direct_costs = sum(labor_costs.values()) + sum(equipment_costs.values()) + sum(material_costs.values())
125
+
126
+ # حساب تكاليف الإدارة
127
+ management_percentage = self.config.get("management_percentage", 0.05)
128
+ indirect_costs["تكاليف الإدارة"] = direct_costs * management_percentage
129
+
130
+ # حساب تكاليف الضمان البنكي
131
+ bank_guarantee_percentage = self.config.get("bank_guarantee_percentage", 0.03)
132
+ indirect_costs["الضمان البنكي"] = direct_costs * bank_guarantee_percentage
133
+
134
+ # حساب تكاليف التأمين
135
+ insurance_percentage = self.config.get("insurance_percentage", 0.02)
136
+ indirect_costs["التأمين"] = direct_costs * insurance_percentage
137
+
138
+ # حساب تكاليف الموقع
139
+ site_costs_per_month = self.config.get("site_costs_per_month", 20000)
140
+ indirect_costs["تكاليف الموقع"] = site_costs_per_month * project_duration
141
+
142
+ # حساب تكاليف النقل
143
+ transportation_percentage = self.config.get("transportation_percentage", 0.04)
144
+ indirect_costs["النقل"] = sum(material_costs.values()) * transportation_percentage
145
+
146
+ # حساب تكاليف الاختبارات
147
+ testing_percentage = self.config.get("testing_percentage", 0.01)
148
+ indirect_costs["الاختبارات والفحص"] = direct_costs * testing_percentage
149
+
150
+ return indirect_costs
151
+
152
+ def _calculate_other_costs(self, project_info: Dict[str, Any]) -> Dict[str, float]:
153
+ """
154
+ حساب التكاليف الأخرى
155
+
156
+ المعاملات:
157
+ ----------
158
+ project_info : Dict[str, Any]
159
+ معلومات المشروع
160
+
161
+ المخرجات:
162
+ --------
163
+ Dict[str, float]
164
+ التكاليف الأخرى
165
+ """
166
+ other_costs = {}
167
+
168
+ # تحديد نوع المشروع
169
+ project_type = project_info.get("sector", "general")
170
+
171
+ # حساب تكاليف الترخيص
172
+ licensing_cost = self.config.get("licensing_costs", {}).get(project_type, 50000)
173
+ other_costs["تراخيص وتصاريح"] = licensing_cost
174
+
175
+ # حساب تكاليف الاستشارات
176
+ consulting_percentage = self.config.get("consulting_percentage", 0.03)
177
+ estimated_value = project_info.get("estimated_value", 1000000)
178
+ other_costs["استشارات"] = estimated_value * consulting_percentage
179
+
180
+ # حساب تكاليف الضمان
181
+ warranty_percentage = self.config.get("warranty_percentage", 0.02)
182
+ other_costs["ضمان وصيانة"] = estimated_value * warranty_percentage
183
+
184
+ # حساب تكاليف المطابقة
185
+ compliance_costs = self.config.get("compliance_costs", {}).get(project_type, 25000)
186
+ other_costs["مطابقة ومعايير"] = compliance_costs
187
+
188
+ # حساب تكاليف أخرى متنوعة
189
+ miscellaneous_percentage = self.config.get("miscellaneous_percentage", 0.05)
190
+ other_costs["متنوعة"] = estimated_value * miscellaneous_percentage
191
+
192
+ return other_costs
193
+
194
+ def _determine_project_type(self, scope_of_work: List[str]) -> str:
195
+ """
196
+ تحديد نوع المشروع بناءً على نطاق العمل
197
+
198
+ المعاملات:
199
+ ----------
200
+ scope_of_work : List[str]
201
+ نطاق العمل
202
+
203
+ المخرجات:
204
+ --------
205
+ str
206
+ نوع المشروع
207
+ """
208
+ # كلمات مفتاحية لكل نوع من المشاريع
209
+ keywords = {
210
+ "building": ["مبنى", "بناء", "تشييد", "عمارة", "هيكل", "مركز"],
211
+ "roads": ["طريق", "جسر", "نفق", "تقاطع", "دوار", "شارع"],
212
+ "infrastructure": ["بنية تحتية", "شبكة", "صرف", "مياه", "كهرباء", "اتصالات"],
213
+ "renovation": ["ترميم", "إصلاح", "صيانة", "تجديد", "تحديث", "تأهيل"],
214
+ "interior": ["تشطيب", "ديكور", "أثاث", "تجهيز", "داخلي", "قواطع"],
215
+ "landscaping": ["تنسيق", "حدائق", "مناظر", "زراعة", "تشجير", "خارجي"]
216
+ }
217
+
218
+ # حساب عدد الكلمات المفتاحية لكل نوع
219
+ scores = {
220
+ project_type: sum(1 for keyword in keywords_list for item in scope_of_work if keyword in item.lower())
221
+ for project_type, keywords_list in keywords.items()
222
+ }
223
+
224
+ # تحديد النوع بناءً على أعلى نتيجة
225
+ if not scores or max(scores.values()) == 0:
226
+ return "general"
227
+
228
+ return max(scores, key=scores.get)
229
+
230
+ def _determine_material_category(self, description: str) -> str:
231
+ """
232
+ تحديد فئة المادة بناءً على وصفها
233
+
234
+ المعاملات:
235
+ ----------
236
+ description : str
237
+ وصف المادة
238
+
239
+ المخرجات:
240
+ --------
241
+ str
242
+ فئة المادة
243
+ """
244
+ # كلمات مفتاحية لكل فئة
245
+ keywords = {
246
+ "مواد بناء أساسية": ["خرسانة", "حديد", "طابوق", "بلوك", "أسمنت", "رمل", "طين", "هيكل"],
247
+ "مواد تشطيب": ["دهان", "بلاط", "سيراميك", "رخام", "جبس", "أسقف", "أرضيات", "زجاج"],
248
+ "مواد كهربائية": ["كهرباء", "أسلاك", "إضاءة", "لوحات", "مفاتيح", "قواطع", "محولات"],
249
+ "مواد سباكة": ["سباكة", "أنابيب", "مواسير", "صرف", "مياه", "خزانات", "صحية"],
250
+ "مواد عزل": ["عزل", "مقاوم", "رطوبة", "حراري", "صوتي", "حريق"],
251
+ "مواد تكييف": ["تكييف", "تبريد", "تدفئة", "تهوية", "مكيفات", "قنوات"]
252
+ }
253
+
254
+ description_lower = description.lower()
255
+
256
+ # البحث عن الكلمات المفتاحية في الوصف
257
+ for category, keywords_list in keywords.items():
258
+ for keyword in keywords_list:
259
+ if keyword in description_lower:
260
+ return category
261
+
262
+ # إذا لم يتم العثور على مطابقة، إرجاع فئة أخرى
263
+ return "مواد أخرى"
264
+
265
+ def _determine_profit_margin(self, project_info: Dict[str, Any],
266
+ scope_of_work: List[str],
267
+ total_cost: float) -> float:
268
+ """
269
+ تحديد هامش الربح المناسب
270
+
271
+ المعاملات:
272
+ ----------
273
+ project_info : Dict[str, Any]
274
+ معلومات المشروع
275
+ scope_of_work : List[str]
276
+ نطاق العمل
277
+ total_cost : float
278
+ إجمالي التكاليف
279
+
280
+ المخرجات:
281
+ --------
282
+ float
283
+ هامش الربح المناسب (نسبة مئوية)
284
+ """
285
+ # الحصول على هامش الربح الأساسي لنوع المشروع
286
+ project_type = self._determine_project_type(scope_of_work)
287
+ base_margin = self.profit_margins.get(project_type, 15.0)
288
+
289
+ # تعديل هامش الربح بناءً على القطاع
290
+ sector = project_info.get("sector", "general")
291
+ sector_adjustment = self.profit_margins.get("sector_adjustments", {}).get(sector, 0.0)
292
+
293
+ # تعديل هامش الربح بناءً على الموقع
294
+ location = project_info.get("location", "").lower()
295
+ location_adjustment = 0.0
296
+
297
+ if "الرياض" in location or "جدة" in location or "الدمام" in location:
298
+ # المدن الرئيسية: هامش ربح أقل بسبب المنافسة
299
+ location_adjustment = -1.0
300
+ elif "نائية" in location or "بعيدة" in location:
301
+ # المناطق النائية: هامش ربح أعلى
302
+ location_adjustment = 2.0
303
+
304
+ # تعديل هامش الربح بناءً على حجم المشروع
305
+ size_adjustment = 0.0
306
+
307
+ if total_cost > 10000000: # مشروع كبير (أكثر من 10 مليون)
308
+ size_adjustment = -2.0 # هامش ربح أقل للمشاريع الكبيرة
309
+ elif total_cost < 1000000: # مشروع صغير (أقل من مليون)
310
+ size_adjustment = 3.0 # هامش ربح أعلى للمشاريع الصغيرة
311
+
312
+ # حساب هامش الربح النهائي
313
+ final_margin = base_margin + sector_adjustment + location_adjustment + size_adjustment
314
+
315
+ # التأكد من أن هامش الربح في النطاق المعقول
316
+ final_margin = max(8.0, min(25.0, final_margin))
317
+
318
+ return round(final_margin, 1)
319
+
320
+ def _generate_cashflow(self, labor_costs: Dict[str, float],
321
+ equipment_costs: Dict[str, float],
322
+ material_costs: Dict[str, float],
323
+ indirect_costs: Dict[str, float],
324
+ other_costs: Dict[str, float],
325
+ project_duration: int) -> List[Dict[str, Any]]:
326
+ """
327
+ إعداد توقعات التدفق النقدي
328
+
329
+ المعاملات:
330
+ ----------
331
+ labor_costs : Dict[str, float]
332
+ تكاليف العمالة
333
+ equipment_costs : Dict[str, float]
334
+ تكاليف المعدات
335
+ material_costs : Dict[str, float]
336
+ تكاليف المواد
337
+ indirect_costs : Dict[str, float]
338
+ التكاليف غير المباشرة
339
+ other_costs : Dict[str, float]
340
+ التكاليف الأخرى
341
+ project_duration : int
342
+ مدة المشروع بالأشهر
343
+
344
+ المخرجات:
345
+ --------
346
+ List[Dict[str, Any]]
347
+ توقعات التدفق النقدي
348
+ """
349
+ cashflow = []
350
+
351
+ # إجمالي التكاليف
352
+ total_labor_cost = sum(labor_costs.values())
353
+ total_equipment_cost = sum(equipment_costs.values())
354
+ total_material_cost = sum(material_costs.values())
355
+ total_indirect_cost = sum(indirect_costs.values())
356
+ total_other_cost = sum(other_costs.values())
357
+
358
+ # إجمالي تكلفة المشروع
359
+ total_cost = total_labor_cost + total_equipment_cost + total_material_cost + total_indirect_cost + total_other_cost
360
+
361
+ # توزيع التكاليف على أشهر المشروع
362
+ for month in range(1, project_duration + 1):
363
+ # حساب نسبة الإنجاز
364
+ progress = min(1.0, month / project_duration)
365
+
366
+ # حساب التكاليف لهذا الشهر
367
+ if month == 1:
368
+ # الشهر الأول: تكاليف بداية المشروع عالية
369
+ labor_cost = total_labor_cost * 0.1
370
+ equipment_cost = total_equipment_cost * 0.3
371
+ material_cost = total_material_cost * 0.2
372
+ indirect_cost = total_indirect_cost * (1 / project_duration)
373
+ other_cost = total_other_cost * 0.3
374
+ elif month == project_duration:
375
+ # الشهر الأخير: تكاليف نهاية المشروع
376
+ labor_cost = total_labor_cost * 0.15
377
+ equipment_cost = total_equipment_cost * 0.05
378
+ material_cost = total_material_cost * 0.05
379
+ indirect_cost = total_indirect_cost * (1 / project_duration)
380
+ other_cost = total_other_cost * 0.2
381
+ else:
382
+ # الأشهر الوسطى: توزيع متساوٍ نسبيًا
383
+ remaining_months = project_duration - 2 # باستثناء الشهر الأول والأخير
384
+ labor_cost = total_labor_cost * 0.75 / remaining_months
385
+ equipment_cost = total_equipment_cost * 0.65 / remaining_months
386
+ material_cost = total_material_cost * 0.75 / remaining_months
387
+ indirect_cost = total_indirect_cost * (1 / project_duration)
388
+ other_cost = total_other_cost * 0.5 / remaining_months
389
+
390
+ # حساب الإيرادات المتوقعة (بافتراض دفعات متساوية)
391
+ expected_revenue = (total_cost * 1.15) / project_duration # إضافة 15% كهامش ربح
392
+
393
+ # إضافة بيانات التدفق النقدي لهذا الشهر
394
+ cashflow.append({
395
+ "month": month,
396
+ "labor_cost": labor_cost,
397
+ "equipment_cost": equipment_cost,
398
+ "material_cost": material_cost,
399
+ "indirect_cost": indirect_cost,
400
+ "other_cost": other_cost,
401
+ "total_cost": labor_cost + equipment_cost + material_cost + indirect_cost + other_cost,
402
+ "expected_revenue": expected_revenue,
403
+ "net_cashflow": expected_revenue - (labor_cost + equipment_cost + material_cost + indirect_cost + other_cost),
404
+ "progress_percentage": progress * 100
405
+ })
406
+
407
+ return cashflow
408
+
409
+ def _load_cost_database(self) -> Dict[str, Any]:
410
+ """
411
+ تحميل قاعدة بيانات التكاليف
412
+
413
+ المخرجات:
414
+ --------
415
+ Dict[str, Any]
416
+ قاعدة بيانات التكاليف
417
+ """
418
+ try:
419
+ file_path = 'data/templates/cost_database.json'
420
+ if os.path.exists(file_path):
421
+ with open(file_path, 'r', encoding='utf-8') as f:
422
+ return json.load(f)
423
+ else:
424
+ logger.warning(f"ملف قاعدة بيانات التكاليف غير موجود: {file_path}")
425
+ # إنشاء قاعدة بيانات افتراضية
426
+ return self._create_default_cost_db()
427
+ except Exception as e:
428
+ logger.error(f"فشل في تحميل قاعدة بيانات التكاليف: {str(e)}")
429
+ return self._create_default_cost_db()
430
+
431
+ def _load_equipment_costs(self) -> Dict[str, Dict[str, Any]]:
432
+ """
433
+ تحميل تكاليف المعدات
434
+
435
+ المخرجات:
436
+ --------
437
+ Dict[str, Dict[str, Any]]
438
+ تكاليف المعدات
439
+ """
440
+ try:
441
+ file_path = 'data/templates/equipment_costs.json'
442
+ if os.path.exists(file_path):
443
+ with open(file_path, 'r', encoding='utf-8') as f:
444
+ return json.load(f)
445
+ else:
446
+ logger.warning(f"ملف تكاليف المعدات غير موجود: {file_path}")
447
+ # إنشاء بيانات افتراضية
448
+ return self._create_default_equipment_costs()
449
+ except Exception as e:
450
+ logger.error(f"فشل في تحميل تكاليف المعدات: {str(e)}")
451
+ return self._create_default_equipment_costs()
452
+
453
+ def _load_labor_costs(self) -> Dict[str, Dict[str, Any]]:
454
+ """
455
+ تحميل تكاليف العمالة
456
+
457
+ المخرجات:
458
+ --------
459
+ Dict[str, Dict[str, Any]]
460
+ تكاليف العمالة
461
+ """
462
+ try:
463
+ file_path = 'data/templates/labor_costs.json'
464
+ if os.path.exists(file_path):
465
+ with open(file_path, 'r', encoding='utf-8') as f:
466
+ return json.load(f)
467
+ else:
468
+ logger.warning(f"ملف تكاليف العمالة غير موجود: {file_path}")
469
+ # إنشاء بيانات افتراضية
470
+ return self._create_default_labor_costs()
471
+ except Exception as e:
472
+ logger.error(f"فشل في تحميل تكاليف العمالة: {str(e)}")
473
+ return self._create_default_labor_costs()
474
+
475
+ def _load_profit_margins(self) -> Dict[str, Any]:
476
+ """
477
+ تحميل معدلات الأرباح النموذجية
478
+
479
+ المخرجات:
480
+ --------
481
+ Dict[str, Any]
482
+ معدلات الأرباح النموذجية
483
+ """
484
+ try:
485
+ # يمكن أن تكون هذه البيانات جزءًا من قاعدة بيانات التكاليف
486
+ profit_margins = self.cost_db.get("profit_margins", {})
487
+ if profit_margins:
488
+ return profit_margins
489
+ else:
490
+ logger.warning("معدلات الأرباح النموذجية غير موجودة في قاعدة البيانات")
491
+ # إنشاء بيانات افتراضية
492
+ return self._create_default_profit_margins()
493
+ except Exception as e:
494
+ logger.error(f"فشل في تحميل معدلات الأرباح النموذجية: {str(e)}")
495
+ return self._create_default_profit_margins()
496
+
497
+ def _create_default_cost_db(self) -> Dict[str, Any]:
498
+ """
499
+ إنشاء قاعدة بيانات تكاليف افتراضية
500
+
501
+ المخرجات:
502
+ --------
503
+ Dict[str, Any]
504
+ قاعدة بيانات تكاليف افتراضية
505
+ """
506
+ return {
507
+ "labor_requirements": {
508
+ "building": {
509
+ "مهندس مدني": 2,
510
+ "مهندس معماري": 1,
511
+ "مهندس كهرباء": 1,
512
+ "مهندس ميكانيكا": 1,
513
+ "مراقب": 3,
514
+ "فني": 10,
515
+ "عامل": 20
516
+ },
517
+ "roads": {
518
+ "مهندس مدني": 3,
519
+ "مهندس مساحة": 2,
520
+ "مراقب": 4,
521
+ "فني": 8,
522
+ "عامل": 30
523
+ },
524
+ "infrastructure": {
525
+ "مهندس مدني": 2,
526
+ "مهندس كهرباء": 2,
527
+ "مهندس ميكانيكا": 2,
528
+ "مراقب": 4,
529
+ "فني": 15,
530
+ "عامل": 25
531
+ },
532
+ "renovation": {
533
+ "مهندس مدني": 1,
534
+ "مهندس معماري": 1,
535
+ "مراقب": 2,
536
+ "فني": 8,
537
+ "عامل": 15
538
+ },
539
+ "interior": {
540
+ "مهندس معماري": 2,
541
+ "مصمم داخلي": 2,
542
+ "فني": 12,
543
+ "عامل": 10
544
+ },
545
+ "landscaping": {
546
+ "مهندس زراعي": 1,
547
+ "مصمم مناظر": 1,
548
+ "فني": 5,
549
+ "عامل": 15
550
+ },
551
+ "general": {
552
+ "مهندس": 2,
553
+ "مراقب": 2,
554
+ "فني": 5,
555
+ "عامل": 10
556
+ }
557
+ },
558
+ "equipment_requirements": {
559
+ "building": {
560
+ "رافعة": {
561
+ "monthly_cost": 30000,
562
+ "purchase_cost": 500000,
563
+ "cost_type": "إيجار",
564
+ "maintenance_cost": 5000
565
+ },
566
+ "خلاطة خرسانة": {
567
+ "monthly_cost": 15000,
568
+ "purchase_cost": 200000,
569
+ "cost_type": "إيجار",
570
+ "maintenance_cost": 3000
571
+ },
572
+ "شاحنة نقل": {
573
+ "monthly_cost": 12000,
574
+ "purchase_cost": 300000,
575
+ "cost_type": "إيجار",
576
+ "maintenance_cost": 4000
577
+ },
578
+ "جرافة": {
579
+ "monthly_cost": 25000,
580
+ "purchase_cost": 450000,
581
+ "cost_type": "إيجار",
582
+ "maintenance_cost": 6000
583
+ },
584
+ "دكاكة": {
585
+ "monthly_cost": 10000,
586
+ "purchase_cost": 180000,
587
+ "cost_type": "إيجار",
588
+ "maintenance_cost": 2500
589
+ },
590
+ "حفارة": {
591
+ "monthly_cost": 28000,
592
+ "purchase_cost": 480000,
593
+ "cost_type": "إيجار",
594
+ "maintenance_cost": 7000
595
+ },
596
+ "مولد كهرباء": {
597
+ "monthly_cost": 8000,
598
+ "purchase_cost": 120000,
599
+ "cost_type": "إيجار",
600
+ "maintenance_cost": 2000
601
+ },
602
+ "معدات رصف": {
603
+ "monthly_cost": 18000,
604
+ "purchase_cost": 350000,
605
+ "cost_type": "إيجار",
606
+ "maintenance_cost": 4500
607
+ },
608
+ "معدات لحام": {
609
+ "monthly_cost": 5000,
610
+ "purchase_cost": 50000,
611
+ "cost_type": "شراء",
612
+ "maintenance_cost": 1000
613
+ },
614
+ "معدات هدم": {
615
+ "monthly_cost": 15000,
616
+ "purchase_cost": 200000,
617
+ "cost_type": "إيجار",
618
+ "maintenance_cost": 3500
619
+ },
620
+ "سقالات": {
621
+ "monthly_cost": 500,
622
+ "purchase_cost": 5000,
623
+ "cost_type": "إيجار",
624
+ "maintenance_cost": 500
625
+ },
626
+ "معدات نجارة": {
627
+ "monthly_cost": 7000,
628
+ "purchase_cost": 80000,
629
+ "cost_type": "شراء",
630
+ "maintenance_cost": 1500
631
+ },
632
+ "معدات دهان": {
633
+ "monthly_cost": 4000,
634
+ "purchase_cost": 40000,
635
+ "cost_type": "شراء",
636
+ "maintenance_cost": 1000
637
+ },
638
+ "معدات ري": {
639
+ "monthly_cost": 6000,
640
+ "purchase_cost": 70000,
641
+ "cost_type": "شراء",
642
+ "maintenance_cost": 1200
643
+ },
644
+ "معدات زراعة": {
645
+ "monthly_cost": 5500,
646
+ "purchase_cost": 65000,
647
+ "cost_type": "شراء",
648
+ "maintenance_cost": 1100
649
+ },
650
+ "معدات يدوية": {
651
+ "monthly_cost": 2000,
652
+ "purchase_cost": 25000,
653
+ "cost_type": "شراء",
654
+ "maintenance_cost": 500
655
+ }
656
+ }
657
+
658
+ def _create_default_labor_costs(self) -> Dict[str, Dict[str, Any]]:
659
+ """
660
+ إنشاء بيانات تكاليف عمالة افتراضية
661
+
662
+ المخرجات:
663
+ --------
664
+ Dict[str, Dict[str, Any]]
665
+ بيانات تكاليف عمالة افتراضية
666
+ """
667
+ return {
668
+ "مهندس مدني": {
669
+ "monthly_cost": 15000,
670
+ "nationality": "سعودي",
671
+ "experience_years": 5,
672
+ "availability": "عالية"
673
+ },
674
+ "مهندس معماري": {
675
+ "monthly_cost": 14000,
676
+ "nationality": "سعودي",
677
+ "experience_years": 5,
678
+ "availability": "عالية"
679
+ },
680
+ "مهندس كهرباء": {
681
+ "monthly_cost": 14500,
682
+ "nationality": "سعودي",
683
+ "experience_years": 5,
684
+ "availability": "متوسطة"
685
+ },
686
+ "مهندس ميكانيكا": {
687
+ "monthly_cost": 14800,
688
+ "nationality": "سعودي",
689
+ "experience_years": 5,
690
+ "availability": "متوسطة"
691
+ },
692
+ "مهندس مساحة": {
693
+ "monthly_cost": 13000,
694
+ "nationality": "سعودي",
695
+ "experience_years": 4,
696
+ "availability": "متوسطة"
697
+ },
698
+ "مهندس زراعي": {
699
+ "monthly_cost": 12000,
700
+ "nationality": "سعودي",
701
+ "experience_years": 4,
702
+ "availability": "منخفضة"
703
+ },
704
+ "مصمم داخلي": {
705
+ "monthly_cost": 12500,
706
+ "nationality": "سعودي",
707
+ "experience_years": 4,
708
+ "availability": "متوسطة"
709
+ },
710
+ "مصمم مناظر": {
711
+ "monthly_cost": 12000,
712
+ "nationality": "سعودي",
713
+ "experience_years": 4,
714
+ "availability": "منخفضة"
715
+ },
716
+ "مراقب": {
717
+ "monthly_cost": 9000,
718
+ "nationality": "سعودي",
719
+ "experience_years": 8,
720
+ "availability": "عالية"
721
+ },
722
+ "فني": {
723
+ "monthly_cost": 6000,
724
+ "nationality": "غير سعودي",
725
+ "experience_years": 10,
726
+ "availability": "عالية"
727
+ },
728
+ "عامل": {
729
+ "monthly_cost": 3500,
730
+ "nationality": "غير سعودي",
731
+ "experience_years": 5,
732
+ "availability": "عالية"
733
+ },
734
+ "مهندس": {
735
+ "monthly_cost": 14000,
736
+ "nationality": "سعودي",
737
+ "experience_years": 5,
738
+ "availability": "عالية"
739
+ }
740
+ }
741
+
742
+ def _create_default_profit_margins(self) -> Dict[str, Any]:
743
+ """
744
+ إنشاء بيانات معدلات أرباح افتراضية
745
+
746
+ المخرجات:
747
+ --------
748
+ Dict[str, Any]
749
+ بيانات معدلات أرباح افتراضية
750
+ """
751
+ return {
752
+ "building": 15.0,
753
+ "roads": 12.0,
754
+ "infrastructure": 14.0,
755
+ "renovation": 18.0,
756
+ "interior": 20.0,
757
+ "landscaping": 22.0,
758
+ "general": 15.0,
759
+ "sector_adjustments": {
760
+ "حكومي": -2.0,
761
+ "خاص": 2.0,
762
+ "نفط وغاز": 3.0,
763
+ "صناعي": 1.0,
764
+ "تجاري": 0.0,
765
+ "سكني": -1.0
766
+ }
767
+ }ة": 1,
768
+ "خلاطة خرسانة": 2,
769
+ "شاحنة نقل": 3,
770
+ "مولد كهرباء": 2,
771
+ "معدات يدوية": 10
772
+ },
773
+ "roads": {
774
+ "جرافة": 2,
775
+ "دكاكة": 3,
776
+ "شاحنة نقل": 5,
777
+ "معدات رصف": 2,
778
+ "معدات يدوية": 10
779
+ },
780
+ "infrastructure": {
781
+ "حفارة": 3,
782
+ "شاحنة نقل": 4,
783
+ "مولد كهرباء": 2,
784
+ "معدات لحام": 5,
785
+ "معدات يدوية": 15
786
+ },
787
+ "renovation": {
788
+ "سقالات": 20,
789
+ "شاحنة نقل": 1,
790
+ "مولد كهرباء": 1,
791
+ "معدات هدم": 3,
792
+ "معدات يدوية": 10
793
+ },
794
+ "interior": {
795
+ "معدات نجارة": 5,
796
+ "معدات دهان": 5,
797
+ "سقالات": 10,
798
+ "مولد كهرباء": 1,
799
+ "معدات يدوية": 15
800
+ },
801
+ "landscaping": {
802
+ "جرافة صغيرة": 1,
803
+ "معدات حفر": 3,
804
+ "معدات ري": 5,
805
+ "معدات زراعة": 5,
806
+ "شاحنة نقل": 1
807
+ },
808
+ "general": {
809
+ "شاحنة نقل": 1,
810
+ "مولد كهرباء": 1,
811
+ "معدات يدوية": 5
812
+ }
813
+ },
814
+ "material_prices": {
815
+ "مواد بناء أساسية": 1000,
816
+ "مواد تشطيب": 1500,
817
+ "مواد كهربائية": 2000,
818
+ "مواد سباكة": 1800,
819
+ "مواد عزل": 2200,
820
+ "مواد تكييف": 2500,
821
+ "مواد أخرى": 1200
822
+ },
823
+ "profit_margins": {
824
+ "building": 15.0,
825
+ "roads": 12.0,
826
+ "infrastructure": 14.0,
827
+ "renovation": 18.0,
828
+ "interior": 20.0,
829
+ "landscaping": 22.0,
830
+ "general": 15.0,
831
+ "sector_adjustments": {
832
+ "حكومي": -2.0,
833
+ "خاص": 2.0,
834
+ "نفط وغاز": 3.0,
835
+ "صناعي": 1.0,
836
+ "تجاري": 0.0,
837
+ "سكني": -1.0
838
+ }
839
+ }
840
+ }
841
+
842
+ def _create_default_equipment_costs(self) -> Dict[str, Dict[str, Any]]:
843
+ """
844
+ إنشاء بيانات تكاليف معدات افتراضية
845
+
846
+ المخرجات:
847
+ --------
848
+ Dict[str, Dict[str, Any]]
849
+ بيانات تكاليف معدات افتراضية
850
+ """
851
+ return {
852
+ "رافع"""
853
+ محلل تقدير التكاليف للمن��قصات
854
+ يقوم بتحليل وتقدير تكاليف المشروع وإعداد ميزانية تقديرية
855
+ """
856
+
857
+ import re
858
+ import json
859
+ import logging
860
+ import os
861
+ import math
862
+ from datetime import datetime, timedelta
863
+ from typing import Dict, List, Any, Tuple, Optional, Union
864
+ import numpy as np
865
+
866
+ logger = logging.getLogger(__name__)
867
+
868
+ class CostEstimator:
869
+ """
870
+ محلل تقدير التكاليف للمناقصات
871
+ """
872
+
873
+ def __init__(self, config=None):
874
+ """
875
+ تهيئة محلل التكاليف
876
+
877
+ المعاملات:
878
+ ----------
879
+ config : Dict, optional
880
+ إعدادات المحلل
881
+ """
882
+ self.config = config or {}
883
+
884
+ # تحميل قواعد بيانات التكاليف
885
+ self.cost_db = self._load_cost_database()
886
+ self.equipment_costs = self._load_equipment_costs()
887
+ self.labor_costs = self._load_labor_costs()
888
+
889
+ # تحميل معدلات الأرباح النموذجية
890
+ self.profit_margins = self._load_profit_margins()
891
+
892
+ logger.info("تم تهيئة محلل تقدير التكاليف")
893
+
894
+ def estimate(self, extracted_text: str) -> Dict[str, Any]:
895
+ """
896
+ تقدير تكاليف المشروع من نص المناقصة
897
+
898
+ المعاملات:
899
+ ----------
900
+ extracted_text : str
901
+ النص المستخرج من المناقصة
902
+
903
+ المخرجات:
904
+ --------
905
+ Dict[str, Any]
906
+ تقديرات التكاليف
907
+ """
908
+ try:
909
+ logger.info("بدء تقدير تكاليف المشروع")
910
+
911
+ # استخراج معلومات المشروع
912
+ project_info = self._extract_project_info(extracted_text)
913
+
914
+ # استخراج نطاق العمل
915
+ scope_of_work = self._extract_scope_of_work(extracted_text)
916
+
917
+ # استخراج المدة الزمنية
918
+ project_duration = self._extract_project_duration(extracted_text)
919
+
920
+ # استخراج الكميات والبنود
921
+ quantities = self._extract_quantities(extracted_text)
922
+
923
+ # حساب تكاليف العمالة
924
+ labor_costs = self._calculate_labor_costs(scope_of_work, project_duration)
925
+
926
+ # حساب تكاليف المعدات
927
+ equipment_costs = self._calculate_equipment_costs(scope_of_work, project_duration)
928
+
929
+ # حساب تكاليف المواد
930
+ material_costs = self._calculate_material_costs(quantities)
931
+
932
+ # حساب التكاليف غير المباشرة
933
+ indirect_costs = self._calculate_indirect_costs(
934
+ labor_costs, equipment_costs, material_costs, project_duration
935
+ )
936
+
937
+ # حساب التكاليف الأخرى
938
+ other_costs = self._calculate_other_costs(project_info)
939
+
940
+ # إجمالي التكاليف
941
+ total_cost = sum([
942
+ sum(labor_costs.values()),
943
+ sum(equipment_costs.values()),
944
+ sum(material_costs.values()),
945
+ sum(indirect_costs.values()),
946
+ sum(other_costs.values())
947
+ ])
948
+
949
+ # تحديد هامش الربح المناسب
950
+ profit_margin = self._determine_profit_margin(
951
+ project_info, scope_of_work, total_cost
952
+ )
953
+
954
+ # حساب قيمة العرض المقترحة
955
+ profit_amount = total_cost * (profit_margin / 100)
956
+ proposed_bid = total_cost + profit_amount
957
+
958
+ # إعداد توقعات التدفق النقدي
959
+ cashflow = self._generate_cashflow(
960
+ labor_costs, equipment_costs, material_costs,
961
+ indirect_costs, other_costs, project_duration
962
+ )
963
+
964
+ # إعداد النتائج
965
+ results = {
966
+ "total_cost": total_cost,
967
+ "profit_margin": profit_margin,
968
+ "profit_amount": profit_amount,
969
+ "proposed_bid": proposed_bid,
970
+ "project_duration": project_duration,
971
+ "breakdown": {
972
+ "labor": labor_costs,
973
+ "equipment": equipment_costs,
974
+ "material": material_costs,
975
+ "indirect": indirect_costs,
976
+ "other": other_costs
977
+ },
978
+ "cashflow": cashflow,
979
+ "project_info": project_info,
980
+ "scope_of_work": scope_of_work
981
+ }
982
+
983
+ logger.info(f"اكتمل تقدير التكاليف: {total_cost:,.2f} ريال، هامش ربح {profit_margin:.1f}%")
984
+ return results
985
+
986
+ except Exception as e:
987
+ logger.error(f"فشل في تقدير التكاليف: {str(e)}")
988
+ return {
989
+ "total_cost": 0,
990
+ "profit_margin": 0,
991
+ "profit_amount": 0,
992
+ "proposed_bid": 0,
993
+ "project_duration": 0,
994
+ "breakdown": {
995
+ "labor": {},
996
+ "equipment": {},
997
+ "material": {},
998
+ "indirect": {},
999
+ "other": {}
1000
+ },
1001
+ "cashflow": [],
1002
+ "error": str(e)
1003
+ }
1004
+
1005
+ def _extract_project_info(self, text: str) -> Dict[str, Any]:
1006
+ """
1007
+ استخراج معلومات المشروع من النص
1008
+
1009
+ المعاملات:
1010
+ ----------
1011
+ text : str
1012
+ النص المستخرج من المناقصة
1013
+
1014
+ المخرجات:
1015
+ --------
1016
+ Dict[str, Any]
1017
+ معلومات المشروع
1018
+ """
1019
+ info = {
1020
+ "title": "غير محدد",
1021
+ "location": "غير محدد",
1022
+ "client": "غير محدد",
1023
+ "sector": "غير محدد",
1024
+ "estimated_value": 0
1025
+ }
1026
+
1027
+ # البحث عن عنوان المشروع
1028
+ title_patterns = [
1029
+ r'اسم المشروع[:\s]+(.*?)(?:\n|$)',
1030
+ r'عنوان المشروع[:\s]+(.*?)(?:\n|$)',
1031
+ r'مسمى المشروع[:\s]+(.*?)(?:\n|$)',
1032
+ r'المشروع[:\s]+(.*?)(?:\n|$)'
1033
+ ]
1034
+
1035
+ for pattern in title_patterns:
1036
+ match = re.search(pattern, text, re.IGNORECASE)
1037
+ if match:
1038
+ info["title"] = match.group(1).strip()
1039
+ break
1040
+
1041
+ # البحث عن موقع المشروع
1042
+ location_patterns = [
1043
+ r'موقع المشروع[:\s]+(.*?)(?:\n|$)',
1044
+ r'الموقع[:\s]+(.*?)(?:\n|$)',
1045
+ r'المدينة[:\s]+(.*?)(?:\n|$)',
1046
+ r'المنطقة[:\s]+(.*?)(?:\n|$)'
1047
+ ]
1048
+
1049
+ for pattern in location_patterns:
1050
+ match = re.search(pattern, text, re.IGNORECASE)
1051
+ if match:
1052
+ info["location"] = match.group(1).strip()
1053
+ break
1054
+
1055
+ # البحث عن العميل
1056
+ client_patterns = [
1057
+ r'العميل[:\s]+(.*?)(?:\n|$)',
1058
+ r'صاحب العمل[:\s]+(.*?)(?:\n|$)',
1059
+ r'الجهة المالكة[:\s]+(.*?)(?:\n|$)',
1060
+ r'الجهة المعلنة[:\s]+(.*?)(?:\n|$)'
1061
+ ]
1062
+
1063
+ for pattern in client_patterns:
1064
+ match = re.search(pattern, text, re.IGNORECASE)
1065
+ if match:
1066
+ info["client"] = match.group(1).strip()
1067
+ break
1068
+
1069
+ # البحث عن القطاع
1070
+ sector_patterns = [
1071
+ r'القطاع[:\s]+(.*?)(?:\n|$)',
1072
+ r'نوع المشروع[:\s]+(.*?)(?:\n|$)',
1073
+ r'فئة المشروع[:\s]+(.*?)(?:\n|$)',
1074
+ r'تصنيف المشروع[:\s]+(.*?)(?:\n|$)'
1075
+ ]
1076
+
1077
+ for pattern in sector_patterns:
1078
+ match = re.search(pattern, text, re.IGNORECASE)
1079
+ if match:
1080
+ info["sector"] = match.group(1).strip()
1081
+ break
1082
+
1083
+ # البحث عن القيمة التقديرية
1084
+ value_patterns = [
1085
+ r'القيمة التقديرية[:\s]+.*?(\d[\d,\.]+)\s*ريال',
1086
+ r'التكلفة التقديرية[:\s]+.*?(\d[\d,\.]+)\s*ريال',
1087
+ r'الميزانية التقديرية[:\s]+.*?(\d[\d,\.]+)\s*ريال',
1088
+ r'قيمة المشروع[:\s]+.*?(\d[\d,\.]+)\s*ريال'
1089
+ ]
1090
+
1091
+ for pattern in value_patterns:
1092
+ match = re.search(pattern, text, re.IGNORECASE)
1093
+ if match:
1094
+ value_str = match.group(1).replace(',', '')
1095
+ try:
1096
+ info["estimated_value"] = float(value_str)
1097
+ except ValueError:
1098
+ pass
1099
+ break
1100
+
1101
+ return info
1102
+
1103
+ def _extract_scope_of_work(self, text: str) -> List[str]:
1104
+ """
1105
+ استخراج نطاق العمل من النص
1106
+
1107
+ المعاملات:
1108
+ ----------
1109
+ text : str
1110
+ النص المستخرج من المناقصة
1111
+
1112
+ المخرجات:
1113
+ --------
1114
+ List[str]
1115
+ قائمة ببنود نطاق العمل
1116
+ """
1117
+ scope_items = []
1118
+
1119
+ # البحث عن قسم نطاق العمل
1120
+ scope_section_patterns = [
1121
+ r'نطاق العمل.*?(?=\n\n|\Z)',
1122
+ r'وصف المشروع.*?(?=\n\n|\Z)',
1123
+ r'وصف الأعمال.*?(?=\n\n|\Z)',
1124
+ r'الأعمال المطلوبة.*?(?=\n\n|\Z)',
1125
+ r'بنود الأعمال.*?(?=\n\n|\Z)'
1126
+ ]
1127
+
1128
+ scope_section = ""
1129
+ for pattern in scope_section_patterns:
1130
+ match = re.search(pattern, text, re.DOTALL)
1131
+ if match:
1132
+ scope_section = match.group(0)
1133
+ break
1134
+
1135
+ if not scope_section:
1136
+ # إذا لم نجد قسمًا محددًا، نحاول البحث عن قائمة بنقاط
1137
+ bullet_lists = re.findall(r'(?:^|\n)(?:[•\-*]\s+.*(?:\n|$))+', text)
1138
+ if bullet_lists:
1139
+ longest_list = max(bullet_lists, key=len)
1140
+ scope_section = longest_list
1141
+
1142
+ # استخراج العناصر من القسم
1143
+ if scope_section:
1144
+ # البحث عن القوائم بنقاط
1145
+ bullet_items = re.findall(r'[•\-*]\s+(.*?)(?:\n|$)', scope_section)
1146
+ if bullet_items:
1147
+ scope_items.extend([item.strip() for item in bullet_items if item.strip()])
1148
+
1149
+ # إذا لم نجد قوائم بنقاط، نقسم النص إلى فقرات
1150
+ if not scope_items:
1151
+ paragraphs = re.split(r'\n\s*\n', scope_section)
1152
+ for paragraph in paragraphs[1:]: # تخطي العنوان
1153
+ if paragraph.strip():
1154
+ scope_items.append(paragraph.strip())
1155
+
1156
+ # إذا لم نتمكن من استخراج أي عناصر، نستخدم القيم الافتراضية
1157
+ if not scope_items:
1158
+ # التخمين بناءً على عنوان المشروع
1159
+ project_info = self._extract_project_info(text)
1160
+ title = project_info.get("title", "").lower()
1161
+
1162
+ # تخمين نطاق العمل بناءً على الكلمات المفتاحية في العنوان
1163
+ if any(word in title for word in ["بناء", "تشييد", "إنشاء", "مبنى"]):
1164
+ scope_items = [
1165
+ "أعمال الحفر والردم",
1166
+ "أعمال الخرسانة",
1167
+ "أعمال البناء",
1168
+ "أعمال التشطيبات",
1169
+ "أعمال الكهرباء",
1170
+ "أعمال السباكة",
1171
+ "أعمال التكييف"
1172
+ ]
1173
+ elif any(word in title for word in ["طريق", "جسر", "كوبري", "نفق"]):
1174
+ scope_items = [
1175
+ "أعمال الحفر والردم",
1176
+ "أعمال الأساسات",
1177
+ "أعمال الخرسانة",
1178
+ "أعمال الأسفلت",
1179
+ "أعمال الإنارة",
1180
+ "أعمال التصريف"
1181
+ ]
1182
+ elif any(word in title for word in ["صيانة", "تأهيل", "ترميم"]):
1183
+ scope_items = [
1184
+ "أعمال الفحص والتقييم",
1185
+ "أعمال الصيانة الوقائية",
1186
+ "أعمال الإصلاح",
1187
+ "أعمال الاستبدال",
1188
+ "أعمال التنظيف"
1189
+ ]
1190
+ else:
1191
+ scope_items = [
1192
+ "أعمال التحضير والتجهيز",
1193
+ "أعمال التنفيذ",
1194
+ "أعمال التشطيب",
1195
+ "أعمال الاختبار والتشغيل",
1196
+ "أعمال التسليم"
1197
+ ]
1198
+
1199
+ return scope_items
1200
+
1201
+ def _extract_project_duration(self, text: str) -> int:
1202
+ """
1203
+ استخراج المدة الزمنية للمشروع بالأشهر
1204
+
1205
+ المعاملات:
1206
+ ----------
1207
+ text : str
1208
+ النص المستخرج من المناقصة
1209
+
1210
+ المخرجات:
1211
+ --------
1212
+ int
1213
+ مدة المشروع بالأشهر
1214
+ """
1215
+ # البحث عن المدة الزمنية
1216
+ duration_patterns = [
1217
+ r'مدة المشروع[:\s]+(\d+)\s*(?:شهر|أشهر|شهور)',
1218
+ r'مدة التنفيذ[:\s]+(\d+)\s*(?:شهر|أشهر|شهور)',
1219
+ r'مدة العقد[:\s]+(\d+)\s*(?:شهر|أشهر|شهور)',
1220
+ r'المدة الزمنية[:\s]+(\d+)\s*(?:شهر|أشهر|شهور)',
1221
+ r'فترة التنفيذ[:\s]+(\d+)\s*(?:شهر|أشهر|شهور)',
1222
+
1223
+ # البحث عن المدة بالأيام
1224
+ r'مدة المشروع[:\s]+(\d+)\s*(?:يوم|أيام)',
1225
+ r'مدة التنفيذ[:\s]+(\d+)\s*(?:يوم|أيام)',
1226
+ r'مدة العقد[:\s]+(\d+)\s*(?:يوم|أيام)',
1227
+ r'المدة الزمنية[:\s]+(\d+)\s*(?:يوم|أيام)',
1228
+ r'فترة التنفيذ[:\s]+(\d+)\s*(?:يوم|أيام)',
1229
+
1230
+ # البحث عن المدة بالسنوات
1231
+ r'مدة المشروع[:\s]+(\d+)\s*(?:سنة|سنوات)',
1232
+ r'مدة ال��نفيذ[:\s]+(\d+)\s*(?:سنة|سنوات)',
1233
+ r'مدة العقد[:\s]+(\d+)\s*(?:سنة|سنوات)',
1234
+ r'المدة الزمنية[:\s]+(\d+)\s*(?:سنة|سنوات)',
1235
+ r'فترة التنفيذ[:\s]+(\d+)\s*(?:سنة|سنوات)'
1236
+ ]
1237
+
1238
+ for pattern in duration_patterns:
1239
+ match = re.search(pattern, text, re.IGNORECASE)
1240
+ if match:
1241
+ duration = int(match.group(1))
1242
+
1243
+ # تحويل المدة إلى أشهر
1244
+ if "يوم" in pattern or "أيام" in pattern:
1245
+ return max(1, round(duration / 30))
1246
+ elif "سنة" in pattern or "سنوات" in pattern:
1247
+ return duration * 12
1248
+ else:
1249
+ return duration
1250
+
1251
+ # إذا لم نتمكن من استخراج المدة، نستخدم قيمة افتراضية
1252
+ return 12 # قيمة افتراضية: 12 شهر
1253
+
1254
+ def _extract_quantities(self, text: str) -> List[Dict[str, Any]]:
1255
+ """
1256
+ استخراج الكميات والبنود من النص
1257
+
1258
+ المعاملات:
1259
+ ----------
1260
+ text : str
1261
+ النص المستخرج من المناقصة
1262
+
1263
+ المخرجات:
1264
+ --------
1265
+ List[Dict[str, Any]]
1266
+ قائمة بالكميات والبنود
1267
+ """
1268
+ quantities = []
1269
+
1270
+ # البحث عن جداول الكميات
1271
+ table_sections = self._extract_tables(text)
1272
+
1273
+ for section in table_sections:
1274
+ # تقسيم الجدول إلى أسطر
1275
+ lines = section.strip().split('\n')
1276
+
1277
+ # تخطي العنوان
1278
+ for i in range(1, len(lines)):
1279
+ line = lines[i].strip()
1280
+ if not line:
1281
+ continue
1282
+
1283
+ # تقسيم السطر إلى أعمدة
1284
+ if '|' in line:
1285
+ columns = [col.strip() for col in line.split('|')]
1286
+ else:
1287
+ # محاولة تقسيم بناءً على المسافات المتعددة
1288
+ columns = re.split(r'\s{2,}', line)
1289
+
1290
+ if len(columns) < 2:
1291
+ continue
1292
+
1293
+ # استخراج البيانات
1294
+ item = {}
1295
+
1296
+ # تحديد الأعمدة المختلفة بناءً على طول القائمة والكلمات المفتاحية
1297
+ if len(columns) >= 4:
1298
+ # افتراض: رقم البند | الوصف | الكمية | الوحدة
1299
+ item = {
1300
+ "id": columns[0],
1301
+ "description": columns[1],
1302
+ "quantity": self._extract_number(columns[2]),
1303
+ "unit": columns[3] if len(columns) > 3 else ""
1304
+ }
1305
+ elif len(columns) == 3:
1306
+ # افتراض: الوصف | الكمية | الوحدة
1307
+ item = {
1308
+ "description": columns[0],
1309
+ "quantity": self._extract_number(columns[1]),
1310
+ "unit": columns[2]
1311
+ }
1312
+ elif len(columns) == 2:
1313
+ # افتراض: الوصف | الكمية
1314
+ item = {
1315
+ "description": columns[0],
1316
+ "quantity": self._extract_number(columns[1]),
1317
+ "unit": ""
1318
+ }
1319
+
1320
+ # إضافة العنصر إذا كان يحتوي على وصف وكمية صالحة
1321
+ if item.get("description") and item.get("quantity", 0) > 0:
1322
+ quantities.append(item)
1323
+
1324
+ # إذا لم نتمكن من استخراج أي كميات، نستخرج من النص العادي
1325
+ if not quantities:
1326
+ # البحث عن الكميات في النص
1327
+ quantity_matches = re.finditer(r'(?:توريد|تركيب|تنفيذ)\s+(\d+(?:,\d+)?(?:\.\d+)?)\s+(\w+)\s+(?:من|لـ|ل)\s+(.+?)(?:\.|\n)', text)
1328
+
1329
+ for match in quantity_matches:
1330
+ quantities.append({
1331
+ "description": match.group(3).strip(),
1332
+ "quantity": self._extract_number(match.group(1)),
1333
+ "unit": match.group(2).strip()
1334
+ })
1335
+
1336
+ return quantities
1337
+
1338
+ def _extract_number(self, text: str) -> float:
1339
+ """
1340
+ استخراج رقم من نص
1341
+
1342
+ المعاملات:
1343
+ ----------
1344
+ text : str
1345
+ النص المحتوي على الرقم
1346
+
1347
+ المخرجات:
1348
+ --------
1349
+ float
1350
+ الرقم المستخرج
1351
+ """
1352
+ if not text:
1353
+ return 0
1354
+
1355
+ # إزالة الفواصل وتحويل النص إلى رقم
1356
+ text = text.replace(',', '')
1357
+ match = re.search(r'\d+(?:\.\d+)?', text)
1358
+
1359
+ if match:
1360
+ try:
1361
+ return float(match.group(0))
1362
+ except ValueError:
1363
+ pass
1364
+
1365
+ return 0
1366
+
1367
+ def _calculate_labor_costs(self, scope_of_work: List[str], project_duration: int) -> Dict[str, float]:
1368
+ """
1369
+ حساب تكاليف العمالة
1370
+
1371
+ المعاملات:
1372
+ ----------
1373
+ scope_of_work : List[str]
1374
+ نطاق العمل
1375
+ project_duration : int
1376
+ مدة المشروع بالأشهر
1377
+
1378
+ المخرجات:
1379
+ --------
1380
+ Dict[str, float]
1381
+ تكاليف العمالة
1382
+ """
1383
+ labor_costs = {}
1384
+
1385
+ # تحديد نوع المشروع بناءً على نطاق العمل
1386
+ project_type = self._determine_project_type(scope_of_work)
1387
+
1388
+ # الحصول على العمالة المطلوبة لهذا النوع من المشاريع
1389
+ required_labor = self.cost_db.get("labor_requirements", {}).get(project_type, {})
1390
+
1391
+ # حساب تكاليف كل نوع من العمالة
1392
+ for labor_type, count in required_labor.items():
1393
+ # الحصول على التكلفة الشهرية
1394
+ monthly_cost = self.labor_costs.get(labor_type, {}).get("monthly_cost", 0)
1395
+
1396
+ # حساب التكلفة الإجمالية
1397
+ total_cost = count * monthly_cost * project_duration
1398
+
1399
+ # إضافة التكلفة إلى القاموس
1400
+ labor_costs[labor_type] = total_cost
1401
+
1402
+ return labor_costs
1403
+
1404
+ def _calculate_equipment_costs(self, scope_of_work: List[str], project_duration: int) -> Dict[str, float]:
1405
+ """
1406
+ حساب تكاليف المعدات بناءً على نطاق العمل ومدة المشروع.
1407
+
1408
+ المعاملات:
1409
+ ----------
1410
+ scope_of_work : List[str]
1411
+ قائمة تحتوي على الأعمال المطلوبة في المشروع.
1412
+ project_duration : int
1413
+ مدة المشروع بالأشهر.
1414
+
1415
+ المخرجات:
1416
+ --------
1417
+ Dict[str, float]
1418
+ قاموس يحتوي على تكلفة كل نوع من المعدات المطلوبة في المشروع.
1419
+ """
1420
+ equipment_costs = {}
1421
+
1422
+ # قائمة أسعار المعدات بناءً على نوع العمل (يمكن توسيعها حسب الحاجة)
1423
+ equipment_prices = {
1424
+ "حفار": 5000, # تكلفة يومية للحفار
1425
+ "رافعة": 8000, # تكلفة يومية للرافعة
1426
+ "خلاطة خرسانة": 3000, # تكلفة يومية للخلاطة
1427
+ "شاحنة نقل": 2000, # تكلفة يومية لشاحنة النقل
1428
+ "مولد كهربائي": 1500 # تكلفة يومية للمولد الكهربائي
1429
+ }
1430
+
1431
+ # عدد الأيام التشغيلية شهريًا (فرضًا 22 يوم عمل في الشهر)
1432
+ working_days_per_month = 22
1433
+ total_days = project_duration * working_days_per_month
1434
+
1435
+ # تحديد المعدات المطلوبة بناءً على نطاق العمل
1436
+ for work in scope_of_work:
1437
+ for equipment, daily_cost in equipment_prices.items():
1438
+ if equipment in work:
1439
+ # إذا كانت المعدات مطلوبة، نحسب التكلفة الإجمالية لمدة المشروع
1440
+ equipment_costs[equipment] = total_days * daily_cost
1441
+
1442
+ return equipment_costs