# Phase 3: Move Encounter Model to Core - Implementation Plan ## Executive Summary **Objective:** Move the `Encounter` model from `emr` app to `core` app to establish it as a fundamental entity alongside `Patient` and `Tenant`. **Complexity:** HIGH - This is a breaking change requiring database migration and updates across 7+ apps. **Status:** READY FOR IMPLEMENTATION --- ## 📊 IMPACT ANALYSIS ### Apps with FK References to Encounter (7 apps): 1. **emr** (source app) - 8 internal references - VitalSigns.encounter - ProblemList.related_encounter - CarePlan (no direct FK, but related via problems) - ClinicalNote.encounter - ClinicalRecommendation.related_encounter - CriticalAlert.related_encounter 2. **billing** - 1 reference - Invoice.encounter (or similar billing model) 3. **pharmacy** - 2 references - Prescription.encounter - MedicationAdministration.encounter 4. **laboratory** - 1 reference - LabOrder.encounter (or similar) 5. **radiology** - 2 references - RadOrder.encounter - Study.encounter (or similar) 6. **operating_theatre** - 1 reference - SurgicalCase.encounter 7. **documentation** - 1 reference - Document.encounter **Total FK References:** ~16 across 7 apps --- ## 🎯 IMPLEMENTATION STRATEGY ### Option A: Incremental Migration (RECOMMENDED for Production) **Pros:** - Lower risk - Can be tested incrementally - Easier rollback - No downtime required **Cons:** - More complex migration scripts - Requires careful sequencing ### Option B: Drop and Reseed (RECOMMENDED for Development) **Pros:** - Clean slate - Simpler implementation - No migration complexity **Cons:** - Data loss - Requires complete reseed - Not suitable for production **RECOMMENDATION:** Use Option A for this implementation --- ## 📋 STEP-BY-STEP IMPLEMENTATION PLAN ### Step 1: Preparation ✅ **Duration:** 30 minutes 1. **Backup Database** ```bash python manage.py dumpdata > backup_before_encounter_migration.json ``` 2. **Create Feature Branch** ```bash git checkout -b feature/move-encounter-to-core ``` 3. **Document Current State** - Record all FK references - Note any custom managers/methods - Identify dependent migrations ### Step 2: Copy Encounter to Core 🔄 **Duration:** 1 hour 1. **Copy Model Class** - Copy `Encounter` model from `emr/models.py` to `core/models.py` - Copy `EncounterManager` as well - Keep all fields, methods, and Meta options identical 2. **Update Meta.db_table** ```python class Meta: db_table = 'core_encounter' # Changed from 'emr_encounter' # ... rest of Meta ``` 3. **Add to core/__init__.py** (if needed for imports) ### Step 3: Create Migration Strategy 🔄 **Duration:** 2 hours **Migration Sequence:** 1. **Migration 1: Create new table in core** ```python # core/migrations/XXXX_add_encounter.py operations = [ migrations.CreateModel( name='Encounter', fields=[...], # All fields ), ] ``` 2. **Migration 2: Copy data from emr to core** ```python # core/migrations/XXXX_copy_encounter_data.py def copy_encounter_data(apps, schema_editor): OldEncounter = apps.get_model('emr', 'Encounter') NewEncounter = apps.get_model('core', 'Encounter') for old in OldEncounter.objects.all(): NewEncounter.objects.create( id=old.id, tenant=old.tenant, patient=old.patient, # ... copy all fields ) operations = [ migrations.RunPython(copy_encounter_data), ] ``` 3. **Migration 3: Update FK references in each app** ```python # For each app (billing, pharmacy, etc.) operations = [ migrations.AlterField( model_name='invoice', # or whatever model name='encounter', field=models.ForeignKey('core.Encounter', ...), ), ] ``` 4. **Migration 4: Remove old Encounter from emr** ```python # emr/migrations/XXXX_remove_encounter.py operations = [ migrations.DeleteModel(name='Encounter'), ] ``` ### Step 4: Update All Model References 🔄 **Duration:** 2 hours **Files to Update:** 1. **billing/models.py** ```python # Change from: encounter = models.ForeignKey('emr.Encounter', ...) # To: encounter = models.ForeignKey('core.Encounter', ...) ``` 2. **pharmacy/models.py** (2 models) - Prescription.encounter - MedicationAdministration.encounter 3. **laboratory/models.py** - LabOrder.encounter 4. **radiology/models.py** (2 models) - RadOrder.encounter - Study.encounter 5. **operating_theatre/models.py** - SurgicalCase.encounter 6. **documentation/models.py** - Document.encounter (already references core.Encounter!) 7. **emr/models.py** (8 models) - VitalSigns.encounter - ProblemList.related_encounter - ClinicalNote.encounter - ClinicalRecommendation.related_encounter - CriticalAlert.related_encounter ### Step 5: Update Imports 🔄 **Duration:** 1 hour **Add backward compatibility import to emr/models.py:** ```python # At the top of emr/models.py from core.models import Encounter, EncounterManager # This allows existing code to still import from emr # from emr.models import Encounter # Still works! ``` **Update any direct imports in:** - Views - Serializers - Forms - Admin - Tests ### Step 6: Update Admin Registrations 🔄 **Duration:** 30 minutes 1. **Remove from emr/admin.py:** ```python # Remove or comment out: # admin.site.register(Encounter, EncounterAdmin) ``` 2. **Add to core/admin.py:** ```python from .models import Encounter @admin.register(Encounter) class EncounterAdmin(admin.ModelAdmin): # ... admin configuration ``` ### Step 7: Update Serializers 🔄 **Duration:** 1 hour **Update all DRF serializers:** 1. **emr/api/serializers.py** ```python from core.models import Encounter # Changed from emr.models ``` 2. **Check other apps** for serializers that reference Encounter ### Step 8: Run Migrations 🔄 **Duration:** 30 minutes (depends on data volume) ```bash # 1. Create migrations python manage.py makemigrations core python manage.py makemigrations billing python manage.py makemigrations pharmacy python manage.py makemigrations laboratory python manage.py makemigrations radiology python manage.py makemigrations operating_theatre python manage.py makemigrations emr # 2. Review migration files # Check that the sequence is correct # 3. Run migrations python manage.py migrate core python manage.py migrate billing python manage.py migrate pharmacy python manage.py migrate laboratory python manage.py migrate radiology python manage.py migrate operating_theatre python manage.py migrate emr # 4. Verify data integrity python manage.py shell >>> from core.models import Encounter >>> from emr.models import Encounter as EMREncounter # Should import from core >>> Encounter.objects.count() # Should match old count ``` ### Step 9: Testing 🔄 **Duration:** 2 hours **Test Checklist:** 1. **Model Tests** - [ ] Encounter model accessible from core - [ ] Encounter model accessible from emr (backward compat) - [ ] All FK relationships work - [ ] Manager methods work (EncounterManager) 2. **Admin Tests** - [ ] Encounter admin accessible - [ ] Can create/edit/delete encounters - [ ] Related objects display correctly 3. **API Tests** - [ ] Encounter endpoints work - [ ] Serializers work correctly - [ ] Nested relationships work 4. **Integration Tests** - [ ] Create encounter from appointment - [ ] Create admission with encounter - [ ] Create prescription with encounter - [ ] Create lab order with encounter - [ ] Create surgical case with encounter 5. **Data Integrity Tests** - [ ] All encounters migrated - [ ] No orphaned records - [ ] FK constraints intact ### Step 10: Documentation Updates 🔄 **Duration:** 1 hour 1. **Update README** - Note that Encounter is now in core 2. **Update API Documentation** - Update endpoint references 3. **Update Developer Guide** - Import from core.models, not emr.models 4. **Create Migration Guide** - For other developers - For deployment --- ## 🔧 MIGRATION SCRIPTS ### Script 1: Verify Current State ```python # tools/verify_encounter_state.py from django.apps import apps def verify_encounter_references(): """Verify all FK references to Encounter before migration.""" encounter_refs = [] for model in apps.get_models(): for field in model._meta.get_fields(): if hasattr(field, 'related_model'): if field.related_model and field.related_model.__name__ == 'Encounter': encounter_refs.append({ 'app': model._meta.app_label, 'model': model.__name__, 'field': field.name, 'related_model': f"{field.related_model._meta.app_label}.{field.related_model.__name__}" }) print(f"Found {len(encounter_refs)} FK references to Encounter:") for ref in encounter_refs: print(f" {ref['app']}.{ref['model']}.{ref['field']} -> {ref['related_model']}") return encounter_refs if __name__ == '__main__': verify_encounter_references() ``` ### Script 2: Data Migration Helper ```python # core/migrations/XXXX_copy_encounter_data.py from django.db import migrations def copy_encounter_data(apps, schema_editor): """Copy all encounter data from emr to core.""" OldEncounter = apps.get_model('emr', 'Encounter') NewEncounter = apps.get_model('core', 'Encounter') encounters_to_create = [] for old in OldEncounter.objects.all(): encounters_to_create.append(NewEncounter( id=old.id, encounter_id=old.encounter_id, tenant=old.tenant, patient=old.patient, provider=old.provider, encounter_type=old.encounter_type, encounter_class=old.encounter_class, start_datetime=old.start_datetime, end_datetime=old.end_datetime, status=old.status, location=old.location, room_number=old.room_number, appointment=old.appointment, admission=old.admission, chief_complaint=old.chief_complaint, reason_for_visit=old.reason_for_visit, priority=old.priority, acuity_level=old.acuity_level, documentation_complete=old.documentation_complete, signed_off=old.signed_off, signed_by=old.signed_by, signed_datetime=old.signed_datetime, billable=old.billable, billing_codes=old.billing_codes, quality_measures=old.quality_measures, created_at=old.created_at, updated_at=old.updated_at, created_by=old.created_by, )) # Bulk create for efficiency NewEncounter.objects.bulk_create(encounters_to_create, batch_size=1000) print(f"Copied {len(encounters_to_create)} encounters from emr to core") def reverse_copy(apps, schema_editor): """Reverse migration - copy back from core to emr.""" NewEncounter = apps.get_model('core', 'Encounter') NewEncounter.objects.all().delete() class Migration(migrations.Migration): dependencies = [ ('core', 'XXXX_add_encounter'), # Previous migration ('emr', 'YYYY_current_state'), # Current emr state ] operations = [ migrations.RunPython(copy_encounter_data, reverse_copy), ] ``` --- ## ⚠️ RISKS & MITIGATION ### Risk 1: Data Loss During Migration **Mitigation:** - Full database backup before starting - Test migration on copy of production data - Verify data counts before/after - Keep old table until verified ### Risk 2: Broken FK Constraints **Mitigation:** - Careful migration sequencing - Test on development environment first - Use transactions in migrations - Rollback plan ready ### Risk 3: Application Downtime **Mitigation:** - Use backward compatibility imports - Deploy during maintenance window - Blue-green deployment if possible - Quick rollback procedure ### Risk 4: Third-party Dependencies **Mitigation:** - Check for any packages that import Encounter - Update package configurations - Test all integrations --- ## 📝 ROLLBACK PLAN If migration fails: 1. **Stop Application** ```bash # Stop all services ``` 2. **Restore Database** ```bash python manage.py flush python manage.py loaddata backup_before_encounter_migration.json ``` 3. **Revert Code Changes** ```bash git checkout main git branch -D feature/move-encounter-to-core ``` 4. **Restart Application** ```bash # Restart services ``` --- ## ✅ SUCCESS CRITERIA Migration is successful when: 1. [ ] All encounters migrated to core.Encounter 2. [ ] All FK references updated to core.Encounter 3. [ ] Old emr.Encounter table removed 4. [ ] Backward compatibility imports work 5. [ ] All tests pass 6. [ ] Admin interface works 7. [ ] API endpoints work 8. [ ] No data loss 9. [ ] No orphaned records 10. [ ] Performance unchanged --- ## 📊 ESTIMATED TIMELINE | Phase | Duration | Complexity | |-------|----------|------------| | Preparation | 30 min | Low | | Copy Model to Core | 1 hour | Medium | | Create Migrations | 2 hours | High | | Update References | 2 hours | Medium | | Update Imports | 1 hour | Low | | Update Admin | 30 min | Low | | Update Serializers | 1 hour | Medium | | Run Migrations | 30 min | High | | Testing | 2 hours | High | | Documentation | 1 hour | Low | | **TOTAL** | **~11 hours** | **HIGH** | --- ## 🎯 NEXT STEPS 1. **Review this plan** with team 2. **Get approval** for breaking change 3. **Schedule maintenance window** (if needed) 4. **Create backup** of production database 5. **Execute migration** on development first 6. **Test thoroughly** 7. **Deploy to production** --- **Created:** 2025-10-06 **Status:** READY FOR IMPLEMENTATION **Priority:** HIGH (Breaking Change)