EGYADMIN commited on
Commit
d0f1b27
·
verified ·
1 Parent(s): 92688b8

Update modules/ai_assistant/ai_app.py

Browse files
Files changed (1) hide show
  1. modules/ai_assistant/ai_app.py +1210 -12
modules/ai_assistant/ai_app.py CHANGED
@@ -34,7 +34,7 @@ class ClaudeAIService:
34
 
35
  def get_api_key(self):
36
  """الحصول على مفتاح API من متغيرات البيئة"""
37
- api_key = os.environ.get("anthropic")
38
  if not api_key:
39
  raise ValueError("مفتاح API لـ Claude غير موجود في متغيرات البيئة")
40
  return api_key
@@ -162,6 +162,558 @@ class ClaudeAIService:
162
  "media_type": file_type,
163
  "data": file_base64
164
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
165
  }
166
  ]
167
  }
@@ -238,7 +790,7 @@ class ClaudeAIService:
238
 
239
  payload = {
240
  "model": model_name,
241
- "max_tokens": 2048,
242
  "messages": claude_messages,
243
  "temperature": 0.7
244
  }
@@ -248,7 +800,7 @@ class ClaudeAIService:
248
  self.api_url,
249
  headers=headers,
250
  json=payload,
251
- timeout=30
252
  )
253
 
254
  # التحقق من نجاح الطلب
@@ -284,15 +836,17 @@ class AIAssistantApp:
284
 
285
  def __init__(self):
286
  """تهيئة وحدة المساعد الذكي"""
 
 
 
287
  # التحقق من توفر مكتبة pdf2image
288
  try:
289
  from pdf2image import convert_from_path
290
- pdf_conversion_available = True
291
  self.pdf_conversion_available = True
292
  self.convert_from_path = convert_from_path
293
  except ImportError:
294
- pdf_conversion_available = False
295
  self.pdf_conversion_available = False
 
296
 
297
  # تحميل النماذج عند بدء التشغيل
298
  self.cost_model = self._load_cost_prediction_model()
@@ -340,22 +894,27 @@ class AIAssistantApp:
340
  """تحميل نموذج التنبؤ بالتكاليف"""
341
  # في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي
342
  # هنا نقوم بإنشاء كائن محاكاة للنموذج
 
343
  return {"name": "cost_prediction_model", "version": "1.0.0", "status": "loaded"}
344
 
345
  def _load_document_classifier_model(self):
346
  """تحميل نموذج تصنيف المستندات"""
 
347
  return {"name": "document_classifier_model", "version": "1.0.0", "status": "loaded"}
348
 
349
  def _load_risk_assessment_model(self):
350
  """تحميل نموذج تقييم المخاطر"""
 
351
  return {"name": "risk_assessment_model", "version": "1.0.0", "status": "loaded"}
352
 
353
  def _load_local_content_model(self):
354
  """تحميل نموذج تحليل المحتوى المحلي"""
 
355
  return {"name": "local_content_model", "version": "1.0.0", "status": "loaded"}
356
 
357
  def _load_entity_recognition_model(self):
358
  """تحميل نموذج ال��عرف على الكيانات"""
 
359
  return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"}
360
 
361
  def render(self):
@@ -515,10 +1074,649 @@ class AIAssistantApp:
515
  # مربع إدخال الرسالة
516
  user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input")
517
 
518
- # التحقق من وجود مفتاح API
519
- api_available = True
520
- try:
521
- self.claude_service.get_api_key()
522
- except ValueError:
523
- api_available = False
524
- st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
 
35
  def get_api_key(self):
36
  """الحصول على مفتاح API من متغيرات البيئة"""
37
+ api_key = os.environ.get("ANTHROPIC_API_KEY") # تصحيح اسم متغير البيئة
38
  if not api_key:
39
  raise ValueError("مفتاح API لـ Claude غير موجود في متغيرات البيئة")
40
  return api_key
 
162
  "media_type": file_type,
163
  "data": file_base64
164
  }
