EGYADMIN commited on
Commit
c3c6055
·
verified ·
1 Parent(s): b66f0a9

Create modules/local_content.py

Browse files
Files changed (1) hide show
  1. modules/local_content.py +706 -0
modules/local_content.py ADDED
@@ -0,0 +1,706 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ }
2
+
3
+ def calculate(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
4
+ """
5
+ حساب نسبة المحتوى المحلي بناءً على البيانات المستخرجة من المستندات
6
+
7
+ المعاملات:
8
+ ----------
9
+ extracted_data : Dict[str, Any]
10
+ البيانات المستخرجة من المستندات
11
+ **kwargs : Dict[str, Any]
12
+ معاملات إضافية مثل نوع المشروع، الميزانية، الموقع، المدة
13
+
14
+ المخرجات:
15
+ --------
16
+ Dict[str, Any]
17
+ نتائج حساب المحتوى المحلي
18
+ """
19
+ # تهيئة نتائج حساب المحتوى المحلي
20
+ local_content_results = {
21
+ "overall_percentage": 0.0,
22
+ "breakdown": {},
23
+ "requirements": [],
24
+ "recommendations": [],
25
+ "nitaqat_analysis": {},
26
+ }
27
+
28
+ # تحديد نوع المشروع والقطاع
29
+ project_type = kwargs.get("project_type", "")
30
+ sector = self._determine_sector(project_type, extracted_data)
31
+
32
+ # استخراج متطلبات المحتوى المحلي من البيانات
33
+ local_content_info = self._extract_local_content_info(extracted_data)
34
+
35
+ # حساب المحتوى المحلي بناءً على القطاع
36
+ sector_data = self.local_content_data["sectors"].get(sector, self.local_content_data["sectors"]["عام"])
37
+
38
+ # تحليل القوى العاملة (الموارد البشرية)
39
+ manpower_analysis = self._analyze_manpower(extracted_data, sector_data)
40
+ local_content_results["breakdown"]["manpower"] = manpower_analysis["percentage"]
41
+
42
+ # تحليل المواد
43
+ materials_analysis = self._analyze_materials(extracted_data, sector_data)
44
+ local_content_results["breakdown"]["materials"] = materials_analysis["percentage"]
45
+
46
+ # تحليل الخدمات
47
+ services_analysis = self._analyze_services(extracted_data, sector_data)
48
+ local_content_results["breakdown"]["services"] = services_analysis["percentage"]
49
+
50
+ # حساب النسبة الإجمالية للمحتوى المحلي
51
+ weights = sector_data["weights"]
52
+ overall_percentage = (
53
+ weights["manpower"] * manpower_analysis["percentage"] +
54
+ weights["materials"] * materials_analysis["percentage"] +
55
+ weights["services"] * services_analysis["percentage"]
56
+ )
57
+
58
+ local_content_results["overall_percentage"] = round(overall_percentage, 2)
59
+
60
+ # تحليل الامتثال لمتطلبات نطاقات
61
+ nitaqat_analysis = self._analyze_nitaqat_compliance(extracted_data, sector)
62
+ local_content_results["nitaqat_analysis"] = nitaqat_analysis
63
+
64
+ # إعداد متطلبات المحتوى المحلي
65
+ requirements = self._prepare_local_content_requirements(sector_data, local_content_info)
66
+ local_content_results["requirements"] = requirements
67
+
68
+ # إعداد توصيات لتحسين المحتوى المحلي
69
+ recommendations = self._generate_recommendations(
70
+ overall_percentage,
71
+ sector_data,
72
+ manpower_analysis,
73
+ materials_analysis,
74
+ services_analysis,
75
+ nitaqat_analysis
76
+ )
77
+ local_content_results["recommendations"] = recommendations
78
+
79
+ return local_content_results
80
+
81
+ def _determine_sector(self, project_type: str, extracted_data: Dict[str, Any]) -> str:
82
+ """
83
+ تحديد القطاع بناءً على نوع المشروع والبيانات المستخرجة
84
+ """
85
+ # قاموس لتحويل أنواع المشاريع الشائعة إلى قطاعات
86
+ project_to_sector = {
87
+ "إنشاءات": "الإنشاءات",
88
+ "مباني": "الإنشاءات",
89
+ "طرق": "الإنشاءات",
90
+ "جسور": "الإنشاءات",
91
+ "تقنية معلومات": "تقنية المعلومات",
92
+ "برمجيات": "تقنية المعلومات",
93
+ "تطبيقات": "تقنية المعلومات",
94
+ "اتصالات": "الاتصالات",
95
+ "صحة": "الصحة",
96
+ "مستشفى": "الصحة",
97
+ "طبي": "الصحة",
98
+ "تعليم": "التعليم",
99
+ "مدارس": "التعليم",
100
+ "جامعات": "التعليم",
101
+ "نقل": "النقل",
102
+ "مواصلات": "النقل",
103
+ "طاقة": "الطاقة",
104
+ "كهرباء": "الطاقة",
105
+ "مياه": "المياه",
106
+ "صرف صحي": "المياه",
107
+ "بيئة": "البيئة",
108
+ "صناعة": "الص��اعة",
109
+ "مصانع": "الصناعة",
110
+ "تجارة": "التجارة",
111
+ "أسواق": "التجارة",
112
+ "سياحة": "السياحة",
113
+ "فنادق": "السياحة",
114
+ "مالية": "الخدمات المالية",
115
+ "بنوك": "الخدمات المالية"
116
+ }
117
+
118
+ # محاولة تحديد القطاع من نوع المشروع
119
+ if project_type:
120
+ for key, value in project_to_sector.items():
121
+ if key in project_type.lower():
122
+ return value
123
+
124
+ # إذا لم يتم تحديد القطاع من نوع المشروع، نحاول تحديده من البيانات المستخرجة
125
+ if "text" in extracted_data:
126
+ text = extracted_data["text"].lower()
127
+ sector_scores = {}
128
+
129
+ for sector in self.sectors:
130
+ score = text.count(sector.lower())
131
+ sector_scores[sector] = score
132
+
133
+ # اختيار القطاع الأكثر ذكراً
134
+ if sector_scores:
135
+ max_sector = max(sector_scores, key=sector_scores.get)
136
+ if sector_scores[max_sector] > 0:
137
+ return max_sector
138
+
139
+ # القطاع الافتراضي إذا لم نتمكن من تحديده
140
+ return "عام"
141
+
142
+ def _extract_local_content_info(self, extracted_data: Dict[str, Any]) -> Dict[str, Any]:
143
+ """
144
+ استخراج معلومات المحتوى المحلي من البيانات المستخرجة
145
+ """
146
+ local_content_info = {
147
+ "mentioned_percentage": None,
148
+ "requirements": [],
149
+ "companies": []
150
+ }
151
+
152
+ # استخراج النسبة المذكورة للمحتوى المحلي
153
+ if "local_content" in extracted_data and extracted_data["local_content"]:
154
+ local_content_data = extracted_data["local_content"]
155
+
156
+ # استخراج النسب المئوية
157
+ percentages = local_content_data.get("percentages", [])
158
+ if percentages:
159
+ # اختيار أعلى نسبة مذكورة
160
+ max_percentage = max(percentages, key=lambda x: x["value"])
161
+ local_content_info["mentioned_percentage"] = max_percentage["value"]
162
+
163
+ # استخراج المتطلبات
164
+ requirements = local_content_data.get("requirements", [])
165
+ local_content_info["requirements"] = requirements
166
+
167
+ # استخراج الشركات المحلية المذكورة
168
+ if "entities" in extracted_data and "organizations" in extracted_data["entities"]:
169
+ organizations = extracted_data["entities"]["organizations"]
170
+
171
+ for org in organizations:
172
+ org_name = org["name"].lower()
173
+
174
+ # البحث في قاعدة بيانات الشركات المحلية
175
+ for company_id, company_data in self.local_companies.items():
176
+ company_name = company_data["name"].lower()
177
+
178
+ # إذا وجدنا تطابق
179
+ if company_name in org_name or org_name in company_name:
180
+ local_content_info["companies"].append(company_data)
181
+
182
+ return local_content_info
183
+
184
+ def _analyze_manpower(self, extracted_data: Dict[str, Any], sector_data: Dict[str, Any]) -> Dict[str, Any]:
185
+ """
186
+ تحليل المحتوى المحلي للقوى العاملة
187
+ """
188
+ manpower_analysis = {
189
+ "percentage": 0.0,
190
+ "saudi_employees": 0,
191
+ "expat_employees": 0,
192
+ "total_employees": 0
193
+ }
194
+
195
+ # استخراج معلومات الموظفين من البيانات
196
+ saudi_keywords = ["سعودي", "مواطن", "توطين", "سعودة"]
197
+ expat_keywords = ["أجنبي", "وافد", "غير سعودي"]
198
+
199
+ # لدينا بعض البيانات الافتراضية للتوضيح
200
+ saudi_count = 0
201
+ expat_count = 0
202
+
203
+ # إذا كانت هناك شركات معروفة في البيانات المستخرجة، نستخدم نسب التوطين الخاصة بها
204
+ if "entities" in extracted_data and "organizations" in extracted_data["entities"]:
205
+ organizations = extracted_data["entities"]["organizations"]
206
+
207
+ for org in organizations:
208
+ org_name = org["name"].lower()
209
+
210
+ for company_id, company_data in self.local_companies.items():
211
+ company_name = company_data["name"].lower()
212
+
213
+ if company_name in org_name or org_name in company_name:
214
+ saudi_percentage = company_data["saudi_employees_percentage"] / 100
215
+ saudi_count += 50 * saudi_percentage # افتراض 50 موظف كمتوسط
216
+ expat_count += 50 * (1 - saudi_percentage)
217
+
218
+ # إذا لم نجد شركات معروفة، نحاول تقدير النسب من النص
219
+ if saudi_count == 0 and expat_count == 0 and "text" in extracted_data:
220
+ text = extracted_data["text"].lower()
221
+
222
+ # عدد مرات ذكر الموظفين السعوديين
223
+ saudi_mentions = sum(text.count(keyword) for keyword in saudi_keywords)
224
+
225
+ # عدد مرات ذكر الموظفين الأجانب
226
+ expat_mentions = sum(text.count(keyword) for keyword in expat_keywords)
227
+
228
+ # تقدير تقريبي للنسب
229
+ total_mentions = saudi_mentions + expat_mentions
230
+ if total_mentions > 0:
231
+ saudi_ratio = saudi_mentions / total_mentions
232
+ saudi_count = int(100 * saudi_ratio)
233
+ expat_count = 100 - saudi_count
234
+
235
+ # إذا لم نتمكن من تقدير النسب، نستخدم قيمة افتراضية
236
+ if saudi_count == 0 and expat_count == 0:
237
+ # القيمة الافتراضية بناءً على متوسط نسب التوطين في القطاع
238
+ if "الإنشاءات" in sector_data:
239
+ saudi_count = 25
240
+ elif "تقنية المعلومات" in sector_data:
241
+ saudi_count = 35
242
+ else:
243
+ saudi_count = 30
244
+
245
+ expat_count = 100 - saudi_count
246
+
247
+ # حساب النسبة المئوية للمحتوى المحلي في القوى العاملة
248
+ total_count = saudi_count + expat_count
249
+
250
+ if total_count > 0:
251
+ manpower_analysis["saudi_employees"] = saudi_count
252
+ manpower_analysis["expat_employees"] = expat_count
253
+ manpower_analysis["total_employees"] = total_count
254
+
255
+ # حساب النسبة المئوية باستخدام الأوزان
256
+ manpower_percentage = (
257
+ self.local_content_data["manpower_categories"]["saudi_employee"] * saudi_count +
258
+ self.local_content_data["manpower_categories"]["expat_employee"] * expat_count
259
+ ) / total_count * 100
260
+
261
+ manpower_analysis["percentage"] = round(manpower_percentage, 2)
262
+
263
+ return manpower_analysis
264
+
265
+ def _analyze_materials(self, extracted_data: Dict[str, Any], sector_data: Dict[str, Any]) -> Dict[str, Any]:
266
+ """
267
+ تحليل المحتوى المحلي للمواد
268
+ """
269
+ materials_analysis = {
270
+ "percentage": 0.0,
271
+ "local_manufacturing": 0,
272
+ "local_assembly": 0,
273
+ "imported_with_local_value_add": 0,
274
+ "fully_imported": 0,
275
+ "total_materials": 0
276
+ }
277
+
278
+ # كلمات دلالية للفئات المختلفة من المواد
279
+ local_manufacturing_keywords = ["تصنيع محلي", "منتج محلي", "صناعة محلية", "صنع في السعودية", "منشأ سعودي"]
280
+ local_assembly_keywords = ["تجميع محلي", "مجمع محلياً", "تم تجميعه في السعودية"]
281
+ imported_with_local_value_keywords = ["قيمة مضافة محلية", "معالجة محلية", "قيمة محلية"]
282
+ fully_imported_keywords = ["مستورد", "استيراد كامل", "منتج أجنبي", "صنع في الخارج"]
283
+
284
+ # تقدير نسب المواد من النص
285
+ if "text" in extracted_data:
286
+ text = extracted_data["text"].lower()
287
+
288
+ # عدد مرات ذكر كل فئة
289
+ local_manufacturing_count = sum(text.count(keyword) for keyword in local_manufacturing_keywords)
290
+ local_assembly_count = sum(text.count(keyword) for keyword in local_assembly_keywords)
291
+ imported_with_local_value_count = sum(text.count(keyword) for keyword in imported_with_local_value_keywords)
292
+ fully_imported_count = sum(text.count(keyword) for keyword in fully_imported_keywords)
293
+
294
+ # إجمالي عدد المرات
295
+ total_count = (local_manufacturing_count + local_assembly_count +
296
+ imported_with_local_value_count + fully_imported_count)
297
+
298
+ if total_count > 0:
299
+ materials_analysis["local_manufacturing"] = local_manufacturing_count
300
+ materials_analysis["local_assembly"] = local_assembly_count
301
+ materials_analysis["imported_with_local_value_add"] = imported_with_local_value_count
302
+ materials_analysis["fully_imported"] = fully_imported_count
303
+ materials_analysis["total_materials"] = total_count
304
+
305
+ # إذا لم نتمكن من تقدير النسب، نستخدم قيم افتراضية بناءً على القطاع
306
+ if materials_analysis["total_materials"] == 0:
307
+ if "الإنشاءات" in sector_data:
308
+ materials_analysis["local_manufacturing"] = 30
309
+ materials_analysis["local_assembly"] = 20
310
+ materials_analysis["imported_with_local_value_add"] = 15
311
+ materials_analysis["fully_imported"] = 35
312
+ elif "تقنية المعلومات" in sector_data:
313
+ materials_analysis["local_manufacturing"] = 10
314
+ materials_analysis["local_assembly"] = 25
315
+ materials_analysis["imported_with_local_value_add"] = 15
316
+ materials_analysis["fully_imported"] = 50
317
+ else:
318
+ materials_analysis["local_manufacturing"] = 20
319
+ materials_analysis["local_assembly"] = 20
320
+ materials_analysis["imported_with_local_value_add"] = 15
321
+ materials_analysis["fully_imported"] = 45
322
+
323
+ materials_analysis["total_materials"] = (
324
+ materials_analysis["local_manufacturing"] +
325
+ materials_analysis["local_assembly"] +
326
+ materials_analysis["imported_with_local_value_add"] +
327
+ materials_analysis["fully_imported"]
328
+ )
329
+
330
+ # حساب النسبة المئوية للمحتوى المحلي في المواد
331
+ total_materials = materials_analysis["total_materials"]
332
+
333
+ if total_materials > 0:
334
+ material_categories = self.local_content_data["material_categories"]
335
+ materials_percentage = (
336
+ material_categories["local_manufacturing"] * materials_analysis["local_manufacturing"] +
337
+ material_categories["local_assembly"] * materials_analysis["local_assembly"] +
338
+ material_categories["imported_with_local_value_add"] * materials_analysis["imported_with_local_value_add"] +
339
+ material_categories["fully_imported"] * materials_analysis["fully_imported"]
340
+ ) / total_materials * 100
341
+
342
+ materials_analysis["percentage"] = round(materials_percentage, 2)
343
+
344
+ return materials_analysis
345
+
346
+ def _analyze_services(self, extracted_data: Dict[str, Any], sector_data: Dict[str, Any]) -> Dict[str, Any]:
347
+ """
348
+ تحليل المحتوى المحلي للخدمات
349
+ """
350
+ services_analysis = {
351
+ "percentage": 0.0,
352
+ "local_service_provider": 0,
353
+ "joint_venture": 0,
354
+ "foreign_provider_with_local_partner": 0,
355
+ "foreign_provider": 0,
356
+ "total_services": 0
357
+ }
358
+
359
+ # كلمات دلالية للفئات المختلفة من الخدمات
360
+ local_provider_keywords = ["مزود خدمة محلي", "شركة محلية", "شركة سعودية", "مؤسسة محلية"]
361
+ joint_venture_keywords = ["مشروع مشترك", "شراكة", "تحالف", "ائتلاف"]
362
+ foreign_with_local_keywords = ["شريك محلي", "وكيل محلي", "وكيل سعودي", "تمثيل محلي"]
363
+ foreign_provider_keywords = ["مزود خدمة أجنبي", "شركة أجنبية", "مقاول أجنبي"]
364
+
365
+ # تقدير نسب الخدمات من النص
366
+ if "text" in extracted_data:
367
+ text = extracted_data["text"].lower()
368
+
369
+ # عدد مرات ذكر كل فئة
370
+ local_provider_count = sum(text.count(keyword) for keyword in local_provider_keywords)
371
+ joint_venture_count = sum(text.count(keyword) for keyword in joint_venture_keywords)
372
+ foreign_with_local_count = sum(text.count(keyword) for keyword in foreign_with_local_keywords)
373
+ foreign_provider_count = sum(text.count(keyword) for keyword in foreign_provider_keywords)
374
+
375
+ # إجمالي عدد المرات
376
+ total_count = (local_provider_count + joint_venture_count +
377
+ foreign_with_local_count + foreign_provider_count)
378
+
379
+ if total_count > 0:
380
+ services_analysis["local_service_provider"] = local_provider_count
381
+ services_analysis["joint_venture"] = joint_venture_count
382
+ services_analysis["foreign_provider_with_local_partner"] = foreign_with_local_count
383
+ services_analysis["foreign_provider"] = foreign_provider_count
384
+ services_analysis["total_services"] = total_count
385
+
386
+ # إذا لم نتمكن من تقدير النسب، نستخدم قيم افتراضية بناءً على القطاع
387
+ if services_analysis["total_services"] == 0:
388
+ if "الإنشاءات" in sector_data:
389
+ services_analysis["local_service_provider"] = 35
390
+ services_analysis["joint_venture"] = 25
391
+ services_analysis["foreign_provider_with_local_partner"] = 20
392
+ services_analysis["foreign_provider"] = 20
393
+ elif "تقنية المعلومات" in sector_data:
394
+ services_analysis["local_service_provider"] = 30
395
+ services_analysis["joint_venture"] = 20
396
+ services_analysis["foreign_provider_with_local_partner"] = 25
397
+ services_analysis["foreign_provider"] = 25
398
+ else:
399
+ services_analysis["local_service_provider"] = 30
400
+ services_analysis["joint_venture"] = 25
401
+ services_analysis["foreign_provider_with_local_partner"] = 20
402
+ services_analysis["foreign_provider"] = 25
403
+
404
+ services_analysis["total_services"] = (
405
+ services_analysis["local_service_provider"] +
406
+ services_analysis["joint_venture"] +
407
+ services_analysis["foreign_provider_with_local_partner"] +
408
+ services_analysis["foreign_provider"]
409
+ )
410
+
411
+ # حساب النسبة المئوية للمحتوى المحلي في الخدمات
412
+ total_services = services_analysis["total_services"]
413
+
414
+ if total_services > 0:
415
+ service_categories = self.local_content_data["service_categories"]
416
+ services_percentage = (
417
+ service_categories["local_service_provider"] * services_analysis["local_service_provider"] +
418
+ service_categories["joint_venture"] * services_analysis["joint_venture"] +
419
+ service_categories["foreign_provider_with_local_partner"] * services_analysis["foreign_provider_with_local_partner"] +
420
+ service_categories["foreign_provider"] * services_analysis["foreign_provider"]
421
+ ) / total_services * 100
422
+
423
+ services_analysis["percentage"] = round(services_percentage, 2)
424
+
425
+ return services_analysis
426
+
427
+ def _analyze_nitaqat_compliance(self, extracted_data: Dict[str, Any], sector: str) -> Dict[str, Any]:
428
+ """
429
+ تحليل الامتثال لمتطلبات نطاقات
430
+ """
431
+ nitaqat_analysis = {
432
+ "compliant": False,
433
+ "nitaqat_category": "غير محدد",
434
+ "company_size": "غير محدد",
435
+ "required_saudi_percentage": 0,
436
+ "estimated_saudi_percentage": 0
437
+ }
438
+
439
+ # تحديد حجم الشركة بناءً على القيمة التقديرية للمشروع
440
+ project_value = 0
441
+ if "financial_data" in extracted_data and "total_cost" in extracted_data["financial_data"]:
442
+ project_value = extracted_data["financial_data"]["total_cost"]["value"]
443
+
444
+ if project_value == 0 and "text" in extracted_data:
445
+ # محاولة استخراج قيمة المشروع من النص
446
+ text = extracted_data["text"].lower()
447
+ value_patterns = [
448
+ r'قيمة المشروع[^\d]*([\d.,]+)[^\d]*(ريال|ر\.س)',
449
+ r'قيمة العقد[^\d]*([\d.,]+)[^\d]*(ريال|ر\.س)',
450
+ r'القيمة الإجمالية[^\d]*([\d.,]+)[^\d]*(ريال|ر\.س)'
451
+ ]
452
+
453
+ for pattern in value_patterns:
454
+ matches = re.findall(pattern, text, re.IGNORECASE)
455
+ if matches:
456
+ try:
457
+ project_value = float(matches[0][0].replace(',', ''))
458
+ break
459
+ except:
460
+ pass
461
+
462
+ # تحديد حجم الشركة بناءً على قيمة المشروع
463
+ company_size = "صغيرة"
464
+ if project_value >= 10000000: # أكثر من 10 مليون ريال
465
+ company_size = "كبيرة"
466
+ elif project_value >= 3000000: # بين 3 و 10 مليون ريال
467
+ company_size = "متوسطة"
468
+
469
+ nitaqat_analysis["company_size"] = company_size
470
+
471
+ # تحديد نسبة التوطين المطلوبة
472
+ if sector in self.nitaqat_data and company_size in self.nitaqat_data[sector]:
473
+ # نفترض أننا نريد الامتثال للفئة الخضراء المتوسطة
474
+ required_percentage = (
475
+ self.nitaqat_data[sector][company_size]["أخضر متوسط"]["min"] +
476
+ self.nitaqat_data[sector][company_size]["أخضر متوسط"]["max"]
477
+ ) / 2
478
+
479
+ nitaqat_analysis["required_saudi_percentage"] = required_percentage
480
+ else:
481
+ # قيمة افتراضية
482
+ nitaqat_analysis["required_saudi_percentage"] = 20
483
+
484
+ # تقدير نسبة التوطين الفعلية
485
+ estimated_saudi_percentage = 0
486
+
487
+ # إذا كانت هناك شركات معروفة في البيانات المستخرجة، نستخدم نسبة التوطين الخاصة بها
488
+ if "entities" in extracted_data and "organizations" in extracted_data["entities"]:
489
+ organizations = extracted_data["entities"]["organizations"]
490
+
491
+ for org in organizations:
492
+ org_name = org["name"].lower()
493
+
494
+ for company_id, company_data in self.local_companies.items():
495
+ company_name = company_data["name"].lower()
496
+
497
+ if company_name in org_name or org_name in company_name:
498
+ estimated_saudi_percentage = company_data["saudi_employees_percentage"]
499
+ nitaqat_analysis["nitaqat_category"] = company_data["nitaqat_category"]
500
+ break
501
+
502
+ # إذا لم نتمكن من تقدير النسبة، نستخدم قيمة افتراضية
503
+ if estimated_saudi_percentage == 0:
504
+ if "الإنشاءات" in sector:
505
+ estimated_saudi_percentage = 15
506
+ elif "تقنية المعلومات" in sector:
507
+ estimated_saudi_percentage = 25
508
+ else:
509
+ estimated_saudi_percentage = 20
510
+
511
+ nitaqat_analysis["estimated_saudi_percentage"] = estimated_saudi_percentage
512
+
513
+ # تحديد فئة نطاقات والامتثال
514
+ if sector in self.nitaqat_data and company_size in self.nitaqat_data[sector]:
515
+ for category, range_data in self.nitaqat_data[sector][company_size].items():
516
+ if range_data["min"] <= estimated_saudi_percentage <= range_data["max"]:
517
+ nitaqat_analysis["nitaqat_category"] = category
518
+ break
519
+
520
+ # تحديد الامتثال
521
+ nitaqat_analysis["compliant"] = (
522
+ nitaqat_analysis["nitaqat_category"] != "أحمر" and
523
+ estimated_saudi_percentage >= nitaqat_analysis["required_saudi_percentage"]
524
+ )
525
+
526
+ return nitaqat_analysis
527
+
528
+ def _prepare_local_content_requirements(self, sector_data: Dict[str, Any], local_content_info: Dict[str, Any]) -> List[Dict[str, Any]]:
529
+ """
530
+ إعداد متطلبات المحتوى المحلي
531
+ """
532
+ requirements = []
533
+
534
+ # الحد الأدنى للمimport os
535
+ import json
536
+ import numpy as np
537
+ import pandas as pd
538
+ from typing import Dict, List, Any, Union, Tuple, Optional
539
+ from datetime import datetime
540
+
541
+ class LocalContentCalculator:
542
+ """
543
+ فئة لحساب وتحليل المحتوى المحلي في المناقصات
544
+ """
545
+
546
+ def __init__(self):
547
+ """
548
+ تهيئة حاسبة المحتوى المحلي
549
+ """
550
+ # تحميل بيانات المحتوى المحلي
551
+ self.local_content_data = self._load_local_content_data()
552
+
553
+ # تحميل قاعدة بيانات الشركات المحلية
554
+ self.local_companies = self._load_local_companies()
555
+
556
+ # تحميل نطاقات الشركات
557
+ self.nitaqat_data = self._load_nitaqat_data()
558
+
559
+ # قائمة القطاعات
560
+ self.sectors = [
561
+ "الإنشاءات", "تقنية المعلومات", "الاتصالات", "الصحة",
562
+ "التعليم", "النقل", "الطاقة", "المياه", "البيئة",
563
+ "الصناعة", "التجارة", "السياحة", "الخدمات المالية"
564
+ ]
565
+
566
+ def _load_local_content_data(self) -> Dict[str, Any]:
567
+ """
568
+ تحميل بيانات المحتوى المحلي
569
+ """
570
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
571
+ return {
572
+ "sectors": {
573
+ "الإنشاءات": {
574
+ "min_percentage": 30,
575
+ "target_percentage": 60,
576
+ "weights": {
577
+ "manpower": 0.35,
578
+ "materials": 0.45,
579
+ "services": 0.2
580
+ }
581
+ },
582
+ "تقنية المعلومات": {
583
+ "min_percentage": 25,
584
+ "target_percentage": 50,
585
+ "weights": {
586
+ "manpower": 0.55,
587
+ "materials": 0.15,
588
+ "services": 0.3
589
+ }
590
+ },
591
+ "عام": {
592
+ "min_percentage": 20,
593
+ "target_percentage": 40,
594
+ "weights": {
595
+ "manpower": 0.4,
596
+ "materials": 0.3,
597
+ "services": 0.3
598
+ }
599
+ }
600
+ },
601
+ "material_categories": {
602
+ "local_manufacturing": 1.0,
603
+ "local_assembly": 0.7,
604
+ "imported_with_local_value_add": 0.4,
605
+ "fully_imported": 0.0
606
+ },
607
+ "manpower_categories": {
608
+ "saudi_employee": 1.0,
609
+ "expat_employee": 0.0
610
+ },
611
+ "service_categories": {
612
+ "local_service_provider": 1.0,
613
+ "joint_venture": 0.6,
614
+ "foreign_provider_with_local_partner": 0.3,
615
+ "foreign_provider": 0.0
616
+ }
617
+ }
618
+
619
+ def _load_local_companies(self) -> Dict[str, Dict[str, Any]]:
620
+ """
621
+ تحميل قاعدة بيانات الشركات المحلية
622
+ """
623
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
624
+ return {
625
+ "company1": {
626
+ "name": "شركة البناء السعودية",
627
+ "cr_number": "1010XXXXXX",
628
+ "sector": "الإنشاءات",
629
+ "local_content_certificate": True,
630
+ "saudi_employees_percentage": 35,
631
+ "nitaqat_category": "أخضر متوسط",
632
+ "local_content_percentage": 45
633
+ },
634
+ "company2": {
635
+ "name": "شركة تقنية المستقبل",
636
+ "cr_number": "1020XXXXXX",
637
+ "sector": "تقنية المعلومات",
638
+ "local_content_certificate": True,
639
+ "saudi_employees_percentage": 42,
640
+ "nitaqat_category": "أخضر مرتفع",
641
+ "local_content_percentage": 52
642
+ },
643
+ "company3": {
644
+ "name": "مصنع المنتجات المعدنية",
645
+ "cr_number": "1030XXXXXX",
646
+ "sector": "الصناعة",
647
+ "local_content_certificate": True,
648
+ "saudi_employees_percentage": 28,
649
+ "nitaqat_category": "أخضر منخفض",
650
+ "local_content_percentage": 61
651
+ }
652
+ }
653
+
654
+ def _load_nitaqat_data(self) -> Dict[str, Dict[str, Any]]:
655
+ """
656
+ تحميل بيانات نطاقات الشركات
657
+ """
658
+ # في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
659
+ return {
660
+ "الإنشاءات": {
661
+ "صغيرة": {
662
+ "أحمر": {"min": 0, "max": 5},
663
+ "أخضر منخفض": {"min": 6, "max": 10},
664
+ "أخضر متوسط": {"min": 11, "max": 15},
665
+ "أخضر مرتفع": {"min": 16, "max": 25},
666
+ "بلاتيني": {"min": 26, "max": 100}
667
+ },
668
+ "متوسطة": {
669
+ "أحمر": {"min": 0, "max": 7},
670
+ "أخضر منخفض": {"min": 8, "max": 14},
671
+ "أخضر متوسط": {"min": 15, "max": 20},
672
+ "أخضر مرتفع": {"min": 21, "max": 30},
673
+ "بلاتيني": {"min": 31, "max": 100}
674
+ },
675
+ "كبيرة": {
676
+ "أحمر": {"min": 0, "max": 9},
677
+ "أخضر منخفض": {"min": 10, "max": 16},
678
+ "أخضر متوسط": {"min": 17, "max": 23},
679
+ "أخضر مرتفع": {"min": 24, "max": 35},
680
+ "بلاتيني": {"min": 36, "max": 100}
681
+ }
682
+ },
683
+ "تقنية المعلومات": {
684
+ "صغيرة": {
685
+ "أحمر": {"min": 0, "max": 6},
686
+ "أخضر منخفض": {"min": 7, "max": 12},
687
+ "أخضر متوسط": {"min": 13, "max": 19},
688
+ "أخضر مرتفع": {"min": 20, "max": 29},
689
+ "بلاتيني": {"min": 30, "max": 100}
690
+ },
691
+ "متوسطة": {
692
+ "أحمر": {"min": 0, "max": 13},
693
+ "أخضر منخفض": {"min": 14, "max": 20},
694
+ "أخضر متوسط": {"min": 21, "max": 27},
695
+ "أخضر مرتفع": {"min": 28, "max": 35},
696
+ "بلاتيني": {"min": 36, "max": 100}
697
+ },
698
+ "كبيرة": {
699
+ "أحمر": {"min": 0, "max": 17},
700
+ "أخضر منخفض": {"min": 18, "max": 25},
701
+ "أخضر متوسط": {"min": 26, "max": 33},
702
+ "أخضر مرتفع": {"min": 34, "max": 40},
703
+ "بلاتيني": {"min": 41, "max": 100}
704
+ }
705
+ }
706
+ }