-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
668 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
166 changes: 166 additions & 0 deletions
166
intrahospital_api/management/commands/merge_all_patients.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
from django.utils import timezone | ||
from django.core.management.base import BaseCommand | ||
from django.db import transaction | ||
from opal.models import Patient | ||
from intrahospital_api.apis.prod_api import ProdApi as ProdAPI | ||
from intrahospital_api import logger, loader, merge_patient, update_demographics | ||
from elcid import episode_categories as elcid_episode_categories | ||
from elcid.utils import timing | ||
from elcid import models | ||
|
||
|
||
# Returns all active merged patients | ||
# used by the merge_all_patients mgmt | ||
# command | ||
GET_ALL_ACTIVE_MERGED_MRNS = """ | ||
SELECT Patient_Number FROM CRS_Patient_Masterfile | ||
WHERE MERGED = 'Y' | ||
AND ACTIVE_INACTIVE = 'ACTIVE' | ||
""" | ||
|
||
def get_all_active_merged_mrns(): | ||
api = ProdAPI() | ||
query_result = api.execute_hospital_query(GET_ALL_ACTIVE_MERGED_MRNS) | ||
return [i["Patient_Number"] for i in query_result] | ||
|
||
|
||
|
||
@transaction.atomic | ||
def our_merge_patient(*, old_patient, new_patient): | ||
""" | ||
All elcid native non-singleton entries to be converted to | ||
the equivalent episode category on the wining MRN, with a reference | ||
created in the original_mrn field where they have been moved | ||
Copy teams from the old infection service episode? To the new | ||
Infection service episode | ||
Copy over any episode categories that do not exist, iterate | ||
over subrecord attached to these and add the PreviousMRN | ||
Singleton entries to pick the latest but create a reversion | ||
history entry for the non-oldest, with a reference to the original_mrn | ||
Non-singletons entries are moved from the old parent to the | ||
new parent. | ||
This is the same as merge_patient.merge_patient but | ||
does not call load_patient. | ||
""" | ||
old_mrn = old_patient.demographics().hospital_number | ||
for patient_related_model in merge_patient.PATIENT_RELATED_MODELS: | ||
merge_patient.move_record( | ||
patient_related_model, | ||
old_patient, | ||
new_patient, | ||
old_mrn, | ||
) | ||
for old_episode in old_patient.episode_set.all(): | ||
# Note: if the old episode has multiple episode | ||
# categories of the same category name | ||
# this will merge those. | ||
new_episode, _ = new_patient.episode_set.get_or_create( | ||
category_name=old_episode.category_name | ||
) | ||
merge_patient.update_tagging(old_episode, new_episode) | ||
for episode_related_model in merge_patient.EPISODE_RELATED_MODELS: | ||
merge_patient.move_record( | ||
episode_related_model, | ||
old_episode, | ||
new_episode, | ||
old_mrn, | ||
) | ||
old_patient.delete() | ||
new_patient.mergedmrn_set.filter(mrn=old_mrn).update( | ||
our_merge_datetime=timezone.now() | ||
) | ||
merge_patient.updates_statuses(new_patient) | ||
|
||
|
||
|
||
def check_and_handle_upstream_merges_for_mrns(mrns): | ||
""" | ||
Takes in a list of MRNs. | ||
Filters those not related to elCID. | ||
If they are inactive, creates a Patient for the | ||
active MRN and creates MergedMRN for all related inactive | ||
MRNs. | ||
If they are active, creates MergedMRN for all | ||
related inactive MRNs. | ||
This function looks the same as | ||
update_demographics.check_and_handle_upstream_merges_for_mrns | ||
but instead of running merge_patient.merge_patient | ||
it runs the above merge_patient function that | ||
does not call load_patient. | ||
""" | ||
cache = update_demographics.get_mrn_to_upstream_merge_data() | ||
now = timezone.now() | ||
active_mrn_to_merged_dicts = {} | ||
# it is possible that the MRNs passed | ||
# in will link to the same active MRN | ||
# so make sure we only have one per | ||
# active MRN | ||
for mrn in mrns: | ||
active_mrn, merged_dicts = update_demographics.get_active_mrn_and_merged_mrn_data( | ||
mrn, cache | ||
) | ||
active_mrn_to_merged_dicts[active_mrn] = merged_dicts | ||
|
||
logger.info('Generating merged MRNs') | ||
for active_mrn, merged_dicts in active_mrn_to_merged_dicts.items(): | ||
to_create = [] | ||
merged_mrns = [i["mrn"] for i in merged_dicts] | ||
active_patient = Patient.objects.filter( | ||
demographics__hospital_number=active_mrn | ||
).first() | ||
merged_mrn_objs = models.MergedMRN.objects.filter( | ||
mrn__in=merged_mrns | ||
) | ||
unmerged_patients = Patient.objects.filter( | ||
demographics__hospital_number__in=merged_mrns | ||
) | ||
# If we have patients that are inactive we need to do a merge. | ||
if len(unmerged_patients) > 0: | ||
if not active_patient: | ||
active_patient, _ = loader.get_or_create_patient( | ||
active_mrn, | ||
elcid_episode_categories.InfectionService, | ||
run_async=False | ||
) | ||
for unmerged_patient in unmerged_patients: | ||
if active_patient: | ||
our_merge_patient( | ||
old_patient=unmerged_patient, | ||
new_patient=active_patient | ||
) | ||
|
||
# If there is an active patient then we need to create merged MRNs. | ||
if active_patient: | ||
# we don't delete and write anew to preserve the our_merge_datetime field | ||
existing_merged_mrns = set([i.mrn for i in merged_mrn_objs]) | ||
new_merged_mrns = set(i["mrn"] for i in merged_dicts) | ||
to_add_merged_mrns = new_merged_mrns - existing_merged_mrns | ||
|
||
for merged_dict in merged_dicts: | ||
if merged_dict["mrn"] in to_add_merged_mrns: | ||
to_create.append( | ||
models.MergedMRN( | ||
patient=active_patient, | ||
our_merge_datetime=now, | ||
**merged_dict | ||
) | ||
) | ||
logger.info('Saving merged MRNs') | ||
models.MergedMRN.objects.bulk_create(to_create) | ||
logger.info(f'Saved {len(to_create)} merged MRNs') | ||
|
||
|
||
class Command(BaseCommand): | ||
@timing | ||
def handle(self, *args, **options): | ||
mrns = get_all_active_merged_mrns() | ||
check_and_handle_upstream_merges_for_mrns(mrns) |
147 changes: 147 additions & 0 deletions
147
intrahospital_api/management/commands/merge_dup_episode_categories.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
from django.utils import timezone | ||
from django.core.management.base import BaseCommand | ||
from django.db import transaction | ||
from opal.models import Patient | ||
from intrahospital_api.apis.prod_api import ProdApi as ProdAPI | ||
from intrahospital_api import logger, merge_patient | ||
from elcid.utils import timing | ||
|
||
|
||
import os | ||
from intrahospital_api import merge_patient | ||
from opal import models as opal_models | ||
from django.db import transaction | ||
from django.db.models import Count | ||
import reversion | ||
from elcid.utils import timing | ||
|
||
DELETE_FILE = "deleted_episodes.txt" | ||
|
||
|
||
def update_singleton(subrecord_cls, old_parent, new_parent): | ||
if new_parent.__class__ == opal_models.Episode: | ||
is_episode_subrecord = True | ||
else: | ||
is_episode_subrecord = False | ||
|
||
if is_episode_subrecord: | ||
old_singleton = subrecord_cls.objects.get(episode=old_parent) | ||
new_singleton = subrecord_cls.objects.get(episode=new_parent) | ||
else: | ||
old_singleton = subrecord_cls.objects.get(patient=old_parent) | ||
new_singleton = subrecord_cls.objects.get(patient=new_parent) | ||
if not old_singleton.updated: | ||
# the old singleton was never editted, we can skip | ||
return | ||
if not new_singleton.updated: | ||
# the new singleton was never editted, we can delete | ||
# it and replace it with the old one. | ||
new_singleton.delete() | ||
if is_episode_subrecord: | ||
old_singleton.episode = new_parent | ||
else: | ||
old_singleton.patient = new_parent | ||
with reversion.create_revision(): | ||
old_singleton.save() | ||
else: | ||
if new_singleton.updated < old_singleton.updated: | ||
# the old singleton is new than the new singleton | ||
# stamp the new singleton as reversion | ||
# then copy over all the fields from the old | ||
# onto the new | ||
for field in old_singleton._meta.get_fields(): | ||
field_name = field.name | ||
if field_name in merge_patient.IGNORED_FIELDS: | ||
continue | ||
setattr(new_singleton, field_name, getattr(old_singleton, field_name)) | ||
with reversion.create_revision(): | ||
new_singleton.save() | ||
else: | ||
# the old singleton is older than the new singleton | ||
# create a reversion record with the data of the old | ||
# singleton, then continue with the more recent data | ||
more_recent_data = {} | ||
for field in new_singleton._meta.get_fields(): | ||
field_name = field.name | ||
if field_name in merge_patient.IGNORED_FIELDS: | ||
continue | ||
more_recent_data[field_name] = getattr(new_singleton, field_name) | ||
setattr(new_singleton, field_name, getattr(old_singleton, field_name)) | ||
new_singleton.save() | ||
for field, value in more_recent_data.items(): | ||
setattr(new_singleton, field, value) | ||
with reversion.create_revision(): | ||
new_singleton.save() | ||
|
||
|
||
def move_non_singletons(subrecord_cls, old_parent, new_parent): | ||
""" | ||
Moves the old_subrecords query set onto the new parent (a patient or episode). | ||
In doing so it updates the previous_mrn field to be that of the old_mrn | ||
""" | ||
if new_parent.__class__ == opal_models.Episode: | ||
is_episode_subrecord = True | ||
else: | ||
is_episode_subrecord = False | ||
if is_episode_subrecord: | ||
old_subrecords = subrecord_cls.objects.filter(episode=old_parent) | ||
else: | ||
old_subrecords = subrecord_cls.objects.filter(patient=old_parent) | ||
for old_subrecord in old_subrecords: | ||
if is_episode_subrecord: | ||
old_subrecord.episode = new_parent | ||
else: | ||
old_subrecord.patient = new_parent | ||
with reversion.create_revision(): | ||
old_subrecord.save() | ||
|
||
|
||
def move_record(subrecord_cls, old_parent, new_parent): | ||
if getattr(subrecord_cls, "_is_singleton", False): | ||
update_singleton(subrecord_cls, old_parent, new_parent) | ||
else: | ||
move_non_singletons(subrecord_cls, old_parent, new_parent) | ||
|
||
|
||
def merge_episode(*, old_episode, new_episode): | ||
for episode_related_model in merge_patient.EPISODE_RELATED_MODELS: | ||
move_record( | ||
episode_related_model, | ||
old_episode, | ||
new_episode, | ||
) | ||
|
||
@transaction.atomic | ||
def merge_patient_episodes(patient_id): | ||
patient = opal_models.Patient.objects.get(id=patient_id) | ||
episodes = patient.episode_set.all() | ||
category_names = list(set([i.category_name for i in episodes])) | ||
for category in category_names: | ||
category_episodes = [i for i in episodes if i.category_name==category] | ||
if len(category_episodes) > 1: | ||
root = category_episodes[0] | ||
for category_episode in category_episodes[1:]: | ||
merge_patient.update_tagging(category_episode, root) | ||
merge_episode(old_episode=category_episode, new_episode=root) | ||
category_episode_id = category_episode.id | ||
patient_id = category_episode.patient_id | ||
category_episode.delete() | ||
with open(DELETE_FILE, 'a') as w: | ||
w.write(f'\n{patient_id},{category_episode_id}') | ||
|
||
|
||
def patient_ids_with_duplicate_episode_categories(): | ||
dups = opal_models.Episode.objects.values('patient_id', 'category_name').annotate( | ||
cnt=Count('id') | ||
).filter(cnt__gte=2) | ||
return [i["patient_id"] for i in dups] | ||
|
||
|
||
class Command(BaseCommand): | ||
@timing | ||
def handle(self, *args, **options): | ||
dups = patient_ids_with_duplicate_episode_categories() | ||
logger.info(f'Looking at {len(dups)}') | ||
for idx, patient_id in enumerate(dups): | ||
logger.info(f'Merging {patient_id} ({idx+1}/{len(dups)})') | ||
merge_patient_episodes(patient_id) |
27 changes: 27 additions & 0 deletions
27
intrahospital_api/management/commands/merge_patients_since.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import datetime | ||
import time | ||
import traceback | ||
from django.core.management.base import BaseCommand | ||
from django.utils import timezone | ||
from opal.models import Patient | ||
from intrahospital_api import update_demographics, logger, constants | ||
from plugins.monitoring.models import Fact | ||
|
||
|
||
class Command(BaseCommand): | ||
def handle(self, *args, **options): | ||
try: | ||
start = time.time() | ||
since = timezone.now() - datetime.timedelta(2) | ||
upstream_merged_mrns = update_demographics.get_all_merged_mrns_since(since) | ||
update_demographics.check_and_handle_upstream_merges_for_mrns(upstream_merged_mrns) | ||
finished = time.time() | ||
now = timezone.now() | ||
minutes_taken = int(int(finished-start)/60) | ||
total_merge_count = Patient.objects.filter(mergedmrn__isnull=False).count() | ||
Fact(when=now, label=constants.MERGE_LOAD_MINUTES, value_int=minutes_taken).save() | ||
Fact(when=now, label=constants.TOTAL_MERGE_COUNT, value_int=total_merge_count).save() | ||
except Exception: | ||
msg = "Loading merges: \n{}".format(traceback.format_exc()) | ||
logger.error(msg) | ||
return |
Oops, something went wrong.