165
+
166
+ # تحويل البيانات إلى DataFrame
167
+ quantities_df = pd.DataFrame(quantities_data)
168
+ quantities_df['النسبة المئوية'] = quantities_df['السعر التقديري'] / quantities_df['السعر التقديري'].sum() * 100
169
+ quantities_df['النسبة المئوية'] = quantities_df['النسبة المئوية'].round(1)
170
+
171
+ # عرض جدول الكميات
172
+ st.dataframe(quantities_df, use_container_width=True)
173
+
174
+ # عرض رسم بياني للتكاليف
175
+ fig = px.bar(
176
+ quantities_df,
177
+ x='البند',
178
+ y='السعر التقديري',
179
+ title='التكاليف التقديرية حسب البند',
180
+ labels={'السعر التقديري': 'التكلفة التقديرية (ريال)', 'البند': 'بنود الأعمال'}
181
+ )
182
+ st.plotly_chart(fig, use_container_width=True)
183
+
184
+ # استخراج الكلمات المفتاحية من المستند
185
+ st.markdown("### الكلمات المفتاحية المستخرجة")
186
+
187
+ keywords = [
188
+ {"word": "غرامة التأخير", "count": 3, "relevance": 0.85},
189
+ {"word": "المحتوى المحلي", "count": 5, "relevance": 0.92},
190
+ {"word": "الدفعة المقدمة", "count": 2, "relevance": 0.78},
191
+ {"word": "مدة التنفيذ", "count": 4, "relevance": 0.88},
192
+ {"word": "الضمان", "count": 3, "relevance": 0.75},
193
+ {"word": "مواقف سيارات", "count": 2, "relevance": 0.70},
194
+ {"word": "تكييف", "count": 3, "relevance": 0.65},
195
+ {"word": "سلامة", "count": 2, "relevance": 0.60}
196
+ ]
197
+
198
+ keywords_df = pd.DataFrame(keywords)
199
+
200
+ # عرض الكلمات المفتاحية كسحابة كلمات
201
+ from matplotlib.pyplot import figure
202
+
203
+ fig, ax = plt.subplots(figsize=(10, 5))
204
+
205
+ # رسم الكلمات بأحجام مختلفة حسب الأهمية
206
+ for i, kw in enumerate(keywords):
207
+ size = 10 + (kw["relevance"] * 30)
208
+ color = plt.cm.viridis(kw["relevance"])
209
+ x = random.uniform(0.1, 0.9)
210
+ y = random.uniform(0.1, 0.9)
211
+ ax.text(x, y, kw["word"], fontsize=size, color=color,
212
+ ha='center', va='center', rotation=random.randint(-30, 30))
213
+
214
+ ax.set_xlim(0, 1)
215
+ ax.set_ylim(0, 1)
216
+ ax.axis('off')
217
+
218
+ st.pyplot(fig)
219
+
220
+ # عرض توصيات
221
+ st.markdown("### التوصيات")
222
+
223
+ st.info("""
224
+ بناءً على تحليل مستند المناقصة، نوصي بالآتي:
225
+
226
+ 1. **التركيز على المحتوى المحلي**: تقديم خطة تفصيلية لتحقيق نسبة 60% من المحتوى المحلي.
227
+
228
+ 2. **مراجعة شروط الدفعات**: التأكد من توفير السيولة المالية اللازمة نظرًا لنظام الدفعات الشهرية.
229
+
230
+ 3. **الانتباه لغرامات التأخير**: وضع جدول زمني واقعي مع هوامش كافية لتجنب غرامات التأخير.
231
+
232
+ 4. **الاستعانة بالمتخصصين**: تكوين فريق فني متكامل يلبي متطلبات الخبرة المطلوبة.
233
+
234
+ 5. **مراجعة تفصيلية لجدول الكميات**: التأكد من شمولية الأسعار لجميع متطلبات المشروع.
235
+ """)
236
+
237
+ def _render_local_content_tab(self):
238
+ """عرض تبويب المحتوى المحلي"""
239
+ st.markdown("### حاسبة المحتوى المحلي")
240
+
241
+ st.markdown("""
242
+ استخدم هذه الأداة لحساب نسبة المحتوى المحلي للمشروع حسب متطلبات هيئة المحتوى المحلي.
243
+ """)
244
+
245
+ # مدخلات القسم الأول: العمالة
246
+ st.markdown("#### القسم الأول: العمالة")
247
+
248
+ col1, col2 = st.columns(2)
249
+
250
+ with col1:
251
+ local_labor_cost = st.number_input(
252
+ "تكلفة العمالة المحلية",
253
+ min_value=0,
254
+ value=500000,
255
+ step=10000,
256
+ format="%d",
257
+ key="local_labor_cost"
258
+ )
259
+
260
+ foreign_labor_cost = st.number_input(
261
+ "تكلفة العمالة الأجنبية",
262
+ min_value=0,
263
+ value=300000,
264
+ step=10000,
265
+ format="%d",
266
+ key="foreign_labor_cost"
267
+ )
268
+
269
+ with col2:
270
+ local_labor_weight = st.slider(
271
+ "وزن العمالة المحلية",
272
+ min_value=0.0,
273
+ max_value=1.0,
274
+ value=0.4,
275
+ step=0.1,
276
+ format="%.1f",
277
+ key="local_labor_weight"
278
+ )
279
+
280
+ # مدخلات القسم الثاني: المواد
281
+ st.markdown("#### القسم الثاني: المواد")
282
+
283
+ col1, col2 = st.columns(2)
284
+
285
+ with col1:
286
+ local_materials_cost = st.number_input(
287
+ "تكلفة المواد المحلية",
288
+ min_value=0,
289
+ value=800000,
290
+ step=10000,
291
+ format="%d",
292
+ key="local_materials_cost"
293
+ )
294
+
295
+ imported_materials_cost = st.number_input(
296
+ "تكلفة المواد المستوردة",
297
+ min_value=0,
298
+ value=1200000,
299
+ step=10000,
300
+ format="%d",
301
+ key="imported_materials_cost"
302
+ )
303
+
304
+ with col2:
305
+ materials_weight = st.slider(
306
+ "وزن المواد",
307
+ min_value=0.0,
308
+ max_value=1.0,
309
+ value=0.3,
310
+ step=0.1,
311
+ format="%.1f",
312
+ key="materials_weight"
313
+ )
314
+
315
+ # مدخلات القسم الثالث: المعدات
316
+ st.markdown("#### القسم الثالث: المعدات")
317
+
318
+ col1, col2 = st.columns(2)
319
+
320
+ with col1:
321
+ local_equipment_cost = st.number_input(
322
+ "تكلفة المعدات المحلية",
323
+ min_value=0,
324
+ value=300000,
325
+ step=10000,
326
+ format="%d",
327
+ key="local_equipment_cost"
328
+ )
329
+
330
+ imported_equipment_cost = st.number_input(
331
+ "تكلفة المعدات المستوردة",
332
+ min_value=0,
333
+ value=700000,
334
+ step=10000,
335
+ format="%d",
336
+ key="imported_equipment_cost"
337
+ )
338
+
339
+ with col2:
340
+ equipment_weight = st.slider(
341
+ "وزن المعدات",
342
+ min_value=0.0,
343
+ max_value=1.0,
344
+ value=0.2,
345
+ step=0.1,
346
+ format="%.1f",
347
+ key="equipment_weight"
348
+ )
349
+
350
+ # مدخلات القسم الرابع: الخدمات
351
+ st.markdown("#### القسم الرابع: الخدمات")
352
+
353
+ col1, col2 = st.columns(2)
354
+
355
+ with col1:
356
+ local_services_cost = st.number_input(
357
+ "تكلفة الخدمات المحلية",
358
+ min_value=0,
359
+ value=400000,
360
+ step=10000,
361
+ format="%d",
362
+ key="local_services_cost"
363
+ )
364
+
365
+ foreign_services_cost = st.number_input(
366
+ "تكلفة الخدمات الأجنبية",
367
+ min_value=0,
368
+ value=200000,
369
+ step=10000,
370
+ format="%d",
371
+ key="foreign_services_cost"
372
+ )
373
+
374
+ with col2:
375
+ services_weight = st.slider(
376
+ "وزن الخدمات",
377
+ min_value=0.0,
378
+ max_value=1.0,
379
+ value=0.1,
380
+ step=0.1,
381
+ format="%.1f",
382
+ key="services_weight"
383
+ )
384
+
385
+ # التحقق من إجمالي الأوزان
386
+ total_weight = local_labor_weight + materials_weight + equipment_weight + services_weight
387
+
388
+ if total_weight != 1.0:
389
+ st.warning(f"إجمالي الأوزان يجب أن يساوي 1.0. الإجمالي الحالي هو {total_weight}.")
390
+
391
+ # زر حساب المحتوى المحلي
392
+ if st.button("حساب المحتوى المحلي", key="calculate_local_content_button"):
393
+ # عرض مؤشر التقدم
394
+ with st.spinner("جاري حساب نسبة المحتوى المحلي..."):
395
+ # محاكاة وقت المعالجة
396
+ time.sleep(1)
397
+
398
+ # حساب نسبة المحتوى المحلي لكل قسم
399
+ labor_total = local_labor_cost + foreign_labor_cost
400
+ labor_local_percentage = (local_labor_cost / labor_total) * 100 if labor_total > 0 else 0
401
+
402
+ materials_total = local_materials_cost + imported_materials_cost
403
+ materials_local_percentage = (local_materials_cost / materials_total) * 100 if materials_total > 0 else 0
404
+
405
+ equipment_total = local_equipment_cost + imported_equipment_cost
406
+ equipment_local_percentage = (local_equipment_cost / equipment_total) * 100 if equipment_total > 0 else 0
407
+
408
+ services_total = local_services_cost + foreign_services_cost
409
+ services_local_percentage = (local_services_cost / services_total) * 100 if services_total > 0 else 0
410
+
411
+ # حساب النسبة الإجمالية المرجحة
412
+ weighted_local_content = (
413
+ (labor_local_percentage * local_labor_weight) +
414
+ (materials_local_percentage * materials_weight) +
415
+ (equipment_local_percentage * equipment_weight) +
416
+ (services_local_percentage * services_weight)
417
+ )
418
+
419
+ # تقييم النتيجة
420
+ target_percentage = 60 # النسبة المستهدفة
421
+
422
+ if weighted_local_content >= target_percentage:
423
+ status = "مطابق"
424
+ status_color = "green"
425
+ elif weighted_local_content >= target_percentage * 0.9: # ضمن 90% من الهدف
426
+ status = "قريب"
427
+ status_color = "orange"
428
+ else:
429
+ status = "غير مطابق"
430
+ status_color = "red"
431
+
432
+ # عرض النتائج
433
+ st.success("تم حساب نسبة المحتوى المحلي بنجاح!")
434
+
435
+ # عرض الملخص
436
+ st.markdown("#### نتائج المحتوى المحلي")
437
+
438
+ col1, col2, col3 = st.columns([2, 1, 1])
439
+
440
+ with col1:
441
+ st.metric(
442
+ "نسبة المحتوى المحلي الإجمالية",
443
+ f"{weighted_local_content:.1f}%",
444
+ f"{weighted_local_content - target_percentage:.1f}%" if weighted_local_content != target_percentage else "0%"
445
+ )
446
+
447
+ with col2:
448
+ st.metric("النسبة المستهدفة", f"{target_percentage}%")
449
+
450
+ with col3:
451
+ st.markdown(f"""
452
+ <div style="
453
+ padding: 10px;
454
+ border-radius: 5px;
455
+ text-align: center;
456
+ background-color: {'#8BC34A' if status == 'مطابق' else '#FF9800' if status == 'قريب' else '#F44336'};
457
+ color: white;
458
+ font-weight: bold;
459
+ margin-top: 21px;
460
+ ">
461
+ {status}
462
+ </div>
463
+ """, unsafe_allow_html=True)
464
+
465
+ # عرض التفاصيل
466
+ st.markdown("#### تفاصيل حساب المحتوى المحلي")
467
+
468
+ # إنشاء بيانات التفاصيل
469
+ details_data = {
470
+ 'القسم': ['العمالة', 'المواد', 'المعدات', 'الخدمات', 'الإجمالي'],
471
+ 'التكلفة المحلية': [
472
+ local_labor_cost,
473
+ local_materials_cost,
474
+ local_equipment_cost,
475
+ local_services_cost,
476
+ local_labor_cost + local_materials_cost + local_equipment_cost + local_services_cost
477
+ ],
478
+ 'التكلفة الإجمالية': [
479
+ labor_total,
480
+ materials_total,
481
+ equipment_total,
482
+ services_total,
483
+ labor_total + materials_total + equipment_total + services_total
484
+ ],
485
+ 'النسبة المحلية': [
486
+ f"{labor_local_percentage:.1f}%",
487
+ f"{materials_local_percentage:.1f}%",
488
+ f"{equipment_local_percentage:.1f}%",
489
+ f"{services_local_percentage:.1f}%",
490
+ f"{weighted_local_content:.1f}%"
491
+ ],
492
+ 'الوزن': [
493
+ f"{local_labor_weight:.1f}",
494
+ f"{materials_weight:.1f}",
495
+ f"{equipment_weight:.1f}",
496
+ f"{services_weight:.1f}",
497
+ "1.0"
498
+ ],
499
+ 'المساهمة المرجحة': [
500
+ f"{labor_local_percentage * local_labor_weight:.1f}%",
501
+ f"{materials_local_percentage * materials_weight:.1f}%",
502
+ f"{equipment_local_percentage * equipment_weight:.1f}%",
503
+ f"{services_local_percentage * services_weight:.1f}%",
504
+ f"{weighted_local_content:.1f}%"
505
+ ]
506
+ }
507
+
508
+ details_df = pd.DataFrame(details_data)
509
+
510
+ # عرض الجدول
511
+ st.dataframe(details_df, use_container_width=True)
512
+
513
+ # عرض رسم بياني للمساهمات
514
+ st.markdown("#### مساهمة كل قسم في المحتوى المحلي")
515
+
516
+ chart_data = details_df.iloc[:-1].copy() # استبعاد صف الإجمالي
517
+
518
+ fig = px.bar(
519
+ chart_data,
520
+ x='القسم',
521
+ y=[local_labor_weight * labor_local_percentage,
522
+ materials_weight * materials_local_percentage,
523
+ equipment_weight * equipment_local_percentage,
524
+ services_weight * services_local_percentage],
525
+ labels={'value': 'المساهمة المرجحة (%)', 'variable': 'القسم'},
526
+ title='مساهمة كل قسم في إجمالي المحتوى المحلي',
527
+ color_discrete_sequence=px.colors.qualitative.Set3
528
+ )
529
+
530
+ # إضافة خط للنسبة المستهدفة
531
+ fig.add_hline(y=target_percentage, line_dash="dash", line_color="red",
532
+ annotation_text=f"النسبة المستهدفة ({target_percentage}%)",
533
+ annotation_position="top right")
534
+
535
+ st.plotly_chart(fig, use_container_width=True)
536
+
537
+ # توصيات لتحسين المحتوى المحلي
538
+ st.markdown("#### توصيات لتحسين المحتوى المحلي")
539
+
540
+ if weighted_local_content < target_percentage:
541
+ st.warning("""
542
+ للوصول إلى النسبة المستهدفة للمحتوى المحلي (60%)، نوصي بما يلي:
543
+ """)
544
+
545
+ recommendations = []
546
+
547
+ if labor_local_percentage < 70:
548
+ recommendations.append("زيادة نسبة توظيف العمالة المحلية والاستفادة من برامج دعم التوطين.")
549
+
550
+ if materials_local_percentage < 50:
551
+ recommendations.append("البحث عن موردين محليين للمواد وإعطاء الأولوية للمنتجات المحلية.")
552
+
553
+ if equipment_local_percentage < 40:
554
+ recommendations.append("استئجار المعدات من شركات محلية بدلاً من الاستيراد المباشر.")
555
+
556
+ if services_local_percentage < 60:
557
+ recommendations.append("التعاقد مع مقدمي خدمات محليين والاستعانة بالشركات المحلية للخدمات المساندة.")
558
+
559
+ for i, rec in enumerate(recommendations):
560
+ st.markdown(f"{i+1}. {rec}")
561
+
562
+ # نموذج محاكاة لتحسين المحتوى المحلي
563
+ st.markdown("#### محاكاة تحسين المحتوى المحلي")
564
+
565
+ st.info("""
566
+ استخدم المحاكاة أدناه لتجربة تعديل النسب والوصول إلى المحتوى المحلي المستهدف.
567
+ """)
568
+
569
+ # أعمدة لإدخال نسب التحسين
570
+ col1, col2, col3, col4 = st.columns(4)
571
+
572
+ with col1:
573
+ labor_improvement = st.slider(
574
+ "تحسين العمالة المحلية",
575
+ min_value=0,
576
+ max_value=100,
577
+ value=10,
578
+ step=5,
579
+ format="%d%%",
580
+ key="labor_improvement"
581
+ ) / 100
582
+
583
+ with col2:
584
+ materials_improvement = st.slider(
585
+ "تحسين المواد المحلية",
586
+ min_value=0,
587
+ max_value=100,
588
+ value=15,
589
+ step=5,
590
+ format="%d%%",
591
+ key="materials_improvement"
592
+ ) / 100
593
+
594
+ with col3:
595
+ equipment_improvement = st.slider(
596
+ "تحسين المعدات المحلية",
597
+ min_value=0,
598
+ max_value=100,
599
+ value=10,
600
+ step=5,
601
+ format="%d%%",
602
+ key="equipment_improvement"
603
+ ) / 100
604
+
605
+ with col4:
606
+ services_improvement = st.slider(
607
+ "تحسين الخدمات المحلية",
608
+ min_value=0,
609
+ max_value=100,
610
+ value=5,
611
+ step=5,
612
+ format="%d%%",
613
+ key="services_improvement"
614
+ ) / 100
615
+
616
+ # حساب المحتوى المحلي بعد التحسين
617
+ improved_labor_percentage = min(100, labor_local_percentage + (labor_improvement * 100))
618
+ improved_materials_percentage = min(100, materials_local_percentage + (materials_improvement * 100))
619
+ improved_equipment_percentage = min(100, equipment_local_percentage + (equipment_improvement * 100))
620
+ improved_services_percentage = min(100, services_local_percentage + (services_improvement * 100))
621
+
622
+ improved_local_content = (
623
+ (improved_labor_percentage * local_labor_weight) +
624
+ (improved_materials_percentage * materials_weight) +
625
+ (improved_equipment_percentage * equipment_weight) +
626
+ (improved_services_percentage * services_weight)
627
+ )
628
+
629
+ # عرض نتائج التحسين
630
+ col1, col2 = st.columns(2)
631
+
632
+ with col1:
633
+ st.metric(
634
+ "المحتوى المحلي الحالي",
635
+ f"{weighted_local_content:.1f}%"
636
+ )
637
+
638
+ with col2:
639
+ st.metric(
640
+ "المحتوى المحلي بعد التحسين",
641
+ f"{improved_local_content:.1f}%",
642
+ f"{improved_local_content - weighted_local_content:.1f}%"
643
+ )
644
+
645
+ # تقييم التكلفة الإضافية للتحسين
646
+ additional_cost = (
647
+ (labor_improvement * foreign_labor_cost) +
648
+ (materials_improvement * imported_materials_cost) +
649
+ (equipment_improvement * imported_equipment_cost) +
650
+ (services_improvement * foreign_services_cost)
651
+ ) * 0.15 # تقدير تكلفة التحويل بنسبة 15%
652
+
653
+ st.metric(
654
+ "التكلفة الإضافية المقدرة للتحسين",
655
+ f"{additional_cost:,.2f} ريال"
656
+ )
657
+
658
+ # عرض حالة التحسين
659
+ if improved_local_content >= target_percentage:
660
+ st.success(f"بعد التحسين، سيصبح المحتوى المحلي {improved_local_content:.1f}% وهو أعلى من النسبة المستهدفة ({target_percentage}%).")
661
+ else:
662
+ st.warning(f"التحسين المقترح غير كافٍ. المحتوى المحلي سيصبح {improved_local_content:.1f}% وهو أقل من النسبة المستهدفة ({target_percentage}%).")
663
+ else:
664
+ st.success(f"""
665
+ تهانينا! المشروع يحقق نسبة محتوى محلي {weighted_local_content:.1f}% وهي أعلى من النسبة المستهدفة ({target_percentage}%).
666
+
667
+ للحفاظ على هذا المستوى أو تحسينه، نوصي بما يلي:
668
+
669
+ 1. توثيق جميع المشتريات المحلية والتأكد من حصولها على شهادات المحتوى المحلي.
670
+ 2. متابعة تحديثات هيئة المحتوى المحلي للاستفادة من الحوافز المتاحة.
671
+ 3. بناء علاقات استراتيجية مع الموردين المحليين لضمان استدامة سلسلة التوريد.
672
+ 4. المشاركة في برامج تطوير الموردين المحليين لتعزيز القدرات المحلية.
673
+ """)
674
+
675
+ def _render_faq_tab(self):
676
+ """عرض تبويب الأسئلة ��لشائعة"""
677
+ st.markdown("### الأسئلة الشائعة")
678
+
679
+ st.markdown("""
680
+ فيما يلي قائمة بالأسئلة الشائعة حول نظام تسعير المناقصات والإجابات عليها.
681
+ """)
682
+
683
+ # فلترة الأسئلة
684
+ search_query = st.text_input("ابحث في الأسئلة الشائعة", key="faq_search")
685
+
686
+ filtered_faqs = self.faqs
687
+ if search_query:
688
+ filtered_faqs = [faq for faq in self.faqs if search_query.lower() in faq["question"].lower()]
689
+
690
+ # عرض الأسئلة المصفاة
691
+ if not filtered_faqs:
692
+ st.info("لا توجد نتائج مطابقة لبحثك. يرجى تعديل كلمات البحث.")
693
+ else:
694
+ for i, faq in enumerate(filtered_faqs):
695
+ with st.expander(faq["question"]):
696
+ st.markdown(faq["answer"])
697
+
698
+ # قسم لإضافة سؤال جديد
699
+ st.markdown("### لم تجد إجابة لسؤالك؟")
700
+
701
+ new_question = st.text_area("اكتب سؤالك هنا", key="new_question")
702
+
703
+ if st.button("إرسال السؤال", key="submit_question_button"):
704
+ if new_question:
705
+ st.success("تم استلام سؤالك بنجاح! سيتم الرد عليه في أقرب وقت ممكن.")
706
+
707
+ # في التطبيق الحقيقي، سيتم حفظ السؤال في قاعدة البيانات
708
+ # ويمكن استخدام Claude AI للإجابة عليه تلقائيًا
709
+ else:
710
+ st.warning("يرجى كتابة سؤالك قبل الإرسال.")
711
+
712
+
713
+ # إنشاء نسخة من التطبيق واستدعاء الدالة الرئيسية للعرض
714
+ if __name__ == "__main__":
715
+ app = AIAssistantApp()
716
+ app.render()
717
  }
