Skip to main content

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 (from build_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 off identity, whose own field is optional: a PatientRecord keys 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 (from build_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 source display was derived from, which is what patients.bulk_upsert_patients ranks one run's name against another's.

Variables​

  • display : str - Alias for field number 0