9.7 KiB
Phase 4: Core Model Consolidation - COMPLETE ✅
Date: 2025-10-06
Status: Core models implemented, Patients app refactored
Next: Update remaining apps (emr, billing, pharmacy, etc.)
🎯 What Was Accomplished
1. Core Models Implementation ✅
Created comprehensive core models in core/models.py:
Mixins (Reusable Components)
TimeStampedModel- Adds created_at, updated_atUserStampedModel- Tracks created_by, updated_byActiveModel- Adds is_active flagTenantManager- Custom manager withfor_tenant()methodTenantScopedModel- Combines all mixins + tenant scoping
Core Primitives
-
Tenant - Multi-tenant organization support
- code, name, status
- Timestamps and active flag
-
Facility - Canonical facility/site entity
- code, name, facility_type
- Address fields (minimal)
- Tenant-scoped with full mixins
-
Patient - Minimal core patient identity
- uuid (auto-generated)
- given_name, family_name
- birth_date, sex
- primary_facility (optional FK)
- Full tenant scoping and audit trail
-
IdentifierType - Extensible identifier system
- code, name, issuer
- Supports MRN, National ID, Insurance ID, etc.
-
Identifier - Patient identifiers
- Links to Patient and IdentifierType
- value, is_primary
- period_start, period_end
- Unique constraint: (tenant, id_type, value)
-
Encounter - Clinical encounter (moved from EMR)
- patient, facility, encounter_class, status
- start_at, end_at, attending
- Full audit trail
- This is the backbone for clinical/billing/rules
-
Attachment - Generic file storage
- file, title, content_type, size_bytes
- Optional links to patient/encounter
- uploaded_by tracking
-
AuditEvent - HIPAA/GDPR compliance
- action, actor, target (GenericFK)
- summary, payload (JSON)
- occurred_at timestamp
2. Patients App Refactored ✅
Transformed patients/models.py to use core.Patient:
PatientProfile (OneToOne with core.Patient)
- Changed: Now links to
core.Patientinstead of being standalone - Keeps: All extended demographics and healthcare fields
- Primary Key: Uses patient (OneToOneField) as primary key
- Inherits: TenantScopedModel (gets all mixins automatically)
Fields Moved to Core:
- ✅ given_name, family_name → core.Patient
- ✅ birth_date, sex → core.Patient
- ✅ UUID → core.Patient
- ✅ is_active, is_deceased → core.Patient (via mixins)
Fields Kept in PatientProfile:
- Extended name (middle_name, preferred_name, suffix)
- Contact info (email, phone, mobile)
- Address (full address fields)
- Demographics (marital_status, language, etc.)
- Healthcare (PCP, allergies, medical_alerts)
- VIP flags, advance directives
- Photo, registration info
Other Models Updated
All now reference core.Patient instead of PatientProfile:
-
EmergencyContact
- FK to
core.Patient - Inherits TenantScopedModel
- FK to
-
InsuranceInfo
- FK to
core.Patient - Inherits TenantScopedModel
- FK to
-
ConsentTemplate
- Inherits TenantScopedModel
- No patient FK (it's a template)
-
ConsentForm
- FK to
core.Patient - Inherits TenantScopedModel
- FK to
-
PatientNote
- FK to
core.Patient - Inherits TenantScopedModel
- FK to
📊 Architecture Changes
Before (Old Structure)
patients/
└── PatientProfile (standalone, all fields)
├── EmergencyContact (FK to PatientProfile)
├── InsuranceInfo (FK to PatientProfile)
└── ConsentForm (FK to PatientProfile)
emr/
└── Encounter (in EMR app)
After (New Structure)
core/
├── Tenant (foundation)
├── Facility (canonical site)
├── Patient (minimal identity) ← CANONICAL
├── IdentifierType
├── Identifier (links to Patient)
├── Encounter (moved from EMR) ← CANONICAL
├── Attachment
└── AuditEvent
patients/
└── PatientProfile (OneToOne with core.Patient, extended fields)
├── EmergencyContact (FK to core.Patient)
├── InsuranceInfo (FK to core.Patient)
└── ConsentForm (FK to core.Patient)
🔑 Key Design Decisions
1. Minimal Core Patient
- Only essential identity fields in core
- Extended demographics in patients.PatientProfile
- Rationale: Keep core lean, domain-specific in apps
2. Identifier System
- Separate IdentifierType table (extensible)
- Multiple identifiers per patient
- is_primary flag for MRN
- Rationale: Supports multiple ID schemes (MRN, National ID, Insurance, etc.)
3. Encounter in Core
- Moved from EMR to core
- Rationale: Used by ALL apps (billing, pharmacy, lab, radiology, etc.)
- Backbone for clinical/billing/rules
4. TenantScopedModel Mixin
- All models inherit standard fields
- Automatic tenant scoping
- Audit trail (created_by, updated_by, timestamps)
- Rationale: DRY principle, consistent behavior
5. OneToOne for PatientProfile
- Uses patient as primary key
- Rationale: 1:1 relationship, no separate ID needed
📝 Database Schema Changes
New Tables Created
-- Core tables
core_tenant
core_facility
core_patient ← NEW CANONICAL
core_identifier_type
core_identifier
core_encounter ← MOVED FROM EMR
core_attachment
core_audit_event
-- Patients tables (refactored)
patients_patient_profile ← NOW LINKS TO core_patient
patients_emergency_contact ← NOW LINKS TO core_patient
patients_insurance_info ← NOW LINKS TO core_patient
patients_consent_template
patients_consent_form ← NOW LINKS TO core_patient
patients_patient_note ← NOW LINKS TO core_patient
Key Relationships
core.Patient (1) ←→ (1) patients.PatientProfile
core.Patient (1) ←→ (N) patients.EmergencyContact
core.Patient (1) ←→ (N) patients.InsuranceInfo
core.Patient (1) ←→ (N) patients.ConsentForm
core.Patient (1) ←→ (N) core.Identifier
core.Patient (1) ←→ (N) core.Encounter
core.Facility (1) ←→ (N) core.Encounter
core.Tenant (1) ←→ (N) ALL models
✅ Benefits Achieved
1. Canonical Patient Identity
- Single source of truth for patient data
- No duplication across apps
- Consistent patient references
2. Proper Separation of Concerns
- Core = fundamental entities
- Patients = extended demographics
- Other apps = domain-specific logic
3. Extensible Identifier System
- Support multiple ID types
- Easy to add new identifier schemes
- Proper constraints and validation
4. Encounter as Foundation
- Available to all apps
- Consistent encounter tracking
- Proper billing/clinical linkage
5. Audit Trail Everywhere
- Every model tracks who/when
- HIPAA/GDPR compliance
- Tenant scoping built-in
6. Clean Migrations Path
- Fresh start approach
- No complex data migrations
- Clear, simple structure
🚀 Next Steps
Phase 4.3: Update EMR App
- Import Encounter from core
- Update all models to use core.Patient
- Update all models to use core.Encounter
- Remove old Encounter model
Phase 4.4: Update Other Apps (9 apps)
- billing - Update patient/encounter FKs
- pharmacy - Update patient/encounter FKs
- laboratory - Update patient/encounter FKs
- radiology - Update patient/encounter FKs
- operating_theatre - Update patient/encounter/facility FKs
- inpatients - Update patient/encounter FKs
- blood_bank - Update patient FKs
- documentation - Update patient/encounter FKs (already done?)
- facility_management - Remove local Facility, use core.Facility
Phase 4.5: Fresh Start
- Delete db.sqlite3
- Delete all migration files (keep init.py)
- Run makemigrations
- Run migrate
- Create superuser
- Reseed data
📚 Import Guide for Other Apps
How to Use Core Models
# In any app's models.py
from core.models import (
Patient, # Canonical patient identity
Encounter, # Canonical encounter
Facility, # Canonical facility
Identifier, # Patient identifiers
IdentifierType, # Identifier types
Attachment, # File attachments
AuditEvent, # Audit logging
TenantScopedModel, # Base model with mixins
)
# Example: Create a model that links to core.Patient
class MyModel(TenantScopedModel):
patient = models.ForeignKey(
'core.Patient',
on_delete=models.CASCADE,
related_name='my_models'
)
encounter = models.ForeignKey(
'core.Encounter',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='my_models'
)
# ... your fields
Accessing Patient Data
# Get patient
patient = Patient.objects.get(uuid='...')
# Get patient profile (extended demographics)
profile = patient.profile # OneToOne relationship
# Get patient identifiers
mrn = patient.identifiers.filter(is_primary=True).first()
national_id = patient.identifiers.filter(id_type__code='national_id').first()
# Get patient encounters
encounters = patient.encounters.all()
# Get patient emergency contacts
contacts = patient.emergency_contacts.all()
🎯 Success Criteria
- Core models created with proper mixins
- Patient identity centralized in core
- Encounter moved to core
- Identifier system implemented
- Patients app refactored to use core.Patient
- All patients models use TenantScopedModel
- EMR app updated (next)
- All other apps updated (next)
- Fresh migrations successful (next)
- Data reseeded successfully (next)
Status: Phase 4.1 & 4.2 COMPLETE ✅
Ready for: Phase 4.3 (Update EMR app)
Estimated Time Remaining: 3-4 hours for remaining apps + migrations