718
  ]
719
  }
 
790
 
791
  payload = {
792
  "model": model_name,
793
+ "max_tokens": 4096, # زيادة الحد الأقصى للإجابة
794
  "messages": claude_messages,
795
  "temperature": 0.7
796
  }
 
800
  self.api_url,
801
  headers=headers,
802
  json=payload,
803
+ timeout=60 # زيادة مهلة الانتظار
804
  )
805
 
806
  # التحقق من نجاح الطلب
 
836
 
837
  def __init__(self):
838
  """تهيئة وحدة المساعد الذكي"""
839
+ # إعداد التسجيل
840
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
841
+
842
  # التحقق من توفر مكتبة pdf2image
843
  try:
844
  from pdf2image import convert_from_path
 
845
  self.pdf_conversion_available = True
846
  self.convert_from_path = convert_from_path
847
  except ImportError:
 
848
  self.pdf_conversion_available = False
849
+ logging.warning("مكتبة pdf2image غير متوفرة. لن يتم دعم تحويل ملفات PDF.")
850
 
851
  # تحميل النماذج عند بدء التشغيل
852
  self.cost_model = self._load_cost_prediction_model()
 
894
  """تحميل نموذج التنبؤ بالتكاليف"""
895
  # في البيئة الإنتاجية، سيتم تحميل نموذج حقيقي
