Skip to main content

eligibility_functions

Pure logic shared by the scan/ehr/patient_eligibility steps.

Derives per-criterion evidence and per-scan / per-patient eligibility status. build_scan_evidence, build_patient_level_evidence and roll_up_patient all consume criteria_matching's structured CriteriaEvaluation output (directly, or via the stored ScanVerdict evidence it produces). No I/O.

Vocabulary:

  • Criterion state (CriterionState): pass / fail / unknown / na (na = not applicable to a scan, i.e. a patient-level criterion).
  • Row status (scan / patient): EligibilityStatuseligible / ineligible / unknown.

Module

Functions

all_criteria_met

def all_criteria_met(    statuses: Iterable[CriterionState],)> bitfount.cache.types._eligibility_shared.EligibilityStatus:

Strict combine over criterion states -> a row status.

na states are ignored. Any fail -> ineligible; else any unknown -> unknown; else eligible (including the empty/all-na case).

Arguments

  • statuses: Criterion states (pass/fail/unknown/na).

Returns The rolled-up EligibilityStatus.

all_criteria_met_criterion

def all_criteria_met_criterion(    states: Iterable[CriterionState],)> CriterionState:

Combine two criterion states for one merged field -> a criterion state.

Unlike all_criteria_met (which returns a row status), this stays in the criterion vocabulary: any fail -> fail; else any unknown -> unknown; else pass.

Arguments

  • states: Criterion states to combine.

Returns A criterion state (pass/fail/unknown).

any_scan_qualifies

def any_scan_qualifies(    scan_statuses: Iterable[EligibilityStatus],)> bitfount.cache.types._eligibility_shared.EligibilityStatus:

Lenient combine over scan statuses -> a patient status.

Any eligible -> eligible; else any unknown -> unknown; else ineligible. Empty -> unknown.

Arguments

  • scan_statuses: Per-scan statuses (eligible/ineligible/unknown).

Returns The rolled-up EligibilityStatus.

build_patient_level_evidence

def build_patient_level_evidence(    evaluation: CriteriaEvaluation,)> tuple[dict[str, bitfount.cache.types._eligibility_shared.CriterionEvidence], bitfount.cache.types._eligibility_shared.EligibilityStatus]:

Build patient-level evidence + its local status from one evaluation.

Named for what it now covers. This was build_ehr_evidence while ehr was the only non-imaging provenance; supplied is a second one, and folding it in here rather than inventing a third evidence table is why the name had to change. It feeds PatientLevelEligibilityRecord, which was always named for the grain rather than the source, so the table needed no rename.

Emits an entry for each patient-grain criterion whose data did not come from imaging: EHR age, code lists, and unlateralised supplied observations. Scan-provenance patient-grain age is surfaced by the scan step, not here, and so is a lateralised supplied observation — that is scan-grain, because the study eye is resolved per scan (see build_scan_evidence).

The returned status is all_criteria_met over this step's own entries only. It is local to this step, and it now spans two sources rather than one: an EHR code list and a supplied BCVA both gate it. The patient_eligibility reducer, not this status, computes the all-patient-grain verdict from the merged tagged evidence (so scan-sourced age still gates).

