functions
Pure helpers for the patient_level_eligibility step.
Patient-identity construction from the EHR cache (falling back to the
evaluation's SubjectIdentity), used by patient_level_eligibility_task, kept
apart from the task orchestration. No I/O.
Module
Functions
build_ehr_lookup
def build_ehr_lookup( ehr_patient_data: CacheAccessor | None,) ‑> dict[str, dict[str, typing.Any]]:Map bitfount_patient_id → EHR identity fields from the EHR cache.
Arguments
ehr_patient_data: Optional EHR cache accessor.
Returns
A lookup keyed by Bitfount patient ID (empty if no EHR data), each
value a {ehr_patient_id, name, mrns, practitioner_name, processed_at, error} dict.
display_name_from_scan_name
def display_name_from_scan_name( scan_name: str,) ‑> DisplayName:Format a scan-sourced patient name for display.
Imaging sources record the name in whatever form their vendor uses — DICOM
stores Family^Given^Middle^Prefix^Suffix, commonly padded to
SMITH^JANE^^^. That form reaches the patient-data API verbatim unless it
is formatted here, and SMITH^JANE^^^ is not a name anyone recognises.
Reduces the name to "Given Family". Returns the original string unchanged
when it cannot be parsed, so an unusual name is still shown rather than
replaced with something emptier. That case reports UNPARSED, which ranks
below every source that did yield a name.
Reports the provenance alongside it, because how much the result can be
trusted is decided entirely by what went in. A name still carrying its ^
delimiters states which component is the family name, and split_full_name
reads it through pydicom. A name without them does not, and the split falls
back to guessing Western order — right for JANE SMITH, wrong for a
flattened SMITH JANE MRS, and nothing in the string distinguishes the
two. Anything that stores the result needs to know which of those happened,
so it travels with the name rather than being re-derived from it later.
Arguments
scan_name: The name as recorded on the scan.
Returns The display form of the name, and its provenance.
ehr_retrieved_at_for
def ehr_retrieved_at_for( bitfount_patient_id: str, ehr_lookup: dict[str, dict[str, Any]],) ‑> datetime.datetime | None:The value for one patient's patient_level_eligibility.ehr_retrieved_at.
Sourced from the served EHR row's own processed_at (via ehr_lookup,
from build_ehr_lookup) rather than a fresh timestamp: the point is when
the patient's EHR data was retrieved, which — under best-effort serving —
may be long before this run, not this run's own time.
A row whose error is set records a lookup that retrieved nothing — its
processed_at is the run that wrote the row, not a moment at which any EHR
data was retrieved. There is no retrieval time for such a patient, so this
reports None: cache/types/patient_level_eligibility/v2/schema.py already
declares NULL to mean "no EHR data at all", and reporting the write time
instead would read fresher than the truth for the one population the field
exists to flag. A row whose every countable fetch group failed carries
error too, and reports None on the same grounds — it holds demographics
but no clinical data for a verdict to rest on.
The lookup value comes off a CacheAccessor DataFrame, so it arrives as
either the stored ISO-8601 string or an already-parsed timestamp; both are
accepted. An unparseable value is dropped rather than stored, because the
column is typed and NULL already carries the right meaning for it.
Arguments
bitfount_patient_id: The patient's Bitfount ID.ehr_lookup: EHR identity lookup (frombuild_ehr_lookup).
Returns
The stored retrieval time, or None — no EHR accessor, no row for this
patient, a row recording a failed lookup, a row with no recorded
processed_at, or one whose recorded value cannot be read as a
timestamp. Every one of those is "not recorded".
identity_for
def identity_for( identity: SubjectIdentity, bitfount_patient_id: str, ehr_lookup: dict[str, dict[str, Any]], now: datetime, task_hash: str, run_id: str | None,) ‑> PatientRecord:Build a patient identity record from EHR data or the evaluation's identity.
The display name is resolved EHR name → the evaluation's identity.name →
the bare bitfount_patient_id. That last resort is an undesirable state
(a named-less patient) and emits a warning.
The EHR name is already split into parts by build_ehr_lookup; the
evaluation's name comes off the scan in its vendor's own form, so it goes
through display_name_from_scan_name first.
Each of the three routes also states its name_provenance, and the scan
route carries the vendor's own string through as name_raw. Neither is for
this record's own use: patients is unpartitioned, so this row is shared
with every other run that reaches the patient — over another datasource, or
under another project — and the store needs to know which of two runs'
names came from the better source. The EHR route stores no name_raw
because there was no string to parse; the name arrived already split into
its own fields, and so did not go through the step that can get it wrong.
Arguments
identity: The subject identity carried on the evaluation (name/MRN fallback).bitfount_patient_id: The patient's ID, which the caller has already established is present. Taken as its own argument rather than read offidentity, whose own field is optional: aPatientRecordkeys on this, so there is no record to build without it, and passing it in puts that requirement in the signature instead of leaving this function to re-check what its caller has already checked.ehr_lookup: EHR identity lookup (frombuild_ehr_lookup).now: The write timestamp.task_hash: Provenance task hash.run_id: Provenance run ID.
Returns
A PatientRecord for the patient.
Classes
DisplayName
class DisplayName(display: ForwardRef('str'), provenance: ForwardRef('NameProvenance')):A rendered display name and what it was rendered from.
Attributes
display: The name to show.provenance: The kind of sourcedisplaywas derived from, which is whatpatients.bulk_upsert_patientsranks one run's name against another's.
Variables
display : str- Alias for field number 0
provenance : NameProvenance- Alias for field number 1