""" EMR app models for hospital management system. Provides electronic medical records, clinical documentation, and patient care management. """ import uuid from django.db import models from django.core.validators import RegexValidator, MinValueValidator, MaxValueValidator from django.utils import timezone from django.conf import settings from datetime import timedelta, datetime, time import json class Encounter(models.Model): """ Clinical encounter model for tracking patient visits and care episodes. """ class EncounterType(models.TextChoices): INPATIENT = 'INPATIENT', 'Inpatient' OUTPATIENT = 'OUTPATIENT', 'Outpatient' EMERGENCY = 'EMERGENCY', 'Emergency' URGENT_CARE = 'URGENT_CARE', 'Urgent Care' OBSERVATION = 'OBSERVATION', 'Observation' TELEMEDICINE = 'TELEMEDICINE', 'Telemedicine' HOME_VISIT = 'HOME_VISIT', 'Home Visit' CONSULTATION = 'CONSULTATION', 'Consultation' FOLLOW_UP = 'FOLLOW_UP', 'Follow-up' PROCEDURE = 'PROCEDURE', 'Procedure' SURGERY = 'SURGERY', 'Surgery' DIAGNOSTIC = 'DIAGNOSTIC', 'Diagnostic' PREVENTIVE = 'PREVENTIVE', 'Preventive Care' class EncounterClass(models.TextChoices): AMB = 'AMB', 'Ambulatory' EMER = 'EMER', 'Emergency' FLD = 'FLD', 'Field' HH = 'HH', 'Home Health' IMP = 'IMP', 'Inpatient' ACUTE = 'ACUTE', 'Inpatient Acute' NONAC = 'NONAC', 'Inpatient Non-Acute' OBSENC = 'OBSENC', 'Observation Encounter' PRENC = 'PRENC', 'Pre-Admission' SS = 'SS', 'Short Stay' VR = 'VR', 'Virtual' class EncounterStatus(models.TextChoices): PLANNED = 'PLANNED', 'Planned' ARRIVED = 'ARRIVED', 'Arrived' TRIAGED = 'TRIAGED', 'Triaged' IN_PROGRESS = 'IN_PROGRESS', 'In Progress' ON_HOLD = 'ON_HOLD', 'On Hold' FINISHED = 'FINISHED', 'Finished' CANCELLED = 'CANCELLED', 'Cancelled' ENTERED_IN_ERROR = 'ENTERED_IN_ERROR', 'Entered in Error' UNKNOWN = 'UNKNOWN', 'Unknown' class Priority(models.TextChoices): ROUTINE = 'ROUTINE', 'Routine' URGENT = 'URGENT', 'Urgent' STAT = 'STAT', 'STAT' EMERGENCY = 'EMERGENCY', 'Emergency' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='encounters', help_text='Organization tenant' ) # Encounter Information encounter_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique encounter identifier' ) # Patient and Provider patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='encounters', help_text='Patient for this encounter' ) provider = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='encounters', help_text='Primary provider for this encounter' ) # Encounter Details encounter_type = models.CharField( max_length=30, choices=EncounterType.choices, help_text='Type of encounter' ) encounter_class = models.CharField( max_length=20, choices=EncounterClass.choices, help_text='Encounter class (HL7 standard)' ) # Timing start_datetime = models.DateTimeField( help_text='Encounter start date and time' ) end_datetime = models.DateTimeField( blank=True, null=True, help_text='Encounter end date and time' ) # Status status = models.CharField( max_length=20, choices=EncounterStatus.choices, default=EncounterStatus.PLANNED, help_text='Current encounter status' ) # Location Information location = models.CharField( max_length=100, blank=True, null=True, help_text='Encounter location' ) room_number = models.CharField( max_length=20, blank=True, null=True, help_text='Room number' ) # Related Records appointment = models.ForeignKey( 'appointments.AppointmentRequest', on_delete=models.SET_NULL, null=True, blank=True, related_name='encounters', help_text='Related appointment' ) admission = models.ForeignKey( 'inpatients.Admission', on_delete=models.SET_NULL, null=True, blank=True, related_name='encounters', help_text='Related admission' ) # Clinical Information chief_complaint = models.TextField( blank=True, null=True, help_text='Chief complaint' ) reason_for_visit = models.TextField( blank=True, null=True, help_text='Reason for visit' ) # Priority and Acuity priority = models.CharField( max_length=20, choices=Priority.choices, default=Priority.ROUTINE, help_text='Encounter priority' ) acuity_level = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(1), MaxValueValidator(5)], help_text='Patient acuity level (1-5, 5 being highest)' ) # Documentation Status documentation_complete = models.BooleanField( default=False, help_text='Documentation is complete' ) signed_off = models.BooleanField( default=False, help_text='Encounter has been signed off' ) signed_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='signed_encounters', help_text='Provider who signed off' ) signed_datetime = models.DateTimeField( blank=True, null=True, help_text='Date and time of sign-off' ) # Billing Information billable = models.BooleanField( default=True, help_text='Encounter is billable' ) billing_codes = models.JSONField( default=list, blank=True, help_text='Associated billing codes' ) # Quality Measures quality_measures = models.JSONField( default=dict, blank=True, help_text='Quality measure data' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_encounters', help_text='User who created the encounter' ) class Meta: db_table = 'emr_encounter' verbose_name = 'Encounter' verbose_name_plural = 'Encounters' ordering = ['-start_datetime'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['patient', 'start_datetime']), models.Index(fields=['provider']), models.Index(fields=['encounter_type']), models.Index(fields=['start_datetime']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.start_datetime.strftime('%Y-%m-%d %H:%M')}" @property def duration(self): """ Calculate encounter duration. """ if self.end_datetime: return self.end_datetime - self.start_datetime elif self.status in ['IN_PROGRESS', 'ON_HOLD']: return timezone.now() - self.start_datetime return None @property def is_active(self): """ Check if encounter is currently active. """ return self.status in ['ARRIVED', 'TRIAGED', 'IN_PROGRESS', 'ON_HOLD'] class VitalSigns(models.Model): """ Vital signs model for tracking patient vital signs measurements. """ class TemperatureMethod(models.TextChoices): ORAL = 'ORAL', 'Oral' RECTAL = 'RECTAL', 'Rectal' AXILLARY = 'AXILLARY', 'Axillary' TYMPANIC = 'TYMPANIC', 'Tympanic' TEMPORAL = 'TEMPORAL', 'Temporal' CORE = 'CORE', 'Core' class BpPosition(models.TextChoices): SITTING = 'SITTING', 'Sitting' STANDING = 'STANDING', 'Standing' LYING = 'LYING', 'Lying' SUPINE = 'SUPINE', 'Supine' class BpCuffSize(models.TextChoices): SMALL = 'SMALL', 'Small' REGULAR = 'REGULAR', 'Regular' LARGE = 'LARGE', 'Large' EXTRA_LARGE = 'EXTRA_LARGE', 'Extra Large' PEDIATRIC = 'PEDIATRIC', 'Pediatric' class HeartRhythm(models.TextChoices): REGULAR = 'REGULAR', 'Regular' REGULARLY_IRREGULAR = 'REGULARLY_IRREGULAR', 'Regularly irregular' IRREGULARLY_IRREGULAR = 'IRREGULARLY_IRREGULAR', 'Irregularly irregular' IRREGULAR_UNSPECIFIED = 'IRREGULAR_UNSPECIFIED', 'Irregular (unspecified)' class OxygenDelivery(models.TextChoices): ROOM_AIR = 'ROOM_AIR', 'Room Air' NASAL_CANNULA = 'NASAL_CANNULA', 'Nasal Cannula' SIMPLE_MASK = 'SIMPLE_MASK', 'Simple Mask' NON_REBREATHER = 'NON_REBREATHER', 'Non-Rebreather Mask' VENTURI_MASK = 'VENTURI_MASK', 'Venturi Mask' CPAP = 'CPAP', 'CPAP' BIPAP = 'BIPAP', 'BiPAP' MECHANICAL_VENTILATION = 'MECHANICAL_VENTILATION', 'Mechanical Ventilation' OTHER = 'OTHER', 'Other' # Encounter relationship encounter = models.ForeignKey( Encounter, on_delete=models.CASCADE, related_name='vital_signs', help_text='Associated encounter' ) # Patient relationship patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='vital_signs', help_text='Patient' ) # Measurement Information measurement_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique measurement identifier' ) # Timing measured_datetime = models.DateTimeField( default=timezone.now, help_text='Date and time of measurement' ) # Vital Signs Measurements temperature = models.DecimalField( max_digits=4, decimal_places=1, blank=True, null=True, help_text='Temperature in Celsius' ) temperature_method = models.CharField( max_length=20, choices=TemperatureMethod.choices, blank=True, null=True, help_text='Temperature measurement method' ) # Blood Pressure systolic_bp = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(50), MaxValueValidator(300)], help_text='Systolic blood pressure (mmHg)' ) diastolic_bp = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(30), MaxValueValidator(200)], help_text='Diastolic blood pressure (mmHg)' ) bp_position = models.CharField( max_length=20, choices=BpPosition.choices, blank=True, null=True, help_text='Patient position during BP measurement' ) bp_cuff_size = models.CharField( max_length=20, choices=BpCuffSize.choices, blank=True, null=True, help_text='Blood pressure cuff size' ) # Heart Rate heart_rate = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(20), MaxValueValidator(300)], help_text='Heart rate (beats per minute)' ) heart_rhythm = models.CharField( max_length=25, choices=HeartRhythm.choices, blank=True, null=True, help_text='Heart rhythm' ) # Respiratory Rate respiratory_rate = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(5), MaxValueValidator(60)], help_text='Respiratory rate (breaths per minute)' ) # Oxygen Saturation oxygen_saturation = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(50), MaxValueValidator(100)], help_text='Oxygen saturation (%)' ) oxygen_delivery = models.CharField( max_length=30, choices=OxygenDelivery.choices, default=OxygenDelivery.ROOM_AIR, help_text='Oxygen delivery method' ) oxygen_flow_rate = models.DecimalField( max_digits=4, decimal_places=1, blank=True, null=True, help_text='Oxygen flow rate (L/min)' ) # Pain Scale pain_scale = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(10)], help_text='Pain scale (0-10)' ) pain_location = models.CharField( max_length=100, blank=True, null=True, help_text='Pain location' ) pain_quality = models.CharField( max_length=50, blank=True, null=True, help_text='Pain quality description' ) # Weight and Height weight = models.DecimalField( max_digits=5, decimal_places=1, blank=True, null=True, help_text='Weight in pounds' ) height = models.DecimalField( max_digits=5, decimal_places=1, blank=True, null=True, help_text='Height in inches' ) # BMI (calculated) bmi = models.DecimalField( max_digits=4, decimal_places=1, blank=True, null=True, help_text='Body Mass Index' ) # Additional Measurements head_circumference = models.DecimalField( max_digits=4, decimal_places=1, blank=True, null=True, help_text='Head circumference in cm (pediatric)' ) # Device Information device_used = models.CharField( max_length=100, blank=True, null=True, help_text='Device used for measurements' ) device_calibrated = models.BooleanField( default=True, help_text='Device was calibrated' ) # Staff Information measured_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='vital_signs_measurements', help_text='Staff member who took measurements' ) verified_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='verified_vital_signs', help_text='Staff member who verified measurements' ) # Quality Flags critical_values = models.JSONField( default=list, blank=True, help_text='Critical values identified' ) alerts_generated = models.JSONField( default=list, blank=True, help_text='Alerts generated from measurements' ) # Notes notes = models.TextField( blank=True, null=True, help_text='Additional notes about measurements' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'emr_vital_signs' verbose_name = 'Vital Signs' verbose_name_plural = 'Vital Signs' ordering = ['-measured_datetime'] indexes = [ models.Index(fields=['patient', 'measured_datetime']), models.Index(fields=['encounter']), models.Index(fields=['measured_datetime']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.measured_datetime.strftime('%Y-%m-%d %H:%M')}" def save(self, *args, **kwargs): """ Calculate BMI if weight and height are provided. """ if self.weight and self.height: # BMI = (weight in pounds / (height in inches)^2) * 703 self.bmi = (self.weight / (self.height ** 2)) * 703 super().save(*args, **kwargs) @property def blood_pressure(self): """ Return formatted blood pressure. """ if self.systolic_bp and self.diastolic_bp: return f"{self.systolic_bp}/{self.diastolic_bp}" return None @property def has_critical_values(self): """ Check if any measurements have critical values. """ return len(self.critical_values) > 0 class ProblemList(models.Model): """ Problem list model for tracking patient problems and diagnoses. """ class CodingSystem(models.TextChoices): ICD10 = 'ICD10', 'ICD-10' ICD9 = 'ICD9', 'ICD-9' SNOMED = 'SNOMED', 'SNOMED CT' CPT = 'CPT', 'CPT' LOINC = 'LOINC', 'LOINC' OTHER = 'OTHER', 'Other' class ProblemType(models.TextChoices): DIAGNOSIS = 'DIAGNOSIS', 'Diagnosis' SYMPTOM = 'SYMPTOM', 'Symptom' FINDING = 'FINDING', 'Finding' COMPLAINT = 'COMPLAINT', 'Complaint' CONDITION = 'CONDITION', 'Condition' DISORDER = 'DISORDER', 'Disorder' SYNDROME = 'SYNDROME', 'Syndrome' INJURY = 'INJURY', 'Injury' ALLERGY = 'ALLERGY', 'Allergy' INTOLERANCE = 'INTOLERANCE', 'Intolerance' RISK_FACTOR = 'RISK_FACTOR', 'Risk Factor' OTHER = 'OTHER', 'Other' class Severity(models.TextChoices): MILD = 'MILD', 'Mild' MODERATE = 'MODERATE', 'Moderate' SEVERE = 'SEVERE', 'Severe' CRITICAL = 'CRITICAL', 'Critical' UNKNOWN = 'UNKNOWN', 'Unknown' class Priority(models.TextChoices): LOW = 'LOW', 'Low' MEDIUM = 'MEDIUM', 'Medium' HIGH = 'HIGH', 'High' URGENT = 'URGENT', 'Urgent' class ProblemStatus(models.TextChoices): ACTIVE = 'ACTIVE', 'Active' INACTIVE = 'INACTIVE', 'Inactive' RESOLVED = 'RESOLVED', 'Resolved' REMISSION = 'REMISSION', 'In Remission' RECURRENCE = 'RECURRENCE', 'Recurrence' RELAPSE = 'RELAPSE', 'Relapse' UNKNOWN = 'UNKNOWN', 'Unknown' OTHER = 'OTHER', 'Other' class Laterality(models.TextChoices): LEFT = 'LEFT', 'Left' RIGHT = 'RIGHT', 'Right' BILATERAL = 'BILATERAL', 'Bilateral' UNILATERAL = 'UNILATERAL', 'Unilateral' NOT_APPLICABLE = 'NOT_APPLICABLE', 'Not Applicable' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='problem_lists', help_text='Organization tenant' ) # Patient relationship patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='problems', help_text='Patient' ) # Problem Information problem_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique problem identifier' ) # Problem Details problem_name = models.CharField( max_length=200, help_text='Problem name or description' ) problem_code = models.CharField( max_length=20, blank=True, null=True, help_text='ICD-10 or SNOMED code' ) coding_system = models.CharField( max_length=20, choices=CodingSystem.choices, blank=True, null=True, help_text='Coding system used' ) # Problem Classification problem_type = models.CharField( max_length=30, choices=ProblemType.choices, help_text='Type of problem' ) # Clinical Information onset_date = models.DateField( blank=True, null=True, help_text='Date of onset' ) onset_description = models.CharField( max_length=100, blank=True, null=True, help_text='Description of onset' ) # Severity and Priority severity = models.CharField( max_length=20, choices=Severity.choices, blank=True, null=True, help_text='Problem severity' ) priority = models.CharField( max_length=20, choices=Priority.choices, default=Priority.MEDIUM, help_text='Problem priority' ) # Status status = models.CharField( max_length=20, choices=ProblemStatus.choices, default=ProblemStatus.ACTIVE, help_text='Current problem status' ) # Resolution Information resolution_date = models.DateField( blank=True, null=True, help_text='Date problem was resolved' ) resolution_notes = models.TextField( blank=True, null=True, help_text='Notes about problem resolution' ) # Provider Information diagnosing_provider = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='diagnosed_problems', help_text='Provider who diagnosed the problem' ) managing_provider = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='managed_problems', help_text='Provider managing the problem' ) # Related Information related_encounter = models.ForeignKey( Encounter, on_delete=models.SET_NULL, null=True, blank=True, related_name='problems_identified', help_text='Encounter where problem was identified' ) # Clinical Context body_site = models.CharField( max_length=100, blank=True, null=True, help_text='Body site affected' ) laterality = models.CharField( max_length=20, choices=Laterality.choices, blank=True, null=True, help_text='Laterality' ) # Problem Notes clinical_notes = models.TextField( blank=True, null=True, help_text='Clinical notes about the problem' ) patient_concerns = models.TextField( blank=True, null=True, help_text='Patient concerns and comments' ) # Goals and Outcomes treatment_goals = models.JSONField( default=list, blank=True, help_text='Treatment goals for this problem' ) outcome_measures = models.JSONField( default=list, blank=True, help_text='Outcome measures being tracked' ) # Verification verified = models.BooleanField( default=False, help_text='Problem has been verified' ) verified_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='verified_problems', help_text='Provider who verified the problem' ) verified_date = models.DateTimeField( blank=True, null=True, help_text='Date problem was verified' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_problems', help_text='User who created the problem' ) class Meta: db_table = 'emr_problem_list' verbose_name = 'Problem' verbose_name_plural = 'Problem List' ordering = ['-created_at'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['patient', 'status']), models.Index(fields=['problem_type']), models.Index(fields=['priority']), models.Index(fields=['onset_date']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.problem_name}" @property def is_active(self): """ Check if problem is currently active. """ return self.status == 'ACTIVE' @property def duration(self): """ Calculate problem duration. """ if self.onset_date: end_date = self.resolution_date or timezone.now().date() return end_date - self.onset_date return None class CarePlan(models.Model): """ Care plan model for managing patient care coordination and goals. """ class PlanType(models.TextChoices): COMPREHENSIVE = 'COMPREHENSIVE', 'Comprehensive Care Plan' DISEASE_SPECIFIC = 'DISEASE_SPECIFIC', 'Disease-Specific Plan' PREVENTIVE = 'PREVENTIVE', 'Preventive Care Plan' CHRONIC_CARE = 'CHRONIC_CARE', 'Chronic Care Management' ACUTE_CARE = 'ACUTE_CARE', 'Acute Care Plan' DISCHARGE = 'DISCHARGE', 'Discharge Planning' REHABILITATION = 'REHABILITATION', 'Rehabilitation Plan' PALLIATIVE = 'PALLIATIVE', 'Palliative Care Plan' MENTAL_HEALTH = 'MENTAL_HEALTH', 'Mental Health Plan' MEDICATION = 'MEDICATION', 'Medication Management' NUTRITION = 'NUTRITION', 'Nutrition Plan' EXERCISE = 'EXERCISE', 'Exercise Plan' OTHER = 'OTHER', 'Other' class PlanCategory(models.TextChoices): ASSESSMENT = 'ASSESSMENT', 'Assessment and Monitoring' TREATMENT = 'TREATMENT', 'Treatment' EDUCATION = 'EDUCATION', 'Patient Education' COORDINATION = 'COORDINATION', 'Care Coordination' PREVENTION = 'PREVENTION', 'Prevention' LIFESTYLE = 'LIFESTYLE', 'Lifestyle Modification' MEDICATION = 'MEDICATION', 'Medication Management' FOLLOW_UP = 'FOLLOW_UP', 'Follow-up Care' EMERGENCY = 'EMERGENCY', 'Emergency Planning' SUPPORT = 'SUPPORT', 'Support Services' class PlanStatus(models.TextChoices): DRAFT = 'DRAFT', 'Draft' ACTIVE = 'ACTIVE', 'Active' ON_HOLD = 'ON_HOLD', 'On Hold' COMPLETED = 'COMPLETED', 'Completed' CANCELLED = 'CANCELLED', 'Cancelled' ENTERED_IN_ERROR = 'ENTERED_IN_ERROR', 'Entered in Error' UNKNOWN = 'UNKNOWN', 'Unknown' class PlanPriority(models.TextChoices): LOW = 'LOW', 'Low' ROUTINE = 'ROUTINE', 'Routine' URGENT = 'URGENT', 'Urgent' STAT = 'STAT', 'STAT' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='care_plans', help_text='Organization tenant' ) # Patient relationship patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='care_plans', help_text='Patient' ) # Care Plan Information care_plan_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique care plan identifier' ) # Plan Details title = models.CharField( max_length=200, help_text='Care plan title' ) description = models.TextField( help_text='Care plan description' ) # Plan Type and Category plan_type = models.CharField( max_length=30, choices=PlanType.choices, help_text='Type of care plan' ) category = models.CharField( max_length=50, choices=PlanCategory.choices, help_text='Care plan category' ) # Timing start_date = models.DateField( help_text='Care plan start date' ) end_date = models.DateField( blank=True, null=True, help_text='Care plan end date' ) target_completion_date = models.DateField( blank=True, null=True, help_text='Target completion date' ) # Status status = models.CharField( max_length=20, choices=PlanStatus.choices, default=PlanStatus.DRAFT, help_text='Care plan status' ) # Priority priority = models.CharField( max_length=20, choices=PlanPriority.choices, default=PlanPriority.ROUTINE, help_text='Care plan priority' ) # Provider Information primary_provider = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='primary_care_plans', help_text='Primary provider responsible for care plan' ) care_team = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='care_team_plans', blank=True, help_text='Care team members' ) # Related Problems related_problems = models.ManyToManyField( ProblemList, related_name='care_plans', blank=True, help_text='Related problems addressed by this plan' ) # Goals and Objectives goals = models.JSONField( default=list, help_text='Care plan goals' ) objectives = models.JSONField( default=list, help_text='Specific objectives and targets' ) # Interventions and Activities interventions = models.JSONField( default=list, help_text='Planned interventions' ) activities = models.JSONField( default=list, help_text='Specific activities and tasks' ) # Monitoring and Evaluation monitoring_parameters = models.JSONField( default=list, help_text='Parameters to monitor' ) evaluation_criteria = models.JSONField( default=list, help_text='Criteria for evaluating progress' ) # Patient Involvement patient_goals = models.TextField( blank=True, null=True, help_text='Patient-identified goals' ) patient_preferences = models.TextField( blank=True, null=True, help_text='Patient preferences and concerns' ) patient_barriers = models.TextField( blank=True, null=True, help_text='Identified barriers to care' ) # Resources and Support resources_needed = models.JSONField( default=list, help_text='Resources needed for plan implementation' ) support_systems = models.JSONField( default=list, help_text='Available support systems' ) # Progress Tracking progress_notes = models.TextField( blank=True, null=True, help_text='Progress notes' ) last_reviewed = models.DateField( blank=True, null=True, help_text='Date of last review' ) next_review_date = models.DateField( blank=True, null=True, help_text='Next scheduled review date' ) # Outcomes outcomes_achieved = models.JSONField( default=list, help_text='Outcomes achieved' ) completion_percentage = models.PositiveIntegerField( default=0, validators=[MinValueValidator(0), MaxValueValidator(100)], help_text='Completion percentage' ) # Approval and Authorization approved = models.BooleanField( default=False, help_text='Care plan has been approved' ) approved_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='approved_care_plans', help_text='Provider who approved the plan' ) approved_date = models.DateTimeField( blank=True, null=True, help_text='Date of approval' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_care_plans', help_text='User who created the care plan' ) class Meta: db_table = 'emr_care_plan' verbose_name = 'Care Plan' verbose_name_plural = 'Care Plans' ordering = ['-created_at'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['patient', 'status']), models.Index(fields=['primary_provider']), models.Index(fields=['start_date', 'end_date']), models.Index(fields=['priority']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.title}" @property def is_active(self): """ Check if care plan is currently active. """ return self.status == 'ACTIVE' @property def is_overdue(self): """ Check if care plan is overdue for review. """ if self.next_review_date: return timezone.now().date() > self.next_review_date return False class ClinicalNote(models.Model): """ Clinical note model for documenting patient care and observations. """ class NoteType(models.TextChoices): PROGRESS = 'PROGRESS', 'Progress' ADMISSION = 'ADMISSION', 'Admission Note' DISCHARGE = 'DISCHARGE', 'Discharge Note' CONSULTATION = 'CONSULTATION', 'Consultation Note' PROCEDURE = 'PROCEDURE', 'Procedure Note' OPERATIVE = 'OPERATIVE', 'Operative Note' NURSING = 'NURSING', 'Nursing Note' THERAPY = 'THERAPY', 'Therapy Note' SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work Note' PSYCHOLOGY = 'PSYCHOLOGY', 'Psychology Note' NUTRITION = 'NUTRITION', 'Nutrition Note' PHARMACY = 'PHARMACY', 'Pharmacy Note' CASE_MANAGEMENT = 'CASE_MANAGEMENT', 'Case Management Note' EDUCATION = 'EDUCATION', 'Patient Education Note' TELEPHONE = 'TELEPHONE', 'Telephone Note' ADDENDUM = 'ADDENDUM', 'Addendum' CORRECTION = 'CORRECTION', 'Correction' OTHER = 'OTHER', 'Other' class NoteStatus(models.TextChoices): DRAFT = 'DRAFT', 'Draft' IN_PROGRESS = 'IN_PROGRESS', 'In Progress' COMPLETED = 'COMPLETED', 'Completed' SIGNED = 'SIGNED', 'Signed' AMENDED = 'AMENDED', 'Amended' CORRECTED = 'CORRECTED', 'Corrected' CANCELLED = 'CANCELLED', 'Cancelled' ERROR = 'ERROR', 'Entered in Error' UNKNOWN = 'UNKNOWN', 'Unknown' class NoteSignatureMethod(models.TextChoices): ELECTRONIC = 'ELECTRONIC', 'Electronic' DIGITAL = 'DIGITAL', 'Digital Signature' BIOMETRIC = 'BIOMETRIC', 'Biometric Signature' PASSWORD = 'PASSWORD', 'Password' TOKEN = 'TOKEN', 'Token Authentication' OTHER = 'OTHER', 'Other' # Encounter relationship encounter = models.ForeignKey( Encounter, on_delete=models.CASCADE, related_name='clinical_notes', help_text='Associated encounter' ) # Patient relationship patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='clinical_notes', help_text='Patient' ) # Note Information note_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique note identifier' ) # Note Details note_type = models.CharField( max_length=30, choices=NoteType.choices, help_text='Type of clinical note' ) title = models.CharField( max_length=200, help_text='Note title' ) # Content content = models.TextField( help_text='Note content' ) # Template Information template = models.ForeignKey( 'NoteTemplate', on_delete=models.SET_NULL, null=True, blank=True, related_name='notes', help_text='Template used for this note' ) structured_data = models.JSONField( default=dict, blank=True, help_text='Structured data from template' ) # Provider Information author = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='authored_notes', help_text='Note author' ) co_signers = models.ManyToManyField( settings.AUTH_USER_MODEL, related_name='co_signed_notes', blank=True, help_text='Co-signers for this note' ) # Status and Workflow status = models.CharField( max_length=20, choices=NoteStatus.choices, default=NoteStatus.DRAFT, help_text='Note status' ) # Signatures electronically_signed = models.BooleanField( default=False, help_text='Note has been electronically signed' ) signed_datetime = models.DateTimeField( blank=True, null=True, help_text='Date and time of signature' ) signature_method = models.CharField( max_length=20, choices=NoteSignatureMethod.choices, blank=True, null=True, help_text='Method of signature' ) # Amendment Information amended_note = models.ForeignKey( 'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='amendments', help_text='Original note if this is an amendment' ) amendment_reason = models.TextField( blank=True, null=True, help_text='Reason for amendment' ) # Quality and Compliance quality_score = models.PositiveIntegerField( blank=True, null=True, validators=[MinValueValidator(0), MaxValueValidator(100)], help_text='Documentation quality score' ) compliance_flags = models.JSONField( default=list, blank=True, help_text='Compliance flags and issues' ) # Timing note_datetime = models.DateTimeField( default=timezone.now, help_text='Date and time note was written' ) # Privacy and Access confidential = models.BooleanField( default=False, help_text='Note contains confidential information' ) restricted_access = models.BooleanField( default=False, help_text='Access to note is restricted' ) access_restrictions = models.JSONField( default=list, blank=True, help_text='Specific access restrictions' ) # Related Information related_problems = models.ManyToManyField( ProblemList, related_name='related_clinical_notes', blank=True, help_text='Related problems' ) related_care_plans = models.ManyToManyField( CarePlan, related_name='clinical_notes', blank=True, help_text='Related care plans' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'emr_clinical_note' verbose_name = 'Clinical Note' verbose_name_plural = 'Clinical Notes' ordering = ['-note_datetime'] indexes = [ models.Index(fields=['patient', 'note_datetime']), models.Index(fields=['encounter']), models.Index(fields=['author']), models.Index(fields=['note_type']), models.Index(fields=['status']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.title} ({self.note_datetime.strftime('%Y-%m-%d')})" @property def is_signed(self): """ Check if note has been signed. """ return self.status == 'SIGNED' and self.electronically_signed @property def word_count(self): """ Calculate word count of note content. """ return len(self.content.split()) class NoteTemplate(models.Model): """ Note template model for standardizing clinical documentation. """ class NoteType(models.TextChoices): PROGRESS = 'PROGRESS', 'Progress Note' ADMISSION = 'ADMISSION', 'Admission Note' DISCHARGE = 'DISCHARGE', 'Discharge Note' CONSULTATION = 'CONSULTATION', 'Consultation Note' PROCEDURE = 'PROCEDURE', 'Procedure Note' OPERATIVE = 'OPERATIVE', 'Operative Note' NURSING = 'NURSING', 'Nursing Note' THERAPY = 'THERAPY', 'Therapy Note' SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work Note' PSYCHOLOGY = 'PSYCHOLOGY', 'Psychology Note' NUTRITION = 'NUTRITION', 'Nutrition Note' PHARMACY = 'PHARMACY', 'Pharmacy Note' CASE_MANAGEMENT = 'CASE_MANAGEMENT', 'Case Management Note' EDUCATION = 'EDUCATION', 'Patient Education Note' TELEPHONE = 'TELEPHONE', 'Telephone Note' OTHER = 'OTHER', 'Other' class NoteSpecialty(models.TextChoices): GENERAL_MEDICINE = 'GENERAL_MEDICINE', 'General Medicine' SURGERY = 'SURGERY', 'Surgery' CARDIOLOGY = 'CARDIOLOGY', 'Cardiology' NEUROLOGY = 'NEUROLOGY', 'Neurology' ONCOLOGY = 'ONCOLOGY', 'Oncology' PEDIATRICS = 'PEDIATRICS', 'Pediatrics' OBSTETRICS = 'OBSTETRICS', 'Obstetrics' GYNECOLOGY = 'GYNECOLOGY', 'Gynecology' ORTHOPEDICS = 'ORTHOPEDICS', 'Orthopedics' PSYCHIATRY = 'PSYCHIATRY', 'Psychiatry' EMERGENCY = 'EMERGENCY', 'Emergency Medicine' CRITICAL_CARE = 'CRITICAL_CARE', 'Critical Care' REHABILITATION = 'REHABILITATION', 'Rehabilitation' NURSING = 'NURSING', 'Nursing' THERAPY = 'THERAPY', 'Therapy' SOCIAL_WORK = 'SOCIAL_WORK', 'Social Work' NUTRITION = 'NUTRITION', 'Nutrition' PHARMACY = 'PHARMACY', 'Pharmacy' OTHER = 'OTHER', 'Other' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='note_templates', help_text='Organization tenant' ) # Template Information template_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique template identifier' ) name = models.CharField( max_length=200, help_text='Template name' ) description = models.TextField( blank=True, null=True, help_text='Template description' ) # Template Type note_type = models.CharField( max_length=30, choices=NoteType.choices, help_text='Type of note this template is for' ) specialty = models.CharField( max_length=100, choices=NoteSpecialty.choices, blank=True, null=True, help_text='Medical specialty' ) # Template Content template_content = models.TextField( help_text='Template content with placeholders' ) structured_fields = models.JSONField( default=list, help_text='Structured fields definition' ) # Usage Information is_active = models.BooleanField( default=True, help_text='Template is active and available for use' ) is_default = models.BooleanField( default=False, help_text='Default template for this note type' ) usage_count = models.PositiveIntegerField( default=0, help_text='Number of times template has been used' ) # Version Control version = models.CharField( max_length=20, default='1.0', help_text='Template version' ) previous_version = models.ForeignKey( 'self', on_delete=models.SET_NULL, null=True, blank=True, related_name='newer_versions', help_text='Previous version of this template' ) # Quality and Compliance quality_indicators = models.JSONField( default=list, help_text='Quality indicators to track' ) compliance_requirements = models.JSONField( default=list, help_text='Compliance requirements' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) created_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='created_note_templates', help_text='User who created the template' ) class Meta: db_table = 'emr_note_template' verbose_name = 'Note Template' verbose_name_plural = 'Note Templates' ordering = ['note_type', 'name'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['note_type', 'specialty']), models.Index(fields=['is_default']), ] unique_together = ['tenant', 'note_type', 'specialty', 'is_default'] def __str__(self): return f"{self.name} ({self.get_note_type_display()})" def save(self, *args, **kwargs): """ Ensure only one default template per note type and specialty. """ if self.is_default: # Remove default flag from other templates NoteTemplate.objects.filter( tenant=self.tenant, note_type=self.note_type, specialty=self.specialty, is_default=True ).exclude(id=self.id).update(is_default=False) super().save(*args, **kwargs) class Icd10(models.Model): """ ICD-10-CM tabular code entry. Handles chapters/sections/diagnoses (with parent-child hierarchy). """ code = models.CharField(max_length=10, unique=True, db_index=True) description = models.TextField(blank=True, null=True) chapter_name = models.CharField(max_length=255, blank=True, null=True) section_name = models.CharField(max_length=255, blank=True, null=True) parent = models.ForeignKey( 'self', on_delete=models.CASCADE, blank=True, null=True, related_name='children' ) is_header = models.BooleanField(default=False) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'emr_icd10' verbose_name = 'ICD-10 Code' verbose_name_plural = 'ICD-10 Codes' ordering = ['code'] def __str__(self): return f"{self.code} — {self.description[:80] if self.description else ''}"