result
Result for the EHR query step (v3).
Classes
EHRQueryResult
class EHRQueryResult(**data: Any):Result of a best-effort EHR criteria query task.
records_stored reports how many EHR rows this run persisted to the cache;
cache exposes the CacheAccessor over the partition for downstream DAG
steps. The accessor is present even when this run stored nothing — with it
unwired, criteria_matching has no EHR columns to tag and reports EHR
criteria as scan evidence with provenance unknown.
The remaining fields exist because a run that could not reach the EHR is
otherwise indistinguishable from a quiet success: the write guard withholds
a failure record rather than overwriting a stored row, so the attempt leaves
no trace in ehr_data at all.
lister_truncated is deliberately absent: the truncation it reports belongs
to ehr_patient_lister, a separate step, and this step's template wires no
lister at all.
Attributes
run_outcome: How well the run primed the serving layer. SeeEHRRunOutcome. Required, with no default: a result that did not say how the run went must not be readable as a healthy prime.skipped_total_failure: Patients whose lookup produced nothing usable this run and whose failure record the write guard withheld in favour of a stored row. Those patients are served from a row as old as the last run that reached the EHR for them, and nothing in the table records this run's attempt. A failure the guard did write is not counted here — it is readable as that row'serror.skipped_unidentified: Rows carrying nothing to look a patient up by. No EHR query was made for them, and no row can record that: the reason fires before abitfount_patient_id— theehr_dataprimary key — exists to key one on.skipped_incomplete_name: Rows whose name did not yield both a given and a family name. Reported here for the same reason.skipped_unparseable_dob: Rows whose date of birth could not be parsed. Reported here for the same reason.groups_unsupported: The fetch groups this backend reported as structurally lacking, unioned over the patients queried. Sourced from the in-memory fetch outcomes, sinceehr_data.errorholds one patient-scope reason and cannot represent per-group state. Sticky across runs — a configuration finding, not an outage.
Create a new model by parsing and validating input data from keyword arguments.
Raises [ValidationError][pydantic_core.ValidationError] if the input data cannot be validated to form a valid model.
self is explicitly positional-only to allow self as a field name.
Ancestors
Variables
- static
groups_unsupported : tuple[EHRUnavailableReason, ...]
- static
model_config
- static
run_outcome : EHRRunOutcome
- static
skipped_incomplete_name : int
- static
skipped_total_failure : int
- static
skipped_unidentified : int
- static
skipped_unparseable_dob : int
cache : CacheAccessor | None- Return theCacheAccessorfor reading this result in a DAG pipeline.
-
records : pandas.core.frame.DataFrame- Retrieve this result's rows as a DataFrame.The DataFrame is fetched from the cache on each access — it is not stored in the result object, allowing lazy access to large datasets without materialising them in memory until needed.
Returns: A
pandas.DataFramewith one row per cached record.Raises: RuntimeError: If no accessor is attached (e.g. the result was serialised across a Prefect task boundary). In a DAG pipeline use
<step_name>.cacheinstead.
Static methods
from_accessor
def from_accessor( accessor: CacheAccessor, *, records_stored: int | None = None, run_outcome: EHRRunOutcome = not_configured, skipped_total_failure: int = 0, skipped_unidentified: int = 0, skipped_incomplete_name: int = 0, skipped_unparseable_dob: int = 0, groups_unsupported: tuple[EHRUnavailableReason, ...] = (),) ‑> Self:Build a result carrying accessor and this run's status.
Widens the base classmethod with the run-status fields, so the task still builds its result in one call rather than constructing it and reaching in to attach the accessor.
Arguments
accessor: TheCacheAccessorbacking<step>.cache, scoped to this run's partition. Passed even when the run stored no rows.records_stored: Rows this run persisted. Defaults to the whole partition's count, per the base class.run_outcome: How well the run primed the serving layer. Defaults toNOT_CONFIGUREDrather thanPRIMED, so the generic cross-phase reconstruction inflows/dag/context.py— which cannot know how the background run went — cannot claim a healthy prime on its behalf.skipped_total_failure: Failure records the write guard withheld.skipped_unidentified: Rows with no usable query key.skipped_incomplete_name: Rows with an unsplittable name.skipped_unparseable_dob: Rows with an unparseable date of birth.groups_unsupported: Fetch groups the backend structurally lacks.
Returns The result, with the accessor attached.
Methods
model_post_init
def model_post_init(self: BaseModel, context: Any, /) ‑> None:Inherited from:
CacheBackedResult.model_post_init :
This function is meant to behave like a BaseModel method to initialise private attributes.
It takes context as an argument since that's what pydantic-core passes when calling it.
Arguments
self: The BaseModel instance.context: The context.