preflight
Decides, before a run touches a cohort, whether the EHR can be reached at all.
An EHR that is configured but unreachable used to be discovered one patient at
a time: every lookup pays a connection timeout, logs its own warning, and the
AuthFailureBudget only intervenes for outright rejections, so a token that
cannot be obtained at all never trips it. On a six-figure cohort that is hours
spent learning something the first request already knew.
This module holds the give-up policy, in the same spirit as auth_budget and
for the same reason: one place decides, so the step and any other caller cannot
drift apart on what "unreachable" means. The probe itself belongs to
EHRDataResource, which owns the session and the base URL; this module only
decides how many times to ask and how long to wait between asks.
The policy is deliberately lenient. A single refusal is not evidence: a rejected token is retried with a forced refresh, since a server saying "no" to the cached token says nothing about a fresh one, and an unreachable endpoint is retried because connections drop. Only a failure that survives every attempt means the step is skipped.
The exception is a token that could not be obtained at all, which ends the probe on the first attempt. Fetching one carries retries of its own -- a token brokered by the desktop app costs three request cycles of ~60s before it gives up -- so retrying that verdict multiplies two budgets and spends minutes re-establishing what the first attempt already found.
Module
Functions
probe_capability_statement
def probe_capability_statement( base_url: str, token: str | None,) ‑> tuple[EHRAvailability, str | None]:Ask the FHIR server for its CapabilityStatement.
Deliberately a bare request rather than FHIRClient._request_capability_statement:
that path retries on config.settings.web_max_retries with a backoff capped
at 60 seconds, and a guard whose purpose is to decide quickly cannot inherit
that. Retrying is this module's own policy, on its own schedule.
Arguments
base_url: The FHIR base URL.token: The bearer token, orNonefor a server configured to take unauthenticated requests (allow_no_ehr_secrets).
Returns
The availability this one request establishes, and the error text to
log when it is not AVAILABLE.
run_preflight
def run_preflight( attempt: ProbeAttempt, *, attempts: int | None = None, initial_backoff: float | None = None, max_backoff: float | None = None, sleep: Callable[[float], None] = <built-in function sleep>,) ‑> EHRPreflightResult:Probe the EHR until it answers, it cannot be asked, or attempts run out.
Arguments
attempt: Makes one probe. Called withforce=Trueafter a rejected token, so the next probe carries a freshly-fetched one.attempts: How many probes to make, unless one reportsNO_TOKEN, which settles it on the spot. Defaults toconfig.settings.ehr_preflight_attempts.initial_backoff: Seconds to wait after the first failure, doubling thereafter. Defaults toconfig.settings.ehr_preflight_initial_backoff.max_backoff: Ceiling on that wait. Defaults toconfig.settings.ehr_preflight_max_backoff.sleep: Injected for tests, which must not spend the backoff.
Returns The verdict, carrying the last failure's text when it failed.
Raises
ValueError: If attempts is less than one. Zero would skip every run without ever asking the EHR anything.
Classes
EHRAvailability
class EHRAvailability(*args, **kwds):What the preflight found when it tried to reach the EHR.
Three ways of being unavailable rather than one, because they point an operator at different things: a credential, a network, or an endpoint.
Subclasses str so members serialise into run-status JSON without a
conversion step, as EHRRunOutcome does.
Ancestors
EHRPreflightResult
class EHRPreflightResult( availability: EHRAvailability, attempts: int, last_error: str | None = None,):What the preflight concluded, and what it cost to conclude it.
Attributes
availability: The verdict. Anything butAVAILABLEskips the step.attempts: Probes made, including the one that settled it.last_error: The final attempt's error text, for the log line that explains the skip.Nonewhen the EHR was available.
Variables
- static
attempts : int
- static
availability : EHRAvailability
- static
last_error : str | None
available : bool- Whether the run should query the EHR.