# 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_at - `UserStampedModel` - Tracks created_by, updated_by - `ActiveModel` - Adds is_active flag - `TenantManager` - Custom manager with `for_tenant()` method - `TenantScopedModel` - Combines all mixins + tenant scoping #### **Core Primitives** 1. **Tenant** - Multi-tenant organization support - code, name, status - Timestamps and active flag 2. **Facility** - Canonical facility/site entity - code, name, facility_type - Address fields (minimal) - Tenant-scoped with full mixins 3. **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 4. **IdentifierType** - Extensible identifier system - code, name, issuer - Supports MRN, National ID, Insurance ID, etc. 5. **Identifier** - Patient identifiers - Links to Patient and IdentifierType - value, is_primary - period_start, period_end - Unique constraint: (tenant, id_type, value) 6. **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** 7. **Attachment** - Generic file storage - file, title, content_type, size_bytes - Optional links to patient/encounter - uploaded_by tracking 8. **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.Patient` instead 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`: 1. **EmergencyContact** - FK to `core.Patient` - Inherits TenantScopedModel 2. **InsuranceInfo** - FK to `core.Patient` - Inherits TenantScopedModel 3. **ConsentTemplate** - Inherits TenantScopedModel - No patient FK (it's a template) 4. **ConsentForm** - FK to `core.Patient` - Inherits TenantScopedModel 5. **PatientNote** - FK to `core.Patient` - Inherits TenantScopedModel --- ## 📊 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 ```sql -- 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 ```python # 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 ```python # 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 - [x] Core models created with proper mixins - [x] Patient identity centralized in core - [x] Encounter moved to core - [x] Identifier system implemented - [x] Patients app refactored to use core.Patient - [x] 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