HH/apps/organizations/patient_lookup.py

107 lines
3.6 KiB
Python

"""
Patient auto-link service.
Given a national ID and/or phone number, try to find an existing Patient — first
locally, then via HIS — and return it so the calling record (complaint, inquiry,
suggestion) can link to it. Never creates a Patient from typed-only data; only
links an existing local patient or creates one from real HIS demographics.
This function never raises: on any failure (HIS unreachable, config missing,
unexpected error) it returns None, so record creation always proceeds with
patient = NULL rather than failing.
"""
import logging
logger = logging.getLogger(__name__)
def find_or_link_patient(national_id=None, phone=None, hospital=None):
"""Return a Patient that matches the given identifiers, or None.
Lookup order:
1. Local Patient table — by national_id_hash (exact), then by phone (exact).
2. HIS — if not found locally, query HIS by SSN/MobileNo; if a match is
returned, materialize it as a local Patient via HISAdapter and link it.
3. None — if neither local nor HIS yields a match (or HIS is unavailable).
Args:
national_id (str|None): Patient national ID / Iqama.
phone (str|None): Patient phone number.
hospital: Optional Hospital instance (used when creating from HIS data).
Returns:
Patient instance or None.
"""
national_id = (national_id or "").strip()
phone = (phone or "").strip()
if not national_id and not phone:
return None
# 1. Local lookup
patient = _lookup_local(national_id, phone)
if patient is not None:
return patient
# 2. HIS lookup (only if integration is configured and reachable)
patient = _lookup_his(national_id, phone, hospital)
return patient # may be None
def _lookup_local(national_id, phone):
from apps.core.encryption import compute_national_id_hash
from apps.organizations.models import Patient
if national_id:
nid_hash = compute_national_id_hash(national_id)
if nid_hash:
patient = Patient.objects.filter(national_id_hash=nid_hash, status="active").first()
if patient:
return patient
if phone:
patient = Patient.objects.filter(phone=phone, status="active").first()
if patient:
return patient
return None
def _lookup_his(national_id, phone, hospital):
"""Query HIS and, on a hit, materialize a local Patient from the demographics."""
try:
from apps.integrations.models import IntegrationConfig
from apps.integrations.services.his_adapter import HISAdapter
from apps.integrations.services.his_client import HISClient
except Exception:
# Integrations app unavailable — not configured.
return None
config = IntegrationConfig.objects.filter(source_system__in=["his", "other"]).first()
if not config:
return None
try:
client = HISClient(config)
patients = client.fetch_patient_by_identifier(ssn=national_id or None, mobile_no=phone or None)
except Exception as e:
logger.info("HIS patient lookup failed (network/config): %s", e)
return None
if not patients:
return None
# Use the first HIS match to get-or-create a local Patient.
his_patient = patients[0]
try:
his_hospital = HISAdapter.get_or_create_hospital(his_patient)
target_hospital = hospital or his_hospital
if target_hospital is None:
return None
return HISAdapter.get_or_create_patient(his_patient, target_hospital)
except Exception as e:
logger.warning("HIS patient materialization failed: %s", e)
return None