896
  # هنا نقوم بإنشاء كائن محاكاة للنموذج
897
+ logging.info("جاري تحميل نموذج التنبؤ بالتكاليف...")
898
  return {"name": "cost_prediction_model", "version": "1.0.0", "status": "loaded"}
899
 
900
  def _load_document_classifier_model(self):
901
  """تحميل نموذج تصنيف المستندات"""
902
+ logging.info("جاري تحميل نموذج تصنيف المستندات...")
903
  return {"name": "document_classifier_model", "version": "1.0.0", "status": "loaded"}
904
 
905
  def _load_risk_assessment_model(self):
906
  """تحميل نموذج تقييم المخاطر"""
907
+ logging.info("جاري تحميل نموذج تقييم المخاطر...")
908
  return {"name": "risk_assessment_model", "version": "1.0.0", "status": "loaded"}
909
 
910
  def _load_local_content_model(self):
911
  """تحميل نموذج تحليل المحتوى المحلي"""
912
+ logging.info("جاري تحميل نموذج تحليل المحتوى المحلي...")
913
  return {"name": "local_content_model", "version": "1.0.0", "status": "loaded"}
914
 
915
  def _load_entity_recognition_model(self):
916
  """تحميل نموذج ال��عرف على الكيانات"""
917
+ logging.info("جاري تحميل نموذج التعرف على الكيانات...")
918
  return {"name": "entity_recognition_model", "version": "1.0.0", "status": "loaded"}
919
 
920
  def render(self):
 
1074
  # مربع إدخال الرسالة
1075
  user_input = st.text_input("اكتب رسالتك هنا", key="ai_assistant_input")
1076
 
