Skip to main content

types

Vocabulary for EHR best-effort serving.

EHRUnavailableReason, FetchGroup and GroupOutcome are the pure vocabulary the EHR best-effort-serving project is built on:

  • EHRDataResource._get_patient_resource (bitfount.steps.protocols.ehr) records a GroupOutcome per FetchGroup it fetches.
  • ehr_query/functions.py's write guard uses "every supported group failed" (derived from those GroupOutcomes) as a predicate for whether a stored ehr_data row survives untouched, and populates the ehr_data.error column with a patient-scope EHRUnavailableReason value.
  • ehr_query v3's result reports an EHRRunOutcome on the run status, and classifies it by splitting the patient-scope reasons on whether the EHR answered at all (NON_ANSWER_PATIENT_SCOPE_REASONS).

This module depends on nothing else under steps/, so all three can share it without import cycles.

Classes

EHRRunOutcome

class EHRRunOutcome(*args, **kwds):

How well one ehr_query run primed the EHR serving layer.

Run-scoped, and about the EHR rather than about any one patient: a patient whose own lookup failed is recorded in that patient's ehr_data row (error), or, where no row can carry it, in the run-status skip counters.

  • PRIMED — the pass had a cohort and no patient it attempted went unanswered. It does not claim the EHR answered for anybody: a run whose every row was skipped for lacking a query key or a splittable name attempted no lookup, has no non-answer to report, and lands here (those rows are reported by the skip counters and their own warning). Every patient turning out not to be in the EHR still counts as answered: that is an answer, not an absence. A pass with no cohort is DEGRADED_TOTAL, not this: having nothing to reject is not the same as rejecting it.
  • DEGRADED_PARTIAL — some patients' data was fetched and stored, and others got no answer at all (the EHR refused them, their lookup broke, or the pass was cut short before reaching them).
  • DEGRADED_TOTAL — no patient's record was reached, so every downstream read is served from whatever earlier runs stored. Also where a pass with no cohort at all lands: its cause is upstream of the EHR (an empty file inventory), but a serving layer nothing was written to reads the same either way, and reporting it primed would claim otherwise.
  • NOT_CONFIGURED — no EHR resource reached the step, so nothing was queried. Named for the absent resource, not for an absent ehr_config: in a DAG run this is a pod that has an ehr_config whose credentials could not be built, which flows/dag/setup.py degrades to ehr_data_resource=None. A pod with no ehr_config at all never reaches this step — _step_resources (flows/dag/hashing.py) raises EHRNotConfiguredError first — so the step cannot distinguish the two and does not claim to.

The degraded pair is decided by NON_ANSWER_PATIENT_SCOPE_REASONS, not by whether an exception escaped: an EHR that is down produces per-patient non-answers without ever raising out of the query loop.

Subclasses str so members serialise straight into run-status JSON and the eligibility pointer's tags without a conversion step.

Variables

  • static DEGRADED_PARTIAL
  • static DEGRADED_TOTAL
  • static NOT_CONFIGURED
  • static PRIMED

EHRUnavailableReason

class EHRUnavailableReason(*args, **kwds):

Why a patient, or one fetch group of a found patient, has no usable EHR data.

Two scopes, in increasing order of granularity, and both describe one patient — their whole lookup, or one group within it. How the run went is EHRRunOutcome, a separate vocabulary on a separate channel; no member here describes a whole-run condition, and none could be stored if it did: a missing ehr_data row cannot self-report why it is missing, and a stored row was written by a run whose fetch succeeded.

Patient scope — the EHR was reachable, but this one patient's whole lookup failed, inside ehr_query's per-row loop (ehr_query/functions.py), before any FetchGroup was reached.

  • NOT_IN_EHR — the name/DOB search matched no patient (NoMatchingPatientError).
  • AMBIGUOUS_MATCH — the name/DOB search matched more than one patient and which is meant cannot be determined (NonSpecificPatientError).
  • INCOMPLETE_NAME — the source name has no separable given/family components to search on.
  • UNIDENTIFIED — the row has no query key (no BitfountPatientID and no Patient ID) to look up at all.
  • AUTH_REJECTED — the EHR rejected the request as unauthenticated (FHIRR4AuthenticationError).
  • FETCH_FAILED — the lookup raised for any other reason, or it returned a patient whose every supported FetchGroup failed, so the record it produced holds demographics and no clinical data.

A patient-scope reason is only storable in ehr_data.error when a bitfount_patient_id — the table's primary key — is derivable at that site. It is for NOT_IN_EHR, AMBIGUOUS_MATCH, AUTH_REJECTED and FETCH_FAILED, all raised after the id is resolved. It is not for UNIDENTIFIED and INCOMPLETE_NAME, raised before any id exists to key a row on; those two stay in this vocabulary but are reported only through the run-status counters skipped_unidentified / skipped_incomplete_name.

