107 lines
3.6 KiB
Python
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
|