|
|
import json |
|
|
from datetime import datetime |
|
|
from typing import List, Dict, Optional |
|
|
|
|
|
class PatientDataExtractor: |
|
|
"""Class to extract patient data from a FHIR Bundle response and map it to discharge form fields.""" |
|
|
|
|
|
def __init__(self, patient_data: str): |
|
|
"""Initialize with patient data in JSON string format.""" |
|
|
self.data = json.loads(patient_data) if isinstance(patient_data, str) else patient_data |
|
|
self.patients = self._extract_patients() |
|
|
self.current_patient_idx = 0 |
|
|
|
|
|
def _extract_patients(self) -> List[Dict]: |
|
|
"""Extract all patient entries from the Bundle.""" |
|
|
if self.data.get("resourceType") != "Bundle" or "entry" not in self.data: |
|
|
raise ValueError("Invalid FHIR Bundle format") |
|
|
return [entry["resource"] for entry in self.data["entry"] if entry["resource"]["resourceType"] == "Patient"] |
|
|
|
|
|
def set_patient_by_index(self, index: int) -> bool: |
|
|
"""Set the current patient by index. Returns True if successful.""" |
|
|
if 0 <= index < len(self.patients): |
|
|
self.current_patient_idx = index |
|
|
return True |
|
|
return False |
|
|
|
|
|
def set_patient_by_id(self, patient_id: str) -> bool: |
|
|
"""Set the current patient by FHIR Patient ID. Returns True if successful.""" |
|
|
for i, patient in enumerate(self.patients): |
|
|
if patient["id"] == patient_id: |
|
|
self.current_patient_idx = i |
|
|
return True |
|
|
return False |
|
|
|
|
|
def _get_current_patient(self) -> Dict: |
|
|
"""Get the currently selected patient resource.""" |
|
|
return self.patients[self.current_patient_idx] |
|
|
|
|
|
def get_first_name(self) -> str: |
|
|
"""Extract patient's first name.""" |
|
|
patient = self._get_current_patient() |
|
|
names = patient.get("name", []) |
|
|
for name in names: |
|
|
if name.get("use") == "official" and "given" in name: |
|
|
return name["given"][0] |
|
|
return "" |
|
|
|
|
|
def get_last_name(self) -> str: |
|
|
"""Extract patient's last name.""" |
|
|
patient = self._get_current_patient() |
|
|
names = patient.get("name", []) |
|
|
for name in names: |
|
|
if name.get("use") == "official" and "family" in name: |
|
|
return name["family"] |
|
|
return "" |
|
|
|
|
|
def get_middle_initial(self) -> str: |
|
|
"""Extract patient's middle initial (second given name initial if present).""" |
|
|
patient = self._get_current_patient() |
|
|
names = patient.get("name", []) |
|
|
for name in names: |
|
|
if name.get("use") == "official" and "given" in name and len(name["given"]) > 1: |
|
|
return name["given"][1][0] |
|
|
return "" |
|
|
|
|
|
def get_dob(self) -> str: |
|
|
"""Extract patient's date of birth.""" |
|
|
patient = self._get_current_patient() |
|
|
return patient.get("birthDate", "") |
|
|
|
|
|
def get_age(self) -> str: |
|
|
"""Calculate patient's age based on birth date.""" |
|
|
dob = self.get_dob() |
|
|
if not dob: |
|
|
return "" |
|
|
birth_date = datetime.strptime(dob, "%Y-%m-%d") |
|
|
today = datetime.now() |
|
|
age = today.year - birth_date.year - ((today.month, today.day) < (birth_date.month, birth_date.day)) |
|
|
return str(age) |
|
|
|
|
|
def get_sex(self) -> str: |
|
|
"""Extract patient's sex (gender).""" |
|
|
patient = self._get_current_patient() |
|
|
return patient.get("gender", "").capitalize() |
|
|
|
|
|
def get_address(self) -> str: |
|
|
"""Extract patient's street address.""" |
|
|
patient = self._get_current_patient() |
|
|
addresses = patient.get("address", []) |
|
|
return addresses[0]["line"][0] if addresses and "line" in addresses[0] else "" |
|
|
|
|
|
def get_city(self) -> str: |
|
|
"""Extract patient's city.""" |
|
|
patient = self._get_current_patient() |
|
|
addresses = patient.get("address", []) |
|
|
return addresses[0]["city"] if addresses and "city" in addresses[0] else "" |
|
|
|
|
|
def get_state(self) -> str: |
|
|
"""Extract patient's state.""" |
|
|
patient = self._get_current_patient() |
|
|
addresses = patient.get("address", []) |
|
|
return addresses[0]["state"] if addresses and "state" in addresses[0] else "" |
|
|
|
|
|
def get_zip_code(self) -> str: |
|
|
"""Extract patient's postal code.""" |
|
|
patient = self._get_current_patient() |
|
|
addresses = patient.get("address", []) |
|
|
return addresses[0]["postalCode"] if addresses and "postalCode" in addresses[0] else "" |
|
|
|
|
|
def get_patient_dict(self) -> Dict[str, str]: |
|
|
"""Return a dictionary of patient data mapped to discharge form fields.""" |
|
|
return { |
|
|
"first_name": self.get_first_name(), |
|
|
"last_name": self.get_last_name(), |
|
|
"middle_initial": self.get_middle_initial(), |
|
|
"dob": self.get_dob(), |
|
|
"age": self.get_age(), |
|
|
"sex": self.get_sex(), |
|
|
"address": self.get_address(), |
|
|
"city": self.get_city(), |
|
|
"state": self.get_state(), |
|
|
"zip_code": self.get_zip_code(), |
|
|
|
|
|
"doctor_first_name": "", |
|
|
"doctor_last_name": "", |
|
|
"doctor_middle_initial": "", |
|
|
"hospital_name": "", |
|
|
"doctor_address": "", |
|
|
"doctor_city": "", |
|
|
"doctor_state": "", |
|
|
"doctor_zip": "", |
|
|
"admission_date": "", |
|
|
"referral_source": "", |
|
|
"admission_method": "", |
|
|
"discharge_date": "", |
|
|
"discharge_reason": "", |
|
|
"date_of_death": "", |
|
|
"diagnosis": "", |
|
|
"procedures": "", |
|
|
"medications": "", |
|
|
"preparer_name": "", |
|
|
"preparer_job_title": "" |
|
|
} |
|
|
|
|
|
def get_all_patients(self) -> List[Dict[str, str]]: |
|
|
"""Return a list of dictionaries for all patients.""" |
|
|
original_idx = self.current_patient_idx |
|
|
all_patients = [] |
|
|
for i in range(len(self.patients)): |
|
|
self.set_patient_by_index(i) |
|
|
all_patients.append(self.get_patient_dict()) |
|
|
self.set_patient_by_index(original_idx) |
|
|
return all_patients |
|
|
|
|
|
def get_patient_ids(self) -> List[str]: |
|
|
"""Return a list of all patient IDs in the Bundle.""" |
|
|
return [patient["id"] for patient in self.patients] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|