Arguments

  • evaluation: A representative evaluation for the patient (patient-level criteria are constant across a patient's scans).

Returns (criteria, status) over the patient-grain, non-imaging criteria only.

build_scan_evidence

def build_scan_evidence(    evaluation: CriteriaEvaluation,)> tuple[dict[str, bitfount.cache.types._eligibility_shared.CriterionEvidence], bitfount.cache.types._eligibility_shared.EligibilityStatus]:

Build scan-provenance evidence + the scan-grain status from one evaluation.

Scoped mostly by provenance (scan), spanning both grains: scan-grain GA metrics and scan-provenance patient-grain criteria (DICOM age). The returned status is by grain: all_criteria_met over scan-grain entries only, so a patient-grain criterion (age) never gates a per-scan verdict (the reducer gates on it by grain).

supplied criteria are partitioned by grain instead, and that is the one asymmetry in this partition. A supplied measurement is not imaging-derived, so on provenance alone it belongs with the EHR criteria — but a lateralised one (laterality.side != "either", hence grain == SCAN) is a per-eye question and the study eye is resolved per scan, so it is kept here and gates scan_status. An unlateralised supplied criterion is patient-grain and goes to build_patient_level_evidence.

That asymmetry exists to close the gating hole this docstring used to warn about. Partitioning purely by provenance lets a grain == SCAN, not-scan-provenance criterion fall through both steps: skipped here as non-scan, and excluded from the reducer's patient-grain sum as scan-grain — gating NOTHING, which reads downstream as passing. A lateralised supplied observation criterion is exactly that shape, and it is the primary enrichment use case (study-eye BCVA), so the hole stopped being hypothetical and had to be closed rather than documented.

The grain == SCAN, provenance == EHR shape still does not occur — every EHR filter is patient-grain — but the predicate below partitions it by grain too, so introducing one no longer silently gates nothing.

Arguments

  • evaluation: The row's CriteriaEvaluation from criteria_matching.

Returns (evidence, scan_status).

combine_statuses_strict

def combine_statuses_strict(    statuses: Iterable[EligibilityStatus],)> bitfount.cache.types._eligibility_shared.EligibilityStatus:

Strict combine over row statuses (eligible/ineligible/unknown).

Any ineligible -> ineligible; else any unknown -> unknown; else eligible (empty -> eligible). Used to combine a patient's patient-level verdict with their determining-scan verdict.

Arguments

  • statuses: Row statuses to combine.

Returns The combined EligibilityStatus.

merge_criteria

def merge_criteria(    ehr_criteria: Mapping[str, CriterionEvidence],    scan_criteria: Mapping[str, CriterionEvidence],)> dict[str, bitfount.cache.types._eligibility_shared.CriterionEvidence]:

Merge ehr + scan-rollup criteria into the served map, guarding clashes.

Single run: the two keyspaces are disjoint (age has one provenance, surfaced by one step). Federated multi-run: a key can appear in both with different provenance (a scan pod's DICOM age and an aggregator's EHR age under one task_hash); _resolve_clash picks one deterministically and warns.

Arguments

  • ehr_criteria: The build_patient_level_evidence map for the patient.
  • scan_criteria: The determining scan's criteria (roll_up_patient).

Returns The merged {output_field: CriterionEvidence} map.

roll_up_patient

def roll_up_patient(    scans: Sequence[ScanVerdict],)> PatientScanEligibilityRollup:

Roll up a patient's scans into a determining-scan verdict + evidence.

Arguments

  • scans: One ScanVerdict per scan, where evidence is the output of build_scan_evidence.

Returns A PatientScanEligibilityRollup (carrying the determining scan's scan-provenance criteria of both grains).

Classes

PatientScanEligibilityRollup

class PatientScanEligibilityRollup(**data: Any):

A patient's eligibility rolled up from their per-scan verdicts.

Named for what it is: the scan-derived half of a patient's eligibility (the patient-level criteria are combined in on top of this by the step layer).

Attributes

  • status: The rolled-up patient status.
  • determined_by_scan: The determining scan's ID, if any.
  • evidence: The rollup evidence (scan-level criteria from the determining scan; patient-level criteria are merged in by the step layer).

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.

Variables

  • static determined_by_scan : str | None
  • static model_config
  • static status : bitfount.cache.types._eligibility_shared.EligibilityStatus

ScanVerdict

class ScanVerdict(    scan_id: ForwardRef('str'),    study_date: ForwardRef('str | None'),    status: ForwardRef('EligibilityStatus'),    evidence: ForwardRef('Evidence'),):

One scan's stored verdict, as roll_up_patient consumes it.

Attributes

  • scan_id: The scan's identifier.
  • study_date: The scan's study date (YYYYMMDD), or None if unknown.
  • status: The scan's build_scan_evidence status.
  • evidence: The scan's per-criterion evidence map.

Variables

  • evidence : dict[str, bitfount.cache.types._eligibility_shared.CriterionEvidence] - Alias for field number 3
  • scan_id : str - Alias for field number 0
  • status : bitfount.cache.types._eligibility_shared.EligibilityStatus - Alias for field number 2
  • study_date : str | None - Alias for field number 1