549 lines
14 KiB
Markdown
549 lines
14 KiB
Markdown
# 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)
|