Marwan Alwali be70e47e22 update
2025-08-30 09:45:26 +03:00

1334 lines
37 KiB
Python

"""
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.
"""
CONTROLLED_SUBSTANCE_SCHEDULE_CHOICES = [
('CI', 'Schedule I'),
('CII', 'Schedule II'),
('CIII', 'Schedule III'),
('CIV', 'Schedule IV'),
('CV', 'Schedule V'),
('NON', 'Non-Controlled'),
]
DOSAGE_FORM_CHOICES = [
('TABLET', 'Tablet'),
('CAPSULE', 'Capsule'),
('LIQUID', 'Liquid'),
('INJECTION', 'Injection'),
('TOPICAL', 'Topical'),
('INHALER', 'Inhaler'),
('PATCH', 'Patch'),
('SUPPOSITORY', 'Suppository'),
('CREAM', 'Cream'),
('OINTMENT', 'Ointment'),
('DROPS', 'Drops'),
('SPRAY', 'Spray'),
('OTHER', 'Other'),
]
UNIT_OF_MEASURE_CHOICES = [
('MG', 'Milligrams'),
('G', 'Grams'),
('MCG', 'Micrograms'),
('ML', 'Milliliters'),
('L', 'Liters'),
('UNITS', 'Units'),
('IU', 'International Units'),
('MEQ', 'Milliequivalents'),
('PERCENT', 'Percent'),
('OTHER', 'Other'),
]
FORMULARY_STATUS_CHOICES = [
('PREFERRED', 'Preferred'),
('NON_PREFERRED', 'Non-Preferred'),
('RESTRICTED', 'Restricted'),
('NOT_COVERED', 'Not Covered'),
('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=CONTROLLED_SUBSTANCE_SCHEDULE_CHOICES,
default='NON',
help_text='DEA controlled substance schedule'
)
# Formulation
dosage_form = models.CharField(
max_length=50,
choices=DOSAGE_FORM_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=UNIT_OF_MEASURE_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=FORMULARY_STATUS_CHOICES,
default='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.
"""
QUANTITY_UNIT_CHOICES = [
('TABLETS', 'Tablets'),
('CAPSULES', 'Capsules'),
('ML', 'Milliliters'),
('GRAMS', 'Grams'),
('UNITS', 'Units'),
('PATCHES', 'Patches'),
('INHALERS', 'Inhalers'),
('BOTTLES', 'Bottles'),
('TUBES', 'Tubes'),
('VIALS', 'Vials'),
('OTHER', 'Other'),
]
STATUS_CHOICES = [
('PENDING', 'Pending'),
('ACTIVE', 'Active'),
('DISPENSED', 'Dispensed'),
('PARTIALLY_DISPENSED', 'Partially Dispensed'),
('COMPLETED', 'Completed'),
('CANCELLED', 'Cancelled'),
('EXPIRED', 'Expired'),
('ON_HOLD', 'On Hold'),
('TRANSFERRED', 'Transferred'),
('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=QUANTITY_UNIT_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=STATUS_CHOICES,
default='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.
"""
STATUS_CHOICES = [
('ACTIVE', 'Active'),
('QUARANTINE', 'Quarantine'),
('EXPIRED', 'Expired'),
('RECALLED', 'Recalled'),
('DAMAGED', 'Damaged'),
('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=STATUS_CHOICES,
default='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.
"""
# 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(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='dispensed_medications',
help_text='Pharmacist who dispensed medication'
)
verified_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
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=[
('DISPENSED', 'Dispensed'),
('PICKED_UP', 'Picked Up'),
('RETURNED', 'Returned'),
('CANCELLED', 'Cancelled'),
],
default='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.
"""
# 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=[
('PO', 'Oral'),
('IV', 'Intravenous'),
('IM', 'Intramuscular'),
('SC', 'Subcutaneous'),
('SL', 'Sublingual'),
('TOP', 'Topical'),
('INH', 'Inhalation'),
('PR', 'Rectal'),
('PV', 'Vaginal'),
('NASAL', 'Nasal'),
('OPTH', 'Ophthalmic'),
('OTIC', 'Otic'),
('OTHER', 'Other'),
],
help_text='Route of administration'
)
# Status
status = models.CharField(
max_length=20,
choices=[
('SCHEDULED', 'Scheduled'),
('GIVEN', 'Given'),
('NOT_GIVEN', 'Not Given'),
('HELD', 'Held'),
('REFUSED', 'Refused'),
('OMITTED', 'Omitted'),
],
default='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=[
('PATIENT_REFUSED', 'Patient Refused'),
('PATIENT_UNAVAILABLE', 'Patient Unavailable'),
('MEDICATION_UNAVAILABLE', 'Medication Unavailable'),
('HELD_BY_PROVIDER', 'Held by Provider'),
('PATIENT_NPO', 'Patient NPO'),
('PATIENT_ASLEEP', 'Patient Asleep'),
('ADVERSE_REACTION', 'Adverse Reaction'),
('OTHER', 'Other'),
],
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.
"""
# 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=[
('MINOR', 'Minor'),
('MODERATE', 'Moderate'),
('MAJOR', 'Major'),
('CONTRAINDICATED', 'Contraindicated'),
],
help_text='Interaction severity'
)
# Interaction Details
interaction_type = models.CharField(
max_length=30,
choices=[
('PHARMACOKINETIC', 'Pharmacokinetic'),
('PHARMACODYNAMIC', 'Pharmacodynamic'),
('ADDITIVE', 'Additive'),
('SYNERGISTIC', 'Synergistic'),
('ANTAGONISTIC', 'Antagonistic'),
('OTHER', 'Other'),
],
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=[
('ESTABLISHED', 'Established'),
('PROBABLE', 'Probable'),
('SUSPECTED', 'Suspected'),
('POSSIBLE', 'Possible'),
('UNLIKELY', 'Unlikely'),
],
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']