import json from datetime import datetime, timezone from typing import Dict, Any, List from sqlalchemy.orm import Session from app.database.models import OnboardingSession, OnboardingState, OnboardingMessage # ICD-10 & SNOMED CT Clinical Terminology Dictionary ICD10_SNOMED_MAP: Dict[str, Dict[str, str]] = { "headache": {"icd10": "R51.9", "snomed": "25064002", "display": "Headache, unspecified"}, "fever": {"icd10": "R50.9", "snomed": "386661006", "display": "Fever, unspecified"}, "cough": {"icd10": "R05.9", "snomed": "49727002", "display": "Cough, unspecified"}, "chest pain": {"icd10": "R07.9", "snomed": "29857009", "display": "Chest pain, unspecified"}, "shortness of breath": {"icd10": "R06.02", "snomed": "267036007", "display": "Shortness of breath"}, "diabetes": {"icd10": "E11.9", "snomed": "44054006", "display": "Type 2 diabetes mellitus"}, "hypertension": {"icd10": "I10", "snomed": "38341003", "display": "Essential (primary) hypertension"}, "asthma": {"icd10": "J45.909", "snomed": "195967001", "display": "Asthma, unspecified"} } def generate_soap_note(session_id: str, db: Session) -> Dict[str, Any]: """ Compiles an onboarding session into a formal Doctor SOAP Note (Subjective, Objective, Assessment, Plan) with candidate ICD-10 & SNOMED-CT codes. """ sess = db.query(OnboardingSession).filter(OnboardingSession.session_id == session_id).first() if not sess: raise ValueError(f"Session {session_id} not found.") state_obj = db.query(OnboardingState).filter(OnboardingState.session_id == session_id).first() state_data = json.loads(state_obj.state_json) if state_obj and state_obj.state_json else {} messages = ( db.query(OnboardingMessage) .filter(OnboardingMessage.session_id == session_id) .order_by(OnboardingMessage.created_at.asc()) .all() ) now_str = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC") # 1. Subjective (S) symptoms = state_data.get("symptoms", []) chief_complaint = sess.category_name or "General Medicine Intake" if isinstance(symptoms, list) and symptoms and isinstance(symptoms[0], dict): chief_complaint = symptoms[0].get("name", chief_complaint).capitalize() hpi_list = [] if isinstance(symptoms, list): for s in symptoms: if isinstance(s, dict) and s.get("name"): hpi_list.append(f"{s['name'].capitalize()} (Duration: {s.get('duration', 'N/A')}, Severity: {s.get('severity', 'N/A')})") history_list = [] med_history = state_data.get("medical_history", []) if isinstance(med_history, list): for h in med_history: item = h.get("condition") if isinstance(h, dict) else str(h) if item: history_list.append(item) medications_list = [] meds = state_data.get("medications", []) if isinstance(meds, list): for m in meds: item = f"{m.get('name')} {m.get('dosage', '')}".strip() if isinstance(m, dict) else str(m) if item: medications_list.append(item) allergies_list = [] algs = state_data.get("allergies", []) if isinstance(algs, list): for a in algs: item = a.get("name") if isinstance(a, dict) else str(a) if item: allergies_list.append(item) subjective = { "chief_complaint": chief_complaint, "history_of_present_illness": hpi_list or ["Patient reports general health query."], "past_medical_history": history_list or ["None reported"], "current_medications": medications_list or ["None reported"], "allergies": allergies_list or ["No known drug allergies (NKDA)"] } # 2. Objective (O) nurse_esc = state_data.get("nurse_escalation", {}) pain_score = nurse_esc.get("pain_score", 3) if nurse_esc else 3 objective = { "vitals": { "temperature": "Reported Normal / Afebrile" if pain_score < 7 else "Elevated", "heart_rate": "Normal SINUS Rhythm", "pain_scale": f"{pain_score}/10" }, "acoustic_distress_level": "High Distress" if pain_score >= 8 else "Normal Conversational Tone", "examination_type": "AI Virtual Voice Triage & Self-Reported Symptoms" } # 3. Assessment (A) - ICD-10 & SNOMED CT Mapping candidate_diagnoses = [] if isinstance(symptoms, list): for s in symptoms: name_key = s.get("name", "").lower() if isinstance(s, dict) else str(s).lower() for key, mapping in ICD10_SNOMED_MAP.items(): if key in name_key or name_key in key: candidate_diagnoses.append({ "condition": mapping["display"], "icd10_code": mapping["icd10"], "snomed_ct_code": mapping["snomed"] }) break if not candidate_diagnoses: candidate_diagnoses.append({ "condition": "General Symptomatic Intake", "icd10_code": "Z00.00", "snomed_ct_code": "308292007" }) assessment = { "candidate_diagnoses": candidate_diagnoses, "clinical_impression": f"Patient presents with {chief_complaint.lower()}. Triage status: {sess.status.upper()}." } # 4. Plan (P) drug_warnings = state_data.get("drug_safety_warnings", []) plan = { "recommended_specialty": sess.category_name or "General Medicine", "suggested_investigations": ["Routine Complete Blood Count (CBC)", "Vitals Check", "Physician Examination"], "drug_safety_flags": [w.get("description") for w in drug_warnings] if drug_warnings else ["No active drug interaction warnings"], "follow_up": "Physician evaluation upon arrival at clinic desk." } soap_note = { "session_id": session_id, "patient": { "id": sess.patient_id or "PAT-UNKNOWN", "name": sess.patient_name or "Patient", "gender": sess.gender or "unknown", "age": sess.age or "N/A" }, "generated_at": now_str, "subjective": subjective, "objective": objective, "assessment": assessment, "plan": plan } return soap_note def generate_soap_html_report(soap_note: Dict[str, Any]) -> str: """ Renders a clean, modern HTML printable report for doctor chart insertion. """ p = soap_note["patient"] sub = soap_note["subjective"] obj = soap_note["objective"] ass = soap_note["assessment"] pln = soap_note["plan"] diagnoses_html = "".join( f"
{d['icd10_code']} | SNOMED-CT: {d['snomed_ct_code']}Chief Complaint: {sub['chief_complaint']}
History of Present Illness:
Past Medical History: {', '.join(sub['past_medical_history'])}
Current Medications: {', '.join(sub['current_medications'])}
Allergies: {', '.join(sub['allergies'])}
Pain Scale: {obj['vitals']['pain_scale']} | Distress Level: {obj['acoustic_distress_level']}
Examination Type: {obj['examination_type']}
Clinical Impression: {ass['clinical_impression']}
Candidate Diagnoses & Coding:
Triage Specialty: {pln['recommended_specialty']}
Suggested Investigations: {', '.join(pln['suggested_investigations'])}
Drug Safety Flags: {', '.join(pln['drug_safety_flags'])}
Follow-Up: {pln['follow_up']}