""" Standards Section Models - Track compliance standards (CBAHI, MOH, CHI, etc.) """ from django.db import models from django.core.validators import FileExtensionValidator from apps.core.models import TimeStampedModel, UUIDModel, StatusChoices class StandardSource(UUIDModel, TimeStampedModel): """Standard sources like CBAHI, MOH, CHI, JCI, etc.""" name = models.CharField(max_length=100) name_ar = models.CharField(max_length=100, blank=True, verbose_name="Name (Arabic)") code = models.CharField(max_length=50, unique=True) description = models.TextField(blank=True) website = models.URLField(blank=True) is_active = models.BooleanField(default=True, db_index=True) class Meta: ordering = ["name"] verbose_name = "Standard Source" verbose_name_plural = "Standard Sources" def __str__(self): return self.name class StandardCategory(UUIDModel, TimeStampedModel): """Group standards by category (Patient Safety, Quality Management, etc.)""" name = models.CharField(max_length=100) name_ar = models.CharField(max_length=100, blank=True, verbose_name="Name (Arabic)") description = models.TextField(blank=True) order = models.PositiveIntegerField(default=0, help_text="Display order") is_active = models.BooleanField(default=True, db_index=True) source = models.ForeignKey( StandardSource, on_delete=models.CASCADE, related_name="categories", null=True, blank=True, help_text="Which source this category belongs to", ) max_score = models.DecimalField( max_digits=8, decimal_places=2, default=0, help_text="Maximum possible score for this category (MOH)" ) class Meta: ordering = ["order", "name"] verbose_name = "Standard Category" verbose_name_plural = "Standard Categories" def __str__(self): return self.name class ActivityType(UUIDModel, TimeStampedModel): """Activity types for standards (Clinical, Administrative, Support, etc.)""" name = models.CharField(max_length=200) name_ar = models.CharField(max_length=200, blank=True, verbose_name="Name (Arabic)") description = models.TextField(blank=True) is_active = models.BooleanField(default=True, db_index=True) class Meta: ordering = ["name"] verbose_name = "Activity Type" verbose_name_plural = "Activity Types" def __str__(self): return self.name class Standard(UUIDModel, TimeStampedModel): """Actual standard requirements""" class ComplianceStatus(models.TextChoices): NOT_ASSESSED = "not_assessed", "Not Assessed" MET = "met", "Met" PARTIALLY_MET = "partially_met", "Partially Met" NOT_MET = "not_met", "Not Met" NOT_APPLICABLE = "not_applicable", "Not Applicable" class AssessmentMethod(models.TextChoices): DOCUMENT_REVIEW = "document_review", "Document Review" STAFF_INTERVIEW = "staff_interview", "Staff Interview" OBSERVATION = "observation", "Observation" EVIDENCE = "evidence", "Evidence" QUALITY_COMMITTEE = "quality_committee", "Quality Committee" MULTIPLE = "multiple", "Multiple Methods" code = models.CharField(max_length=50, db_index=True, help_text="e.g., CBAHI-PS-01, 22.1, 1.1") title = models.CharField(max_length=500) title_ar = models.CharField(max_length=500, blank=True, verbose_name="Title (Arabic)") description = models.TextField(blank=True, help_text="Full description of the standard") source = models.ForeignKey(StandardSource, on_delete=models.PROTECT, related_name="standards") category = models.ForeignKey(StandardCategory, on_delete=models.PROTECT, related_name="standards") departments = models.ManyToManyField( "organizations.Department", blank=True, related_name="standards", help_text="Select departments this standard applies to (empty = applies to all)", ) activity_type = models.ForeignKey( ActivityType, on_delete=models.SET_NULL, null=True, blank=True, related_name="standards", help_text="Activity type for this standard", ) parent_standard = models.ForeignKey( "self", on_delete=models.CASCADE, null=True, blank=True, related_name="sub_standards", help_text="Parent standard for sub-items like 4.3.1 under 4.3", ) assessment_method = models.CharField( max_length=30, choices=AssessmentMethod.choices, blank=True, help_text="How compliance is assessed" ) assessment_method_ar = models.CharField( max_length=200, blank=True, help_text="Arabic assessment method text from source file" ) order_within_category = models.PositiveIntegerField(default=0, help_text="Display order within category") effective_date = models.DateField(null=True, blank=True, help_text="When standard becomes effective") review_date = models.DateField(null=True, blank=True, help_text="Next review date") is_active = models.BooleanField(default=True, db_index=True) is_heading = models.BooleanField( default=False, help_text="True for section headers (e.g. 'القيادة رقم 22: ...')" ) is_assessable = models.BooleanField( default=True, help_text="If False, this standard is informational only (no assessment required). Headings are always non-assessable.", ) class Meta: ordering = ["source", "category", "order_within_category", "code"] verbose_name = "Standard" verbose_name_plural = "Standards" def __str__(self): return f"{self.code}: {self.title}" class StandardCompliance(UUIDModel, TimeStampedModel): """Track compliance status per hospital per standard""" class ComplianceStatus(models.TextChoices): NOT_ASSESSED = "not_assessed", "Not Assessed" MET = "met", "Met" PARTIALLY_MET = "partially_met", "Partially Met" NOT_MET = "not_met", "Not Met" NOT_APPLICABLE = "not_applicable", "Not Applicable" class Priority(models.TextChoices): HIGH = "high", "High" MEDIUM = "medium", "Medium" LOW = "low", "Low" class AssessmentCode(models.TextChoices): TM = "TM", "Fully Met" TM2 = "TM2", "Met (Level 2)" TM3 = "TM3", "Met (Level 3)" PM = "PM", "Partially Met" PM2 = "PM2", "Partially Met (Level 2)" PM3 = "PM3", "Partially Met (Level 3)" NM = "NM", "Not Met" NM2 = "NM2", "Not Met (Level 2)" NM3 = "NM3", "Not Met (Level 3)" hospital = models.ForeignKey( "organizations.Hospital", on_delete=models.CASCADE, related_name="standard_compliance_records", null=True, blank=True, ) department = models.ForeignKey( "organizations.Department", on_delete=models.CASCADE, related_name="compliance_records", null=True, blank=True, ) standard = models.ForeignKey(Standard, on_delete=models.CASCADE, related_name="compliance_records") status = models.CharField( max_length=20, choices=ComplianceStatus.choices, default=ComplianceStatus.NOT_ASSESSED, db_index=True ) status_ar = models.CharField(max_length=50, blank=True, help_text="Original Arabic status text from source") last_assessed_date = models.DateField(null=True, blank=True, help_text="Date of last assessment") assessor = models.ForeignKey( "accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="assessments" ) notes = models.TextField(blank=True, help_text="Assessment notes") recommendations = models.TextField(blank=True, help_text="Recommendations / strengths / findings") evidence_summary = models.TextField(blank=True, help_text="Summary of evidence") target_status = models.CharField( max_length=20, choices=ComplianceStatus.choices, blank=True, default="", help_text="Target compliance status" ) corrective_action = models.TextField(blank=True, help_text="Corrective action plan") priority = models.CharField( max_length=10, choices=Priority.choices, blank=True, default="" ) target_date = models.CharField( max_length=100, blank=True, default="", help_text="Target completion date (text, from source)" ) target_date_actual = models.DateField(null=True, blank=True, help_text="Parsed target date") score = models.DecimalField(max_digits=8, decimal_places=2, null=True, blank=True, help_text="Score achieved") max_score = models.DecimalField( max_digits=8, decimal_places=2, null=True, blank=True, help_text="Maximum possible score" ) assessment_code = models.CharField( max_length=5, choices=AssessmentCode.choices, blank=True, default="", help_text="TM/PM/NM assessment code" ) assessment_code_target = models.CharField( max_length=5, blank=True, default="", help_text="Target assessment code (col D in MOH framework)" ) supporting_documents = models.TextField(blank=True, help_text="Supporting document references") action_note = models.TextField(blank=True, help_text="Action notes / improvement suggestions") class Meta: ordering = ["-created_at"] verbose_name = "Standard Compliance" verbose_name_plural = "Standard Compliance" unique_together = [["hospital", "standard"]] def __str__(self): hosp = self.hospital.name_en if self.hospital else "No Hospital" return f"{hosp} - {self.standard.code} - {self.status}" class StandardAttachment(UUIDModel, TimeStampedModel): """Proof of compliance - evidence documents""" compliance = models.ForeignKey( StandardCompliance, on_delete=models.CASCADE, related_name="attachments", null=True, blank=True ) standard = models.ForeignKey( Standard, on_delete=models.CASCADE, related_name="direct_attachments", null=True, blank=True, help_text="Direct evidence attached to the standard itself", ) file = models.FileField( upload_to="standards/attachments/%Y/%m/", validators=[ FileExtensionValidator( allowed_extensions=["pdf", "doc", "docx", "xls", "xlsx", "jpg", "jpeg", "png", "zip"] ) ], ) filename = models.CharField(max_length=255, help_text="Original filename") description = models.TextField(blank=True, help_text="Attachment description") uploaded_by = models.ForeignKey( "accounts.User", on_delete=models.SET_NULL, null=True, blank=True, related_name="uploaded_standards_attachments" ) class Meta: ordering = ["-created_at"] verbose_name = "Standard Attachment" verbose_name_plural = "Standard Attachments" def __str__(self): ref = self.compliance or self.standard or "No Reference" return f"{ref} - {self.filename}"