Group scope — the patient was found, but one FetchGroup within their lookup failed independently of the others (see _get_patient_resource's per-group fetch isolation, and FetchGroup's docstring for which fields each group covers). Each of conditions, procedures, medications, observations, allergies, devices and appointments has an _unsupported / _unavailable pair; demographics has neither (see FetchGroup). _unsupported is a backend that structurally lacks the endpoint — a configuration problem, sticky across runs. _unavailable is a fetch that was attempted and failed this time — an outage, not sticky. The two are told apart by the exception the fetch raised: EHRFetchUnsupportedError (externals/ehr/exceptions.py) is the first, anything else the second.

  • CONDITIONS_UNSUPPORTED / CONDITIONS_UNAVAILABLE
  • PROCEDURES_UNSUPPORTED / PROCEDURES_UNAVAILABLE
  • MEDICATIONS_UNSUPPORTED / MEDICATIONS_UNAVAILABLE
  • OBSERVATIONS_UNSUPPORTED / OBSERVATIONS_UNAVAILABLE
  • ALLERGIES_UNSUPPORTED / ALLERGIES_UNAVAILABLE
  • DEVICES_UNSUPPORTED / DEVICES_UNAVAILABLE
  • APPOINTMENTS_UNSUPPORTED / APPOINTMENTS_UNAVAILABLE

Settled vs transient: NOT_IN_EHR, INCOMPLETE_NAME and UNIDENTIFIED describe a patient whom re-running the same query will not resolve differently — the source data itself is insufficient or the EHR has already given a definitive answer. Every other patient-scope reason may improve on the next attempt with nothing else changing. Neither bucket makes a claim beyond that horizon: a patient not in the EHR today can be enrolled tomorrow. See SETTLED_PATIENT_SCOPE_REASONS / TRANSIENT_PATIENT_SCOPE_REASONS for the programmatic partition.

Subclasses str so members serialise straight into ehr_data.error and run-status JSON without a conversion step.

Variables

  • static ALLERGIES_UNAVAILABLE
  • static ALLERGIES_UNSUPPORTED
  • static AMBIGUOUS_MATCH
  • static APPOINTMENTS_UNAVAILABLE
  • static APPOINTMENTS_UNSUPPORTED
  • static AUTH_REJECTED
  • static CONDITIONS_UNAVAILABLE
  • static CONDITIONS_UNSUPPORTED
  • static DEVICES_UNAVAILABLE
  • static DEVICES_UNSUPPORTED
  • static FETCH_FAILED
  • static INCOMPLETE_NAME
  • static MEDICATIONS_UNAVAILABLE
  • static MEDICATIONS_UNSUPPORTED
  • static NOT_IN_EHR
  • static OBSERVATIONS_UNAVAILABLE
  • static OBSERVATIONS_UNSUPPORTED
  • static PROCEDURES_UNAVAILABLE
  • static PROCEDURES_UNSUPPORTED
  • static UNIDENTIFIED

FetchGroup

class FetchGroup(*args, **kwds):

The fetch boundaries inside EHRDataResource._get_patient_resource.

Used for per-group outcome reporting and by the write guard's "every supported group failed" predicate; not a serving unit — a patient lookup either succeeds or fails as a whole (the EHRPatientResource it produces), and these groups only describe which part of that resource is populated.

  • DEMOGRAPHICS — names, date of birth, gender, MRN, address, contacts. Read straight off patient_querier.fhir_patient_info, which is already fetched as part of resolving the patient at all, not an independent fetch. It has no matching EHRUnavailableReason member: if demographics are missing, the whole lookup failed and that is a patient-scope reason instead.

  • CONDITIONScondition_codes_json.

  • PROCEDURESprocedure_codes_json.

  • MEDICATIONSmedication_codes_json.

    Those three come from one patient_querier.get_patient_code_states(include_medications=True) call, which guards each of its sub-calls separately, so each list fails independently and any of them can be blank while the others hold data; all three are blank together only when the call itself raises. They are three members rather than one "codes" member for exactly that reason — a group is a failure boundary, and these three do not share one. It is also the only naming that says anything: every EHR value here is a code, so codes named the encoding rather than the content.

  • OBSERVATIONSobservation_codes_json, from two independently fetched calls (get_patient_observations for explicit (code_system, code) pairs, get_patient_observations_by_category for FHIR observation-category values) whose results are merged and deduplicated. One failing does not blank the other.

  • ALLERGIESallergy_codes_json, from patient_querier.get_patient_allergies(), fetched unfiltered the same as conditions/procedures/medications. FHIR-only for now: NextGen raises NextGenAllergiesNotImplementedError, so a NextGen-backed task reports this group _unsupported rather than _unavailable.

  • DEVICESdevice_codes_json, from patient_querier.get_patient_devices(), fetched unfiltered the same as allergies. FHIR-only for now: NextGen raises NextGenDevicesNotImplementedError, so a NextGen-backed task reports this group _unsupported rather than _unavailable.

  • APPOINTMENTSprevious_appointments, previous_encounters and next_appointment, each fetched independently so one backend failure does not blank the others.

Subclasses str so members serialise straight into reporting output.

Variables

  • static ALLERGIES
  • static APPOINTMENTS
  • static CONDITIONS
  • static DEMOGRAPHICS
  • static DEVICES
  • static MEDICATIONS
  • static OBSERVATIONS
  • static PROCEDURES

GroupOutcome

class GroupOutcome(    group: ForwardRef(''codeBlockAnchor[FetchGroup](/api/bitfount/steps/ehr_query/types#fetchgroup)''), reason: ForwardRef(''codeBlockAnchor[EHRUnavailableReason](/api/bitfount/steps/ehr_query/types#ehrunavailablereason)' | None'),):

One FetchGroup's outcome for one patient's lookup.

Attributes

  • group: Which FetchGroup this outcome describes.
  • reason: None when the group was retrieved successfully. Otherwise the group-scope EHRUnavailableReason explaining why it was not (an _unsupported or _unavailable member named after group).

Variables