Files
aionboarding/ai-patient-onboarding/app/services/fhir_exporter.py
T

187 lines
7.5 KiB
Python

import json
from datetime import datetime, timezone
from typing import Dict, Any, List, Optional
from uuid import uuid4
from sqlalchemy.orm import Session
from app.database.models import OnboardingSession, OnboardingState, OnboardingMessage
def export_session_to_fhir_bundle(session_id: str, db: Session) -> Dict[str, Any]:
"""
Exports a completed or in-progress onboarding session state into standard HL7 FHIR R4 Bundle.
Resources Generated:
1. Patient (Demographics)
2. Encounter (Clinical Intake Session)
3. Condition (Symptoms & Past Medical History)
4. MedicationStatement (Current Active Medications)
5. AllergyIntolerance (Confirmed Allergies)
"""
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 {}
now_iso = datetime.now(timezone.utc).isoformat()
patient_ref = f"Patient/{sess.patient_id or 'PAT-UNKNOWN'}"
encounter_id = f"enc-{session_id.lower()}"
entries: List[Dict[str, Any]] = []
# 1. FHIR Patient Resource
patient_resource = {
"resourceType": "Patient",
"id": sess.patient_id or "PAT-UNKNOWN",
"identifier": [{
"system": "http://hospital.org/patients",
"value": sess.patient_id or "PAT-UNKNOWN"
}],
"name": [{
"text": sess.patient_name or "Patient",
"family": (sess.patient_name or "Patient").split()[-1],
"given": (sess.patient_name or "Patient").split()[:-1] or [sess.patient_name or "Patient"]
}],
"gender": sess.gender if sess.gender in ["male", "female", "other"] else "unknown"
}
entries.append({"resource": patient_resource})
# 2. FHIR Encounter Resource
encounter_resource = {
"resourceType": "Encounter",
"id": encounter_id,
"status": "finished" if sess.status == "completed" else "in-progress",
"class": {
"system": "http://terminology.hl7.org/CodeSystem/v3-ActCode",
"code": "AMB",
"display": "ambulatory"
},
"type": [{
"coding": [{
"system": "http://snomed.info/sct",
"code": "32485007",
"display": "Hospital admission intake"
}],
"text": sess.category_name or "General Intake"
}],
"subject": {"reference": patient_ref},
"period": {
"start": sess.created_at.isoformat() if sess.created_at else now_iso
}
}
entries.append({"resource": encounter_resource})
# 3. FHIR Condition Resources (Symptoms)
symptoms = state_data.get("symptoms", [])
if isinstance(symptoms, list):
for idx, sym in enumerate(symptoms):
if isinstance(sym, dict) and sym.get("name"):
cond_resource = {
"resourceType": "Condition",
"id": f"cond-sym-{idx+1}-{uuid4().hex[:4]}",
"clinicalStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "active"
}]
},
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-category",
"code": "encounter-diagnosis",
"display": "Encounter Diagnosis"
}]
}],
"code": {
"text": sym["name"]
},
"subject": {"reference": patient_ref},
"encounter": {"reference": f"Encounter/{encounter_id}"},
"onsetDateTime": now_iso,
"note": [{"text": f"Duration: {sym.get('duration', 'N/A')}, Severity: {sym.get('severity', 'N/A')}"}]
}
entries.append({"resource": cond_resource})
# 4. FHIR Condition Resources (Medical History)
history = state_data.get("medical_history", [])
if isinstance(history, list):
for idx, hist in enumerate(history):
cond_name = hist.get("condition") if isinstance(hist, dict) else str(hist)
if cond_name:
cond_resource = {
"resourceType": "Condition",
"id": f"cond-hist-{idx+1}-{uuid4().hex[:4]}",
"clinicalStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-clinical",
"code": "active"
}]
},
"category": [{
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/condition-category",
"code": "problem-list-item",
"display": "Problem List Item"
}]
}],
"code": {
"text": cond_name
},
"subject": {"reference": patient_ref}
}
entries.append({"resource": cond_resource})
# 5. FHIR MedicationStatement Resources
medications = state_data.get("medications", [])
if isinstance(medications, list):
for idx, med in enumerate(medications):
med_name = med.get("name") if isinstance(med, dict) else str(med)
dosage_str = med.get("dosage", "") if isinstance(med, dict) else ""
if med_name:
med_resource = {
"resourceType": "MedicationStatement",
"id": f"med-{idx+1}-{uuid4().hex[:4]}",
"status": "active",
"medicationCodeableConcept": {
"text": med_name
},
"subject": {"reference": patient_ref},
"dosage": [{"text": dosage_str}] if dosage_str else []
}
entries.append({"resource": med_resource})
# 6. FHIR AllergyIntolerance Resources
allergies = state_data.get("allergies", [])
if isinstance(allergies, list):
for idx, alg in enumerate(allergies):
alg_name = alg.get("name") if isinstance(alg, dict) else str(alg)
if alg_name:
alg_resource = {
"resourceType": "AllergyIntolerance",
"id": f"alg-{idx+1}-{uuid4().hex[:4]}",
"clinicalStatus": {
"coding": [{
"system": "http://terminology.hl7.org/CodeSystem/allergyintolerance-clinical",
"code": "active"
}]
},
"code": {
"text": alg_name
},
"patient": {"reference": patient_ref}
}
entries.append({"resource": alg_resource})
# Assemble FHIR R4 Bundle
fhir_bundle = {
"resourceType": "Bundle",
"id": f"bundle-{session_id.lower()}",
"type": "collection",
"timestamp": now_iso,
"total": len(entries),
"entry": entries
}
return fhir_bundle