Skip to main content

models

Pydantic request/response models for the patient-data API.

These models are the single source of truth for the served OpenAPI document: create_app publishes app.openapi() at /openapi.json and /openapi.yaml, and dev-scripts/dump_patient_api_openapi.py writes the same document to openapi.yaml beside this module. Anything a consumer needs to know about a response shape therefore belongs in the annotations, the docstrings, or the json_schema_extra examples here — not in a separate hand-written spec, which would drift.

The evidence blob is where that matters most. EligibilityCriterion is structurally uniform, but its entries fall into families that differ in which fields are populated; each family is declared below as a narrowing subclass (CRITERION_VARIANTS) so the families reach the OpenAPI document as named schemas rather than living only in prose.

Classes

AppointmentCriterion

class AppointmentCriterion(**data: Any):

Variant: appointment history / current patient.

Patient-grain, EHR-provenance MethodFilters reading the appointment and encounter columns together, so value is always null.

  • Current patient records {"current_patient_months": {"lte": <months>}}, the recency window it enforces. The filter compares appointment dates against a cutoff derived from that window rather than comparing a month count, so read the pair as the configured window, not as a comparison reproducible from value.

  • Appointment history records an empty operator map: it has two operands (years, min_per_year) under one config field, and the map holds one operand per operator token. Read both from the task config.

  • Appointment history (appointment_history) — the history spans years years and each of those years holds at least min_per_year appointments. One criterion, not two.

  • Current patient (current_patient_months) — an appointment within the last N months.

Both are unknown — never fail — when the ehr_query step ran without fetch_appointments, or when the appointment records were only partly readable.

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 family : Literal[<CriterionFamily.APPOINTMENT: 'appointment'>]
  • static grain : Literal[<CriterionGrain.PATIENT: 'patient'>]
  • static model_config
  • static output_field : Literal['Appointment history', 'Current patient']
  • static provenance : Literal[<CriterionProvenance.EHR: 'ehr'>, <CriterionProvenance.UNKNOWN: 'unknown'>]
  • static unit : None
  • static value : None

AuthenticatedUser

class AuthenticatedUser(**data: Any):

Authenticated caller identity produced by the auth dependency.

Arguments

  • username: The verified username of the authenticated caller, matching the user the pod is logged in as.

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 model_config
  • static username : str

CodeListCriterion

class CodeListCriterion(**data: Any):

Variant: an EHR code list (conditions / procedures).

A MethodFilter outcome whose value is its CodeMatchEvidence — the codes that actually drove this patient's verdict — rather than a scalar. null when the code column was absent, since nothing was inspected; an empty payload would imply the codes were checked and found wanting.

matched_codes holds every code that qualified, ascending by code, each tagged with the scope that qualified it. A code that matched but decided nothing is absent: one ruled out by its recency window, and one whose inferred laterality disagreed with the scan eye. pending_laterality_review and pending_date_review appear only when matched_codes is empty — once something qualified, the criterion is settled and a code awaiting review is moot.

The config field records the whole configured code list as its operand, under the code_match operator: {"conditions_inclusion": {"code_match": ["H35.31", "H35.3*"]}}. Entries are literal codes and regex patterns, so the pair tells a consumer what was checked.

