""" Patient Monitoring — Care periods, eligibility, daily checkins, monitoring plans, execution logs. """ import re import uuid from django.core.exceptions import ValidationError from django.db import models from access_core.constants import TRACK_CHOICES # ICD-10-CM canonical (dotted) form: a 3-character category — letter plus two # alphanumerics in the shapes L##, L#L (e.g. C4A), or LL# (e.g. QA1) — plus an # optional dot and 1-4 alphanumeric characters. CDC NCHS publishes codes without # the dot ("E119"); canonical_icd10_code() below derives the dotted form. ICD10_CODE_RE = re.compile(r"^[A-Z](?:\d[A-Z0-9]|[A-Z]\d)(?:\.[A-Z0-9]{1,4})?$") def canonical_icd10_code(code: str) -> str: """Return the dotted display form of a CDC-published (dotless) code. ICD-10-CM codes longer than three characters carry exactly one dot, after the category: "E119" -> "E11.9". Three-character root codes pass through. """ return code if len(code) == 3 else f"{code[:3]}.{code[3:]}" class ClinicalTrack(models.Model): TRACK_CHOICES = TRACK_CHOICES id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) track_name = models.TextField(choices=TRACK_CHOICES, unique=True) description = models.TextField(null=True, blank=True) is_active = models.BooleanField(default=True) def __str__(self): return self.track_name class Meta: app_label = "monitoring" class EligibilityRequest(models.Model): """A reviewer's determination to check an intake record for eligibility in a specific clinical track, with supporting ICD-10-CM diagnoses. Track and ICD-10 codes are NOT part of the Healthie intake data — a reviewer assigns them manually based on the intake assessment and other available records. A patient may have multiple requests (one per track); each maps to one $check-eligibility submission and, if eligible, one $align submission. Model rule: eCKM and CKM must not be pursued simultaneously (Participation Agreement §7.02(I)) — enforced at the CMS boundary, noted here for reviewers. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.PROTECT, related_name="eligibility_requests", ) clinical_track = models.ForeignKey(ClinicalTrack, on_delete=models.PROTECT) note = models.TextField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"EligibilityRequest: {self.intake_record} -> {self.clinical_track.track_name}" class Meta: app_label = "monitoring" class Icd10Code(models.Model): """One ICD-10-CM code from the annual CDC NCHS snapshot. ``code`` is stored exactly as CDC publishes it (no dot, e.g. "E119"); the canonical dotted form reviewers enter and CMS submissions carry ("E11.9") is derived with :func:`canonical_icd10_code`. The table always holds exactly one fiscal-year snapshot of the complete official code set (~75k rows). Refresh it with ``manage.py load_icd10_codes`` (provenance in that command's docstring); codes absent from a new snapshot are deleted, which is how annual deletions are tracked. """ code = models.CharField(max_length=7, unique=True) description = models.TextField() fiscal_year = models.PositiveSmallIntegerField() def __str__(self): return f"{canonical_icd10_code(self.code)} ({self.description})" class Meta: app_label = "monitoring" class EligibilityRequestCondition(models.Model): """An ICD-10-CM diagnosis supporting an eligibility request. Zero or more per request: $check-eligibility accepts zero conditions, $align requires at least one (Operations Manual v0.9.12). Only codes a reviewer actually determined are submitted — no defaults are invented. """ id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) request = models.ForeignKey( EligibilityRequest, on_delete=models.PROTECT, related_name="conditions" ) icd10_code = models.CharField(max_length=10) icd10_description = models.TextField(null=True, blank=True) def clean(self): if self.icd10_code and not ICD10_CODE_RE.match(self.icd10_code): raise ValidationError( { "icd10_code": ( "Invalid ICD-10-CM format — expected e.g. N18 or E11.9." ) } ) def __str__(self): return f"{self.icd10_code} ({self.request.clinical_track.track_name})" class Meta: app_label = "monitoring" class EligibilityCheck(models.Model): CHECK_STATUS_CHOICES = [ ("pending", "Pending"), ("eligible", "Eligible"), ("ineligible", "Ineligible"), ("control_group", "Control Group"), ("error", "Error"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.PROTECT, related_name="eligibility_checks", ) eligibility_request = models.ForeignKey( EligibilityRequest, on_delete=models.PROTECT, related_name="checks" ) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True, related_name="eligibility_checks", ) requested_track = models.ForeignKey(ClinicalTrack, on_delete=models.PROTECT) check_status = models.TextField(choices=CHECK_STATUS_CHOICES, default="pending") cms_response = models.JSONField(null=True, blank=True) ineligibility_reason = models.TextField(null=True, blank=True) checked_at = models.DateTimeField(auto_now_add=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Eligibility: {self.intake_record} -> {self.check_status}" class Meta: app_label = "monitoring" class Alignment(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.PROTECT, related_name="alignments" ) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True, related_name="alignments", ) clinical_track = models.ForeignKey(ClinicalTrack, on_delete=models.PROTECT) aligned_at = models.DateTimeField() cms_response = models.JSONField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Alignment: {self.intake_record} -> {self.clinical_track}" class Meta: app_label = "monitoring" class Unalignment(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="unalignments" ) unaligned_at = models.DateTimeField() reason = models.TextField(null=True, blank=True) cms_response = models.JSONField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Unalignment: {self.beneficiary} ({self.unaligned_at})" class Meta: app_label = "monitoring" class EnrollmentDecision(models.Model): DECISION_CHOICES = [ ("approved", "Approved"), ("rejected", "Rejected"), ("deferred", "Deferred"), ("withdrawn", "Withdrawn"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) intake_record = models.ForeignKey( "intake.IntakeRecord", on_delete=models.PROTECT, related_name="enrollment_decisions", ) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.SET_NULL, null=True, blank=True ) decision = models.TextField(choices=DECISION_CHOICES) decision_by = models.ForeignKey("identity.User", on_delete=models.PROTECT) decision_at = models.DateTimeField(auto_now_add=True) reason = models.TextField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Enrollment: {self.intake_record} -> {self.decision}" class Meta: app_label = "monitoring" class CarePeriod(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="care_periods" ) clinical_track = models.ForeignKey(ClinicalTrack, on_delete=models.PROTECT) start_date = models.DateField() end_date = models.DateField(null=True, blank=True) baseline_status = models.TextField(default="pending") operational_status = models.TextField(default="active") physician_review_status = models.TextField(default="not_required") reporting_schedule = models.JSONField(null=True, blank=True) reconciliation_status = models.TextField(default="not_applicable") protocol_version = models.ForeignKey( "ProtocolVersion", on_delete=models.SET_NULL, null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"CP: {self.beneficiary} - {self.clinical_track} ({self.start_date})" class Meta: app_label = "monitoring" class ProtocolVersion(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) version_number = models.TextField() clinical_track = models.ForeignKey(ClinicalTrack, on_delete=models.PROTECT) description = models.TextField(null=True, blank=True) protocol_content = models.JSONField(null=True, blank=True) is_active = models.BooleanField(default=True) effective_from = models.DateField() effective_to = models.DateField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Protocol v{self.version_number} ({self.clinical_track})" class Meta: app_label = "monitoring" class ProtocolAssignmentVersion(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey("identity.Beneficiary", on_delete=models.PROTECT) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) protocol_version = models.ForeignKey(ProtocolVersion, on_delete=models.PROTECT) assigned_at = models.DateTimeField(auto_now_add=True) assigned_by = models.ForeignKey("identity.User", on_delete=models.PROTECT) notes = models.TextField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Assignment: {self.beneficiary} -> v{self.protocol_version}" class Meta: app_label = "monitoring" class PatientExecutionLog(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="execution_logs" ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) protocol_assignment = models.ForeignKey( ProtocolAssignmentVersion, on_delete=models.SET_NULL, null=True, blank=True ) execution_date = models.DateField() completed = models.BooleanField(default=False) duration_minutes = models.IntegerField(null=True, blank=True) body_areas = models.TextField(null=True, blank=True) steps_completed = models.IntegerField(null=True, blank=True) steps_skipped = models.TextField(null=True, blank=True) missed_reason = models.TextField(null=True, blank=True) unusual_reaction = models.TextField(null=True, blank=True) source_system = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Execution: {self.beneficiary} ({self.execution_date})" class Meta: app_label = "monitoring" class DailyStatusLog(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="status_logs" ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) log_date = models.DateField() symptoms_new_worsening = models.TextField(null=True, blank=True) pain_change = models.TextField(null=True, blank=True) fatigue = models.TextField(null=True, blank=True) sleep_quality = models.TextField(null=True, blank=True) dizziness = models.BooleanField(null=True, blank=True) shortness_of_breath = models.BooleanField(null=True, blank=True) chest_pain = models.BooleanField(null=True, blank=True) fever = models.BooleanField(null=True, blank=True) swelling = models.BooleanField(null=True, blank=True) bleeding = models.BooleanField(null=True, blank=True) skin_reaction = models.BooleanField(null=True, blank=True) gi_reaction = models.BooleanField(null=True, blank=True) urination_change = models.BooleanField(null=True, blank=True) other_concern = models.TextField(null=True, blank=True) source_system = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Status: {self.beneficiary} ({self.log_date})" class Meta: app_label = "monitoring" class TrackSpecificDailyLog(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="track_logs" ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) log_date = models.DateField() clinical_track = models.TextField(choices=TRACK_CHOICES) home_bp_systolic = models.FloatField(null=True, blank=True) home_bp_diastolic = models.FloatField(null=True, blank=True) home_weight_lbs = models.FloatField(null=True, blank=True) blood_sugar = models.FloatField(null=True, blank=True) urination_changes = models.TextField(null=True, blank=True) swelling = models.TextField(null=True, blank=True) energy_level = models.TextField(null=True, blank=True) pain_score_0_10 = models.FloatField(null=True, blank=True) function_today = models.TextField(null=True, blank=True) activity_tolerance = models.TextField(null=True, blank=True) sleep_disruption = models.BooleanField(null=True, blank=True) flare_status = models.BooleanField(null=True, blank=True) pain_medication_use = models.TextField(null=True, blank=True) source_system = models.TextField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return ( f"Track log: {self.beneficiary} - {self.clinical_track} ({self.log_date})" ) class Meta: app_label = "monitoring" class MonitoringPlanTemplate(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) template_name = models.TextField() clinical_track = models.TextField( choices=[("all", "All Tracks")] + TRACK_CHOICES, default="all" ) trigger_description = models.TextField(null=True, blank=True) is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Template: {self.template_name} ({self.clinical_track})" class Meta: app_label = "monitoring" class MonitoringPlan(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="monitoring_plans", ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) template = models.ForeignKey( MonitoringPlanTemplate, on_delete=models.SET_NULL, null=True, blank=True ) created_by = models.ForeignKey("identity.User", on_delete=models.PROTECT) clinical_trigger = models.TextField() baseline_abnormality = models.TextField(null=True, blank=True) status = models.TextField(default="active") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Plan: {self.beneficiary} - {self.clinical_trigger}" class Meta: app_label = "monitoring" class MonitoringPlanItem(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) monitoring_plan = models.ForeignKey( MonitoringPlan, on_delete=models.CASCADE, related_name="items" ) item_description = models.TextField() monitoring_category = models.TextField() measure_name = models.TextField(null=True, blank=True) loinc_code = models.ForeignKey( "clinical.LoincCode", on_delete=models.SET_NULL, null=True, blank=True ) frequency_interval = models.TextField(null=True, blank=True) planned_due_date = models.DateField(null=True, blank=True) ordering_clinician = models.ForeignKey( "identity.User", on_delete=models.SET_NULL, null=True, blank=True ) cms_reportable = models.BooleanField(default=False) reportability_reason = models.TextField(null=True, blank=True) evidence_layer = models.TextField() status = models.TextField(default="scheduled") created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) def __str__(self): return f"Item: {self.monitoring_plan} - {self.item_description}" class Meta: app_label = "monitoring" class MonitoringPlanResult(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) monitoring_plan_item = models.ForeignKey( MonitoringPlanItem, on_delete=models.CASCADE, related_name="results" ) result_date = models.DateField() value_text = models.TextField(null=True, blank=True) value_numeric = models.FloatField(null=True, blank=True) unit = models.TextField(null=True, blank=True) source_type = models.TextField() source_record_id = models.UUIDField() source_document = models.ForeignKey( "curation.ClinicalDocument", on_delete=models.SET_NULL, null=True, blank=True ) baseline_comparison = models.TextField(null=True, blank=True) prior_result_comparison = models.TextField(null=True, blank=True) review_status = models.TextField(default="pending") review_user = models.ForeignKey( "identity.User", on_delete=models.SET_NULL, null=True, blank=True ) completed_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Result: {self.monitoring_plan_item} ({self.result_date})" class Meta: app_label = "monitoring" class DailyCheckinTemplate(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) template_name = models.TextField() clinical_track = models.TextField( choices=[("all", "All Tracks")] + TRACK_CHOICES, default="all" ) version = models.TextField() is_active = models.BooleanField(default=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Checkin template: {self.template_name} v{self.version}" class Meta: app_label = "monitoring" class DailyCheckinItem(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) template = models.ForeignKey( DailyCheckinTemplate, on_delete=models.CASCADE, related_name="items" ) item_key = models.TextField() item_label = models.TextField() item_type = models.TextField() options = models.JSONField(null=True, blank=True) is_required = models.BooleanField(default=True) display_order = models.IntegerField() safety_rule = models.JSONField(null=True, blank=True) class Meta: app_label = "monitoring" ordering = ["display_order"] def __str__(self): return f"{self.item_key}: {self.item_label}" class DailyCheckinSubmission(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="checkin_submissions", ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) template = models.ForeignKey(DailyCheckinTemplate, on_delete=models.PROTECT) submission_date = models.DateField() source_system = models.TextField() status = models.TextField(default="submitted") created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Checkin: {self.beneficiary} ({self.submission_date})" class Meta: app_label = "monitoring" class DailyCheckinResponse(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) submission = models.ForeignKey( DailyCheckinSubmission, on_delete=models.CASCADE, related_name="responses" ) item = models.ForeignKey(DailyCheckinItem, on_delete=models.PROTECT) value = models.JSONField() created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Response: {self.submission} - {self.item}" class Meta: app_label = "monitoring" class RawDailyCheckinSubmission(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="raw_checkins", null=True, blank=True, ) raw_payload = models.JSONField() source_system = models.TextField() source_record_id = models.TextField(null=True, blank=True) ingested_at = models.DateTimeField(auto_now_add=True) form_version = models.TextField(null=True, blank=True) def __str__(self): who = self.beneficiary.id if self.beneficiary else "unknown" return f"Raw checkin: {who} ({self.source_system})" class Meta: app_label = "monitoring" class MissedCheckinEvent(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="missed_checkins" ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) missed_date = models.DateField() consecutive_days_missed = models.IntegerField(default=1) notified = models.BooleanField(default=False) notified_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Missed: {self.beneficiary} ({self.missed_date})" class Meta: app_label = "monitoring" class PatientAdherenceSummary(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="adherence_summaries", ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) period_start = models.DateField() period_end = models.DateField() expected_checkins = models.IntegerField(default=0) completed_checkins = models.IntegerField(default=0) missed_checkins = models.IntegerField(default=0) adherence_rate = models.FloatField(default=0) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Adherence: {self.beneficiary} ({self.adherence_rate:.0%})" class Meta: app_label = "monitoring" class MonthlyPatientSummary(models.Model): id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="monthly_summaries", ) care_period = models.ForeignKey(CarePeriod, on_delete=models.PROTECT) month = models.DateField() summary_content = models.JSONField() review_status = models.TextField(default="generated") review_user = models.ForeignKey( "identity.User", on_delete=models.SET_NULL, null=True, blank=True ) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Monthly: {self.beneficiary} ({self.month})" class Meta: app_label = "monitoring" class PatientNotification(models.Model): NOTIFICATION_TYPE_CHOICES = [ ("missed_checkin_reminder", "Missed Checkin Reminder"), ("safety_alert", "Safety Alert"), ("general", "General"), ("protocol_update", "Protocol Update"), ] CHANNEL_CHOICES = [ ("email", "Email"), ("sms", "SMS"), ("in_app", "In-App"), ] id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) beneficiary = models.ForeignKey( "identity.Beneficiary", on_delete=models.PROTECT, related_name="notifications" ) notification_type = models.TextField(choices=NOTIFICATION_TYPE_CHOICES) message = models.TextField() channel = models.TextField(choices=CHANNEL_CHOICES) sent_at = models.DateTimeField(null=True, blank=True) read_at = models.DateTimeField(null=True, blank=True) created_at = models.DateTimeField(auto_now_add=True) def __str__(self): return f"Notification: {self.beneficiary} ({self.notification_type})" class Meta: app_label = "monitoring"