Skip to main content

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, or None for 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 with force=True after a rejected token, so the next probe carries a freshly-fetched one.
  • attempts: How many probes to make, unless one reports NO_TOKEN, which settles it on the spot. Defaults to config.settings.ehr_preflight_attempts.
  • initial_backoff: Seconds to wait after the first failure, doubling thereafter. Defaults to config.settings.ehr_preflight_initial_backoff.
  • max_backoff: Ceiling on that wait. Defaults to config.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.

Variables​

  • static AVAILABLE
  • static NO_TOKEN
  • static TOKEN_REJECTED
  • static UNREACHABLE

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 but AVAILABLE skips 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. None when the EHR was available.

Variables​

  • static attempts : int
  • static last_error : str | None
  • available : bool - Whether the run should query the EHR.