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 aGroupOutcomeperFetchGroupit fetches.ehr_query/functions.py's write guard uses "every supported group failed" (derived from thoseGroupOutcomes) as a predicate for whether a storedehr_datarow survives untouched, and populates theehr_data.errorcolumn with a patient-scopeEHRUnavailableReasonvalue.ehr_queryv3's result reports anEHRRunOutcomeon 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 isDEGRADED_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 absentehr_config: in a DAG run this is a pod that has anehr_configwhose credentials could not be built, whichflows/dag/setup.pydegrades toehr_data_resource=None. A pod with noehr_configat all never reaches this step —_step_resources(flows/dag/hashing.py) raisesEHRNotConfiguredErrorfirst — 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.
Ancestors
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 (noBitfountPatientIDand noPatient 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 supportedFetchGroupfailed, 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_UNAVAILABLEPROCEDURES_UNSUPPORTED/PROCEDURES_UNAVAILABLEMEDICATIONS_UNSUPPORTED/MEDICATIONS_UNAVAILABLEOBSERVATIONS_UNSUPPORTED/OBSERVATIONS_UNAVAILABLEALLERGIES_UNSUPPORTED/ALLERGIES_UNAVAILABLEDEVICES_UNSUPPORTED/DEVICES_UNAVAILABLEAPPOINTMENTS_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.
Ancestors
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 offpatient_querier.fhir_patient_info, which is already fetched as part of resolving the patient at all, not an independent fetch. It has no matchingEHRUnavailableReasonmember: if demographics are missing, the whole lookup failed and that is a patient-scope reason instead. -
CONDITIONS—condition_codes_json. -
PROCEDURES—procedure_codes_json. -
MEDICATIONS—medication_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, socodesnamed the encoding rather than the content. -
OBSERVATIONS—observation_codes_json, from two independently fetched calls (get_patient_observationsfor explicit(code_system, code)pairs,get_patient_observations_by_categoryfor FHIRobservation-categoryvalues) whose results are merged and deduplicated. One failing does not blank the other. -
ALLERGIES—allergy_codes_json, frompatient_querier.get_patient_allergies(), fetched unfiltered the same as conditions/procedures/medications. FHIR-only for now: NextGen raisesNextGenAllergiesNotImplementedError, so a NextGen-backed task reports this group_unsupportedrather than_unavailable. -
DEVICES—device_codes_json, frompatient_querier.get_patient_devices(), fetched unfiltered the same as allergies. FHIR-only for now: NextGen raisesNextGenDevicesNotImplementedError, so a NextGen-backed task reports this group_unsupportedrather than_unavailable. -
APPOINTMENTS—previous_appointments,previous_encountersandnext_appointment, each fetched independently so one backend failure does not blank the others.
Subclasses str so members serialise straight into reporting output.
Ancestors
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: WhichFetchGroupthis outcome describes.reason:Nonewhen the group was retrieved successfully. Otherwise the group-scopeEHRUnavailableReasonexplaining why it was not (an_unsupportedor_unavailablemember named aftergroup).
Variables
group : FetchGroup- Alias for field number 0
reason : EHRUnavailableReason | None- Alias for field number 1