code_match rather than in/nin deliberately. Those tokens promise the verdict is reproducible by testing the recorded operand for membership, and it is not: the real predicate matches literal-or-regex against two separately configured lists (any-eye codes short-circuit; study-eye codes qualify only when the scan laterality is known and the code's inferred laterality includes it), with a third config input deciding the verdict when a study-eye code matches but its own laterality is indeterminate. The operand says what was checked; the token stops a consumer claiming how.

fail on an inclusion list means no qualifying code was found; on an exclusion list it means an excluded code matched. unknown means the code column was absent or unevaluable (e.g. the EHR step did not run), never that the patient has no codes.

A study-eye variant (conditions_inclusion_study_eye, conditions_exclusion_study_eye, procedures_exclusion_study_eye) folds into this same criterion under its non-suffixed config key, so it is not separately visible — and since an outcome carries one config field, the recorded operand is the any-eye list alone; those study-eye codes are not keyed anywhere in the evidence. Neither are eligible_on_*_codes_lat_unknown or code_exclusion_time_windows, which modify this filter rather than producing their own criterion.

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 config_fields : dict[typing.Literal['conditions_inclusion', 'conditions_exclusion', 'procedures_inclusion', 'procedures_exclusion'], dict[CriterionOperator, bool | int | float | str | list[typing.Any] | None]]
  • static family : Literal[<CriterionFamily.CODE_LIST: 'code_list'>]
  • static grain : Literal[<CriterionGrain.PATIENT: 'patient'>]
  • static model_config
  • static output_field : Literal['Conditions inclusion', 'Conditions exclusion', 'Procedures inclusion', 'Procedures exclusion']
  • static provenance : Literal[<CriterionProvenance.EHR: 'ehr'>, <CriterionProvenance.SUPPLIED: 'supplied'>, <CriterionProvenance.UNKNOWN: 'unknown'>]
  • static unit : None

CompoundScanCriterion

class CompoundScanCriterion(**data: Any):

Variant: a compound scan rule (drusen OR-signal, N-scan run).

A scan-grain MethodFilter whose verdict spans several columns, so it has no single value.

The two members differ in whether their bound is recorded:

  • Drusen inclusion records {"drusen_threshold": {"gte": <threshold>}} — the filter tests each drusen probability against exactly that threshold.

  • A <Display Name> N-scan criterion records an empty operator map. Its bound cannot be stated as one comparison: the filter compares a scan count against ceil(typical_width_micrometers / slice_thickness), so the configured width is in different units from the quantity compared, and the threshold applied moves per row with the scan's slice thickness. Read the width from the task config; do not read the empty map as "unbounded".

  • Drusen inclusion — passes when any of the hard/soft/confluent drusen probabilities is at or above drusen_threshold. The per-subtype thresholds are separate ScanMetricCriterion entries on their own probability columns, not part of this one.

  • <Display Name> N-scan — fails when the biomarker spans a long enough consecutive-B-scan run. One per configured N-scan width, over Diffuse Edema, Epiretinal Fibrosis, Intraretinal fluid (IRF), Subretinal Fluid (SRF), Subretinal Hyperreflective Material (SHRM) and Diabetic Macular Edema.

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 family : Literal[<CriterionFamily.COMPOUND_SCAN: 'compound_scan'>]
  • static grain : Literal[<CriterionGrain.SCAN: 'scan'>]
  • static model_config
  • static provenance : Literal[<CriterionProvenance.SCAN: 'scan'>, <CriterionProvenance.UNKNOWN: 'unknown'>]
  • static unit : None
  • static value : None

EligibilityCounts

class EligibilityCounts(**data: Any):

Per-trial patient counts by eligibility status.

The three buckets partition the trial's patient_eligibility rows, so they sum to that trial's cohort size — not to the pod's total patient count, since a patient with no rollup row for the trial is in no bucket.

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 eligible : int
  • static ineligible : int
  • static model_config
  • static unknown : int

EligibilityCriterion

class EligibilityCriterion(**data: Any):

One criterion's served evidence entry (mirrors stored CriterionEvidence).

The value type of evidence.criteria, and the schema every entry validates against. Entries are structurally uniform but fall into families, each published as a narrowing schema of this one and listed under this schema's x-variants; x-family-variants maps a family tag to the schema documenting it.

Classify an entry by family. It is the wire tag naming which variant applies, declared by the criterion's producer rather than inferred, so a consumer does not have to pattern-match output_field spellings or guess from whether value is None. It is not a strict discriminator: a criterion produced by a hand-authored task-config ColumnFilter, and any row written before the tag was recorded, carry family: null. Handle that case; for those rows only, fall back to output_field and config_fields.

Arguments

  • status: The criterion state. na is in the vocabulary but is never written into a stored evidence map, so expect only pass/fail/unknown.
  • output_field: The criterion's logical field name, and its key in evidence.criteria too (the two always agree). Either a produced column name (total_ga_area, Age (yrs), cst_mean_um) or a human-readable filter name for a compound criterion (Conditions inclusion, Drusen inclusion, Subretinal Fluid (SRF) N-scan).
  • value: The observed value, or None if missing/not applicable. A scalar for the column-backed families, and a CodeMatchEvidence object for a code-list criterion — the codes that drove this patient's verdict. One field for every shape of observed value; read family to know which to expect. The configured code list is served as well, but as the operand under config_fields, not here.

None is four distinct situations, indistinguishable from the value alone — use family and status to tell them apart: a non-code-list MethodFilter criterion, an absent target column, a NaN/NaT cell, and a non-finite float mapped to null on write (NaN and inf are not valid JSON).

  • config_fields: {config_field: {operator: operand}} for the config input(s) that produced this criterion. Only bounds in force for the run appear. An empty operator map means "no comparison recorded", not "no comparison made"; exactly two criteria carry one — the N-scan runs and appointment_history, neither of which can state its bound as a single comparison (see CompoundScanCriterion and AppointmentCriterion). The whole map is empty for a criterion with no mapped config field and for a legacy row written before the field existed.
  • grain: The cardinality axis (scan/patient), or None for a legacy row written before provenance tagging.
  • provenance: The source axis (scan/ehr), or None for a legacy row.
  • unit: The value's display unit (e.g. "mm²", "nL"), or None when the criterion is unitless, its column carries no catalogued unit, or the row was written before units were recorded.
  • family: The criterion's semantic family — the tag naming which variant below applies. None for a criterion built from a hand-authored task-config ColumnFilter, and for a row written before the tag existed.

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 model_config
  • static output_field : str
  • static unit : str | None
  • static value : bool | int | float | str | list[typing.Any] | dict[str, typing.Any] | None

EligibilityEvidence

class EligibilityEvidence(**data: Any):

Eligibility evidence for a patient on a specific trial.

Mirrors the patient_eligibility row: status and determined_by_scan are the row's own columns, and evidence is a JSON object holding the per-criterion calculated values under a criteria sub-map.

Arguments

  • bitfount_patient_id: The Bitfount patient ID.
  • project_id: The trial (project) ID.
  • status: Overall eligibility status for the patient on this trial.
  • determined_by_scan: The scan_id of the scan that decided the verdict, chosen deterministically (most recent study_date, then scan_id), or None — e.g. a patient with no qualifying scan, which is also why the evidence map can then hold patient-grain criteria only.
  • evidence: Typed object of per-criterion calculated evidence under evidence.criteria, each entry an EligibilityCriterion carrying a config_fields map — {config_field: {operator: operand}} for the criteria-matching config input(s) that produced it (operators gt/gte/lt/lte/eq/ne, plus in/nin whose operand is a list of candidate values; every typed MethodFilter criterion carries {config_field: {}}) — which makes the evidence self-describing and is what evidence_mapping is built from. Each entry also carries grain/provenance, which are None for a legacy row written before provenance tagging, and unit (the value's unit, None when unitless or written before units were recorded).
  • evidence_mapping: Convenience map from each criteria-matching config input field (e.g. total_ga_area_lower_bound) to the criterion key within evidence.criteria it relates to, so a caller can resolve an input field to its evidence entry without scanning. Rebuilt from each criterion's config_fields. A range criterion's lower + upper bound both map to the one merged criterion. Only fields whose criterion is present in this evidence are included. Not injective in one case: every generic criterion shares the column_criteria key, so with several configured this names only one of them.
  • ehr_retrieved_at: When this patient's EHR data underlying this trial's verdict was retrieved — the served patient_level_eligibility row's own stored retrieval time, not recomputed at read time. Under best-effort EHR serving, a run that could not reach the EHR serves each patient's last stored row rather than aborting, so a verdict can be computed from EHR data that is now stale; this is how old it is. None is several situations a consumer must not conflate: a row written before this field was recorded, a patient with no EHR row at all (e.g. a scan-only trial, or a patient the EHR leg never evaluated), and a patient whose EHR lookup was attempted and retrieved nothing (no EHR data, so no retrieval time to report) — all serve None, and none of them means the others.

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 bitfount_patient_id : str
  • static determined_by_scan : str | None
  • static evidence_mapping : dict[str, str]
  • static model_config
  • static project_id : str
  • static status : bitfount.cache.types._eligibility_shared.EligibilityStatus

EligibilityEvidenceBody

class EligibilityEvidenceBody(**data: Any):

The evidence object: per-criterion entries under criteria.

criteria is keyed by output_field, and each entry repeats its own key in output_field. Only criteria in force for the run appear, so the absence of a key means "not configured", never "passed". The key set is open — a generic column_criteria contributes an arbitrary column name.

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 model_config

ErrorDetail

class ErrorDetail(**data: Any):

FastAPI's HTTPException response body.

Arguments

  • detail: The failure reason. Never carries a filesystem path or a file basename, either of which can itself encode PHI.

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 detail : str
  • static model_config

GenericColumnCriterion

class GenericColumnCriterion(**data: Any):

Variant: a generic column_criteria scalar comparison.

The escape hatch — a config-declared comparison against any produced column by name. output_field is that column name, so it is open-ended: do not enumerate it client-side.

This is the only variant whose value can be a bool or a str, and the only one whose axes are genuinely unconstrained: grain is resolved from the field catalog (or the column's provenance, else defaults to scan), and provenance from the column's source.

Every generic criterion shares the single column_criteria config key, with two consequences worth coding against:

  • evidence_mapping["column_criteria"] can name only one criterion. With several generic criteria configured, the rest are reachable only by output_field.
  • Two generic criteria on one column merge into one entry, and if they also share an operator token the evidence keeps only the last threshold (the pod logs a warning).

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 family : Literal[<CriterionFamily.GENERIC_COLUMN: 'generic_column'>]
  • static model_config

GenericMethodCriterion

class GenericMethodCriterion(**data: Any):

Variant: a generic column_criteria glob / collection-membership test.

A generic criterion that could not be a vectorised column comparison, so it ran as a MethodFilter and carries value: null even though it targets a single column. Three routes land here:

  • a str column whose criterion value contains a * wildcard (per-row glob matching),
  • a collection-dtype column (Previous Appointments Info, Previous Encounters Info), where membership is tested inside the cell rather than against it, and
  • an operator illegal for the column's catalogued dtype. That criterion is deliberately not dropped — a dropped criterion would leave the row falsely unconstrained — but degrades to unknown for every row.

Conditions and Procedures are collection-dtype too but are not a route here: a generic membership criterion on an EHR code column is refused outright and produces no criterion at all, because those cells hold code objects rather than bare values. Use the typed conditions_/procedures_ code lists, which produce a CodeListCriterion.

All three routes need the column's catalogued dtype, so a criterion on an uncatalogued column cannot reach the collection or illegal-operator routes — only the glob route, which is decided by the criterion value alone.

Unlike the other MethodFilter variants, config_fields.column_criteria does carry an operator token and operand here, the generic criterion knowing its own operator. For in/not in that operand is the whole candidate list.

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 family : Literal[<CriterionFamily.GENERIC_METHOD: 'generic_method'>]
  • static model_config
  • static value : None

HealthStatus

class HealthStatus(**data: Any):

Liveness payload for the unauthenticated /health endpoint.

Arguments

  • status: Always "ok" when the API process is serving requests.

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 model_config
  • static status : Literal['ok']

LegacyCriterion

class LegacyCriterion(**data: Any):

Variant: a row written before provenance/unit tagging.

grain, provenance and unit are null, and config_fields is empty — which also makes the row's evidence_mapping empty, since that is rebuilt purely from config_fields. A client must not require any of these fields to be populated.

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 grain : None
  • static model_config
  • static provenance : None
  • static unit : None

PatientList

class PatientList(**data: Any):

Envelope for the patient list endpoint.

The endpoint is paginated server-side: items holds the requested page, and total is the count of all patients matching the search/filter before pagination — so a client can render "page X of Y".

Arguments

  • items: The patient summaries on the requested page.
  • total: The number of matching patients before pagination.

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 model_config
  • static total : int

PatientMetricCriterion

class PatientMetricCriterion(**data: Any):

Variant: a numeric comparison against a patient-grain column.

The patient-grain counterpart to ScanMetricCriterion: same ColumnFilter shape and same config_fields structure, one entry per applied bound, but evaluated once per patient rather than once per scan.

Today its only member is Age (yrs), which is why output_field pins that one literal — the family is named for the kind of criterion rather than the field, so a second patient-grain numeric criterion widens the literal instead of needing a new family.

Age is also the one criterion that routinely crosses the two axes, which is why provenance is left unpinned here: ehr when age came from an EHR date_of_birth, scan when it was computed from a DICOM birth date. Always unitless.

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 family : Literal[<CriterionFamily.PATIENT_METRIC: 'patient_metric'>]
  • static grain : Literal[<CriterionGrain.PATIENT: 'patient'>]
  • static model_config
  • static output_field : Literal['Age (yrs)']
  • static unit : None
  • static value : int | None

PatientSummary

class PatientSummary(**data: Any):

Summary view of a patient for list and detail endpoints.

Both endpoints serve this same shape.

Arguments

  • bitfount_patient_id: The Bitfount patient ID.
  • ehr_patient_id: The EHR patient ID, if known.
  • name: The patient's display name, as stored. Names are reduced to "Given Family" when the record is written, whether they came from the EHR or from a scan, so vendor-specific forms such as DICOM's FAMILY^GIVEN^MIDDLE do not reach this field. A name that could not be parsed is stored — and served — unchanged. Not normalised here.
  • eligible_trials: All trial (project) IDs the patient is eligible for, ascending. Unaffected by the list endpoint's eligible_trial filter.
  • mrns: The patient's medical record number(s), if known.
  • last_analysed_image_date: When the patient's most recently analysed image was processed, or None if no image has been analysed for them yet. An aware UTC timestamp — the maximum processed_at over the patient's scan rows, so it is a processing time, not a capture time.

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 bitfount_patient_id : str
  • static ehr_patient_id : str | None
  • static eligible_trials : list[str]
  • static model_config
  • static mrns : list[str]
  • static name : str

ScanImageFrame

class ScanImageFrame(**data: Any):

One frame's entry in a scan-image archive's manifest.json.

Arguments

  • index: The frame's position in the archive, from 0.
  • filename: The frame's archive member name (frame_NNN.webp).
  • modality: The frame's modality in the SDK's vocabulary — the same two values a task filter or a datasource takes. null when the source states none (a DICOM carrying no acquisition-device-type tag) or states something that is neither an OCT nor an SLO: a colour photo, an angiography series, an encapsulated PDF. Read series_modality to tell those apart, and switch on this field rather than that one.
  • series_modality: The source's own, finer modality label — the lower-cased private_eye description, so oct but also slo - red, colour photo, red-free (cross-polarized). Carries what modality's normalisation drops: which SLO channel a frame came from, and what a null-modality series actually is. null on the DICOM path, whose tag is already in the SDK vocabulary and has no finer label behind it. Open-ended, and a vendored parser's spelling rather than this API's — do not switch on it.
  • laterality: The series' detected laterality, or null when unknown — which is every DICOM-path frame, since the requested laterality filter is never echoed back as detected metadata. B (both eyes) and U (explicitly unknown) are possible alongside L/R.
  • source_width: The frame's width before scaling, in px.
  • source_height: The frame's height before scaling, in px.
  • width: The encoded width, in px — the requested width clamped to source_width.
  • height: The encoded height, in px.

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 filename : str
  • static height : int
  • static index : int
  • static laterality : Optional[Literal['L', 'R', 'B', 'U']]
  • static modality : Literal['OCT', 'SLO', None]
  • static model_config
  • static series_modality : str | None
  • static source_height : int
  • static source_width : int
  • static width : int

ScanImageManifest

class ScanImageManifest(**data: Any):

The manifest.json inside a GET /scans/image archive.

Documented for consumers of the archive; never served as a response body.

Arguments

  • format: The frame encoding — always webp.
  • quality: The WebP quality the frames were encoded at (0-100).
  • acquisition_datetime: When the series was acquired, or null when the file states nothing — a normal outcome, not an error, so a consumer must render the absence rather than assume a date. Read from the source's own metadata: on the DICOM path the first of AcquisitionDateTime, AcquisitionDate(+AcquisitionTime), ContentDate(+ContentTime) or StudyDate(+StudyTime) that states a value; on the native-vendor path the exam's scan_datetime, falling back to the earliest B-scan capture time. A date whose paired time is absent reads as midnight. Carries a UTC offset only where the source stated one — a DICOM date/time pair states no zone, and one is not invented.
  • frames: One entry per frame_NNN.webp member, in archive order.

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 format : Literal['webp']
  • static model_config
  • static quality : int

ScanMetricCriterion

class ScanMetricCriterion(**data: Any):

Variant: a numeric comparison against a produced scan-metric column.

A ColumnFilter outcome. grain is scan, value is numeric, and there is one config_fields key per applied bound — a two-sided range merges into this single entry.

output_field is one of:

  • GA: total_ga_area (mm²), largest_lesion_size (mm²), distance_from_fovea_centre (mm), max_cnv_probability (unitless).
  • Biomarker areas: <label>_area (mm²), over the area-eligible segmentation labels.
  • Biomarker probabilities: max_<label>_probability (unitless), over the segmentation labels plus the model classification pathologies (wet_amd, dry_amd, geographic_atrophy, ...).
  • CST/GCC: cst_mean_um (µm), gcc_mean_superior, gcc_mean_inferior (unitless — the algorithm does not document a unit).
  • Fluid volumes (nL): fluid_subretinal_fluid_volume, fluid_intraretinal_cystoid_fluid_volume, fluid_serous_rpe_detachment_volume, fluid_total_fluid_volume.

Bound directions, for reading config_fields: GA area and lesion size are exclusive (gt/lt); CNV is lt; fovea distance and every CST/GCC/fluid range are inclusive (gte/lte); the lte biomarker thresholds (hypertransmission, neurosensory retina atrophy, hard exudates, wet AMD) are exclusionary; the drusen thresholds are gte inclusion signals; and exclude_foveal_ga records {"ne": 0.0} on distance_from_fovea_centre.

provenance is unknown when the run built the criterion without a datasource context, or the target column was absent from the provenance map.

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 family : Literal[<CriterionFamily.SCAN_METRIC: 'scan_metric'>]
  • static grain : Literal[<CriterionGrain.SCAN: 'scan'>]
  • static model_config
  • static provenance : Literal[<CriterionProvenance.SCAN: 'scan'>, <CriterionProvenance.UNKNOWN: 'unknown'>]
  • static value : int | float | None

ScanSegmentationIndex

class ScanSegmentationIndex(**data: Any):

The segmentations.json inside a GET /scans/image archive.

Present when either include_segmentation_masks or include_segmentation_vectors is set; absent from the archive entirely when neither is. Documented for consumers of the archive; never served as a response body, and — like the frames and the manifest beside it — served compactly, since it carries one entry per class per frame.

Arguments

  • frames: One entry per served frame, in frame order.

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 model_config

SegmentationClass

class SegmentationClass(**data: Any):

One segmentation class on one frame, from one model.

Carries mask, instances, or both — never neither, since a class present in neither requested output is omitted from classes entirely. Each key is absent, not null, when its layer was not requested.

Arguments

  • name: The normalised class name (every non-word character replaced with an underscore, then lower-cased), and the stable key to match state and filters on. It is also what segmentation_classes filters against.
  • label: The human-readable name to render, falling back to name for a class with no registered label.
  • colour: Advisory display RGB, as [r, g, b], falling back to [200, 200, 200]. It matches the PDF report's legend; a UI may override it.
  • probability: The highest instance probability for this class on this frame, or null when the model reported none.
  • mask: Archive-relative path of this class's mask PNG. Absent when masks were not requested, and also when downscaling to the served width erased every pixel of the raster — which is what can leave a thin polyline class with vectors and no mask.
  • instances: The class's raw drawable instances, un-collapsed, so per-instance identity and probability survive (a mask unions them into one plane). Absent when vectors were not requested.

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 colour : tuple[int, int, int]
  • static label : str
  • static mask : str | None
  • static model_config
  • static name : str
  • static probability : float | None

SegmentationFrame

class SegmentationFrame(**data: Any):

All segmentation output for one served frame.

Arguments

  • index: The frame's index, matching its frame_NNN.webp member.
  • filename: That member's name, so a consumer need not rebuild it from index.
  • width: The served frame's width in px. A mask PNG is this wide, so it composites onto the frame with no client-side scaling.
  • height: The served frame's height in px.
  • source_width: The frame's native width in px, before scaling.
  • source_height: The frame's native height in px.
  • sources: One entry per model that produced output for this frame. Empty (not absent) for a frame nothing produced output for.

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 filename : str
  • static height : int
  • static index : int
  • static model_config
  • static source_height : int
  • static source_width : int
  • static width : int

SegmentationInstance

class SegmentationInstance(**data: Any):

One drawable instance inside a segmentations.json class entry.

Coordinates are in the model's own pixel space (mask_width/mask_height on the parent source), not the served frame's — see scale_x/scale_y.

Only the geometry belonging to type is present: a key that does not apply is omitted rather than served as null. probability is the exception — it is always present, and is null when the model reported none.

Arguments

  • type: The instance's geometry. A polygon and an ellipse bound a region whose interior carries the class; a polyline is an open curve with no interior, used for retinal-layer boundaries, and a rasterised one is given a 3px stroke in model space and nothing wider. Instance types carrying no area (point, and anything unrecognised) are dropped before the index is built, so none reaches a consumer.
  • probability: The model's confidence for this instance, or null when it reported none. Per instance, not per class: the parent class entry carries its own.
  • points: Flat [x0, y0, x1, y1, …], on polygon and polyline only.
  • cx: Ellipse centre x. Present on ellipse only, as are the four below.
  • cy: Ellipse centre y.
  • rx: Ellipse semi-axis x.
  • ry: Ellipse semi-axis y.
  • angle: Ellipse rotation, in degrees.

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 angle : int | None
  • static cx : int | None
  • static cy : int | None
  • static model_config
  • static points : list[int] | None
  • static probability : float | None
  • static rx : int | None
  • static ry : int | None
  • static type : Literal['polygon', 'polyline', 'ellipse']

SegmentationSource

class SegmentationSource(**data: Any):

Every class one model produced for one frame.

Two models can produce output for the same frame, and both can carry the same class name, so a legend is grouped by source rather than by class name alone.

Arguments

  • model_ref: The model that produced this output.
  • model_version: That model's version.
  • kind: Which pipeline recognised the output's shape.
  • mask_width: Width of the model's own coordinate space, which every instances coordinate is in.
  • mask_height: Height of that space.
  • dims_match_source: Whether that space matches the frame's native dims. false means the model inferred at some other size, which a client may want to treat with suspicion.
  • scale_x: width / mask_width, full precision — the factor mapping a vector's x onto the served frame. An SVG overlay needs no arithmetic at all: viewBox="0 0 {mask_width} {mask_height}" absorbs it, which is why the server never pre-scales a vector (scaling rx/ry independently would deform a rotated ellipse).
  • scale_y: height / mask_height, the same for y.
  • classes: One entry per class this model produced on this frame.

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 dims_match_source : bool
  • static kind : Literal['pathology', 'retinal_layers']
  • static mask_height : int
  • static mask_width : int
  • static model_config
  • static model_ref : str
  • static model_version : int
  • static scale_x : float
  • static scale_y : float