Samvickid/pubmed-hr
0
1"""2Comprehensive Fake Data Generator — PubMed Center3Generates rich patient data: medications, procedures, labs, vitals, follow-ups.4All data links to existing 1006 patients and feeds into booking intelligence.5"""6import json, random, datetime, os, sys7from collections import defaultdict8 9random.seed(42)10 11BASE = os.path.dirname(os.path.abspath(__file__))12PATIENTS_FILE = os.path.join(BASE, "patients.json")13SUMMARIES_FILE = os.path.join(BASE, "booking_summary.json")14OUTPUT_FILE = os.path.join(BASE, "rich_patient_data.json")15 16# ── Load existing data ──17with open(PATIENTS_FILE) as f:18 patients = json.load(f)19 20with open(SUMMARIES_FILE) as f:21 summaries = json.load(f)22 23patient_lookup = {p["patient_number"]: p for p in patients}24summary_lookup = {s["patient_id"]: s for s in summaries.get("patient_summaries", [])}25 26print(f"Loaded {len(patients)} patients, {len(summary_lookup)} summaries")27 28# ── 40+ Real Medications ──29MEDICATIONS = [30 ("Metformin", "500mg", "850mg", "1000mg", "Diabetes, Type 2"),31 ("Aspirin", "81mg", "325mg", "", "Antiplatelet, CVD prevention"),32 ("Amlodipine", "5mg", "10mg", "", "Hypertension"),33 ("Lisinopril", "5mg", "10mg", "20mg", "Hypertension, Heart failure"),34 ("Atorvastatin", "10mg", "20mg", "40mg", "Hypercholesterolemia"),35 ("Metoprolol", "25mg", "50mg", "100mg", "Hypertension, Angina"),36 ("Insulin Glargine", "10U", "20U", "30U", "Diabetes, Type 1 & 2"),37 ("Levothyroxine", "25mcg", "50mcg", "100mcg", "Hypothyroidism"),38 ("Omeprazole", "20mg", "40mg", "", "GERD, Acid reflux"),39 ("Losartan", "25mg", "50mg", "100mg", "Hypertension"),40 ("Hydrochlorothiazide", "12.5mg", "25mg", "", "Hypertension"),41 ("Simvastatin", "10mg", "20mg", "40mg", "Hypercholesterolemia"),42 ("Warfarin", "2mg", "5mg", "7.5mg", "Atrial fibrillation, DVT"),43 ("Clopidogrel", "75mg", "", "", "Antiplatelet, post-stent"),44 ("Furosemide", "20mg", "40mg", "80mg", "Heart failure, Edema"),45 ("Prednisone", "5mg", "10mg", "20mg", "Inflammation, Asthma"),46 ("Albuterol Inhaler", "90mcg", "", "", "Asthma, COPD"),47 ("Gabapentin", "100mg", "300mg", "600mg", "Neuropathic pain"),48 ("Sertraline", "25mg", "50mg", "100mg", "Depression, Anxiety"),49 ("Pantoprazole", "20mg", "40mg", "", "GERD"),50 ("Digoxin", "0.125mg", "0.25mg", "", "Heart failure, AFib"),51 ("Spironolactone", "25mg", "50mg", "", "Heart failure"),52 ("Carvedilol", "3.125mg", "6.25mg", "25mg", "Heart failure"),53 ("Empagliflozin", "10mg", "25mg", "", "Diabetes, Heart failure"),54 ("Dapagliflozin", "5mg", "10mg", "", "Diabetes, Heart failure"),55 ("Semaglutide", "0.25mg", "0.5mg", "1mg", "Diabetes, Weight loss"),56 ("Fenofibrate", "48mg", "145mg", "", "Hypertriglyceridemia"),57 ("Allopurinol", "100mg", "300mg", "", "Gout"),58 ("Febuxostat", "40mg", "80mg", "", "Gout"),59 ("Vitamin D3", "1000IU", "2000IU", "5000IU", "Vitamin D deficiency"),60 ("Calcium Carbonate", "500mg", "1000mg", "", "Osteoporosis"),61 ("Alendronate", "70mg", "", "", "Osteoporosis"),62 ("Tramadol", "50mg", "100mg", "", "Moderate pain"),63 ("Acetaminophen", "500mg", "650mg", "1000mg", "Mild pain, Fever"),64 ("Ibuprofen", "200mg", "400mg", "600mg", "Inflammation, Pain"),65 ("Naproxen", "250mg", "500mg", "", "Arthritis, Pain"),66 ("Montelukast", "10mg", "", "", "Asthma, Allergies"),67 ("Cetirizine", "10mg", "", "", "Allergies"),68 ("Doxycycline", "100mg", "", "", "Antibiotic"),69 ("Amoxicillin", "250mg", "500mg", "", "Antibiotic"),70 ("Azithromycin", "250mg", "500mg", "", "Antibiotic"),71 ("Ciprofloxacin", "250mg", "500mg", "", "Antibiotic"),72 ("Fluoxetine", "10mg", "20mg", "40mg", "Depression, OCD"),73 ("Bupropion", "150mg", "300mg", "", "Depression, Smoking cessation"),74]75 76# ── Procedures ──77PROCEDURES = [78 ("Lab Work - Complete Blood Count", "CBC"),79 ("Lab Work - Basic Metabolic Panel", "BMP"),80 ("Lab Work - Lipid Panel", "Lipids"),81 ("Lab Work - HbA1c", "HbA1c"),82 ("Lab Work - Thyroid Panel", "TSH"),83 ("ECG / EKG", "ECG"),84 ("Chest X-Ray", "CXR"),85 ("Mammogram", "Mammo"),86 ("Colonoscopy", "Colon"),87 ("Bone Density Scan", "DEXA"),88 ("Stress Test", "Stress"),89 ("Echocardiogram", "Echo"),90 ("MRI - Brain", "MRI_Brain"),91 ("CT Scan - Abdomen", "CT_Abd"),92 ("Ultrasound - Abdomen", "US_Abd"),93 ("Holter Monitor", "Holter"),94 ("Pulmonary Function Test", "PFT"),95 ("Skin Biopsy", "Skin_Bx"),96 ("Joint Injection", "Joint_Inject"),97 ("Suture Removal", "Suture"),98 ("Wound Dressing", "Wound"),99 ("Vaccination - Influenza", "Flu_Vax"),100 ("Vaccination - COVID-19", "COVID_Vax"),101 ("Vaccination - Pneumococcal", "Pneumo_Vax"),102]103 104# ── Follow-up Reasons ──105FOLLOW_UP_REASONS = [106 "Post-operative follow-up — wound healing normal",107 "Lab results review — all within normal range",108 "Medication adjustment — dosage decreased due to side effects",109 "Blood pressure check — controlled on current regimen",110 "Glucose monitoring follow-up — HbA1c improving",111 "Post-discharge follow-up — recovering well at home",112 "Pain management review — pain controlled with current meds",113 "Physical therapy progress assessment",114 "Specialist referral follow-up — plan discussed with patient",115 "Vaccination follow-up — no adverse reactions",116 "Imaging results discussion — findings reviewed with patient",117 "Pre-operative clearance — cleared for surgery",118 "Post-procedure check — no complications",119 "Chronic disease management — treatment plan updated",120 "Medication refill — 3-month supply authorized",121 "Weight management follow-up — 5 lbs lost since last visit",122 "Smoking cessation follow-up — patient using nicotine patch",123 "Mental health follow-up — mood stable on current meds",124 "Cardiac rehab progress — tolerating increased activity",125 "Dietitian consult follow-up — dietary changes implemented",126]127 128# ── Vitals ──129def generate_vitals(age, gender):130 """Generate realistic vitals based on age and gender"""131 base_sbp = 110 + random.gauss(0, 10)132 base_dbp = 70 + random.gauss(0, 5)133 if age > 60:134 base_sbp += 10135 if age > 75:136 base_sbp += 5137 return {138 "blood_pressure_systolic": round(base_sbp + random.gauss(0, 5), 0),139 "blood_pressure_diastolic": round(base_dbp + random.gauss(0, 3), 0),140 "heart_rate": round(72 + random.gauss(0, 8), 0),141 "temperature": round(36.8 + random.gauss(0, 0.2), 1),142 "respiratory_rate": round(16 + random.gauss(0, 2), 0),143 "oxygen_saturation": round(97 + random.gauss(0, 1.5), 0),144 "weight_kg": round(70 + random.gauss(0, 15) + (5 if age > 50 else 0), 1),145 "height_cm": round(170 + random.gauss(0, 10), 1),146 }147 148def generate_labs(age, gender):149 """Generate realistic lab results"""150 return {151 "hemoglobin": round(14 + random.gauss(0, 1), 1),152 "wbc": round(7 + random.gauss(0, 2), 1),153 "platelets": round(250 + random.gauss(0, 50), 0),154 "glucose_fasting": round(95 + random.gauss(0, 15) + (20 if age > 55 else 0), 0),155 "hba1c": round(5.7 + random.gauss(0, 0.5), 1),156 "creatinine": round(0.9 + random.gauss(0, 0.2), 2),157 "egfr": round(90 - random.gauss(0, 10) - (age // 2) * 0.5, 0),158 "total_cholesterol": round(190 + random.gauss(0, 20), 0),159 "ldl": round(110 + random.gauss(0, 15), 0),160 "hdl": round(50 + random.gauss(0, 10) + (7 if gender == "F" else 0), 0),161 "triglycerides": round(130 + random.gauss(0, 25), 0),162 "potassium": round(4.2 + random.gauss(0, 0.3), 1),163 "sodium": round(140 + random.gauss(0, 3), 0),164 "alt": round(25 + random.gauss(0, 8), 0),165 "ast": round(22 + random.gauss(0, 7), 0),166 "vitamin_d": round(35 + random.gauss(0, 10), 0),167 "tsh": round(2.5 + random.gauss(0, 1), 1),168 "crp": round(3 + random.gauss(0, 2), 1)169 }170 171# ── Procedure Codes ──172PROCEDURE_CODES = {173 "CBC": "80050", "BMP": "80048", "Lipids": "80061", "HbA1c": "83036",174 "TSH": "84443", "ECG": "93005", "CXR": "71045", "Mammo": "77065",175 "Colon": "45378", "DEXA": "77080", "Stress": "93015", "Echo": "93306",176 "MRI_Brain": "70551", "CT_Abd": "74176", "US_Abd": "76700",177 "Holter": "93224", "PFT": "94010", "Skin_Bx": "11100",178 "Joint_Inject": "20610", "Suture": "12001", "Wound": "97602",179 "Flu_Vax": "90656", "COVID_Vax": "91300", "Pneumo_Vax": "90670"180}181 182# ── Helper ──183def random_date(start="2023-01-01", end="2026-06-17"):184 s = datetime.date.fromisoformat(start)185 e = datetime.date.fromisoformat(end)186 return s + datetime.timedelta(days=random.randint(0, (e - s).days))187 188def prev_date(days_ago):189 return (datetime.date.today() - datetime.timedelta(days=days_ago)).isoformat()190 191# ── Generate Rich Data ──192print("\nGenerating rich patient data...")193rich_data = {}194 195for i, patient in enumerate(patients):196 pid = patient["patient_number"]197 age = patient.get("age", 45)198 gender = patient.get("gender", random.choice(["M", "F"]))199 name = patient.get("name", f"Patient {pid}")200 201 # Get summary if exists202 summary = summary_lookup.get(pid)203 total_visits = summary.get("total_visits", random.randint(100, 1100)) if summary else random.randint(100, 1100)204 existing_meds = summary.get("medications", []) if summary else []205 existing_conditions = summary.get("conditions", []) if summary else []206 207 #── Medications (5-12 per patient with prescription details) ──208 num_meds = random.randint(5, 12)209 210 # Start with existing meds, then add more211 med_names_in_use = set()212 medications = []213 214 # Parse existing meds215 for em in existing_meds:216 # "Metformin 500mg" -> name, dose217 parts = em.split()218 if len(parts) >= 2 and num_meds > 0:219 try:220 dose = parts[-1]221 name_part = " ".join(parts[:-1])222 med = random.choice([m for m in MEDICATIONS if m[0] == name_part or m[0].split()[0] == name_part.split()[0]])223 except:224 med = random.choice(MEDICATIONS)225 med_names_in_use.add(med[0])226 start_dt = random_date("2022-01-01", "2024-06-01")227 medications.append({228 "name": f"{med[0]} {med[1]}",229 "generic_name": med[0],230 "dosage": med[1],231 "dosage_form": "Tablet" if "mg" in med[1] else "Injection",232 "frequency": random.choice(["Once daily", "Twice daily", "Three times daily", "As needed"]),233 "route": "Oral" if "Inhaler" not in med[0] else "Inhalation",234 "prescribed_by": random.choice(["Dr. Samuel Alobo", "Dr. Jane Doe", "Dr. James Chen", "Dr. Maria Santos"]),235 "start_date": start_dt.isoformat(),236 "end_date": None,237 "status": "active",238 "refills_remaining": random.randint(0, 11),239 "pharmacy": random.choice(["PubMed Center Pharmacy", "CVS Pharmacy", "Walgreens", "Rite Aid"]),240 "notes": random.choice(["Take with food", "Take in the morning", "Take before bed", "Monitor for dizziness"])241 })242 num_meds -= 1243 244 # Add more meds if we need more245 extra_meds = random.sample([m for m in MEDICATIONS if m[0] not in med_names_in_use], min(num_meds, len(MEDICATIONS) - len(med_names_in_use)))246 for med in extra_meds:247 start_dt = random_date("2022-01-01", "2025-06-01")248 dose = random.choice([d for d in [med[1], med[2], med[3]] if d])249 end_date = None250 status = "active"251 if random.random() < 0.2: # 20% historical252 end_date = random_date(start_dt.isoformat(), "2026-05-01")253 status = "discontinued"254 medications.append({255 "name": f"{med[0]} {dose}",256 "generic_name": med[0],257 "dosage": dose,258 "dosage_form": "Tablet" if "mg" in dose else ("Injection" if "U" in dose else "Inhaler"),259 "frequency": random.choice(["Once daily", "Twice daily", "Three times daily", "As needed"]),260 "route": "Oral" if "Inhaler" not in med[0] else "Inhalation",261 "prescribed_by": random.choice(["Dr. Samuel Alobo", "Dr. Jane Doe", "Dr. James Chen", "Dr. Maria Santos",262 "Dr. Robert Mitchell", "Dr. Laura Chen", "Dr. Karen Walsh", "Dr. David Park"]),263 "start_date": start_dt.isoformat(),264 "end_date": end_date.isoformat() if end_date else None,265 "status": status,266 "refills_remaining": random.randint(0, 11) if status == "active" else 0,267 "pharmacy": random.choice(["PubMed Center Pharmacy", "CVS Pharmacy", "Walgreens", "Rite Aid", "Kaiser Pharmacy"]),268 "notes": random.choice(["Take with food", "Take in the morning", "Take before bed", "Monitor for dizziness",269 "Avoid grapefruit juice", "Take on empty stomach", "Do not crush"])270 })271 272 #── Procedures (each patient has 5-25 procedures across their visit history) ──273 num_procedures = random.randint(5, 25)274 procedures = []275 for _ in range(num_procedures):276 proc = random.choice(PROCEDURES)277 proc_date = random_date()278 proc_code = PROCEDURE_CODES.get(proc[1], "00000")279 procedures.append({280 "procedure_name": proc[0],281 "procedure_code": proc_code,282 "category": proc[1],283 "date": proc_date.isoformat(),284 "performed_by": random.choice(["Dr. Samuel Alobo", "Dr. Jane Doe", "Dr. James Chen", "Dr. Maria Santos",285 "Dr. Robert Mitchell", "Dr. Laura Chen", "Dr. Paul Okonkwo"]),286 "location": random.choice(["PubMed Center Main", "PubMed Center - East Wing", "PubMed Center - West Wing"]),287 "result": random.choice(["Normal", "Normal", "Normal", "Abnormal - requiring follow-up", "Normal", "Inconclusive"]),288 "notes": random.choice(["Procedure tolerated well", "Patient comfortable throughout", "No complications",289 "Mild discomfort reported", "Standard protocol followed"])290 })291 292 #── Lab Results (one per visit basically, sampled) ──293 num_labs = min(random.randint(3, 15), total_visits // 20 + 3)294 labs = []295 for _ in range(num_labs):296 lab_date = random_date()297 lab_data = generate_labs(age, gender)298 labs.append({299 "date": lab_date.isoformat(),300 "results": lab_data,301 "notes": random.choice(["Fasting sample", "Non-fasting", "Random draw", "Post-prandial"])302 })303 304 #── Vitals (sampled) ──305 num_vitals = min(random.randint(3, 20), total_visits // 30 + 2)306 vitals_list = []307 for _ in range(num_vitals):308 v_date = random_date()309 v = generate_vitals(age, gender)310 vitals_list.append({311 "date": v_date.isoformat(),312 "vitals": v313 })314 315 #── Follow-ups ──316 num_followups = random.randint(2, 10)317 followups = []318 for _ in range(num_followups):319 f_date = random_date("2023-06-01", "2026-06-17")320 completed = random.random() < 0.85321 followups.append({322 "scheduled_date": f_date.isoformat(),323 "completed": completed,324 "completed_date": f_date.isoformat() if completed else None,325 "reason": random.choice(FOLLOW_UP_REASONS),326 "provider": random.choice(["Dr. Samuel Alobo", "Dr. Jane Doe", "Dr. James Chen", "Dr. Maria Santos",327 "Dr. Robert Mitchell", "Nurse Practitioner Wilson"]),328 "outcome": random.choice(["Resolved", "Improving", "Stable", "Referred to specialist", "Medication adjusted"]) if completed else None,329 "notes": random.choice(["Patient advised to continue current plan", "Return in 3 months", "Schedule imaging",330 "Lab work ordered", "Referral placed"])331 })332 333 #── Allergies ──334 num_allergies = random.randint(0, 4)335 allergies_list = []336 allergy_options = ["Penicillin", "Sulfa", "Aspirin", "Ibuprofen", "Codeine", "Morphine",337 "Latex", "Pollen", "Shellfish", "Peanuts", "Seasonal",338 "ACE Inhibitors", "Statins", "Metformin", "Contrast Dye"]339 for _ in range(num_allergies):340 allergy = random.choice(allergy_options)341 allergies_list.append({342 "allergen": allergy,343 "reaction": random.choice(["Rash", "Hives", "Swelling", "Anaphylaxis", "Nausea", "Dizziness"]),344 "severity": random.choice(["Mild", "Moderate", "Severe"]),345 "recorded_date": random_date("2020-01-01", "2025-01-01").isoformat()346 })347 348 #── Insurance Info ──349 insurance_plans = ["Aetna PPO", "Blue Cross Blue Shield", "Cigna HMO", "United Healthcare",350 "Medicare Part D", "Medicaid", "Humana Gold Plus", "Kaiser Permanente"]351 insurance = {352 "provider": random.choice(insurance_plans),353 "member_id": f"PMC-{random.randint(100000,999999)}",354 "group_number": f"GRP-{random.randint(100,999)}",355 "copay": random.choice([10, 15, 20, 25, 30, 40, 50]),356 "deductible_met": random.random() < 0.7,357 "effective_date": random_date("2022-01-01", "2025-01-01").isoformat()358 }359 360 #── Build rich patient record ──361 rich_data[pid] = {362 "patient_id": pid,363 "name": name,364 "age": age,365 "gender": gender,366 "medications": medications,367 "procedures": sorted(procedures, key=lambda x: x["date"]),368 "lab_results": sorted(labs, key=lambda x: x["date"]),369 "vitals_history": sorted(vitals_list, key=lambda x: x["date"]),370 "follow_ups": sorted(followups, key=lambda x: x["scheduled_date"]),371 "allergies": allergies_list,372 "insurance": insurance,373 "active_conditions": existing_conditions,374 "total_visits": total_visits,375 "updated_at": datetime.datetime.now().isoformat()376 }377 378 if (i+1) % 100 == 0:379 print(f" Generated {i+1}/{len(patients)} patients...")380 381# ── Save ──382with open(OUTPUT_FILE, "w") as f:383 json.dump(rich_data, f, indent=2)384 385print(f"\n✅ Rich data saved to {OUTPUT_FILE}")386print(f" Patients: {len(rich_data)}")387print(f" Total medications: {sum(len(d['medications']) for d in rich_data.values())}")388print(f" Total procedures: {sum(len(d['procedures']) for d in rich_data.values())}")389print(f" Total lab_results: {sum(len(d['lab_results']) for d in rich_data.values())}")390print(f" Total vitals records: {sum(len(d['vitals_history']) for d in rich_data.values())}")391print(f" Total follow-ups: {sum(len(d['follow_ups']) for d in rich_data.values())}")392print(f" File size: {os.path.getsize(OUTPUT_FILE) / 1024 / 1024:.1f} MB")393 