14 KiB
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):
-
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
-
billing - 1 reference
- Invoice.encounter (or similar billing model)
-
pharmacy - 2 references
- Prescription.encounter
- MedicationAdministration.encounter
-
laboratory - 1 reference
- LabOrder.encounter (or similar)
-
radiology - 2 references
- RadOrder.encounter
- Study.encounter (or similar)
-
operating_theatre - 1 reference
- SurgicalCase.encounter
-
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
-
Backup Database
python manage.py dumpdata > backup_before_encounter_migration.json -
Create Feature Branch
git checkout -b feature/move-encounter-to-core -
Document Current State
- Record all FK references
- Note any custom managers/methods
- Identify dependent migrations
Step 2: Copy Encounter to Core 🔄
Duration: 1 hour
-
Copy Model Class
- Copy
Encountermodel fromemr/models.pytocore/models.py - Copy
EncounterManageras well - Keep all fields, methods, and Meta options identical
- Copy
-
Update Meta.db_table
class Meta: db_table = 'core_encounter' # Changed from 'emr_encounter' # ... rest of Meta -
Add to core/init.py (if needed for imports)
Step 3: Create Migration Strategy 🔄
Duration: 2 hours
Migration Sequence:
-
Migration 1: Create new table in core
# core/migrations/XXXX_add_encounter.py operations = [ migrations.CreateModel( name='Encounter', fields=[...], # All fields ), ] -
Migration 2: Copy data from emr to core
# 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), ] -
Migration 3: Update FK references in each app
# For each app (billing, pharmacy, etc.) operations = [ migrations.AlterField( model_name='invoice', # or whatever model name='encounter', field=models.ForeignKey('core.Encounter', ...), ), ] -
Migration 4: Remove old Encounter from emr
# emr/migrations/XXXX_remove_encounter.py operations = [ migrations.DeleteModel(name='Encounter'), ]
Step 4: Update All Model References 🔄
Duration: 2 hours
Files to Update:
-
billing/models.py
# Change from: encounter = models.ForeignKey('emr.Encounter', ...) # To: encounter = models.ForeignKey('core.Encounter', ...) -
pharmacy/models.py (2 models)
- Prescription.encounter
- MedicationAdministration.encounter
-
laboratory/models.py
- LabOrder.encounter
-
radiology/models.py (2 models)
- RadOrder.encounter
- Study.encounter
-
operating_theatre/models.py
- SurgicalCase.encounter
-
documentation/models.py
- Document.encounter (already references core.Encounter!)
-
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:
# 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
-
Remove from emr/admin.py:
# Remove or comment out: # admin.site.register(Encounter, EncounterAdmin) -
Add to core/admin.py:
from .models import Encounter @admin.register(Encounter) class EncounterAdmin(admin.ModelAdmin): # ... admin configuration
Step 7: Update Serializers 🔄
Duration: 1 hour
Update all DRF serializers:
-
emr/api/serializers.py
from core.models import Encounter # Changed from emr.models -
Check other apps for serializers that reference Encounter
Step 8: Run Migrations 🔄
Duration: 30 minutes (depends on data volume)
# 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:
-
Model Tests
- Encounter model accessible from core
- Encounter model accessible from emr (backward compat)
- All FK relationships work
- Manager methods work (EncounterManager)
-
Admin Tests
- Encounter admin accessible
- Can create/edit/delete encounters
- Related objects display correctly
-
API Tests
- Encounter endpoints work
- Serializers work correctly
- Nested relationships work
-
Integration Tests
- Create encounter from appointment
- Create admission with encounter
- Create prescription with encounter
- Create lab order with encounter
- Create surgical case with encounter
-
Data Integrity Tests
- All encounters migrated
- No orphaned records
- FK constraints intact
Step 10: Documentation Updates 🔄
Duration: 1 hour
-
Update README
- Note that Encounter is now in core
-
Update API Documentation
- Update endpoint references
-
Update Developer Guide
- Import from core.models, not emr.models
-
Create Migration Guide
- For other developers
- For deployment
🔧 MIGRATION SCRIPTS
Script 1: Verify Current State
# 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
# 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:
-
Stop Application
# Stop all services -
Restore Database
python manage.py flush python manage.py loaddata backup_before_encounter_migration.json -
Revert Code Changes
git checkout main git branch -D feature/move-encounter-to-core -
Restart Application
# Restart services
✅ SUCCESS CRITERIA
Migration is successful when:
- All encounters migrated to core.Encounter
- All FK references updated to core.Encounter
- Old emr.Encounter table removed
- Backward compatibility imports work
- All tests pass
- Admin interface works
- API endpoints work
- No data loss
- No orphaned records
- 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
- Review this plan with team
- Get approval for breaking change
- Schedule maintenance window (if needed)
- Create backup of production database
- Execute migration on development first
- Test thoroughly
- Deploy to production
Created: 2025-10-06
Status: READY FOR IMPLEMENTATION
Priority: HIGH (Breaking Change)