1077
+ # زر الإرسال
1078
+ if st.button("إرسال", key="send_message_button"):
1079
+ # التحقق من وجود مفتاح API
1080
+ api_available = True
1081
+ try:
1082
+ self.claude_service.get_api_key()
1083
+ except ValueError:
1084
+ api_available = False
1085
+ st.warning("مفتاح API لـ Claude غير متوفر. يرجى إضافته في إعدادات النظام.")
1086
+
1087
+ # معالجة الرسالة إذا تم توفير إدخال
1088
+ if user_input and api_available:
1089
+ # إضافة رسالة المستخدم إلى المحادثة
1090
+ st.session_state.ai_assistant_messages.append({"role": "user", "content": user_input})
1091
+
1092
+ # معالجة الملف المرفوع إذا وجد
1093
+ if uploaded_file is not None:
1094
+ # حفظ الملف المرفوع مؤقتًا
1095
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file:
1096
+ tmp_file.write(uploaded_file.getvalue())
1097
+ temp_file_path = tmp_file.name
1098
+
1099
+ try:
1100
+ # إذا كان الملف PDF وكان تحويل PDF متاحًا
1101
+ if uploaded_file.name.lower().endswith('.pdf') and self.pdf_conversion_available:
1102
+ # تحويل PDF إلى صورة
1103
+ images = self.convert_from_path(temp_file_path, first_page=1, last_page=1)
1104
+ if images:
1105
+ # حفظ الصورة الأولى مؤقتًا
1106
+ image_path = f"{temp_file_path}.jpg"
1107
+ images[0].save(image_path, 'JPEG')
1108
+
1109
+ # تحليل الصورة باستخدام Claude
1110
+ result = self.claude_service.analyze_image(
1111
+ image_path=image_path,
1112
+ prompt=f"المستخدم رفع ملف PDF وسأل: {user_input}. يرجى تحليل هذه الصفحة من الملف وتقديم إجابة مناسبة.",
1113
+ model_name=selected_model
1114
+ )
1115
+
1116
+ # تنظيف الملفات المؤقتة
1117
+ os.remove(image_path)
1118
+ else:
1119
+ result = {"error": "فشل في تحويل ملف PDF إلى صورة."}
1120
+ else:
1121
+ # تحليل الصورة مباشرة باستخدام Claude
1122
+ result = self.claude_service.analyze_image(
1123
+ image_path=temp_file_path,
1124
+ prompt=f"المستخدم رفع صورة وسأل: {user_input}. يرجى تحليل الصورة وتقديم إجابة مناسبة.",
1125
+ model_name=selected_model
1126
+ )
1127
+ except Exception as e:
1128
+ result = {"error": f"حدث خطأ أثناء معالجة الملف: {str(e)}"}
1129
+
1130
+ # تنظيف الملف المؤقت
1131
+ os.remove(temp_file_path)
1132
+
1133
+ # إضافة استجابة المساعد
1134
+ if "error" in result:
1135
+ assistant_response = f"عذراً، حدث خطأ أثناء معالجة الملف: {result['error']}"
1136
+ else:
1137
+ assistant_response = result["content"]
1138
+ else:
1139
+ # إرسال المحادثة إلى Claude للحصول على استجابة
1140
+ result = self.claude_service.chat_completion(
1141
+ messages=st.session_state.ai_assistant_messages,
1142
+ model_name=selected_model
1143
+ )
1144
+
1145
+ # إضافة استجابة ��لمساعد
1146
+ if "error" in result:
1147
+ assistant_response = f"عذراً، حدث خطأ أثناء الاتصال بالمساعد الذكي: {result['error']}"
1148
+ else:
1149
+ assistant_response = result["content"]
1150
+
1151
+ # إضافة استجابة المساعد إلى المحادثة
1152
+ st.session_state.ai_assistant_messages.append({"role": "assistant", "content": assistant_response})
1153
+
1154
+ # تحديث واجهة المستخدم
1155
+ st.experimental_rerun()
1156
+
1157
+ def _render_cost_prediction_tab(self):
1158
+ """عرض تبويب التنبؤ بالتكاليف"""
1159
+ st.markdown("### التنبؤ بالتكاليف")
1160
+
1161
+ st.markdown("""
1162
+ استخدم هذه الأداة للتنبؤ بتكاليف المشروع بناءً على البيانات التاريخية والمعايير المشابهة.
1163
+ """)
1164
+
1165
+ col1, col2 = st.columns(2)
1166
+
1167
+ with col1:
1168
+ project_type = st.selectbox(
1169
+ "نوع المشروع",
1170
+ ["بناء", "صيانة", "تصميم", "استشارات", "توريد"],
1171
+ key="cost_project_type"
1172
+ )
1173
+
1174
+ project_size = st.select_slider(
1175
+ "حجم المشروع",
1176
+ options=["صغير", "متوسط", "كبير", "ضخم"],
1177
+ key="cost_project_size"
1178
+ )
1179
+
1180
+ duration = st.number_input(
1181
+ "المدة المتوقعة (بالأشهر)",
1182
+ min_value=1,
1183
+ max_value=60,
1184
+ value=12,
1185
+ key="cost_duration"
1186
+ )
1187
+
1188
+ with col2:
1189
+ location = st.selectbox(
1190
+ "الموقع",
1191
+ ["المنطقة الشرقية", "المنطقة الغربية", "المنطقة الوسطى", "المنطقة الشمالية", "المنطقة الجنوبية"],
1192
+ key="cost_location"
1193
+ )
1194
+
1195
+ complexity = st.select_slider(
1196
+ "درجة التعقيد",
1197
+ options=["منخفضة", "متوسطة", "عالية", "معقدة جداً"],
1198
+ key="cost_complexity"
1199
+ )
1200
+
1201
+ resources = st.multiselect(
1202
+ "الموارد المطلوبة",
1203
+ ["عمالة محلية", "عمالة أجنبية", "معدات ثقيلة", "تكنولوجيا متقدمة", "مواد أولية مستوردة"],
1204
+ default=["عمالة محلية"],
1205
+ key="cost_resources"
1206
+ )
1207
+
1208
+ # زر التنبؤ بالتكاليف
1209
+ if st.button("تنبؤ بالتكاليف", key="predict_cost_button"):
1210
+ # عرض مؤشر التقدم
1211
+ with st.spinner("جاري حساب التكاليف المتوقعة..."):
1212
+ # محاكاة وقت المعالجة
1213
+ time.sleep(1.5)
1214
+
1215
+ # توليد بيانات محاكاة للعرض
1216
+ base_cost = 0
1217
+ if project_size == "صغير":
1218
+ base_cost = random.uniform(100000, 500000)
1219
+ elif project_size == "متوسط":
1220
+ base_cost = random.uniform(500000, 2000000)
1221
+ elif project_size == "كبير":
1222
+ base_cost = random.uniform(2000000, 10000000)
1223
+ else: # ضخم
1224
+ base_cost = random.uniform(10000000, 50000000)
1225
+
1226
+ # تعديل التكلفة بناءً على التعقيد
1227
+ complexity_factor = 1.0
1228
+ if complexity == "منخفضة":
1229
+ complexity_factor = 0.8
1230
+ elif complexity == "متوسطة":
1231
+ complexity_factor = 1.0
1232
+ elif complexity == "عالية":
1233
+ complexity_factor = 1.3
1234
+ else: # معقدة جداً
1235
+ complexity_factor = 1.6
1236
+
1237
+ # تعديل التكلفة بناءً على المدة
1238
+ duration_factor = 1.0 + (duration / 60)
1239
+
1240
+ # حساب التكلفة الإجمالية
1241
+ total_cost = base_cost * complexity_factor * duration_factor
1242
+
1243
+ # توليد بيانات تفصيلية للتكاليف
1244
+ labor_cost = total_cost * random.uniform(0.3, 0.5)
1245
+ materials_cost = total_cost * random.uniform(0.2, 0.4)
1246
+ equipment_cost = total_cost * random.uniform(0.1, 0.2)
1247
+ overhead_cost = total_cost - labor_cost - materials_cost - equipment_cost
1248
+
1249
+ # عرض النتائج
1250
+ st.success("تم حساب التكاليف المتوقعة بنجاح!")
1251
+
1252
+ # عر�� ملخص التكاليف
1253
+ st.markdown("#### ملخص التكاليف المتوقعة")
1254
+
1255
+ col1, col2 = st.columns(2)
1256
+
1257
+ with col1:
1258
+ st.metric("التكلفة الإجمالية المتوقعة", f"{total_cost:,.2f} ريال")
1259
+ st.metric("تكلفة العمالة", f"{labor_cost:,.2f} ريال")
1260
+ st.metric("تكلفة المواد", f"{materials_cost:,.2f} ريال")
1261
+
1262
+ with col2:
1263
+ st.metric("تكلفة المعدات", f"{equipment_cost:,.2f} ريال")
1264
+ st.metric("المصاريف العامة", f"{overhead_cost:,.2f} ريال")
1265
+ st.metric("مؤشر الثقة", f"{random.uniform(70, 95):.1f}%")
1266
+
1267
+ # عرض التكاليف بشكل رسم بياني
1268
+ cost_data = {
1269
+ 'البند': ['عمالة', 'مواد', 'معدات', 'مصاريف عامة'],
1270
+ 'التكلفة': [labor_cost, materials_cost, equipment_cost, overhead_cost]
1271
+ }
1272
+ cost_df = pd.DataFrame(cost_data)
1273
+
1274
+ fig = px.pie(cost_df, values='التكلفة', names='البند', title='توزيع التكاليف المتوقعة')
1275
+ st.plotly_chart(fig)
1276
+
1277
+ # عرض نصائح لتحسين التكلفة
1278
+ st.markdown("#### نصائح لتحسين التكلفة")
1279
+ st.info("""
1280
+ * استخدام عمالة محلية لتقليل تكاليف العمالة والاستفادة من برامج دعم التوطين.
1281
+ * شراء المواد مباشرة من المورد الرئيسي لتقليل تكاليف الوسطاء.
1282
+ * استئجار المعدات بدلاً من شرائها إذا كانت مدة استخدامها قصيرة.
1283
+ * الاستفادة من برامج المحتوى المحلي للحصول على حوافز السعر.
1284
+ """)
1285
+
1286
+ def _render_risk_analysis_tab(self):
1287
+ """عرض تبويب تحليل المخاطر"""
1288
+ st.markdown("### تحليل المخاطر للمشاريع")
1289
+
1290
+ st.markdown("""
1291
+ استخدم هذه الأداة لتحديد وتقييم المخاطر المحتملة للمشروع، ووضع خطط الاستجابة المناسبة.
1292
+ """)
1293
+
1294
+ col1, col2 = st.columns(2)
1295
+
1296
+ with col1:
1297
+ project_type = st.selectbox(
1298
+ "نوع المشروع",
1299
+ ["بناء", "صيانة", "تصميم", "استشارات", "توريد"],
1300
+ key="risk_project_type"
1301
+ )
1302
+
1303
+ duration = st.number_input(
1304
+ "المدة المتوقعة (بالأشهر)",
1305
+ min_value=1,
1306
+ max_value=60,
1307
+ value=12,
1308
+ key="risk_duration"
1309
+ )
1310
+
1311
+ with col2:
1312
+ complexity = st.select_slider(
1313
+ "درجة التعقيد",
1314
+ options=["منخفضة", "متوسطة", "عالية", "معقدة جداً"],
1315
+ key="risk_complexity"
1316
+ )
1317
+
1318
+ value = st.number_input(
1319
+ "قيمة المشروع (بالريال)",
1320
+ min_value=100000,
1321
+ max_value=100000000,
1322
+ value=1000000,
1323
+ step=100000,
1324
+ format="%d",
1325
+ key="risk_value"
1326
+ )
1327
+
1328
+ # زر تحليل المخاطر
1329
+ if st.button("تحليل المخاطر", key="analyze_risk_button"):
1330
+ # عرض مؤشر التقدم
1331
+ with st.spinner("جاري تحليل المخاطر المحتملة..."):
1332
+ # محاكاة وقت المعالجة
1333
+ time.sleep(1.5)
1334
+
1335
+ # توليد بيانات محاكاة للمخاطر
1336
+ risk_data = [
1337
+ {
1338
+ "category": "مالية",
1339
+ "risk": "تغيرات في أسعار المواد",
1340
+ "impact": random.uniform(3, 5),
1341
+ "probability": random.uniform(2, 4),
1342
+ "severity": 0, # سيتم حسابها لاحقًا
1343
+ "mitigation": "إضافة بند تعديل الأسعار في العقد، وتأمين المواد الرئيسية مبكرًا"
1344
+ },
1345
+ {
1346
+ "category": "فنية",
1347
+ "risk": "صعوبات في التنفيذ",
1348
+ "impact": random.uniform(2, 5),
1349
+ "probability": random.uniform(1, 4),
1350
+ "severity": 0,
1351
+ "mitigation": "إجراء دراسة فنية مفصلة قبل البدء، والاستعانة بخبراء متخصصين"
1352
+ },
1353
+ {
1354
+ "category": "إدارية",
1355
+ "risk": "تأخر في الجدول الزمني",
1356
+ "impact": random.uniform(3, 5),
1357
+ "probability": random.uniform(2, 5),
1358
+ "severity": 0,
1359
+ "mitigation": "وضع خطة زمنية واقعية مع هوامش احتياطية، ومتابعة التنفيذ أسبوعيًا"
1360
+ },
1361
+ {
1362
+ "category": "تنظيمية",
1363
+ "risk": "تغييرات في اللوائح والأنظمة",
1364
+ "impact": random.uniform(2, 4),
1365
+ "probability": random.uniform(1, 3),
1366
+ "severity": 0,
1367
+ "mitigation": "متابعة التحديثات التنظيمية، والتنسيق المستمر مع الجهات المعنية"
1368
+ },
1369
+ {
1370
+ "category": "بيئية",
1371
+ "risk": "ظروف مناخية غير متوقعة",
1372
+ "impact": random.uniform(1, 4),
1373
+ "probability": random.uniform(1, 3),
1374
+ "severity": 0,
1375
+ "mitigation": "جدولة الأنشطة الحساسة في المواسم المناسبة، وتوفير بدائل للظروف الطارئة"
1376
+ }
1377
+ ]
1378
+
1379
+ # حساب شدة المخاطر (Impact × Probability)
1380
+ for risk in risk_data:
1381
+ risk["severity"] = risk["impact"] * risk["probability"]
1382
+
1383
+ # ترتيب المخاطر حسب الشدة
1384
+ risk_data = sorted(risk_data, key=lambda x: x["severity"], reverse=True)
1385
+
1386
+ # تحويل البيانات إلى DataFrame
1387
+ risk_df = pd.DataFrame(risk_data)
1388
+
1389
+ # عرض النتائج
1390
+ st.success("تم تحليل المخاطر المحتملة بنجاح!")
1391
+
1392
+ # عرض جدول المخاطر
1393
+ st.markdown("#### سجل المخاطر المحتملة")
1394
+
1395
+ # تنسيق العرض
1396
+ risk_display = risk_df.copy()
1397
+ risk_display.columns = ["الفئة", "المخاطرة", "التأثير", "الاحتمالية", "الشدة", "إجراءات التخفيف"]
1398
+ risk_display["التأثير"] = risk_display["التأثير"].round(1)
1399
+ risk_display["الاحتمالية"] = risk_display["الاحتمالية"].round(1)
1400
+ risk_display["الشدة"] = risk_display["الشدة"].round(1)
1401
+
1402
+ st.dataframe(risk_display, use_container_width=True)
1403
+
1404
+ # عرض مصفوفة المخاطر
1405
+ st.markdown("#### مصفوفة المخاطر")
1406
+
1407
+ # إنشاء مصفوفة المخاطر باستخدام matplotlib
1408
+ fig, ax = plt.subplots(figsize=(10, 8))
1409
+
1410
+ # تعيين حدود المصفوفة
1411
+ ax.set_xlim(0.5, 5.5)
1412
+ ax.set_ylim(0.5, 5.5)
1413
+
1414
+ # إنشاء تدرج الألوان للخلفية
1415
+ risk_levels = np.zeros((5, 5))
1416
+ for i in range(5):
1417
+ for j in range(5):
1418
+ risk_levels[i, j] = (i + 1) * (j + 1)
1419
+
1420
+ # تلوين الخلفية
1421
+ cmap = plt.cm.RdYlGn_r
1422
+ risk_levels_normalized = risk_levels / 25.0
1423
+ plt.imshow(risk_levels_normalized, cmap=cmap, extent=[0.5, 5.5, 0.5, 5.5], alpha=0.3, origin='lower')
1424
+
1425
+ # إضافة خطوط الشبكة
1426
+ for i in range(6):
1427
+ plt.axhline(y=i + 0.5, color='gray', linestyle='-', alpha=0.3)
1428
+ plt.axvline(x=i + 0.5, color='gray', linestyle='-', alpha=0.3)
1429
+
1430
+ # وضع المخاطر على المصفوفة
1431
+ for i, risk in enumerate(risk_data):
1432
+ ax.scatter(risk["probability"], risk["impact"], s=300, alpha=0.8,
1433
+ color=plt.cm.cool(i/len(risk_data)),
1434
+ edgecolor='black', linewidth=1.5)
1435
+ ax.text(risk["probability"], risk["impact"], str(i+1),
1436
+ ha='center', va='center', fontweight='bold')
1437
+
1438
+ # إضافة التسميات
1439
+ ax.set_xlabel('الاحتمالية', fontsize=14)
1440
+ ax.set_ylabel('التأثير', fontsize=14)
1441
+ ax.set_title('مصفوفة المخاطر', fontsize=16)
1442
+
1443
+ # إضافة شرح للأرقام
1444
+ legend_text = "\n".join([f"{i+1}. {risk['risk']}" for i, risk in enumerate(risk_data)])
1445
+ props = dict(boxstyle='round', facecolor='white', alpha=0.5)
1446
+ ax.text(1.05, 0.95, legend_text, transform=ax.transAxes, fontsize=10,
1447
+ verticalalignment='top', bbox=props)
1448
+
1449
+ st.pyplot(fig)
1450
+
1451
+ # عرض توصيات إدارة المخاطر
1452
+ st.markdown("#### توصيات إدارة المخاطر")
1453
+
1454
+ high_risks = [risk for risk in risk_data if risk["severity"] > 12]
1455
+ medium_risks = [risk for risk in risk_data if 6 < risk["severity"] <= 12]
1456
+ low_risks = [risk for risk in risk_data if risk["severity"] <= 6]
1457
+
1458
+ st.warning(f"""
1459
+ **المخاطر العالية ({len(high_risks)}):**
1460
+ يجب وضع خطة استجابة تفصيلية لكل مخاطرة عالية وتخصيص مسؤول متابعة لها.
1461
+ """)
1462
+
1463
+ st.info(f"""
1464
+ **المخاطر المتوسطة ({len(medium_risks)}):**
1465
+ مراقبة هذه المخاطر بشكل دوري والتأكد من تنفيذ إجراءات التخفيف.
1466
+ """)
1467
+
1468
+ st.success(f"""
1469
+ **المخاطر المنخفضة ({len(low_risks)}):**
1470
+ يمكن قبول هذه المخاطر مع المراقبة الدورية.
1471
+ """)
1472
+
1473
+ # توصيات عامة
1474
+ st.markdown("""
1475
+ #### إجراءات موصى بها:
1476
+
1477
+ 1. تخصيص احتياطي للمخاطر بقيمة تتراوح بين 5-10% من قيمة المشروع.
1478
+ 2. تعيين مسؤول إدارة مخاطر للمشروع.
1479
+ 3. مراجعة سجل المخاطر بشكل أسبوعي.
1480
+ 4. توثيق الدروس المستفادة من المخاطر التي تحققت.
1481
+ """)
1482
+
1483
+ def _render_document_analysis_tab(self):
1484
+ """عرض تبويب تحليل المستندات"""
1485
+ st.markdown("### تحليل مستندات المناقصة")
1486
+
1487
+ st.markdown("""
1488
+ استخدم هذه الأداة لتحليل مستندات المناقصة واستخراج المعلومات المهمة منها.
1489
+ """)
1490
+
1491
+ # خيارات تحميل المستندات
1492
+ upload_method = st.radio(
1493
+ "طريقة تحميل المستندات",
1494
+ ["تحميل ملف", "نسخ ولصق النص"],
1495
+ horizontal=True,
1496
+ key="doc_upload_method"
1497
+ )
1498
+
1499
+ document_text = ""
1500
+
1501
+ if upload_method == "تحميل ملف":
1502
+ uploaded_file = st.file_uploader(
1503
+ "ارفع مستند المناقصة (PDF، DOCX، TXT)",
1504
+ type=["pdf", "docx", "txt"],
1505
+ key="document_file_upload"
1506
+ )
1507
+
1508
+ if uploaded_file is not None:
1509
+ # حفظ الملف مؤقتًا
1510
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{uploaded_file.name.split('.')[-1]}") as tmp_file:
1511
+ tmp_file.write(uploaded_file.getvalue())
1512
+ temp_file_path = tmp_file.name
1513
+
1514
+ # محاكاة استخراج النص من الملف
1515
+ try:
1516
+ st.info("تم استلام الملف، جاري معالجته...")
1517
+ # محاكاة وقت المعالجة
1518
+ time.sleep(1)
1519
+
1520
+ # في التطبيق الحقيقي، سيتم استخراج النص من الملف حسب نوعه
1521
+ document_text = "هذا نموذج لنص مستند المناقصة تم استخراجه من الملف المرفوع.\n\n"
1522
+ document_text += "مناقصة رقم: 2025/123\n"
1523
+ document_text += "اسم المشروع: إنشاء مباني إدارية\n\n"
1524
+ document_text += "متطلبات المشروع:\n"
1525
+ document_text += "1. تصميم وتنفيذ مبنى إداري مكون من 5 طوابق\n"
1526
+ document_text += "2. إنشاء مواقف سيارات بسعة 200 سيارة\n"
1527
+ document_text += "3. توفير أنظمة تكييف وتهوية متطورة\n\n"
1528
+ document_text += "الشروط العامة:\n"
1529
+ document_text += "- مدة التنفيذ: 18 شهرًا من تاريخ استلام الموقع\n"
1530
+ document_text += "- غرامة التأخير: 0.5% من قيمة العقد عن كل أسبوع تأخير بحد أقصى 10%\n"
1531
+ document_text += "- الدفعة المقدمة: 10% من قيمة العقد مقابل ضمان بنكي\n"
1532
+ document_text += "- محتوى محلي: لا يقل عن 60% من إجمالي العقد\n"
1533
+
1534
+ st.success("تم معالجة الملف بنجاح")
1535
+
1536
+ except Exception as e:
1537
+ st.error(f"حدث خطأ أثناء معالجة الملف: {str(e)}")
1538
+
1539
+ # تنظيف الملف المؤقت
1540
+ try:
1541
+ os.remove(temp_file_path)
1542
+ except:
1543
+ pass
1544
+ else:
1545
+ document_text = st.text_area(
1546
+ "الصق نص المناقصة هنا",
1547
+ height=300,
1548
+ key="document_text_input"
1549
+ )
1550
+
1551
+ # خيارات التحليل
1552
+ st.markdown("### خيارات التحليل")
1553
+
1554
+ analysis_options = st.multiselect(
1555
+ "اختر عناصر التحليل",
1556
+ [
1557
+ "المعلومات الأساسية",
1558
+ "الشروط العامة",
1559
+ "المتطلبات الفنية",
1560
+ "المحتوى المحلي",
1561
+ "مواصفات المشروع",
1562
+ "جدول الكميات"
1563
+ ],
1564
+ default=["المعلومات الأساسية", "الشروط العامة"],
1565
+ key="doc_analysis_options"
1566
+ )
1567
+
1568
+ # زر تحليل المستند
1569
+ if st.button("تحليل المستند", key="analyze_document_button") and document_text:
1570
+ # عرض مؤشر التقدم
1571
+ with st.spinner("جاري تحليل المستند..."):
1572
+ # محاكاة وقت المعالجة
1573
+ time.sleep(2)
1574
+
1575
+ # عرض نتائج التحليل
1576
+ st.success("تم تحليل المستند بنجاح!")
1577
+
1578
+ # تقسيم النتائج إلى أقسام
1579
+ col1, col2 = st.columns(2)
1580
+
1581
+ with col1:
1582
+ # المعلومات الأساسية
1583
+ if "المعلومات الأساسية" in analysis_options:
1584
+ st.markdown("#### المعلومات الأساسية")
1585
+ st.info("""
1586
+ **رقم المناقصة:** 2025/123
1587
+
1588
+ **اسم المشروع:** إنشاء مباني إدارية
1589
+
1590
+ **الجهة المالكة:** وزارة الأشغال العامة
1591
+
1592
+ **تاريخ الطرح:** 15/03/2025
1593
+
1594
+ **تاريخ الإقفال:** 15/04/2025
1595
+
1596
+ **مدة التنفيذ:** 18 شهرًا
1597
+ """)
1598
+
1599
+ # الشروط العامة
1600
+ if "الشروط العامة" in analysis_options:
1601
+ st.markdown("#### الشروط العامة")
1602
+ st.info("""
1603
+ **الضمان الابتدائي:** 2% من قيمة العطاء
1604
+
1605
+ **الضمان النهائي:** 5% من قيمة العقد
1606
+
1607
+ **غرامة التأخير:** 0.5% أسبوعيًا بحد أقصى 10%
1608
+
1609
+ **نظام الدفعات:** شهرية حسب الإنجاز
1610
+
1611
+ **الدفعة المقدمة:** 10% مقابل ضمان بنكي
1612
+
1613
+ **متطلبات التأمين:** تأمين شامل ضد الأخطار + تأمين مسؤولية مدنية
1614
+ """)
1615
+
1616
+ # المتطلبات الفنية
1617
+ if "المتطلبات الفنية" in analysis_options:
1618
+ st.markdown("#### المتطلبات الفنية")
1619
+ st.info("""
1620
+ **الشهادات المطلوبة:**
1621
+ - ISO 9001 لنظام إدارة الجودة
1622
+ - ISO 14001 لنظام الإدارة البيئية
1623
+ - شهادة تصنيف المقاولين (درجة أولى)
1624
+
1625
+ **الخبرات المطلوبة:**
1626
+ - 3 مشاريع مماثلة في آخر 5 سنوات
1627
+ - خبرة لا تقل عن 10 سنوات في المجال
1628
+
1629
+ **الكادر الفني:**
1630
+ - مدير مشروع (خبرة 15 سنة)
1631
+ - مهندس مدني (خبرة 10 سنوات)
1632
+ - مهندس معماري (خبرة 8 سنوات)
1633
+ - مهندس كهرباء (خبرة 8 سنوات)
1634
+ - مهندس ميكانيكا (خبرة 8 سنوات)
1635
+ """)
1636
+
1637
+ with col2:
1638
+ # المحتوى المحلي
1639
+ if "المحتوى المحلي" in analysis_options:
1640
+ st.markdown("#### متطلبات المحتوى المحلي")
1641
+ st.info("""
1642
+ **نسبة المحتوى المحلي المطلوبة:** 60%
1643
+
1644
+ **العناصر المحددة:**
1645
+ - توظيف 70% من القوى العاملة محلية
1646
+ - استخدام 50% من المواد من الصناعة المحلية
1647
+ - التعاقد مع موردين محليين بنسبة 40%
1648
+
1649
+ **المستندات المطلوبة:**
1650
+ - شهادة المحتوى المحلي للمنتجات
1651
+ - خطة تفصيلية لتحقيق نسبة المحتوى المحلي
1652
+ - التزام بتقديم تقارير دورية
1653
+ """)
1654
+
1655
+ # مواصفات المشروع
1656
+ if "مواصفات المشروع" in analysis_options:
1657
+ st.markdown("#### مواصفات المشروع")
1658
+ st.info("""
1659
+ **المباني:**
1660
+ - مبنى إداري رئيسي (5 طوابق، 15,000 م²)
1661
+ - مبنى خدمات (طابقين، 2,000 م²)
1662
+ - مواقف سيارات (200 سيارة)
1663
+
1664
+ **التشطيبات:**
1665
+ - واجهات زجاجية عاكسة
1666
+ - أرضيات رخام في المداخل والردهات
1667
+ - أنظمة تكييف مركزية موفرة للطاقة
1668
+ - أنظمة الأمن والسلامة متطورة
1669
+
1670
+ **المعايير:**
1671
+ - تصميم موفر للطاقة حسب متطلبات LEED
1672
+ - مراعاة متطلبات الوصول الشامل
1673
+ - الالتزام بكود البناء السعودي
1674
+ """)
1675
+
1676
+ # جدول الكميات
1677
+ if "جدول الكميات" in analysis_options:
1678
+ st.markdown("#### تحليل جدول الكميات")
1679
+
1680
+ # إنشاء بيانات جدول الكميات
1681
+ quantities_data = {
1682
+ 'البند': [
1683
+ 'أعمال الحفر والردم',
1684
+ 'أعمال الخرسانة',
1685
+ 'أعمال البناء',
1686
+ 'أعمال التشطيبات',
1687
+ 'أعمال الكهرباء',
1688
+ 'أعمال السباكة',
1689
+ 'أعمال التكييف',
1690
+ 'أعمال السلامة'
1691
+ ],
1692
+ 'الكمية': [
1693
+ 15000,
1694
+ 7500,
1695
+ 8000,
1696
+ 17000,
1697
+ 1,
1698
+ 1,
1699
+ 1,
1700
+ 1
1701
+ ],
1702
+ 'الوحدة': [
1703
+ 'م³',
1704
+ 'م³',
1705
+ 'م²',
1706
+ 'م²',
1707
+ 'مقطوعية',
1708
+ 'مقطوعية',
1709
+ 'مقطوعية',
1710
+ 'مقطوعية'
1711
+ ],
1712
+ 'السعر التقديري': [
1713
+ 800000,
1714
+ 3000000,
1715
+ 2500000,
1716
+ 4000000,
1717
+ 1500000,
1718
+ 1200000,
1719
+ 2000000,
1720
+ 1000000
1721
+ ]
1722
+ }