Spaces:
Running
on
T4
Running
on
T4
Update modules/local_content.py
Browse files- modules/local_content.py +175 -418
modules/local_content.py
CHANGED
@@ -1,4 +1,177 @@
|
|
1 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
def calculate(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
4 |
"""
|
@@ -408,420 +581,4 @@
|
|
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 |
-
# الحد الأدنى للمحتوى المحلي
|
535 |
-
min_percentage = sector_data["min_percentage"]
|
536 |
-
target_percentage = sector_data["target_percentage"]
|
537 |
-
|
538 |
-
# إضافة متطلب الحد الأدنى للمحتوى المحلي
|
539 |
-
requirements.append({
|
540 |
-
"title": "الحد الأدنى للمحتوى المحلي",
|
541 |
-
"description": f"يجب أن تكون نسبة المحتوى المحلي للمشروع لا تقل عن {min_percentage}%",
|
542 |
-
"importance": "عالية",
|
543 |
-
"category": "محتوى محلي"
|
544 |
-
})
|
545 |
-
|
546 |
-
# إضافة متطلب النسبة المستهدفة للمحتوى المحلي
|
547 |
-
requirements.append({
|
548 |
-
"title": "النسبة المستهدفة للمحتوى المحلي",
|
549 |
-
"description": f"النسبة المستهدفة للمحتوى المحلي هي {target_percentage}% وسيتم منح نقاط إضافية للعروض التي تتجاوز الحد الأدنى",
|
550 |
-
"importance": "متوسطة",
|
551 |
-
"category": "محتوى محلي"
|
552 |
-
})
|
553 |
-
|
554 |
-
# إضافة متطلب توطين الوظائف
|
555 |
-
requirements.append({
|
556 |
-
"title": "توطين الوظائف",
|
557 |
-
"description": "يجب أن تكون الشركة ملتزمة بمتطلبات نطاقات وأن تكون في النطاق الأخضر على الأقل",
|
558 |
-
"importance": "عالية",
|
559 |
-
"category": "محتوى محلي"
|
560 |
-
})
|
561 |
-
|
562 |
-
# إضافة متطلب شهادة المحتوى المحلي
|
563 |
-
requirements.append({
|
564 |
-
"title": "شهادة المحتوى المحلي",
|
565 |
-
"description": "يجب تقديم شهادة المحتوى المحلي الصادرة من هيئة المحتوى المحلي والمشتريات الحكومية",
|
566 |
-
"importance": "عالية",
|
567 |
-
"category": "محتوى محلي"
|
568 |
-
})
|
569 |
-
|
570 |
-
# إضافة متطلب خطة تطوير المحتوى المحلي
|
571 |
-
requirements.append({
|
572 |
-
"title": "خطة تطوير المحتوى المحلي",
|
573 |
-
"description": "يجب تقديم خطة لتطوير المحتوى المحلي تتضمن تفاصيل الموردين المحليين وتوطين التقنية والخبرات",
|
574 |
-
"importance": "متوسطة",
|
575 |
-
"category": "محتوى محلي"
|
576 |
-
})
|
577 |
-
|
578 |
-
# إضافة متطلبات إضافية من البيانات المستخرجة
|
579 |
-
if local_content_info["requirements"]:
|
580 |
-
for i, req in enumerate(local_content_info["requirements"]):
|
581 |
-
requirements.append({
|
582 |
-
"title": f"متطلب محتوى محلي {i+1}",
|
583 |
-
"description": req,
|
584 |
-
"importance": "عالية",
|
585 |
-
"category": "محتوى محلي",
|
586 |
-
"source": "مستخرج"
|
587 |
-
})
|
588 |
-
|
589 |
-
return requirements
|
590 |
-
|
591 |
-
def _generate_recommendations(self, overall_percentage: float, sector_data: Dict[str, Any],
|
592 |
-
manpower_analysis: Dict[str, Any], materials_analysis: Dict[str, Any],
|
593 |
-
services_analysis: Dict[str, Any], nitaqat_analysis: Dict[str, Any]) -> List[str]:
|
594 |
-
"""
|
595 |
-
إعداد توصيات لتحسين المحتوى المحلي
|
596 |
-
"""
|
597 |
-
recommendations = []
|
598 |
-
|
599 |
-
# الحد الأدنى والنسبة المستهدفة
|
600 |
-
min_percentage = sector_data["min_percentage"]
|
601 |
-
target_percentage = sector_data["target_percentage"]
|
602 |
-
|
603 |
-
# التحقق من الامتثال للحد الأدنى
|
604 |
-
if overall_percentage < min_percentage:
|
605 |
-
recommendations.append(
|
606 |
-
f"زيادة نسبة المحتوى المحلي من {overall_percentage}% إلى {min_percentage}% على الأقل للامتثال للحد الأدنى المطلوب"
|
607 |
-
)
|
608 |
-
elif overall_percentage < target_percentage:
|
609 |
-
recommendations.append(
|
610 |
-
f"زيادة نسبة المحتوى المحلي من {overall_percentage}% إلى {target_percentage}% للوصول إلى النسبة المستهدفة"
|
611 |
-
)
|
612 |
-
|
613 |
-
# توصيات لتحسين القوى العاملة
|
614 |
-
if manpower_analysis["percentage"] < 60:
|
615 |
-
recommendations.append(
|
616 |
-
f"زيادة نسبة القوى العاملة السعودية من {manpower_analysis['percentage']}% إلى 60% على الأقل"
|
617 |
-
)
|
618 |
-
|
619 |
-
# توصيات لتحسين المواد
|
620 |
-
if materials_analysis["percentage"] < 50:
|
621 |
-
recommendations.append(
|
622 |
-
"زيادة استخدام المواد المصنعة محلياً أو المجمعة محلياً بدلاً من المواد المستوردة"
|
623 |
-
)
|
624 |
-
|
625 |
-
if materials_analysis["fully_imported"] > materials_analysis["local_manufacturing"]:
|
626 |
-
recommendations.append(
|
627 |
-
"تقليل الاعتماد على المواد المستوردة بالكامل وزيادة استخدام المواد المصنعة محلياً"
|
628 |
-
)
|
629 |
-
|
630 |
-
# توصيات لتحسين الخدمات
|
631 |
-
if services_analysis["percentage"] < 70:
|
632 |
-
recommendations.append(
|
633 |
-
"زيادة الاعتماد على مزودي الخدمات المحليين بدلاً من المزودين الأجانب"
|
634 |
-
)
|
635 |
-
|
636 |
-
if services_analysis["foreign_provider"] > services_analysis["local_service_provider"]:
|
637 |
-
recommendations.append(
|
638 |
-
"تقليل الاعتماد على مزودي الخدمات الأجانب وزيادة الاعتماد على مزودي الخدمات المحليين"
|
639 |
-
)
|
640 |
-
|
641 |
-
# توصيات للامتثال لنطاقات
|
642 |
-
if not nitaqat_analysis["compliant"]:
|
643 |
-
recommendations.append(
|
644 |
-
f"زيادة نسبة التوطين في القوى العاملة من {nitaqat_analysis['estimated_saudi_percentage']}% إلى {nitaqat_analysis['required_saudi_percentage']}% على الأقل للامتثال لمتطلبات نطاقات"
|
645 |
-
)
|
646 |
-
|
647 |
-
# توصيات عامة
|
648 |
-
recommendations.extend([
|
649 |
-
"تطوير برامج تدريب وتأهيل للكوادر السعودية لزيادة نسبة التوطين",
|
650 |
-
"الاستفادة من برامج دعم المحتوى المحلي المقدمة من هيئة المحتوى المحلي والمشتريات الحكومية",
|
651 |
-
"بناء شراكات مع المصنعين المحليين لتوطين سلسلة الإمداد",
|
652 |
-
"الاستفادة من حوافز القروض والتمويل المقدمة للشركات التي تساهم في زيادة المحتوى المحلي"
|
653 |
-
])
|
654 |
-
|
655 |
-
return recommendationsimport os
|
656 |
-
import json
|
657 |
-
import numpy as np
|
658 |
-
import pandas as pd
|
659 |
-
from typing import Dict, List, Any, Union, Tuple, Optional
|
660 |
-
from datetime import datetime
|
661 |
-
|
662 |
-
class LocalContentCalculator:
|
663 |
-
"""
|
664 |
-
فئة لحساب وتحليل المحتوى المحلي في المناقصات
|
665 |
-
"""
|
666 |
-
|
667 |
-
def __init__(self):
|
668 |
-
"""
|
669 |
-
تهيئة حاسبة المحتوى المحلي
|
670 |
-
"""
|
671 |
-
# تحميل بيانات المحتوى المحلي
|
672 |
-
self.local_content_data = self._load_local_content_data()
|
673 |
-
|
674 |
-
# تحميل قاعدة بيانات الشركات المحلية
|
675 |
-
self.local_companies = self._load_local_companies()
|
676 |
-
|
677 |
-
# تحميل نطاقات الشركات
|
678 |
-
self.nitaqat_data = self._load_nitaqat_data()
|
679 |
-
|
680 |
-
# قائمة القطاعات
|
681 |
-
self.sectors = [
|
682 |
-
"الإنشاءات", "تقنية المعلومات", "الاتصالات", "الصحة",
|
683 |
-
"التعليم", "النقل", "الطاقة", "المياه", "البيئة",
|
684 |
-
"الصناعة", "التجارة", "السياحة", "الخدمات المالية"
|
685 |
-
]
|
686 |
-
|
687 |
-
def _load_local_content_data(self) -> Dict[str, Any]:
|
688 |
-
"""
|
689 |
-
تحميل بيانات المحتوى المحلي
|
690 |
-
"""
|
691 |
-
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
692 |
-
return {
|
693 |
-
"sectors": {
|
694 |
-
"الإنشاءات": {
|
695 |
-
"min_percentage": 30,
|
696 |
-
"target_percentage": 60,
|
697 |
-
"weights": {
|
698 |
-
"manpower": 0.35,
|
699 |
-
"materials": 0.45,
|
700 |
-
"services": 0.2
|
701 |
-
}
|
702 |
-
},
|
703 |
-
"تقنية المعلومات": {
|
704 |
-
"min_percentage": 25,
|
705 |
-
"target_percentage": 50,
|
706 |
-
"weights": {
|
707 |
-
"manpower": 0.55,
|
708 |
-
"materials": 0.15,
|
709 |
-
"services": 0.3
|
710 |
-
}
|
711 |
-
},
|
712 |
-
"عام": {
|
713 |
-
"min_percentage": 20,
|
714 |
-
"target_percentage": 40,
|
715 |
-
"weights": {
|
716 |
-
"manpower": 0.4,
|
717 |
-
"materials": 0.3,
|
718 |
-
"services": 0.3
|
719 |
-
}
|
720 |
-
}
|
721 |
-
},
|
722 |
-
"material_categories": {
|
723 |
-
"local_manufacturing": 1.0,
|
724 |
-
"local_assembly": 0.7,
|
725 |
-
"imported_with_local_value_add": 0.4,
|
726 |
-
"fully_imported": 0.0
|
727 |
-
},
|
728 |
-
"manpower_categories": {
|
729 |
-
"saudi_employee": 1.0,
|
730 |
-
"expat_employee": 0.0
|
731 |
-
},
|
732 |
-
"service_categories": {
|
733 |
-
"local_service_provider": 1.0,
|
734 |
-
"joint_venture": 0.6,
|
735 |
-
"foreign_provider_with_local_partner": 0.3,
|
736 |
-
"foreign_provider": 0.0
|
737 |
-
}
|
738 |
-
}
|
739 |
-
|
740 |
-
def _load_local_companies(self) -> Dict[str, Dict[str, Any]]:
|
741 |
-
"""
|
742 |
-
تحميل قاعدة بيانات الشركات المحلية
|
743 |
-
"""
|
744 |
-
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
745 |
-
return {
|
746 |
-
"company1": {
|
747 |
-
"name": "شركة البناء السعودية",
|
748 |
-
"cr_number": "1010XXXXXX",
|
749 |
-
"sector": "الإنشاءات",
|
750 |
-
"local_content_certificate": True,
|
751 |
-
"saudi_employees_percentage": 35,
|
752 |
-
"nitaqat_category": "أخضر متوسط",
|
753 |
-
"local_content_percentage": 45
|
754 |
-
},
|
755 |
-
"company2": {
|
756 |
-
"name": "شركة تقنية المستقبل",
|
757 |
-
"cr_number": "1020XXXXXX",
|
758 |
-
"sector": "تقنية المعلومات",
|
759 |
-
"local_content_certificate": True,
|
760 |
-
"saudi_employees_percentage": 42,
|
761 |
-
"nitaqat_category": "أخضر مرتفع",
|
762 |
-
"local_content_percentage": 52
|
763 |
-
},
|
764 |
-
"company3": {
|
765 |
-
"name": "مصنع المنتجات المعدنية",
|
766 |
-
"cr_number": "1030XXXXXX",
|
767 |
-
"sector": "الصناعة",
|
768 |
-
"local_content_certificate": True,
|
769 |
-
"saudi_employees_percentage": 28,
|
770 |
-
"nitaqat_category": "أخضر منخفض",
|
771 |
-
"local_content_percentage": 61
|
772 |
-
}
|
773 |
-
}
|
774 |
-
|
775 |
-
def _load_nitaqat_data(self) -> Dict[str, Dict[str, Any]]:
|
776 |
-
"""
|
777 |
-
تحميل بيانات نطاقات الشركات
|
778 |
-
"""
|
779 |
-
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
780 |
-
return {
|
781 |
-
"الإنشاءات": {
|
782 |
-
"صغيرة": {
|
783 |
-
"أحمر": {"min": 0, "max": 5},
|
784 |
-
"أخضر منخفض": {"min": 6, "max": 10},
|
785 |
-
"أخضر متوسط": {"min": 11, "max": 15},
|
786 |
-
"أخضر مرتفع": {"min": 16, "max": 25},
|
787 |
-
"بلاتيني": {"min": 26, "max": 100}
|
788 |
-
},
|
789 |
-
"متوسطة": {
|
790 |
-
"أحمر": {"min": 0, "max": 7},
|
791 |
-
"أخضر منخفض": {"min": 8, "max": 14},
|
792 |
-
"أخضر متوسط": {"min": 15, "max": 20},
|
793 |
-
"أخضر مرتفع": {"min": 21, "max": 30},
|
794 |
-
"بلاتيني": {"min": 31, "max": 100}
|
795 |
-
},
|
796 |
-
"كبيرة": {
|
797 |
-
"أحمر": {"min": 0, "max": 9},
|
798 |
-
"أخضر منخفض": {"min": 10, "max": 16},
|
799 |
-
"أخضر متوسط": {"min": 17, "max": 23},
|
800 |
-
"أخضر مرتفع": {"min": 24, "max": 35},
|
801 |
-
"بلاتيني": {"min": 36, "max": 100}
|
802 |
-
}
|
803 |
-
},
|
804 |
-
"تقنية المعلومات": {
|
805 |
-
"صغيرة": {
|
806 |
-
"أحمر": {"min": 0, "max": 6},
|
807 |
-
"أخضر منخفض": {"min": 7, "max": 12},
|
808 |
-
"أخضر متوسط": {"min": 13, "max": 19},
|
809 |
-
"أخضر مرتفع": {"min": 20, "max": 29},
|
810 |
-
"بلاتيني": {"min": 30, "max": 100}
|
811 |
-
},
|
812 |
-
"متوسطة": {
|
813 |
-
"أحمر": {"min": 0, "max": 13},
|
814 |
-
"أخضر منخفض": {"min": 14, "max": 20},
|
815 |
-
"أخضر متوسط": {"min": 21, "max": 27},
|
816 |
-
"أخضر مرتفع": {"min": 28, "max": 35},
|
817 |
-
"بلاتيني": {"min": 36, "max": 100}
|
818 |
-
},
|
819 |
-
"كبيرة": {
|
820 |
-
"أحمر": {"min": 0, "max": 17},
|
821 |
-
"أخضر منخفض": {"min": 18, "max": 25},
|
822 |
-
"أخضر متوسط": {"min": 26, "max": 33},
|
823 |
-
"أخضر مرتفع": {"min": 34, "max": 40},
|
824 |
-
"بلاتيني": {"min": 41, "max": 100}
|
825 |
-
}
|
826 |
-
}
|
827 |
-
}
|
|
|
1 |
+
import os
|
2 |
+
import json
|
3 |
+
import re
|
4 |
+
import numpy as np
|
5 |
+
import pandas as pd
|
6 |
+
from typing import Dict, List, Any, Union, Tuple, Optional
|
7 |
+
from datetime import datetime
|
8 |
+
|
9 |
+
class LocalContentCalculator:
|
10 |
+
"""
|
11 |
+
فئة لحساب وتحليل المحتوى المحلي في المناقصات
|
12 |
+
"""
|
13 |
+
|
14 |
+
def __init__(self):
|
15 |
+
"""
|
16 |
+
تهيئة حاسبة المحتوى المحلي
|
17 |
+
"""
|
18 |
+
# تحميل بيانات المحتوى المحلي
|
19 |
+
self.local_content_data = self._load_local_content_data()
|
20 |
+
|
21 |
+
# تحميل قاعدة بيانات الشركات المحلية
|
22 |
+
self.local_companies = self._load_local_companies()
|
23 |
+
|
24 |
+
# تحميل نطاقات الشركات
|
25 |
+
self.nitaqat_data = self._load_nitaqat_data()
|
26 |
+
|
27 |
+
# قائمة القطاعات
|
28 |
+
self.sectors = [
|
29 |
+
"الإنشاءات", "تقنية المعلومات", "الاتصالات", "الصحة",
|
30 |
+
"التعليم", "النقل", "الطاقة", "المياه", "البيئة",
|
31 |
+
"الصناعة", "التجارة", "السياحة", "الخدمات المالية"
|
32 |
+
]
|
33 |
+
|
34 |
+
def _load_local_content_data(self) -> Dict[str, Any]:
|
35 |
+
"""
|
36 |
+
تحميل بيانات المحتوى المحلي
|
37 |
+
"""
|
38 |
+
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
39 |
+
return {
|
40 |
+
"sectors": {
|
41 |
+
"الإنشاءات": {
|
42 |
+
"min_percentage": 30,
|
43 |
+
"target_percentage": 60,
|
44 |
+
"weights": {
|
45 |
+
"manpower": 0.35,
|
46 |
+
"materials": 0.45,
|
47 |
+
"services": 0.2
|
48 |
+
}
|
49 |
+
},
|
50 |
+
"تقنية المعلومات": {
|
51 |
+
"min_percentage": 25,
|
52 |
+
"target_percentage": 50,
|
53 |
+
"weights": {
|
54 |
+
"manpower": 0.55,
|
55 |
+
"materials": 0.15,
|
56 |
+
"services": 0.3
|
57 |
+
}
|
58 |
+
},
|
59 |
+
"عام": {
|
60 |
+
"min_percentage": 20,
|
61 |
+
"target_percentage": 40,
|
62 |
+
"weights": {
|
63 |
+
"manpower": 0.4,
|
64 |
+
"materials": 0.3,
|
65 |
+
"services": 0.3
|
66 |
+
}
|
67 |
+
}
|
68 |
+
},
|
69 |
+
"material_categories": {
|
70 |
+
"local_manufacturing": 1.0,
|
71 |
+
"local_assembly": 0.7,
|
72 |
+
"imported_with_local_value_add": 0.4,
|
73 |
+
"fully_imported": 0.0
|
74 |
+
},
|
75 |
+
"manpower_categories": {
|
76 |
+
"saudi_employee": 1.0,
|
77 |
+
"expat_employee": 0.0
|
78 |
+
},
|
79 |
+
"service_categories": {
|
80 |
+
"local_service_provider": 1.0,
|
81 |
+
"joint_venture": 0.6,
|
82 |
+
"foreign_provider_with_local_partner": 0.3,
|
83 |
+
"foreign_provider": 0.0
|
84 |
+
}
|
85 |
+
}
|
86 |
+
|
87 |
+
def _load_local_companies(self) -> Dict[str, Dict[str, Any]]:
|
88 |
+
"""
|
89 |
+
تحميل قاعدة بيانات الشركات المحلية
|
90 |
+
"""
|
91 |
+
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
92 |
+
return {
|
93 |
+
"company1": {
|
94 |
+
"name": "شركة البناء السعودية",
|
95 |
+
"cr_number": "1010XXXXXX",
|
96 |
+
"sector": "الإنشاءات",
|
97 |
+
"local_content_certificate": True,
|
98 |
+
"saudi_employees_percentage": 35,
|
99 |
+
"nitaqat_category": "أخضر متوسط",
|
100 |
+
"local_content_percentage": 45
|
101 |
+
},
|
102 |
+
"company2": {
|
103 |
+
"name": "شركة تقنية المستقبل",
|
104 |
+
"cr_number": "1020XXXXXX",
|
105 |
+
"sector": "تقنية المعلومات",
|
106 |
+
"local_content_certificate": True,
|
107 |
+
"saudi_employees_percentage": 42,
|
108 |
+
"nitaqat_category": "أخضر مرتفع",
|
109 |
+
"local_content_percentage": 52
|
110 |
+
},
|
111 |
+
"company3": {
|
112 |
+
"name": "مصنع المنتجات المعدنية",
|
113 |
+
"cr_number": "1030XXXXXX",
|
114 |
+
"sector": "الصناعة",
|
115 |
+
"local_content_certificate": True,
|
116 |
+
"saudi_employees_percentage": 28,
|
117 |
+
"nitaqat_category": "أخضر منخفض",
|
118 |
+
"local_content_percentage": 61
|
119 |
+
}
|
120 |
+
}
|
121 |
+
|
122 |
+
def _load_nitaqat_data(self) -> Dict[str, Dict[str, Any]]:
|
123 |
+
"""
|
124 |
+
تحميل بيانات نطاقات الشركات
|
125 |
+
"""
|
126 |
+
# في التطبيق الفعلي، قد تُحمل هذه البيانات من ملف أو قاعدة بيانات
|
127 |
+
return {
|
128 |
+
"الإنشاءات": {
|
129 |
+
"صغير��": {
|
130 |
+
"أحمر": {"min": 0, "max": 5},
|
131 |
+
"أخضر منخفض": {"min": 6, "max": 10},
|
132 |
+
"أخضر متوسط": {"min": 11, "max": 15},
|
133 |
+
"أخضر مرتفع": {"min": 16, "max": 25},
|
134 |
+
"بلاتيني": {"min": 26, "max": 100}
|
135 |
+
},
|
136 |
+
"متوسطة": {
|
137 |
+
"أحمر": {"min": 0, "max": 7},
|
138 |
+
"أخضر منخفض": {"min": 8, "max": 14},
|
139 |
+
"أخضر متوسط": {"min": 15, "max": 20},
|
140 |
+
"أخضر مرتفع": {"min": 21, "max": 30},
|
141 |
+
"بلاتيني": {"min": 31, "max": 100}
|
142 |
+
},
|
143 |
+
"كبيرة": {
|
144 |
+
"أحمر": {"min": 0, "max": 9},
|
145 |
+
"أخضر منخفض": {"min": 10, "max": 16},
|
146 |
+
"أخضر متوسط": {"min": 17, "max": 23},
|
147 |
+
"أخضر مرتفع": {"min": 24, "max": 35},
|
148 |
+
"بلاتيني": {"min": 36, "max": 100}
|
149 |
+
}
|
150 |
+
},
|
151 |
+
"تقنية المعلومات": {
|
152 |
+
"صغيرة": {
|
153 |
+
"أحمر": {"min": 0, "max": 6},
|
154 |
+
"أخضر منخفض": {"min": 7, "max": 12},
|
155 |
+
"أخضر متوسط": {"min": 13, "max": 19},
|
156 |
+
"أخضر مرتفع": {"min": 20, "max": 29},
|
157 |
+
"بلاتيني": {"min": 30, "max": 100}
|
158 |
+
},
|
159 |
+
"متوسطة": {
|
160 |
+
"أحمر": {"min": 0, "max": 13},
|
161 |
+
"أخضر منخفض": {"min": 14, "max": 20},
|
162 |
+
"أخضر متوسط": {"min": 21, "max": 27},
|
163 |
+
"أخضر مرتفع": {"min": 28, "max": 35},
|
164 |
+
"بلاتيني": {"min": 36, "max": 100}
|
165 |
+
},
|
166 |
+
"كبيرة": {
|
167 |
+
"أحمر": {"min": 0, "max": 17},
|
168 |
+
"أخضر منخفض": {"min": 18, "max": 25},
|
169 |
+
"أخضر متوسط": {"min": 26, "max": 33},
|
170 |
+
"أخضر مرتفع": {"min": 34, "max": 40},
|
171 |
+
"بلاتيني": {"min": 41, "max": 100}
|
172 |
+
}
|
173 |
+
}
|
174 |
+
}
|
175 |
|
176 |
def calculate(self, extracted_data: Dict[str, Any], **kwargs) -> Dict[str, Any]:
|
177 |
"""
|
|
|
581 |
services_analysis["foreign_provider"]
|
582 |
)
|
583 |
|
584 |
+
# حساب النسبة المئوية للمحتوى المحلي في الخدمات
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|