212 lines
8.7 KiB
Python
212 lines
8.7 KiB
Python
"""
|
|
Seed today's test data for HIS integration cron testing.
|
|
|
|
Creates ~20 patients with recent timestamps so the 5-minute
|
|
fetch_his_surveys cron will pick them up.
|
|
|
|
Usage:
|
|
python manage.py seed_his_today_data [--clear]
|
|
"""
|
|
|
|
import copy
|
|
import random
|
|
import string
|
|
|
|
from django.core.management.base import BaseCommand
|
|
from django.utils import timezone
|
|
|
|
|
|
SEED_PREFIX = "SEED"
|
|
|
|
|
|
ED_EVENTS = [
|
|
("Consultation", 0),
|
|
("Doctor assignment", 5),
|
|
("Drug Prescription", 10),
|
|
("End Of the Episode", 15),
|
|
]
|
|
|
|
IP_EVENTS = [
|
|
("IP Admissions", 0),
|
|
("Bed Allocation", 2),
|
|
("Fit for discharge", 60),
|
|
("Discharge date", 65),
|
|
("Discharge followUp", 63),
|
|
("IP Bill", 67),
|
|
]
|
|
|
|
OP_EVENTS = [
|
|
("Consultation", 0),
|
|
("Doctor Visited", 3),
|
|
("Rad Prescription", 8),
|
|
("Radiology Bill", 10),
|
|
("Radiology Token", 10),
|
|
("Radiology Patient Arrived", 15),
|
|
("Radiology Examination completed", 20),
|
|
]
|
|
|
|
PATIENT_SCENARIOS = [
|
|
{"type": "ED", "discharged": True, "admit_min_ago": 90, "discharge_min_ago": 75},
|
|
{"type": "ED", "discharged": True, "admit_min_ago": 60, "discharge_min_ago": 45},
|
|
{"type": "ED", "discharged": True, "admit_min_ago": 30, "discharge_min_ago": 15},
|
|
{"type": "ED", "discharged": True, "admit_min_ago": 15, "discharge_min_ago": 10},
|
|
{"type": "ED", "discharged": False, "admit_min_ago": 20, "discharge_min_ago": None},
|
|
{"type": "ED", "discharged": False, "admit_min_ago": 8, "discharge_min_ago": None},
|
|
{"type": "IP", "discharged": True, "admit_min_ago": 120, "discharge_min_ago": 60},
|
|
{"type": "IP", "discharged": True, "admit_min_ago": 90, "discharge_min_ago": 30},
|
|
{"type": "IP", "discharged": True, "admit_min_ago": 60, "discharge_min_ago": 10},
|
|
{"type": "IP", "discharged": True, "admit_min_ago": 45, "discharge_min_ago": 5},
|
|
{"type": "IP", "discharged": False, "admit_min_ago": 90, "discharge_min_ago": None},
|
|
{"type": "IP", "discharged": False, "admit_min_ago": 30, "discharge_min_ago": None},
|
|
{"type": "OP", "discharged": True, "admit_min_ago": 200, "discharge_min_ago": None, "last_event_min_ago": 180},
|
|
{"type": "OP", "discharged": True, "admit_min_ago": 150, "discharge_min_ago": None, "last_event_min_ago": 120},
|
|
{"type": "OP", "discharged": True, "admit_min_ago": 120, "discharge_min_ago": None, "last_event_min_ago": 90},
|
|
{"type": "OP", "discharged": False, "admit_min_ago": 30, "discharge_min_ago": None, "last_event_min_ago": 10},
|
|
{"type": "OP", "discharged": False, "admit_min_ago": 15, "discharge_min_ago": None, "last_event_min_ago": 5},
|
|
{"type": "OP", "discharged": False, "admit_min_ago": 8, "discharge_min_ago": None, "last_event_min_ago": 3},
|
|
]
|
|
|
|
|
|
def _generate_admission_id(index):
|
|
suffix = "".join(random.choices(string.digits, k=6))
|
|
return f"{SEED_PREFIX}-{index}-{suffix}"
|
|
|
|
|
|
def _format_his_date(dt):
|
|
return dt.strftime("%d-%b-%Y %H:%M")
|
|
|
|
|
|
class Command(BaseCommand):
|
|
help = "Seed today's test data for HIS cron testing"
|
|
|
|
def add_arguments(self, parser):
|
|
parser.add_argument("--clear", action="store_true", help="Remove previously seeded data")
|
|
|
|
def handle(self, *args, **options):
|
|
from apps.integrations.models import HISTestPatient, HISTestVisit
|
|
|
|
if options["clear"]:
|
|
deleted_p, _ = HISTestPatient.objects.filter(admission_id__startswith=SEED_PREFIX).delete()
|
|
deleted_v, _ = HISTestVisit.objects.filter(admission_id__startswith=SEED_PREFIX).delete()
|
|
self.stdout.write(f"Cleared {deleted_p} patients, {deleted_v} visits")
|
|
return
|
|
|
|
self.stdout.write(self.style.MIGRATE_HEADING("=" * 60))
|
|
self.stdout.write(self.style.MIGRATE_HEADING("Seed Today's HIS Test Data"))
|
|
self.stdout.write(self.style.MIGRATE_HEADING("=" * 60))
|
|
|
|
now = timezone.now()
|
|
|
|
existing = list(HISTestPatient.objects.exclude(admission_id__startswith=SEED_PREFIX).order_by("?")[:18])
|
|
|
|
if len(existing) < 18:
|
|
self.stderr.write(self.style.ERROR("Not enough source patients. Run load_his_test_data first."))
|
|
return
|
|
|
|
self.stdout.write(f"Source patients available: {len(existing)}")
|
|
|
|
patients_batch = []
|
|
visits_batch = []
|
|
summary = {"ED": 0, "IP": 0, "OP": 0}
|
|
|
|
for i, scenario in enumerate(PATIENT_SCENARIOS):
|
|
source = existing[i % len(existing)]
|
|
pt_type = scenario["type"]
|
|
summary[pt_type] = summary.get(pt_type, 0) + 1
|
|
|
|
admit_date = now - timezone.timedelta(minutes=scenario["admit_min_ago"])
|
|
admission_id = _generate_admission_id(i)
|
|
|
|
patient_data = copy.deepcopy(source.patient_data)
|
|
patient_data["AdmissionID"] = admission_id
|
|
patient_data["AdmitDate"] = _format_his_date(admit_date)
|
|
patient_data["PatientType"] = pt_type
|
|
patient_data["PatientTypeID"] = {"ED": "3", "IP": "2", "OP": "1"}[pt_type]
|
|
|
|
if scenario["discharged"] and scenario.get("discharge_min_ago"):
|
|
discharge_date = now - timezone.timedelta(minutes=scenario["discharge_min_ago"])
|
|
patient_data["DischargeDate"] = _format_his_date(discharge_date)
|
|
else:
|
|
discharge_date = None
|
|
patient_data["DischargeDate"] = None
|
|
|
|
patients_batch.append(
|
|
HISTestPatient(
|
|
admission_id=admission_id,
|
|
patient_id=source.patient_id,
|
|
patient_type=pt_type,
|
|
reg_code=source.reg_code,
|
|
ssn=source.ssn,
|
|
mobile_no=source.mobile_no,
|
|
admit_date=admit_date,
|
|
discharge_date=discharge_date,
|
|
patient_data=patient_data,
|
|
hospital_id=source.hospital_id,
|
|
hospital_name=source.hospital_name,
|
|
patient_name=source.patient_name,
|
|
)
|
|
)
|
|
|
|
visit_category = {"ED": "ED", "IP": "IP", "OP": "OP"}[pt_type]
|
|
patient_type_visit = {"ED": "ER", "IP": "IP", "OP": "OP"}[pt_type]
|
|
events = {"ED": ED_EVENTS, "IP": IP_EVENTS, "OP": OP_EVENTS}[pt_type]
|
|
|
|
last_event_min_ago = scenario.get("last_event_min_ago")
|
|
if last_event_min_ago is None:
|
|
if scenario["discharged"] and scenario.get("discharge_min_ago"):
|
|
last_event_min_ago = scenario["discharge_min_ago"]
|
|
else:
|
|
last_event_min_ago = max(scenario["admit_min_ago"] - 5, 1)
|
|
|
|
for j, (event_name, offset_min) in enumerate(events):
|
|
if not scenario["discharged"] and event_name in (
|
|
"Fit for discharge",
|
|
"Discharge date",
|
|
"Discharge followUp",
|
|
"IP Bill",
|
|
"End Of the Episode",
|
|
):
|
|
continue
|
|
|
|
if pt_type == "OP" and not scenario["discharged"] and j == len(events) - 1:
|
|
event_date = now - timezone.timedelta(minutes=last_event_min_ago)
|
|
else:
|
|
event_date = admit_date + timezone.timedelta(minutes=offset_min)
|
|
|
|
visit_data = {
|
|
"PatientType": patient_type_visit,
|
|
"Type": event_name,
|
|
"BillDate": _format_his_date(event_date),
|
|
"AdmissionID": admission_id,
|
|
"PatientID": source.patient_id,
|
|
"RegCode": source.reg_code,
|
|
"SSN": source.ssn,
|
|
"MobileNo": source.mobile_no,
|
|
}
|
|
|
|
visits_batch.append(
|
|
HISTestVisit(
|
|
admission_id=admission_id,
|
|
patient_id=source.patient_id,
|
|
visit_category=visit_category,
|
|
event_type=event_name,
|
|
bill_date=event_date,
|
|
reg_code=source.reg_code,
|
|
ssn=source.ssn,
|
|
mobile_no=source.mobile_no,
|
|
visit_data=visit_data,
|
|
)
|
|
)
|
|
|
|
HISTestPatient.objects.bulk_create(patients_batch, batch_size=1000)
|
|
self.stdout.write(f"Created {len(patients_batch)} patients")
|
|
|
|
HISTestVisit.objects.bulk_create(visits_batch, batch_size=2000)
|
|
self.stdout.write(f"Created {len(visits_batch)} visits")
|
|
|
|
self.stdout.write(self.style.MIGRATE_HEADING("\nSummary"))
|
|
self.stdout.write(f" ED: {summary['ED']} (4 discharged, 2 active)")
|
|
self.stdout.write(f" IP: {summary['IP']} (4 discharged, 2 active)")
|
|
self.stdout.write(f" OP: {summary['OP']} (3 complete, 3 in progress)")
|
|
self.stdout.write(self.style.SUCCESS("\nDone! Run fetch_his_surveys to test."))
|