""" Pharmacy app models for hospital management system. Provides medication management, prescription processing, and pharmacy operations. """ 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, date from decimal import Decimal import json class Medication(models.Model): """ Medication model for drug database and formulary management. """ class ControlledSubstanceSchedule(models.TextChoices): CI = 'CI', 'Schedule I' CII = 'CII', 'Schedule II' CIII = 'CIII', 'Schedule III' CIV = 'CIV', 'Schedule IV' CV = 'CV', 'Schedule V' NON = 'NON', 'Non-Controlled' class DosageForm(models.TextChoices): TABLET = 'TABLET', 'Tablet' CAPSULE = 'CAPSULE', 'Capsule' LIQUID = 'LIQUID', 'Liquid' INJECTION = 'INJECTION', 'Injection' TOPICAL = 'TOPICAL', 'Topical' INHALER = 'INHALER', 'Inhaler' PATCH = 'PATCH', 'Patch' SUPPOSITORY = 'SUPPOSITORY', 'Suppository' CREAM = 'CREAM', 'Cream' OINTMENT = 'OINTMENT', 'Ointment' DROPS = 'DROPS', 'Drops' SPRAY = 'SPRAY', 'Spray' OTHER = 'OTHER', 'Other' class UnitOfMeasure(models.TextChoices): MG = 'MG', 'Milligrams' G = 'G', 'Grams' MCG = 'MCG', 'Micrograms' ML = 'ML', 'Milliliters' L = 'L', 'Liters' UNITS = 'UNITS', 'Units' IU = 'IU', 'International Units' MEQ = 'MEQ', 'Milliequivalents' PERCENT = 'PERCENT', 'Percent' OTHER = 'OTHER', 'Other' class FormularyStatus(models.TextChoices): PREFERRED = 'PREFERRED', 'Preferred' NON_PREFERRED = 'NON_PREFERRED', 'Non-Preferred' RESTRICTED = 'RESTRICTED', 'Restricted' NOT_COVERED = 'NOT_COVERED', 'Not Covered' PRIOR_AUTH = 'PRIOR_AUTH', 'Prior Authorization Required' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='medications', help_text='Organization tenant' ) # Medication Information medication_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique medication identifier' ) # Drug Identification generic_name = models.CharField( max_length=200, help_text='Generic drug name' ) brand_name = models.CharField( max_length=200, blank=True, null=True, help_text='Brand/trade name' ) # NDC and Coding ndc_number = models.CharField( max_length=20, blank=True, null=True, validators=[RegexValidator(r'^\d{4,5}-\d{3,4}-\d{1,2}$')], help_text='National Drug Code (NDC) number' ) rxcui = models.CharField( max_length=20, blank=True, null=True, help_text='RxNorm Concept Unique Identifier' ) # Drug Classification drug_class = models.CharField( max_length=100, help_text='Therapeutic drug class' ) controlled_substance_schedule = models.CharField( max_length=5, choices=ControlledSubstanceSchedule.choices, default=ControlledSubstanceSchedule.NON, help_text='DEA controlled substance schedule' ) # Formulation dosage_form = models.CharField( max_length=50, choices=DosageForm.choices, help_text='Dosage form' ) strength = models.CharField( max_length=50, help_text='Drug strength (e.g., 500mg, 10mg/ml)' ) unit_of_measure = models.CharField( max_length=20, choices=UnitOfMeasure.choices, help_text='Unit of measure' ) # Clinical Information indications = models.TextField( help_text='Clinical indications' ) contraindications = models.TextField( blank=True, null=True, help_text='Contraindications' ) side_effects = models.TextField( blank=True, null=True, help_text='Common side effects' ) warnings = models.TextField( blank=True, null=True, help_text='Warnings and precautions' ) # Dosing Information adult_dose_range = models.CharField( max_length=100, blank=True, null=True, help_text='Adult dosing range' ) pediatric_dose_range = models.CharField( max_length=100, blank=True, null=True, help_text='Pediatric dosing range' ) max_daily_dose = models.CharField( max_length=50, blank=True, null=True, help_text='Maximum daily dose' ) # Administration routes_of_administration = models.JSONField( default=list, help_text='Routes of administration' ) administration_instructions = models.TextField( blank=True, null=True, help_text='Administration instructions' ) # Storage and Handling storage_requirements = models.CharField( max_length=100, blank=True, null=True, help_text='Storage requirements' ) special_handling = models.TextField( blank=True, null=True, help_text='Special handling requirements' ) # Formulary Status formulary_status = models.CharField( max_length=20, choices=FormularyStatus.choices, default=FormularyStatus.PREFERRED, help_text='Formulary status' ) # Availability is_active = models.BooleanField( default=True, help_text='Medication is active and available' ) is_available = models.BooleanField( default=True, help_text='Currently available in inventory' ) # Pricing unit_cost = models.DecimalField( max_digits=10, decimal_places=2, blank=True, null=True, help_text='Cost per unit' ) awp = models.DecimalField( max_digits=10, decimal_places=2, blank=True, null=True, help_text='Average Wholesale Price' ) # Manufacturer Information manufacturer = models.CharField( max_length=100, blank=True, null=True, help_text='Manufacturer name' ) manufacturer_ndc = models.CharField( max_length=20, blank=True, null=True, help_text='Manufacturer NDC' ) # 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_medications', help_text='User who created the medication record' ) class Meta: db_table = 'pharmacy_medication' verbose_name = 'Medication' verbose_name_plural = 'Medications' ordering = ['generic_name'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['generic_name']), models.Index(fields=['brand_name']), models.Index(fields=['ndc_number']), models.Index(fields=['drug_class']), models.Index(fields=['controlled_substance_schedule']), ] unique_together = ['tenant', 'ndc_number'] def __str__(self): if self.brand_name: return f"{self.generic_name} ({self.brand_name}) {self.strength}" return f"{self.generic_name} {self.strength}" @property def is_controlled_substance(self): """ Check if medication is a controlled substance. """ return self.controlled_substance_schedule != 'NON' @property def display_name(self): """ Get display name for medication. """ return str(self) class Prescription(models.Model): """ Prescription model for electronic prescription management. """ class QuantityUnit(models.TextChoices): TABLETS = 'TABLETS', 'Tablets' CAPSULES = 'CAPSULES', 'Capsules' ML = 'ML', 'Milliliters' GRAMS = 'GRAMS', 'Grams' UNITS = 'UNITS', 'Units' PATCHES = 'PATCHES', 'Patches' INHALERS = 'INHALERS', 'Inhalers' BOTTLES = 'BOTTLES', 'Bottles' TUBES = 'TUBES', 'Tubes' VIALS = 'VIALS', 'Vials' OTHER = 'OTHER', 'Other' class PrescriptionStatus(models.TextChoices): PENDING = 'PENDING', 'Pending' ACTIVE = 'ACTIVE', 'Active' DISPENSED = 'DISPENSED', 'Dispensed' PARTIALLY_DISPENSED = 'PARTIALLY_DISPENSED', 'Partially Dispensed' COMPLETED = 'COMPLETED', 'Completed' CANCELLED = 'CANCELLED', 'Cancelled' EXPIRED = 'EXPIRED', 'Expired' ON_HOLD = 'ON_HOLD', 'On Hold' TRANSFERRED = 'TRANSFERRED', 'Transferred' DRAFT = 'DRAFT', 'Draft' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='prescriptions', help_text='Organization tenant' ) # Prescription Information prescription_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique prescription identifier' ) prescription_number = models.CharField( max_length=20, unique=True, help_text='Prescription number' ) # Patient and Provider patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='prescriptions', help_text='Patient' ) prescriber = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='prescribed_medications', help_text='Prescribing provider' ) # Medication medication = models.ForeignKey( Medication, on_delete=models.CASCADE, related_name='prescriptions', help_text='Prescribed medication' ) # Prescription Details quantity_prescribed = models.PositiveIntegerField( help_text='Quantity prescribed' ) quantity_unit = models.CharField( max_length=20, choices=QuantityUnit.choices, help_text='Unit of quantity' ) # Dosing Instructions dosage_instructions = models.TextField( help_text='Dosing instructions (SIG)' ) frequency = models.CharField( max_length=50, help_text='Frequency of administration' ) duration = models.CharField( max_length=50, blank=True, null=True, help_text='Duration of therapy' ) # Refills refills_authorized = models.PositiveIntegerField( default=0, help_text='Number of refills authorized' ) refills_remaining = models.PositiveIntegerField( default=0, help_text='Number of refills remaining' ) # Dates date_prescribed = models.DateTimeField( default=timezone.now, help_text='Date and time prescribed' ) date_written = models.DateField( help_text='Date written on prescription' ) expiration_date = models.DateField( blank=True, null=True, help_text='Prescription expiration date' ) # Status status = models.CharField( max_length=20, choices=PrescriptionStatus.choices, default=PrescriptionStatus.PENDING, help_text='Prescription status' ) # Clinical Information indication = models.CharField( max_length=200, blank=True, null=True, help_text='Indication for prescription' ) diagnosis_code = models.CharField( max_length=20, blank=True, null=True, help_text='ICD-10 diagnosis code' ) # Special Instructions pharmacy_notes = models.TextField( blank=True, null=True, help_text='Notes for pharmacy' ) patient_instructions = models.TextField( blank=True, null=True, help_text='Patient instructions' ) # Prior Authorization prior_authorization_required = models.BooleanField( default=False, help_text='Prior authorization required' ) prior_authorization_number = models.CharField( max_length=50, blank=True, null=True, help_text='Prior authorization number' ) prior_authorization_expiry = models.DateField( blank=True, null=True, help_text='Prior authorization expiry date' ) # Generic Substitution generic_substitution_allowed = models.BooleanField( default=True, help_text='Generic substitution allowed' ) dispense_as_written = models.BooleanField( default=False, help_text='Dispense as written (DAW)' ) # Electronic Prescription electronic_prescription = models.BooleanField( default=True, help_text='Electronic prescription' ) e_prescription_id = models.CharField( max_length=50, blank=True, null=True, help_text='Electronic prescription ID' ) # Related Information encounter = models.ForeignKey( 'emr.Encounter', on_delete=models.SET_NULL, null=True, blank=True, related_name='prescriptions', help_text='Related encounter' ) # Verification verified = models.BooleanField( default=False, help_text='Prescription has been verified' ) verified_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='verified_prescriptions', help_text='Pharmacist who verified prescription' ) verified_datetime = models.DateTimeField( blank=True, null=True, help_text='Date and time of verification' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'pharmacy_prescription' verbose_name = 'Prescription' verbose_name_plural = 'Prescriptions' ordering = ['-date_prescribed'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['patient', 'status']), models.Index(fields=['prescriber']), models.Index(fields=['medication']), models.Index(fields=['date_prescribed']), models.Index(fields=['prescription_number']), ] def __str__(self): return f"{self.prescription_number} - {self.patient.get_full_name()} - {self.medication.display_name}" def save(self, *args, **kwargs): """ Generate prescription number if not provided. """ if not self.prescription_number: # Generate prescription number (simple implementation) last_rx = Prescription.objects.filter(tenant=self.tenant).order_by('-id').first() if last_rx: last_number = int(last_rx.prescription_number.split('-')[-1]) self.prescription_number = f"RX-{self.tenant.id}-{last_number + 1:06d}" else: self.prescription_number = f"RX-{self.tenant.id}-000001" # Set expiration date if not provided if not self.expiration_date: if self.medication.is_controlled_substance: # Controlled substances expire in 6 months self.expiration_date = self.date_written + timedelta(days=180) else: # Non-controlled substances expire in 1 year self.expiration_date = self.date_written + timedelta(days=365) # Set refills remaining if self.refills_remaining == 0 and self.refills_authorized > 0: self.refills_remaining = self.refills_authorized super().save(*args, **kwargs) @property def is_expired(self): """ Check if prescription is expired. """ return timezone.now().date() > self.expiration_date @property def days_until_expiry(self): """ Calculate days until expiry. """ if self.expiration_date: return (self.expiration_date - timezone.now().date()).days return None class InventoryItem(models.Model): """ Inventory item model for pharmacy stock management. """ class InventoryStatus(models.TextChoices): ACTIVE = 'ACTIVE', 'Active' QUARANTINE = 'QUARANTINE', 'Quarantine' EXPIRED = 'EXPIRED', 'Expired' RECALLED = 'RECALLED', 'Recalled' DAMAGED = 'DAMAGED', 'Damaged' RETURNED = 'RETURNED', 'Returned' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='pharmacy_inventory', help_text='Organization tenant' ) # Medication medication = models.ForeignKey( Medication, on_delete=models.CASCADE, related_name='inventory_items', help_text='Medication' ) # Inventory Information inventory_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique inventory identifier' ) # Lot Information lot_number = models.CharField( max_length=50, help_text='Lot/batch number' ) expiration_date = models.DateField( help_text='Expiration date' ) # Quantity quantity_on_hand = models.PositiveIntegerField( default=0, help_text='Current quantity on hand' ) quantity_allocated = models.PositiveIntegerField( default=0, help_text='Quantity allocated for pending orders' ) quantity_available = models.PositiveIntegerField( default=0, help_text='Available quantity (on hand - allocated)' ) # Reorder Information reorder_point = models.PositiveIntegerField( default=10, help_text='Reorder point' ) reorder_quantity = models.PositiveIntegerField( default=100, help_text='Reorder quantity' ) # Location storage_location = models.CharField( max_length=50, help_text='Storage location (e.g., Shelf A1, Refrigerator 2)' ) bin_location = models.CharField( max_length=20, blank=True, null=True, help_text='Specific bin location' ) # Cost Information unit_cost = models.DecimalField( max_digits=10, decimal_places=2, help_text='Cost per unit' ) total_cost = models.DecimalField( max_digits=12, decimal_places=2, help_text='Total cost of inventory' ) # Supplier Information supplier = models.CharField( max_length=100, help_text='Supplier name' ) purchase_order_number = models.CharField( max_length=50, blank=True, null=True, help_text='Purchase order number' ) # Dates received_date = models.DateField( help_text='Date received' ) last_counted = models.DateField( blank=True, null=True, help_text='Last physical count date' ) # Status status = models.CharField( max_length=20, choices=InventoryStatus.choices, default=InventoryStatus.ACTIVE, help_text='Inventory status' ) # Quality Control quality_checked = models.BooleanField( default=False, help_text='Quality check completed' ) quality_check_date = models.DateField( blank=True, null=True, help_text='Quality check date' ) quality_notes = models.TextField( blank=True, null=True, help_text='Quality control notes' ) # 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_pharmacy_inventory_items', help_text='User who created the inventory item' ) class Meta: db_table = 'pharmacy_inventory_item' verbose_name = 'Inventory Item' verbose_name_plural = 'Inventory Items' ordering = ['medication__generic_name', 'expiration_date'] indexes = [ models.Index(fields=['tenant', 'status']), models.Index(fields=['medication']), models.Index(fields=['lot_number']), models.Index(fields=['expiration_date']), models.Index(fields=['storage_location']), ] unique_together = ['tenant', 'medication', 'lot_number'] def __str__(self): return f"{self.medication.display_name} - Lot: {self.lot_number}" def save(self, *args, **kwargs): """ Calculate available quantity and total cost. """ self.quantity_available = max(0, self.quantity_on_hand - self.quantity_allocated) self.total_cost = self.quantity_on_hand * self.unit_cost super().save(*args, **kwargs) @property def is_expired(self): """ Check if inventory item is expired. """ return timezone.now().date() > self.expiration_date @property def days_until_expiry(self): """ Calculate days until expiry. """ return (self.expiration_date - timezone.now().date()).days @property def needs_reorder(self): """ Check if item needs reordering. """ return self.quantity_available <= self.reorder_point class DispenseRecord(models.Model): """ Dispense record model for tracking medication dispensing. """ class DispenseStatus(models.TextChoices): DISPENSED = 'DISPENSED', 'Dispensed' PICKED_UP = 'PICKED_UP', 'Picked Up' RETURNED = 'RETURNED', 'Returned' CANCELLED = 'CANCELLED', 'Cancelled' # Prescription relationship prescription = models.ForeignKey( Prescription, on_delete=models.CASCADE, related_name='dispense_records', help_text='Related prescription' ) # Inventory item inventory_item = models.ForeignKey( InventoryItem, on_delete=models.CASCADE, related_name='dispense_records', help_text='Inventory item dispensed' ) # Dispense Information dispense_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique dispense identifier' ) # Quantity quantity_dispensed = models.PositiveIntegerField( help_text='Quantity dispensed' ) quantity_remaining = models.PositiveIntegerField( help_text='Quantity remaining on prescription' ) # Dates date_dispensed = models.DateTimeField( default=timezone.now, help_text='Date and time dispensed' ) # Staff dispensed_by = models.ForeignKey( 'hr.Employee', on_delete=models.CASCADE, related_name='dispensed_medications', help_text='Pharmacist who dispensed medication' ) verified_by = models.ForeignKey( 'hr.Employee', on_delete=models.SET_NULL, null=True, blank=True, related_name='verified_dispenses', help_text='Pharmacist who verified dispense' ) # Pricing unit_price = models.DecimalField( max_digits=10, decimal_places=2, help_text='Price per unit' ) total_price = models.DecimalField( max_digits=10, decimal_places=2, help_text='Total price' ) copay_amount = models.DecimalField( max_digits=8, decimal_places=2, default=Decimal('0.00'), help_text='Patient copay amount' ) insurance_amount = models.DecimalField( max_digits=10, decimal_places=2, default=Decimal('0.00'), help_text='Insurance payment amount' ) # Patient Information patient_counseled = models.BooleanField( default=False, help_text='Patient was counseled' ) counseling_notes = models.TextField( blank=True, null=True, help_text='Counseling notes' ) # Refill Information is_refill = models.BooleanField( default=False, help_text='This is a refill' ) refill_number = models.PositiveIntegerField( default=0, help_text='Refill number' ) # Status status = models.CharField( max_length=20, choices=DispenseStatus.choices, default=DispenseStatus.DISPENSED, help_text='Dispense status' ) # Pickup Information picked_up_by = models.CharField( max_length=100, blank=True, null=True, help_text='Person who picked up medication' ) pickup_datetime = models.DateTimeField( blank=True, null=True, help_text='Date and time of pickup' ) identification_verified = models.BooleanField( default=False, help_text='Identification was verified' ) # Quality Control quality_check_performed = models.BooleanField( default=False, help_text='Quality check performed' ) quality_notes = models.TextField( blank=True, null=True, help_text='Quality control notes' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'pharmacy_dispense_record' verbose_name = 'Dispense Record' verbose_name_plural = 'Dispense Records' ordering = ['-date_dispensed'] indexes = [ models.Index(fields=['prescription']), models.Index(fields=['inventory_item']), models.Index(fields=['date_dispensed']), models.Index(fields=['dispensed_by']), models.Index(fields=['status']), ] def __str__(self): return f"{self.prescription.prescription_number} - {self.quantity_dispensed} units" def save(self, *args, **kwargs): """ Calculate total price and update prescription status. """ self.total_price = self.quantity_dispensed * self.unit_price # Update prescription quantity remaining if self.prescription: total_dispensed = self.prescription.dispense_records.aggregate( total=models.Sum('quantity_dispensed') )['total'] or 0 if not self.pk: # New dispense record total_dispensed += self.quantity_dispensed self.quantity_remaining = max(0, self.prescription.quantity_prescribed - total_dispensed) # Update prescription status if self.quantity_remaining == 0: self.prescription.status = 'COMPLETED' elif total_dispensed > 0: self.prescription.status = 'PARTIALLY_DISPENSED' else: self.prescription.status = 'DISPENSED' self.prescription.save() super().save(*args, **kwargs) @property def patient(self): """ Get patient from prescription. """ return self.prescription.patient @property def medication(self): """ Get medication from inventory item. """ return self.inventory_item.medication class MedicationAdministration(models.Model): """ Medication Administration Record (MAR) for inpatient medication tracking. """ class RouteGiven(models.TextChoices): PO = 'PO', 'Oral' IV = 'IV', 'Intravenous' IM = 'IM', 'Intramuscular' SC = 'SC', 'Subcutaneous' SL = 'SL', 'Sublingual' TOP = 'TOP', 'Topical' INH = 'INH', 'Inhalation' PR = 'PR', 'Rectal' PV = 'PV', 'Vaginal' NASAL = 'NASAL', 'Nasal' OPTH = 'OPTH', 'Ophthalmic' OTIC = 'OTIC', 'Otic' OTHER = 'OTHER', 'Other' class MedicationStatus(models.TextChoices): SCHEDULED = 'SCHEDULED', 'Scheduled' GIVEN = 'GIVEN', 'Given' NOT_GIVEN = 'NOT_GIVEN', 'Not Given' HELD = 'HELD', 'Held' REFUSED = 'REFUSED', 'Refused' OMITTED = 'OMITTED', 'Omitted' class ReasonNotGiven(models.TextChoices): PATIENT_REFUSED = 'PATIENT_REFUSED', 'Patient Refused' PATIENT_UNAVAILABLE = 'PATIENT_UNAVAILABLE', 'Patient Unavailable' MEDICATION_UNAVAILABLE = 'MEDICATION_UNAVAILABLE', 'Medication Unavailable' HELD_BY_PROVIDER = 'HELD_BY_PROVIDER', 'Held by Provider' PATIENT_NPO = 'PATIENT_NPO', 'Patient NPO' PATIENT_ASLEEP = 'PATIENT_ASLEEP', 'Patient Asleep' ADVERSE_REACTION = 'ADVERSE_REACTION', 'Adverse Reaction' OTHER = 'OTHER', 'Other' # Prescription relationship prescription = models.ForeignKey( Prescription, on_delete=models.CASCADE, related_name='administration_records', help_text='Related prescription' ) # Patient and Encounter patient = models.ForeignKey( 'patients.PatientProfile', on_delete=models.CASCADE, related_name='medication_administrations', help_text='Patient' ) encounter = models.ForeignKey( 'emr.Encounter', on_delete=models.SET_NULL, null=True, blank=True, related_name='medication_administrations', help_text='Related encounter' ) # Administration Information administration_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique administration identifier' ) # Scheduled vs Actual scheduled_datetime = models.DateTimeField( help_text='Scheduled administration time' ) actual_datetime = models.DateTimeField( blank=True, null=True, help_text='Actual administration time' ) # Dosage dose_given = models.CharField( max_length=50, help_text='Dose given' ) route_given = models.CharField( max_length=30, choices=RouteGiven.choices, help_text='Route of administration' ) # Status status = models.CharField( max_length=20, choices=MedicationStatus.choices, default=MedicationStatus.SCHEDULED, help_text='Administration status' ) # Staff administered_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='administered_medications', help_text='Nurse who administered medication' ) witnessed_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='witnessed_administrations', help_text='Witness (for controlled substances)' ) # Reason for Not Given reason_not_given = models.CharField( max_length=100, blank=True, null=True, choices=ReasonNotGiven.choices, help_text='Reason medication was not given' ) reason_notes = models.TextField( blank=True, null=True, help_text='Additional notes about reason' ) # Patient Response patient_response = models.TextField( blank=True, null=True, help_text='Patient response to medication' ) side_effects_observed = models.TextField( blank=True, null=True, help_text='Side effects observed' ) # Site Information (for injections) injection_site = models.CharField( max_length=50, blank=True, null=True, help_text='Injection site' ) site_condition = models.CharField( max_length=100, blank=True, null=True, help_text='Condition of injection site' ) # Verification double_checked = models.BooleanField( default=False, help_text='Medication was double-checked' ) double_checked_by = models.ForeignKey( settings.AUTH_USER_MODEL, on_delete=models.SET_NULL, null=True, blank=True, related_name='double_checked_administrations', help_text='Second nurse who verified medication' ) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: db_table = 'pharmacy_medication_administration' verbose_name = 'Medication Administration' verbose_name_plural = 'Medication Administrations' ordering = ['-scheduled_datetime'] indexes = [ models.Index(fields=['patient', 'scheduled_datetime']), models.Index(fields=['prescription']), models.Index(fields=['encounter']), models.Index(fields=['status']), models.Index(fields=['administered_by']), ] def __str__(self): return f"{self.patient.get_full_name()} - {self.prescription.medication.display_name} - {self.scheduled_datetime}" @property def is_overdue(self): """ Check if administration is overdue. """ if self.status == 'SCHEDULED': return timezone.now() > self.scheduled_datetime + timedelta(minutes=30) return False @property def medication(self): """ Get medication from prescription. """ return self.prescription.medication class DrugInteraction(models.Model): """ Drug interaction model for clinical decision support. """ class Severity(models.TextChoices): MINOR = 'MINOR', 'Minor' MODERATE = 'MODERATE', 'Moderate' MAJOR = 'MAJOR', 'Major' CONTRAINDICATED = 'CONTRAINDICATED', 'Contraindicated' class InteractionType(models.TextChoices): PHARMACOKINETIC = 'PHARMACOKINETIC', 'Pharmacokinetic' PHARMACODYNAMIC = 'PHARMACODYNAMIC', 'Pharmacodynamic' ADDITIVE = 'ADDITIVE', 'Additive' SYNERGISTIC = 'SYNERGISTIC', 'Synergistic' ANTAGONISTIC = 'ANTAGONISTIC', 'Antagonistic' OTHER = 'OTHER', 'Other' class EvidenceLevel(models.TextChoices): ESTABLISHED = 'ESTABLISHED', 'Established' PROBABLE = 'PROBABLE', 'Probable' SUSPECTED = 'SUSPECTED', 'Suspected' POSSIBLE = 'POSSIBLE', 'Possible' UNLIKELY = 'UNLIKELY', 'Unlikely' # Tenant relationship tenant = models.ForeignKey( 'core.Tenant', on_delete=models.CASCADE, related_name='drug_interactions', help_text='Organization tenant' ) # Medications medication_1 = models.ForeignKey( Medication, on_delete=models.CASCADE, related_name='interactions_as_drug1', help_text='First medication' ) medication_2 = models.ForeignKey( Medication, on_delete=models.CASCADE, related_name='interactions_as_drug2', help_text='Second medication' ) # Interaction Information interaction_id = models.UUIDField( default=uuid.uuid4, unique=True, editable=False, help_text='Unique interaction identifier' ) # Severity severity = models.CharField( max_length=20, choices=Severity.choices, help_text='Interaction severity' ) # Interaction Details interaction_type = models.CharField( max_length=30, choices=InteractionType.choices, help_text='Type of interaction' ) mechanism = models.TextField( help_text='Mechanism of interaction' ) clinical_effect = models.TextField( help_text='Clinical effect of interaction' ) # Management management_recommendations = models.TextField( help_text='Management recommendations' ) monitoring_parameters = models.TextField( blank=True, null=True, help_text='Parameters to monitor' ) # Evidence evidence_level = models.CharField( max_length=20, choices=EvidenceLevel.choices, help_text='Level of evidence' ) # References references = models.TextField( blank=True, null=True, help_text='Literature references' ) # Status is_active = models.BooleanField( default=True, help_text='Interaction is active' ) # 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_drug_interactions', help_text='User who created the interaction record' ) class Meta: db_table = 'pharmacy_drug_interaction' verbose_name = 'Drug Interaction' verbose_name_plural = 'Drug Interactions' ordering = ['-severity', 'medication_1__generic_name'] indexes = [ models.Index(fields=['tenant', 'is_active']), models.Index(fields=['medication_1']), models.Index(fields=['medication_2']), models.Index(fields=['severity']), ] unique_together = ['tenant', 'medication_1', 'medication_2'] def __str__(self): return f"{self.medication_1.display_name} + {self.medication_2.display_name} ({self.severity})" @property def is_major_interaction(self): """ Check if interaction is major or contraindicated. """ return self.severity in ['MAJOR', 'CONTRAINDICATED']