414 lines
17 KiB
Python
414 lines
17 KiB
Python
import re
|
|
from decimal import Decimal
|
|
from pathlib import Path
|
|
|
|
from django.core.management.base import BaseCommand
|
|
|
|
from openpyxl import load_workbook
|
|
|
|
from apps.standards.models import (
|
|
StandardSource,
|
|
StandardCategory,
|
|
Standard,
|
|
StandardCompliance,
|
|
)
|
|
from apps.organizations.models import Hospital
|
|
|
|
DATA_DIR = Path("data/Documents")
|
|
NUZHA_ID = "b81b9f41-2044-4160-9e6a-9be6583cddbc"
|
|
|
|
STATUS_MAP = {
|
|
"محقق بشكل كامل": Standard.ComplianceStatus.MET,
|
|
"محقق جزئيا": Standard.ComplianceStatus.PARTIALLY_MET,
|
|
"غير محقق": Standard.ComplianceStatus.NOT_MET,
|
|
"مطبق": Standard.ComplianceStatus.MET,
|
|
"مطبق جزئيا": Standard.ComplianceStatus.PARTIALLY_MET,
|
|
"غير مطبق": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق جزئيا": Standard.ComplianceStatus.PARTIALLY_MET,
|
|
"غير مطبق جزئياً": Standard.ComplianceStatus.PARTIALLY_MET,
|
|
"غير مطبق كليا": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق كلياً": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق نهائيا": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق نهائياً": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق ": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق ": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق ": Standard.ComplianceStatus.NOT_MET,
|
|
"غير مطبق ": Standard.ComplianceStatus.NOT_MET,
|
|
}
|
|
|
|
METHOD_MAP = {
|
|
"مراجعة المستند": Standard.AssessmentMethod.DOCUMENT_REVIEW,
|
|
"مراجعة الملف الوظيفي": Standard.AssessmentMethod.DOCUMENT_REVIEW,
|
|
"مراجعة الملف الوظيفي/ لجنة الجودة/ مقابلة القيادة التنفيذية": Standard.AssessmentMethod.MULTIPLE,
|
|
"مقابلة الموظفين": Standard.AssessmentMethod.STAFF_INTERVIEW,
|
|
"مقابلة الموظفين/ ملاحظة": Standard.AssessmentMethod.MULTIPLE,
|
|
"ملاحظة": Standard.AssessmentMethod.OBSERVATION,
|
|
"ملاحظة/ مقابلة الموظفين": Standard.AssessmentMethod.MULTIPLE,
|
|
"إثبات": Standard.AssessmentMethod.EVIDENCE,
|
|
"إثبات ": Standard.AssessmentMethod.EVIDENCE,
|
|
"إثبات/ لجنة الجودة": Standard.AssessmentMethod.MULTIPLE,
|
|
"مراجعة المستند/ مقابلة الموظفين": Standard.AssessmentMethod.MULTIPLE,
|
|
"مقابلة الموظفين/ إثبات": Standard.AssessmentMethod.MULTIPLE,
|
|
"ملاحظة/ مقابلة الموظفين": Standard.AssessmentMethod.MULTIPLE,
|
|
"مراجعة المستند/مراجعة السجلات الطبية المغلقة": Standard.AssessmentMethod.DOCUMENT_REVIEW,
|
|
"لجنة الجودة": Standard.AssessmentMethod.QUALITY_COMMITTEE,
|
|
"مقابلة القيادة التنفيذية/ إثبات": Standard.AssessmentMethod.MULTIPLE,
|
|
}
|
|
|
|
PRIORITY_MAP = {
|
|
"عالي": StandardCompliance.Priority.HIGH,
|
|
"عالي ": StandardCompliance.Priority.HIGH,
|
|
"متوسط": StandardCompliance.Priority.MEDIUM,
|
|
"منخفض": StandardCompliance.Priority.LOW,
|
|
}
|
|
|
|
CBAHI_SHEET_CATEGORIES = {
|
|
"القيادة": ("Leadership", "القيادة"),
|
|
"حقوق المرضى وذويهم": ("Patient Rights", "حقوق المرضى وذويهم"),
|
|
}
|
|
|
|
MOH_FRAMEWORK_CATEGORIES = {
|
|
"1": ("Commitment & Leadership", "الالتزام والقيادة", 23),
|
|
"2": ("Advocacy & Participation", "الدعاية والمشاركة", 21),
|
|
"3": ("Resources & Empowerment", "توفير الموارد والتمكين", 26),
|
|
"4": ("Learning & Action", "التعلم والعمل", 18),
|
|
"5": ("Governance & Policies", "الحوكمة والسياسات", 12),
|
|
}
|
|
|
|
MOH_CORRECTIVE_CATEGORIES = {
|
|
"الالتزام والقيادة": ("Commitment & Leadership", "الالتزام والقيادة"),
|
|
"الدعاية والمشاركة": ("Advocacy & Participation", "الدعاية والمشاركة"),
|
|
"توفير الموارد والتمكين": ("Resources & Empowerment", "توفير الموارد والتمكين"),
|
|
"التعلم والعمل": ("Learning & Action", "التعلم والعمل"),
|
|
"الحوكمة و السياسات": ("Governance & Policies", "الحوكمة والسياسات"),
|
|
"الحوكمة و السياسات ": ("Governance & Policies", "الحوكمة والسياسات"),
|
|
}
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Import standards data from Excel files"
|
|
|
|
def handle(self, *args, **options):
|
|
hospital = Hospital.objects.get(id=NUZHA_ID)
|
|
|
|
cbahi_source = self._ensure_source("CBAHI", "سباهي", "CBAHI")
|
|
moh_source = self._ensure_source("MOH", "وزارة الصحة", "MOH")
|
|
|
|
self._clear_existing()
|
|
|
|
self.stdout.write("Importing CBAHI standards...")
|
|
self._import_cbahi(cbahi_source, hospital)
|
|
|
|
self.stdout.write("Importing MOH corrective action...")
|
|
self._import_moh_corrective(moh_source, hospital)
|
|
|
|
self.stdout.write("Importing MOH assessment framework...")
|
|
self._import_moh_framework(moh_source, hospital)
|
|
|
|
self.stdout.write(self.style.SUCCESS("Import complete!"))
|
|
|
|
def _ensure_source(self, name, name_ar, code):
|
|
source, _ = StandardSource.objects.get_or_create(
|
|
code=code,
|
|
defaults={"name": name, "name_ar": name_ar},
|
|
)
|
|
return source
|
|
|
|
def _clear_existing(self):
|
|
StandardCompliance.objects.all().delete()
|
|
Standard.objects.all().delete()
|
|
StandardCategory.objects.filter(source__isnull=False).delete()
|
|
|
|
def _map_status(self, status_text):
|
|
if not status_text:
|
|
return Standard.ComplianceStatus.NOT_ASSESSED
|
|
s = str(status_text).strip()
|
|
return STATUS_MAP.get(s, Standard.ComplianceStatus.NOT_ASSESSED)
|
|
|
|
def _map_method(self, method_text):
|
|
if not method_text:
|
|
return ""
|
|
m = str(method_text).strip()
|
|
return METHOD_MAP.get(m, "")
|
|
|
|
def _map_priority(self, priority_text):
|
|
if not priority_text:
|
|
return ""
|
|
p = str(priority_text).strip()
|
|
return PRIORITY_MAP.get(p, "")
|
|
|
|
def _import_cbahi(self, source, hospital):
|
|
wb = load_workbook(DATA_DIR / "Standards - CBAHI - Patient Affairs.xlsx", data_only=True)
|
|
|
|
for sheet_name, (en_name, ar_name) in CBAHI_SHEET_CATEGORIES.items():
|
|
ws = wb[sheet_name]
|
|
category = StandardCategory.objects.create(
|
|
name=en_name,
|
|
name_ar=ar_name,
|
|
source=source,
|
|
order=1 if sheet_name == "القيادة" else 2,
|
|
)
|
|
|
|
current_parent = None
|
|
order = 0
|
|
|
|
for row_idx in range(4, ws.max_row + 1):
|
|
col_a = ws.cell(row_idx, 1).value
|
|
col_b = ws.cell(row_idx, 2).value
|
|
col_c = ws.cell(row_idx, 3).value
|
|
col_d = ws.cell(row_idx, 4).value
|
|
col_e = ws.cell(row_idx, 5).value
|
|
|
|
if not col_a and not col_b:
|
|
continue
|
|
|
|
col_a_str = str(col_a).strip() if col_a else ""
|
|
col_b_str = str(col_b).strip() if col_b else ""
|
|
|
|
is_heading = bool(col_a_str and not col_b_str and "رقم" in col_a_str)
|
|
|
|
if is_heading:
|
|
order += 1
|
|
match = re.search(r"رقم\s+(\d+)", col_a_str)
|
|
heading_num = match.group(1) if match else str(order)
|
|
current_parent = Standard.objects.create(
|
|
code=f"CBAHI-{sheet_name[:3]}-{heading_num}",
|
|
title=col_a_str,
|
|
title_ar=col_a_str,
|
|
source=source,
|
|
category=category,
|
|
is_heading=True,
|
|
order_within_category=order,
|
|
)
|
|
continue
|
|
|
|
if col_a_str and col_b_str:
|
|
order += 1
|
|
code = col_a_str
|
|
if not code.startswith("CBAHI"):
|
|
code = f"CBAHI-{sheet_name[:3]}-{col_a_str}"
|
|
|
|
standard = Standard.objects.create(
|
|
code=code,
|
|
title=col_b_str,
|
|
title_ar=col_b_str,
|
|
source=source,
|
|
category=category,
|
|
parent_standard=current_parent,
|
|
assessment_method=self._map_method(col_c),
|
|
assessment_method_ar=str(col_c).strip() if col_c else "",
|
|
order_within_category=order,
|
|
)
|
|
|
|
status = self._map_status(col_d)
|
|
if status != Standard.ComplianceStatus.NOT_ASSESSED or col_e:
|
|
StandardCompliance.objects.create(
|
|
hospital=hospital,
|
|
standard=standard,
|
|
status=status,
|
|
status_ar=str(col_d).strip() if col_d else "",
|
|
recommendations=str(col_e).strip() if col_e else "",
|
|
)
|
|
|
|
def _import_moh_corrective(self, source, hospital):
|
|
wb = load_workbook(DATA_DIR / "Standards - MOH - الاجراء التصحيحي.xlsx", data_only=True)
|
|
ws = wb.active
|
|
|
|
categories = {}
|
|
current_category = None
|
|
order = 0
|
|
|
|
for row_idx in range(3, ws.max_row + 1):
|
|
col_a = ws.cell(row_idx, 1).value
|
|
col_b = ws.cell(row_idx, 2).value
|
|
col_d = ws.cell(row_idx, 4).value
|
|
col_e = ws.cell(row_idx, 5).value
|
|
col_f = ws.cell(row_idx, 6).value
|
|
col_g = ws.cell(row_idx, 7).value
|
|
col_h = ws.cell(row_idx, 8).value
|
|
|
|
if not col_b:
|
|
continue
|
|
|
|
col_a_str = str(col_a).strip() if col_a else ""
|
|
col_b_str = str(col_b).strip() if col_b else ""
|
|
|
|
if col_a_str and col_b_str and len(col_a_str) > 3:
|
|
cat_info = MOH_CORRECTIVE_CATEGORIES.get(col_a_str)
|
|
if cat_info:
|
|
en_name, ar_name = cat_info
|
|
if en_name not in categories:
|
|
category = StandardCategory.objects.create(
|
|
name=f"MOH - {en_name}",
|
|
name_ar=ar_name,
|
|
source=source,
|
|
order=list(MOH_CORRECTIVE_CATEGORIES.keys()).index(col_a_str.strip().rstrip()) + 1,
|
|
)
|
|
categories[en_name] = category
|
|
current_category = categories[en_name]
|
|
order = 0
|
|
|
|
if not current_category:
|
|
continue
|
|
|
|
order += 1
|
|
code = f"MOH-CA-{current_category.order:02d}-{order:03d}"
|
|
|
|
standard = Standard.objects.create(
|
|
code=code,
|
|
title=col_b_str,
|
|
title_ar=col_b_str,
|
|
source=source,
|
|
category=current_category,
|
|
order_within_category=order,
|
|
)
|
|
|
|
status = self._map_status(col_d)
|
|
has_corrective = bool(col_f and str(col_f).strip())
|
|
priority = self._map_priority(col_g)
|
|
|
|
StandardCompliance.objects.create(
|
|
hospital=hospital,
|
|
standard=standard,
|
|
status=status,
|
|
status_ar=str(col_d).strip() if col_d else "",
|
|
target_status=StandardCompliance.ComplianceStatus.MET if has_corrective else "",
|
|
corrective_action=str(col_f).strip() if col_f else "",
|
|
priority=priority,
|
|
target_date=str(col_h).strip() if col_h else "",
|
|
)
|
|
|
|
def _import_moh_framework(self, source, hospital):
|
|
wb = load_workbook(DATA_DIR / "Standards - MOH - الاطار الاسترشادي.xlsx", data_only=True)
|
|
ws = wb["H"]
|
|
|
|
categories = {}
|
|
current_category = None
|
|
current_section = None
|
|
order = 0
|
|
|
|
for row_idx in range(6, ws.max_row + 1):
|
|
col_a = ws.cell(row_idx, 1).value
|
|
col_b = ws.cell(row_idx, 2).value
|
|
col_c = ws.cell(row_idx, 3).value
|
|
col_d = ws.cell(row_idx, 4).value
|
|
col_e = ws.cell(row_idx, 5).value
|
|
col_f = ws.cell(row_idx, 6).value
|
|
col_g = ws.cell(row_idx, 7).value
|
|
col_h = ws.cell(row_idx, 8).value
|
|
col_n = ws.cell(row_idx, 14).value
|
|
|
|
if not col_a and not col_b:
|
|
continue
|
|
|
|
col_a_str = str(col_a).strip() if col_a else ""
|
|
col_b_str = str(col_b).strip() if col_b else ""
|
|
|
|
if col_a_str in MOH_FRAMEWORK_CATEGORIES and "نقطة" in col_b_str:
|
|
info = MOH_FRAMEWORK_CATEGORIES[col_a_str]
|
|
en_name, ar_name, max_score = info
|
|
category = StandardCategory.objects.create(
|
|
name=f"MOH Framework - {en_name}",
|
|
name_ar=ar_name,
|
|
source=source,
|
|
order=int(col_a_str),
|
|
max_score=max_score,
|
|
)
|
|
categories[col_a_str] = category
|
|
current_category = category
|
|
current_section = col_a_str
|
|
order = 0
|
|
continue
|
|
|
|
if not current_category:
|
|
continue
|
|
|
|
is_sub_section = re.match(r"^\d+\.\d+$", col_a_str)
|
|
if is_sub_section and col_b_str and not col_c:
|
|
order += 1
|
|
current_section_detail = col_a_str
|
|
Standard.objects.create(
|
|
code=f"MOH-FW-{current_category.order}-{col_a_str}",
|
|
title=col_b_str,
|
|
title_ar=col_b_str,
|
|
source=source,
|
|
category=current_category,
|
|
is_heading=True,
|
|
order_within_category=order,
|
|
)
|
|
continue
|
|
|
|
if col_a_str and col_b_str:
|
|
try:
|
|
int(col_a_str)
|
|
except ValueError:
|
|
continue
|
|
|
|
order += 1
|
|
code = f"MOH-FW-{current_category.order}-{order:03d}"
|
|
|
|
assessment_code = ""
|
|
if col_c:
|
|
ac = str(col_c).strip()
|
|
valid_codes = [c[0] for c in StandardCompliance.AssessmentCode.choices]
|
|
if ac in valid_codes:
|
|
assessment_code = ac
|
|
|
|
assessment_code_target = ""
|
|
if col_d:
|
|
act = str(col_d).strip()
|
|
valid_codes = [c[0] for c in StandardCompliance.AssessmentCode.choices]
|
|
if act in valid_codes:
|
|
assessment_code_target = act
|
|
|
|
score = None
|
|
max_score = None
|
|
if col_e is not None:
|
|
try:
|
|
score = Decimal(str(col_e).strip())
|
|
except Exception:
|
|
pass
|
|
if col_f is not None:
|
|
try:
|
|
ms = str(col_f).strip()
|
|
if ms:
|
|
max_score = Decimal(ms)
|
|
except Exception:
|
|
pass
|
|
|
|
status = Standard.ComplianceStatus.NOT_ASSESSED
|
|
status_ar = ""
|
|
if col_n:
|
|
status_ar = str(col_n).strip()
|
|
status = STATUS_MAP.get(status_ar, Standard.ComplianceStatus.NOT_ASSESSED)
|
|
elif assessment_code:
|
|
if assessment_code.startswith("TM"):
|
|
status = Standard.ComplianceStatus.MET
|
|
elif assessment_code.startswith("PM"):
|
|
status = Standard.ComplianceStatus.PARTIALLY_MET
|
|
elif assessment_code.startswith("NM"):
|
|
status = Standard.ComplianceStatus.NOT_MET
|
|
|
|
standard = Standard.objects.create(
|
|
code=code,
|
|
title=col_b_str,
|
|
title_ar=col_b_str,
|
|
source=source,
|
|
category=current_category,
|
|
order_within_category=order,
|
|
)
|
|
|
|
StandardCompliance.objects.create(
|
|
hospital=hospital,
|
|
standard=standard,
|
|
status=status,
|
|
status_ar=status_ar,
|
|
assessment_code=assessment_code,
|
|
assessment_code_target=assessment_code_target,
|
|
score=score,
|
|
max_score=max_score,
|
|
supporting_documents=str(col_g).strip() if col_g else "",
|
|
action_note=str(col_h).strip() if col_h else "",
|
|
)
|