Skip to main content

criteria_matching_types

Shared, typed structures — the canonical output of criteria_matching.

Producer (criteria_matching) emits one CriteriaEvaluation per evaluated row (a scan, or a patient on the EHR grain), pairing the row's SubjectIdentity with its per-criterion CriterionOutcomes. Every consumer (CSV tabulation, scan/EHR eligibility, PDF) reads these instead of re-parsing stringly-typed DataFrame columns or inverting the config->filter map. This module depends on no other steps/ module, so producer and consumers can share it freely.

Module

Functions

attach_criteria_tree_verdict

def attach_criteria_tree_verdict(    evaluations: list[CriteriaEvaluation], criteria_tree: CriterionTreeNode | None,)> None:

Fold a criteria_tree's verdict into each evaluation, in place.

For each evaluation:

  1. Moves every outcome whose tree_node_id is set out of outcomes into a leaf-id-to-outcome map.
  2. Evaluates criteria_tree against that map via evaluate_criterion_tree, which builds one outcome per node (leaf and combinator alike, stamping each one's resolved tree_group along the way) and collects review_flags in the same pass.
  3. Stores every node's outcome on tree_node_outcomes. A copy of the root's outcome — with config_field/output_field/column_name set to the fixed CRITERIA_TREE_FIELD and tree_node_id cleared, same as every prior phase's hand-built synthetic outcome — is appended back into outcomes.

The copy, not the same object, is what goes back into outcomes: the root's real tree_node_id/output_field/column_name (its own node address, f"criteria_tree.{id}" for a combinator, or a real leaf's own identity when the whole tree is a single leaf) must survive unchanged in tree_node_outcomes for per-node introspection, while outcomes still needs exactly the fixed CRITERIA_TREE_FIELD tag: tabulate_criteria_outcomes keys off that constant (via column_name) to recognise the tree's synthetic combined outcome and exclude it from FILTER_CONTEXT_COLUMN, and attach_criteria_tree_verdict itself keys off tree_node_id is not None to recognise a tree outcome and move it out of outcomes — the copy must clear it, or a second pass over the same evaluations would mistake this already-attached root for a leaf to move again.

The root's outcome — and every combinator's — has grain/provenance derived from its children by _combinator_outcome, not fixed: a tree of only EHR-provenance, patient-grain leaves now correctly derives grain=PATIENT, provenance=EHR and routes through build_patient_level_evidence/ patient_grain_status accordingly, rather than being misclassified as scan data.

A no-op when criteria_tree is None.

Arguments

  • evaluations: The evaluations to update in place.
  • criteria_tree: The configured tree, or None.

evaluate_criterion_tree

def evaluate_criterion_tree(    node: CriterionTreeNode,    leaf_outcomes: Mapping[str, CriterionOutcome],    node_outcomes: list[CriterionOutcome],    node_groups: Mapping[str, str | None] | None = None,    review_flags: list[str] | None = None,)> CriterionOutcome:

Recursively reduce a criteria_tree node, building one outcome per node.

Dispatches by isinstance rather than by comparing node.node; each group class declares only its own valid fields. A leaf's outcome is looked up directly from leaf_outcomes (built by the filter pipeline, real grain/provenance already stamped). A combinator's outcome is synthesised by _combinator_outcome. Every node's outcome — leaf and combinator alike — is appended to node_outcomes before this call returns, so a caller can inspect every node's and every group's own state and source, not only the root's.

Arguments

  • node: The (sub)tree to evaluate.
  • leaf_outcomes: Each leaf id's already-built CriterionOutcome (from the filter pipeline). Every criteria_tree leaf always produces a filter, and so an outcome, even when it degrades to an unknown one — a leaf id absent here is a caller error, and raises KeyError rather than degrading silently.
  • node_outcomes: Every node's own outcome, appended here in post-order (children before parents, root last) as this call recurses.
  • node_groups: Each node id's resolved (inherited) group, from iter_tree_nodes_with_resolved_group. None (the default) leaves every node's tree_group unset — the shape most tests of the combining rule itself need, without a caller building a group map that plays no part in the state/grain/provenance being checked.
  • review_flags: When passed, any node (leaf or group) whose own state resolves to PASS and whose review_reason is set has that reason appended, collected in the same walk as the boolean reduction. None (the default) skips collection entirely.

Returns The node's own outcome (its combined state, plus derived grain/provenance for a combinator).

iter_tree_leaves

def iter_tree_leaves(    node: CriterionTreeNode,)> collections.abc.Iterator[ColumnCriterion | CodeCriterion | ObservationCriterion | MedicationCriterion | AllergyCriterion | DeviceCriterion]:

Depth-first leaves of a criteria_tree node.

Arguments

  • node: The (sub)tree to walk.

Returns An iterator over every leaf (CriterionLeaf) reachable from node.

iter_tree_nodes

Depth-first every node (leaf or combinator) of a criteria_tree.

NotGroup walks its single child; AndGroup/OrGroup/AtLeastGroup walk their children list.

Arguments

  • node: The (sub)tree to walk.

Returns An iterator over node itself and every node reachable from it.

iter_tree_nodes_with_resolved_group

def iter_tree_nodes_with_resolved_group(    node: CriterionTreeNode,    inherited_group: str | None = None,)> collections.abc.Iterator[tuple[ColumnCriterion | CodeCriterion | ObservationCriterion | MedicationCriterion | AllergyCriterion | DeviceCriterion | AndGroup | OrGroup | AtLeastGroup | NotGroup, str | None]]:

Depth-first (node, resolved group) pairs, resolving group by inheritance.

Every node — leaf or combinator — is yielded, unlike iter_tree_nodes' plain walk: a combinator's own outcome needs its resolved group exactly as a leaf's does, so both are resolved by the same walk here rather than a leaf-only one plus a second pass for combinators.

A node's resolved group is its own group when set, else the nearest ancestor's resolved group, else None when no ancestor sets one either. This is a pure function of the tree's own group fields: no node stores a parent reference, and nothing here mutates the tree. CriteriaMatchConfig's validator (_check_group_inheritance) separately guarantees no node overrides an ancestor's group with a different one, so in practice every descendant under one ancestor's group always resolves to that same value.

Arguments

  • node: The (sub)tree to walk.
  • inherited_group: The resolved group inherited from ancestors already visited. Callers should not pass this; it is set on the recursive calls this function makes into its own children.

Returns An iterator over (node, resolved_group) for node and every node reachable from it.

match_strategy_for

def match_strategy_for(    dtype: str, op: str, has_glob: bool = False,)> MatchStrategy:

Which MatchStrategy handles a legal (dtype, op) pair.

Returns COLUMN (a vectorized ColumnFilter: numeric/bool comparisons, numeric membership via .isin, and *-less str equality/membership), SCALAR_MATCH (a per-row str glob MethodFilter), or COLLECTION (collection-cell membership via make_collection_membership_filter). A str column routes to COLUMN unless the value carries a * glob (has_glob), in which case the per-row matcher is required. Assumes the pair is already known legal (see ALLOWED_OPS_BY_DTYPE in bitfount.steps.types.operators).

Arguments

  • dtype: The column's catalogued dtype tag.
  • op: The criterion operator.
  • has_glob: Whether any criterion value contains a * wildcard (only consulted for str columns).

Returns The matching strategy for the pair.

tree_depth

def tree_depth(node: CriterionTreeNode)> int:

Longest path from node to any leaf, counting the leaf itself as 1.

Arguments

  • node: The (sub)tree to measure.

Returns The tree's depth, at least 1.

Classes

AllergyCriterion

class AllergyCriterion(**data: Any):

An EHR allergy/intolerance, matched by its own code or a reaction substance.

Per FHIR R4's AllergyIntolerance (https://hl7.org/fhir/R4/allergyintolerance.html), a qualifying code can live in either the resource's own top-level code (a CodeableConcept, 0..1 — a resource may carry no coded diagnosis at all) or any of its reaction[].substance entries (also a CodeableConcept, 0..*). An entry qualifies against code/code_system if either location matches — see _build_allergy_filter, which implements this OR across both locations.

Arguments

  • node: Discriminator identifying this as an allergy-type criteria_tree node.

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 node : Literal['allergy']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

AndGroup

class AndGroup(**data: Any):

A criteria_tree node requiring every child to pass.

Arguments

  • children: The child nodes; at least one required.
  • review_reason: See ColumnCriterion.review_reason. Every child is necessary for an AndGroup to pass, so a flagged child that passes is always part of what produced the result.

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 node : Literal['and']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

AppointmentHistoryCriterion

class AppointmentHistoryCriterion(**data: Any):

The compound appointment-history criterion.

A patient qualifies when their appointment/encounter history spans at least years years and every one of those years holds at least min_per_year appointments. The two bounds are one criterion, not two: a length with no density, or a density with no length, does not describe anything checkable, so a half-specified criterion is rejected.

Both fields None means the criterion is not configured. That spelling is load-bearing: a task template renders this mapping unconditionally, so two blank modeller variables arrive here as {years: None, min_per_year: None} rather than as an absent key.

Arguments

  • years: Required length of history, in years.
  • min_per_year: Minimum appointments required in each year of the window.

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 min_per_year : int | None
  • static model_config
  • static years : int | None
  • is_configured : bool - Whether both bounds are set, so a filter should be emitted.

AtLeastGroup

class AtLeastGroup(**data: Any):

A criteria_tree node requiring at least min_count children to pass.

Arguments

  • min_count: How many children must pass; must not exceed the number of children.
  • children: The child nodes; at least one required.
  • review_reason: See ColumnCriterion.review_reason.

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 min_count : int
  • static model_config
  • static node : Literal['at_least']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

CodeCriterion

class CodeCriterion(**data: Any):

A condition or procedure code, matched by presence/date/count only.

Arguments

  • resource: Which resource code is matched against.
  • node: Discriminator identifying this as a code-type criteria_tree node.

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 node : Literal['code']
  • static resource : Literal['condition', 'procedure']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

CodeLaterality

class CodeLaterality(**data: Any):

Scopes a code-addressed leaf's match to one side, and how to treat unknowns.

Arguments

  • side: Which side of a paired structure, relative to the row's own SubjectIdentity.laterality"index" (the study side), "contralateral" (the opposite side), or "either" (no filtering; the default).
  • is_unknown_ehr_resource_laterality_eligible: Whether an entry qualifies against side when its own recorded side cannot be inferred — as opposed to the row's own scan laterality being unresolved, which fail_on_unknown_scan_laterality governs instead (see "Matching laterality" in the design notes). False (the default) means such an entry does not qualify, the conservative choice.
  • fail_on_unknown_scan_laterality: The verdict for the whole leaf when the row's own scan laterality cannot be resolved at all, so side cannot be evaluated against anything. False (the default) resolves to unknown, genuinely not knowing; True resolves to a definite fail instead, letting a sibling any-eye leaf in an OrGroup decide the group's outcome rather than the unresolved side dragging the whole group to unknown. Both flags are only valid when side is not "either" — with "either" neither the entry-level nor the scan-level unresolved case can ever arise.

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 fail_on_unknown_scan_laterality : bool
  • static is_unknown_ehr_resource_laterality_eligible : bool
  • static model_config
  • static side : Literal['index', 'contralateral', 'either']

CodeMatch

class CodeMatch(*args, **kwargs):

One code that qualified, and how it qualified.

Attributes

  • code: The code as the EHR recorded it, not the configured pattern it matched — a configured H35.3* reports the H35.31 actually present.
  • scope: any_eye for a code qualifying regardless of laterality, study_eye for one whose inferred laterality had to include the scan eye (and did).

Variables

  • static code : str
  • static scope : Literal['any_eye', 'study_eye']

CodeMatchEvidence

class CodeMatchEvidence(*args, **kwargs):

Which EHR codes drove a code-list criterion's verdict, structurally.

The typed counterpart to a code filter's prose context string. The filters already know the codes; this carries them in a form a consumer can render or filter on without parsing English, and it is persisted inside the criterion's evidence so the patient API can serve it.

A TypedDict rather than a model for the same reason as CriterionEvidence (which nests it): the value is persisted as JSON, so it must stay a plain dict through json_safe and json.dumps. total=False because a row written before this existed carries no key at all — read every key with .get.

Only the codes that decided something appear. A configured code that simply did not match is absent; so is a code matched inside a recency window whose event date proved too old, and a study-eye code whose laterality was inferred and disagreed with the scan eye. All three contributed nothing to the verdict.

Attributes

  • matched_codes: Every code that qualified, ascending by code. For an inclusion criterion these are the codes that made the patient eligible; for an exclusion criterion, the codes that disqualified them. Empty when nothing matched decisively.

Plural, and complete: the filters used to stop at their first decisive match, so the evidence could name only one of several qualifying codes. They now scan every entry. The verdict is unchanged either way — the first match already settled it — so this widens what is reported without moving any patient between states.

  • pending_laterality_review: Codes that matched a study-eye list but whose own laterality could not be inferred, so they could not decide the verdict. Their presence is why the criterion's state follows the configured eligible_on_*_codes_lat_unknown rather than the codes themselves — a reviewer resolves them by hand. Reported only when nothing qualified: a populated matched_codes settles the criterion, which makes an indeterminate code moot.
  • matched_values: The measurements that qualified, for a criterion whose verdict turned on an observation's own value. Empty or absent for every other code-list criterion, which has no measurement to report — a condition code either is or is not present.

Reported alongside matched_codes rather than instead of it: the code says which concept qualified, the value says what it read.

  • pending_date_review: Codes that matched an exclusion list carrying a recency window, but whose event date is unknown, so recency could not be confirmed. Exclusion-only: the inclusion filter has no windows. Reported under the same condition as pending_laterality_review.

Variables

  • static pending_date_review : list[str]
  • static pending_laterality_review : list[str]

CodeSpec

class CodeSpec(**data: Any):

One glob-capable code, optionally pinned to its own code system.

Lets a _CodeAddressedLeaf's code list mix codes from more than one system — e.g. an ICD-10 code alongside a SNOMED one — which a single leaf-level code_system shared across every code cannot express.

Arguments

  • code: The code to match, glob-capable (e.g. "E11*").
  • code_system: This code's own system (e.g. "icd10", "snomed"), or None to fall back to the leaf's own code_system default (see _CodeAddressedLeaf.codes).

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 code : str
  • static code_system : str | None
  • static model_config

CodeValueBound

class CodeValueBound(**data: Any):

A comparison against an observation entry's own resolved value.

Six of FHIR's eleven Observation.value[x] shapes resolve to a shape this class can compare against:

  • valueQuantity. A number with a unit.
  • valueBoolean.
  • valueInteger.
  • valueString. valueTime resolves the same way. A time string compares the same way as any other string.
  • valueCodeableConcept. Matched structurally, through CodeableConceptBound (see value below).

The remaining shapes never resolve: valueRange, valueRatio, valuePeriod, valueDateTime, and valueSampledData. An entry carrying one of these never qualifies against a CodeValueBound. These shapes are not yet supported.

Arguments

  • op: The comparison operator. Reuses ColumnCriterion.op's exact symbol set, from the same OPERATOR_SPECS table (bitfount.steps.types.operators). Also reuses that table's per-dtype legality rule. For example, an ordering operator is illegal against a str or bool value. The rule is checked per entry at match time, not once at parse time. A ColumnCriterion knows its column's dtype up front. An entry's own resolved value shape is only known once matching starts.

Which comparisons op allows depends on value's shape:

  • Against a valueString, "==", "!=", "in", and "not in" match value as a glob rather than by plain equality. Only * acts as a wildcard, matched with fullmatch. CodeCriterion.code already uses this convention. A target with no * behaves as plain equality. So a compound free-text value can match on one part of it. For example, the BCVA reading "20/60 (30.0) 0.67pt 0.18M -1" matches value="*20/60*" without matching the whole string.
  • Against a CodeableConceptBound value, only "==" and "!=" are legal. Ordering and membership do not apply to a structural match.
  • value: The threshold or target value, or a CodeableConceptBound that matches a valueCodeableConcept entry structurally.
  • unit: A bound on the entry's own unit, via UnitBound (see its own docstring for why this needs to be an object rather than a bare str | None). Absent: unit checking is skipped entirely, so a valueQuantity entry qualifies on its number alone, whatever unit it does or does not state. UnitBound(value=None) (i.e. unit: {}): the entry must itself state no unit at all — the safer, opt-in alternative to leaving unit unset. UnitBound(value="mg"): the entry's own unit must equal "mg" exactly. Must be unset when value is a boolean, integer, or string, since none of those carry a unit of their own. Must also be unset when value is a CodeableConceptBound.

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 op : Literal['>', '>=', '<', '<=', '==', '!=', 'in', 'not in']
  • static value : bool | int | float | str | list[str | int | float] | CodeableConceptBound

CodeableConceptBound

class CodeableConceptBound(**data: Any):

A bound on an observation entry's valueCodeableConcept.

Mirrors the real FHIR CodeableConcept shape (see hl7.org/fhir/R4/datatypes.html#CodeableConcept): a concept expressed as a coding (system/code/display) and/or free-text text. Each field you set narrows the match. Each field left unset adds no constraint.

Arguments

  • coding: An optional bound on the entry's system/code/display (see CodingBound). Unset imposes no constraint on coding.
  • text: Match against the entry's own free-text text, as a *-glob.

Variables

  • static model_config
  • static text : str | None

CodingBound

class CodingBound(**data: Any):

A bound on one CodeableConcept.coding entry.

Every field left unset imposes no constraint; each field that is set narrows the match further — the fields are AND'd together, not OR'd.

Arguments

  • system: Exact match against the coding's system URI. Not glob-capable — a system is an identifier, not free text.
  • code: Match against the coding's code, as a *-glob (the same convention CodeCriterion.code uses), e.g. code="8302-*". A target with no * behaves as plain equality.
  • display: Match against the coding's display, as a *-glob.

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 code : str | None
  • static display : str | None
  • static model_config
  • static system : str | None

ColumnCriterion

class ColumnCriterion(**data: Any):

A generic, column-addressed eligibility criterion.

The expandable escape hatch: match any produced column by name without a dedicated typed field. Prefer a typed CriteriaMatchConfig field when one exists for the column.

Arguments

  • column: The DataFrame column to match against.
  • op: Comparison operator applied as column op value. Symbols only — a deliberately narrower surface than the alias set a ColumnFilter accepts.
  • value: The threshold/target value, or a DerivedFromColumn spec resolving it from another column instead of a literal.
  • grain: scan or patient; when None, resolved from the field catalog.
  • node: Discriminator identifying this as a column-type criteria_tree node; irrelevant outside a tree (every column_criteria entry also carries it, unused).

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 column : str
  • static model_config
  • static node : Literal['column']
  • static op : Literal['>', '>=', '<', '<=', '==', '!=', 'in', 'not in']
  • static value : bool | int | float | str | list[str | int | float] | DerivedFromColumn

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

CountSelector

class CountSelector(**data: Any):

Reduce candidate entries by requiring at least min_count of them.

Arguments

  • min_count: The minimum number of qualifying entries required.

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 min_count : int
  • static mode : Literal['count']
  • static model_config

CriteriaEvaluation

class CriteriaEvaluation(**data: Any):

One evaluated subject: its identity paired with its per-criterion outcomes.

Arguments

  • identity: The evaluated subject's identity.
  • outcomes: The per-criterion outcomes (one per filter/bound; a range field contributes two, sharing an output_field). Never contains a criteria_tree leaf outcome once attach_criteria_tree_verdict has run — those live in tree_node_outcomes instead, alongside every combinator's own outcome, plus one entry here (the root's, duplicated) for the tree's own combined state.
  • tree_node_outcomes: Every criteria_tree node's own outcome — leaf and combinator alike, including the root — in post-order (children before parents). Leaves are moved here out of outcomes by attach_criteria_tree_verdict so matches_all never re-ANDs an individual branch's leaf (which would defeat OR/AT_LEAST/NOT); every combinator's outcome is synthesised fresh, with grain/provenance derived from its children (see _combinator_outcome) rather than fixed. Empty when no criteria_tree is configured.
  • review_flags: Every triggered review_reason from the tree (leaf or group) whose own state resolved to pass, in tree order, without deduplication. Populated by attach_criteria_tree_verdict; a downstream triage signal only — never affects matches_all.

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 review_flags : list[str]
  • matches_all : bool - Whether every criterion passed (reproduces FILTER_MATCHING_COLUMN).

    Any non-pass (fail or unknown) means the row does not match; an empty outcome list (no active filters) is vacuously True.

    Returns: True iff every outcome's state is pass.

CriterionFamily

class CriterionFamily(*args, **kwds):

Which kind of criterion this is — the semantic family it belongs to.

Declared by the build site in build_eligibility_filters, not derived, and that is the point: the families are a semantic partition, so two of them are mechanically indistinguishable. A CODE_LIST and an APPOINTMENT criterion are both patient-grain, EHR-provenance MethodFilters with no recorded operator; nothing about the filter tells them apart. Only the producer knows which it built.

Orthogonal to the three axes already carried:

  • CriterionKind — which filter type ran (column/method).
  • CriterionGrain — cardinality (per-scan vs per-patient).
  • CriterionProvenance — data source (scan vs EHR).

A family implies constraints on all three (a CODE_LIST is always a patient-grain EHR method filter) but none of them recovers the family.

Its purpose on the wire is to tell a consumer how to read value and which fields to expect populated, without inferring it from output_field spellings. The patient API's EligibilityCriterion variants are keyed to these members one-to-one.

Subclasses str so members serialise straight into JSON evidence.

Variables

  • static APPOINTMENT
  • static CODE_LIST
  • static COMPOUND_SCAN
  • static GENERIC_COLUMN
  • static GENERIC_METHOD
  • static PATIENT_METRIC
  • static SCAN_METRIC

CriterionGrain

class CriterionGrain(*args, **kwds):

Whether a criterion is evaluated per scan or per patient.

Variables

  • static PATIENT
  • static SCAN

CriterionKind

class CriterionKind(*args, **kwds):

Which filter type produced a CriterionOutcome.

The explicit filter-type discriminator consumers branch on: COLUMN for a ColumnFilter outcome, METHOD for a MethodFilter outcome. Subclasses str so members serialise straight into JSON evidence.

Variables

  • static COLUMN
  • static METHOD

CriterionOutcome

class CriterionOutcome(**data: Any):

One criterion's outcome for one row, carrying enough to regenerate any string.

Arguments

  • config_field: Originating CriteriaMatchConfig field, or None if unmapped.
  • output_field: Logical field name / evidence key (filter column or name).
  • column_name: Exact flat-df column name the filter would write (for tabulation).
  • operator: The ColumnFilter operator (">", ...); None for a MethodFilter that does not know its own operator.
  • operand: The configured value the operator compared against — the comparison's right-hand side. Named operand rather than threshold because it is not always a bound: in/not in put the whole candidate list here. None for a filter with no recorded comparison.
  • value: The observed value on the row, or None if missing/not applicable. A ColumnFilter reports its cell; a code-list MethodFilter reports its CodeMatchEvidence — the codes that drove the verdict — so a structured verdict lands in the same field as a scalar one rather than in a field of its own. Read family to know which to expect. Every other MethodFilter has no single observed value and reports None.
  • state: pass/fail/unknown (producer never emits na).
  • context: Runtime context that cannot be regenerated, as prose (e.g. matched codes). Consumed by the report's context column; for a code-list criterion value carries the same information structurally.
  • failed_message: A MethodFilter's regenerated failure reason (its static message, or a "missing values"/"unable to determine" variant), or None for a ColumnFilter outcome.
  • grain: scan or patient — the cardinality (per-scan vs per-patient), not the data source (see provenance).
  • provenance: scan or ehr — the data source (distinct from grain). Defaults to unknown; stamped by build_criterion_outcomes from the source column, not by the filter (age can be either source).
  • unit: The target column's display unit (e.g. "mm²"), or None when the field is unitless or uncatalogued. Like provenance, not a filter attribute — stamped by build_criterion_outcomes from the target column's FieldSpec in the field catalog.
  • absent_column: For a ColumnFilter outcome whose target column was entirely absent from the row (as opposed to present with a NaN value), the raw configured column name to render into the regenerated "not found" reason instead of its display rename. None when the column was present (even if NaN), and always None for a MethodFilter outcome.
  • matched_column: The source column this row's verdict actually read, for a filter that can read more than one. Set only by the observation filter, which spans OBSERVATIONS_COLUMN and SUPPLIED_OBSERVATIONS_COLUMN, and only when the row produced qualifying candidates. build_criterion_outcomes prefers it over the filter-level provenance_column when stamping provenance, which is what makes provenance per-row rather than per-filter for a multi-column filter. None for every single-column filter and for a row that matched nothing — the filter-level constant is used then.
  • kind: column for a ColumnFilter outcome, method for a MethodFilter outcome. The explicit filter-type discriminator consumers branch on.
  • family: The semantic criterion family (see CriterionFamily), carried through from the filter that produced this outcome. None only for a filter built without one — a directly constructed filter in a test, or a user-authored ColumnFilter in a task config, neither of which comes from build_eligibility_filters.
  • tree_node_id: The criteria_tree leaf id this outcome was built from, or None for an outcome not built from a tree leaf. attach_criteria_tree_verdict uses this to move the outcome out of CriteriaEvaluation.outcomes into tree_node_outcomes, so a tree leaf's own PASS/FAIL is never re-ANDed by matches_all. A combinator's own synthesised outcome (see _combinator_outcome) also sets this, to its own node id, so every entry in tree_node_outcomes is addressable the same way.
  • tree_group: The tree node's resolved (inherited) group reporting label, stamped by evaluate_criterion_tree for every node — leaf or combinator. None when neither the node nor any ancestor set one, and always None for a non-tree outcome.

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 absent_column : str | None
  • static column_name : str
  • static config_field : str | None
  • static context : str | None
  • static failed_message : str | None
  • static matched_column : str | None
  • static model_config
  • static operand : Any
  • static operator : str | None
  • static output_field : str
  • static tree_group : str | None
  • static tree_node_id : str | None
  • static unit : str | None
  • static value : Any

CriterionProvenance

class CriterionProvenance(*args, **kwds):

Where a criterion's evaluated data was pulled from — its data source.

The source axis, orthogonal to CriterionGrain (the cardinality axis, per-scan vs per-patient). A patient-grain criterion can still be scan-provenance: age computed from a DICOM birth-date is grain=patient, provenance=scan. Subclasses str so members serialise straight into JSON evidence. Not a filter attribute — resolved in build_criterion_outcomes from the source column each value was built from.

Variables

  • static EHR
  • static SCAN
  • static SUPPLIED
  • static UNKNOWN

CriterionState

class CriterionState(*args, **kwds):

A single criterion's evaluated state.

na = not applicable to this row (a patient-level criterion on a scan row); only consumers assign na, never the producer. Subclasses str so members serialise straight into JSON evidence.

Variables

  • static FAIL
  • static NA
  • static PASS
  • static UNKNOWN

DateWindow

class DateWindow(**data: Any):

A window a matching event's date must fall within.

Each bound is independent and optional: a literal date pins it exactly; a RelativeDate pins it relative to the task's own reference date at match time (see _code_date_bounds); None leaves that side open (start unset means all history; end unset means no upper bound). Named after FHIR's own Period datatype (start/end), which this plays the same role for.

Each of start/end can independently be absolute or relative, so e.g. "between 2 years ago and 6 months ago", or "more than 4 weeks ago" (end a RelativeDate, start left as all history), are both expressible.

Arguments

  • start: The window's start.
  • end: The window's end.

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

DerivedFromColumn

class DerivedFromColumn(**data: Any):

A ColumnCriterion.value resolved from another column, not a literal.

resolved = ceil(numerator / row[divisor_column]), clamped to minimum when minimum is set. Exists for a criterion whose bound is not a fixed value: the config author does not supply it directly — criteria_matching computes it per row, from a second column. n_scan_*_typical_width_micrometers is the existing example: it needs ceil(width / slice_thickness), and a plain literal value cannot express that. slice_thickness varies by device and protocol, so it cannot be a fixed config value either.

Arguments

  • numerator: The constant divided by the value in divisor_column to resolve the comparison bound.
  • divisor_column: The column read (from the same row) as the divisor.
  • round: How the division result is rounded before comparison. Only "ceil" is supported: the one existing use rounds up, since a partial unit never satisfies a "spans at least N of them" bound.
  • minimum: A floor applied to the resolved value after rounding, or None for no floor. A numerator of 0 would otherwise resolve to 0 (ceil(0 / x) == 0), making the comparison vacuous.
  • on_unusable_divisor: The state to resolve to when divisor_column is missing, NaN, or 0True (pass), False (fail), or None (unknown). No resolved value can be computed in that case. There is no correct default for every criterion: whether "unevaluable" means "don't know" or "don't exclude" depends on the specific criterion, not on this mechanism.

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 divisor_column : str
  • static minimum : float | None
  • static model_config
  • static numerator : float
  • static on_unusable_divisor : bool | None
  • static round : Literal['ceil']

DeviceCriterion

class DeviceCriterion(**data: Any):

An EHR device record, matched by its own type code.

FHIR R4 Device (https://hl7.org/fhir/R4/device.html) carries no field meaning "when this became relevant to the patient" (only manufactureDate/expirationDate, neither clinical) — unlike every other code-addressed leaf, this one has no date_range at all, not merely an unset one. selector is declared fresh here rather than inherited and narrowed: a mutable pydantic field's type must stay invariant across a class hierarchy, so narrowing an inherited field in a subclass is unsound and pyrefly rejects it (bad-override-mutable-attribute). Declaring it fresh here instead excludes "latest"/"earliest" (neither can select a "most recent" entry with no date to order by) and "all" (a poor fit for trial criteria — a patient's devices are typically unrelated to each other, e.g. a pacemaker and a hip implant — and mathematically identical to "any" here regardless, since with no secondary per-entry check both reduce to "at least one candidate").

Arguments

  • node: Discriminator identifying this as a device-type criteria_tree node.
  • selector: "any", or a CountSelector requiring at least N qualifying devices (see above for why "all"/"latest"/ "earliest" are excluded).

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 node : Literal['device']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

FieldSpec

class FieldSpec(**data: Any):

A single matchable field a source can contribute to criteria matching.

Arguments

  • name: The flat DataFrame column name a criterion targets.
  • dtype: A coarse type tag ("float", "int", "bool", "str").
  • unit: Human-readable display unit (e.g. "mm²"), or None when unitless. Carries the character a consumer should render; an ASCII fallback belongs to whichever sink cannot take it, not here.
  • grain: Whether the field is per-scan or per-patient.

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 dtype : str
  • static model_config
  • static name : str
  • static unit : str | None

MatchStrategy

class MatchStrategy(*args, **kwds):

How a generic column_criteria is matched against its column.

The dispatch discriminator _build_generic_filter branches on when turning a criterion into a concrete filter — it names which matching mechanism applies, not merely which factory is called. Orthogonal to CriterionKind (the filter-type an outcome carries, column vs method): both SCALAR_MATCH and COLLECTION build a MethodFilter, so this three-way choice does not collapse onto that two-way one. Subclasses str so it compares equal to the historical literal keys.

Variables

  • static COLLECTION
  • static COLUMN
  • static SCALAR_MATCH

MeasuredValue

class MeasuredValue(*args, **kwargs):

One observation entry's own measurement, as evidence.

An observation criterion's verdict turns on a number, not merely on a code being present, so reporting only the code leaves a reviewer unable to see what was measured. A BCVA criterion reporting pass says nothing about whether the study eye read 20/40 or 20/200; this carries the reading itself.

A TypedDict, and total=False, for the same reasons as CodeMatchEvidence, which nests it: the value is persisted as JSON, and a row written before this existed carries no key — read every key with .get.

Attributes

  • code: The code the entry was recorded under.
  • value: The entry's own resolved value — a number for a quantity, a string for a free-text reading (a Snellen "20/40"). A valueCodeableConcept carries no value field of its own, so it reports its text, else display, else code.
  • unit: The entry's unit, when it stated one.
  • date: The entry's date as "%Y-%m-%d", when it stated one. Which reading qualified matters as much as the value: a window satisfied by a five-year-old measurement is a different fact about a patient than one satisfied last week.
  • laterality: "L" or "R" when the entry resolves to exactly one eye, None when it is bilateral or indeterminate. scope says whether the eye had to match the scan's; this says which eye was actually read, which a reviewer needs when both eyes were measured at one visit and only one of them qualified.
  • scope: any_eye for an entry qualifying regardless of laterality, study_eye for one whose laterality had to include the scan eye (and did) — the same distinction CodeMatch.scope draws.

Variables

  • static code : str
  • static date : str | None
  • static laterality : str | None
  • static scope : Literal['any_eye', 'study_eye']
  • static unit : str | None
  • static value : float | str | None

MedicationCriterion

class MedicationCriterion(**data: Any):

An EHR medication code, optionally bounded by its own dosage.

Arguments

  • node: Discriminator identifying this as a medication-type criteria_tree node.
  • dose: An optional bound (or list of bounds) on the medication's own dosage (see MedicationDoseBound). When set, a matching code entry additionally qualifies only if any of its dosage instructions satisfies every given bound. A list is not the same as one MedicationCriterion leaf per bound under an AndGroup: each leaf there would find its own qualifying entry independently, so e.g. a dose_quantity leaf and a frequency leaf could each be satisfied by a different instruction on the same entry. A list here pins every bound to one instruction at once — e.g. "40mg, given at least twice daily," not "40mg on some instruction, and at least twice daily on some (possibly different) instruction." When unset, behaves exactly like CodeCriterion: presence/date/laterality only, dosage ignored.

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 node : Literal['medication']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

MedicationDoseQuantityBound

class MedicationDoseQuantityBound(**data: Any):

A comparison against one dose_and_rate reading's own dose_quantity.

Arguments

  • field: Discriminator identifying this as a dose_quantity bound.
  • dose_type: Which dose_and_rate reading to use — "ordered" (the prescriber's own instruction, sometimes per-weight) or "calculated" (a separately computed absolute amount, which also covers a MedicationAdministration-sourced reading: an exact amount actually given, not a prescriber's own instruction). These are different measurements, not interchangeable variants of one fact. A config author who wants "either reading qualifies" writes two leaves under an OrGroup rather than picking one.
  • op: The comparison operator. "in"/"not in" are deliberately excluded, unlike CodeValueBound.op's full set: a dose is a continuous quantity, not a small enumerated set of values to check membership against, and "one of these exact strengths" (the one real case for it) is a config author writing one OrGroup leaf per strength rather than a membership list.
  • value: The threshold value.
  • unit: The unit dose_unit must equal. Required rather than optional, unlike CodeValueBound.unit: a dose reading in the wrong unit is not a "probably fine" comparison the way a lab value might be. "ordered" and "calculated" readings routinely differ by exactly a unit (mg/kg vs mg).

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 dose_type : Literal['ordered', 'calculated']
  • static field : Literal['dose_quantity']
  • static model_config
  • static op : Literal['>', '>=', '<', '<=', '==', '!=']
  • static value : bool | int | float | str

MedicationFrequencyBound

class MedicationFrequencyBound(**data: Any):

A comparison against an instruction's own frequency, pinned to one period.

Arguments

  • field: Discriminator identifying this as a frequency bound.
  • op: The comparison operator, as MedicationDoseQuantityBound.op"in"/"not in" excluded for the same reason: "twice or three times daily" is an OrGroup of two == leaves, not a membership list.
  • value: The threshold value.
  • frequency_period: The period an entry's own frequency must be measured over. frequency alone is not a clinically meaningful
  • bound: "at least twice" could mean twice daily or twice weekly, and those are different prescriptions. This pins the comparison to one instruction's own period, exactly (not compared via op), the same way dose_type pins one dose_and_rate reading.
  • frequency_period_unit: The period_unit an entry's own frequency must be measured over, matched exactly alongside frequency_period.

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 field : Literal['frequency']
  • static frequency_period : float
  • static frequency_period_unit : str
  • static model_config
  • static op : Literal['>', '>=', '<', '<=', '==', '!=']
  • static value : bool | int | float | str

MethodOutcome

class MethodOutcome(    passed: ForwardRef('bool | None'),    context: ForwardRef('str | None') = None,    code_match: ForwardRef(''codeBlockAnchor[CodeMatchEvidence](/api/bitfount/steps/criteria_matching_types#codematchevidence)' | None') = None,    matched_column: ForwardRef('str | None') = None,):

What a MethodFilter's per-row callable returns.

A NamedTuple with defaults, so the plain (eligible, context) tuple every existing filter returns is still a valid return value and needs no change: MethodFilter.evaluate_row normalises whatever it gets through MethodOutcome(*result). A filter that has structured detail to report adds the third field.

Attributes

  • passed: The verdict — None for unknown (e.g. the filter's data is absent for this row), which never becomes pass.
  • context: Human-readable runtime detail that cannot be regenerated from the filter's configuration (the report's context column renders this).
  • code_match: The same detail structurally, for a code-list filter. Unlike context, this is persisted into the criterion's evidence and served by the patient API.
  • matched_column: For a filter that reads more than one source column, the column this row's qualifying candidates came from. Only the observation filter sets it (it spans OBSERVATIONS_COLUMN and SUPPLIED_OBSERVATIONS_COLUMN); it is what lets build_criterion_outcomes stamp provenance per row instead of per filter. None when the filter reads one column, or when this row produced no candidates to attribute.

Variables

  • context : str | None - Alias for field number 1
  • matched_column : str | None - Alias for field number 3
  • passed : bool | None - Alias for field number 0

NotGroup

class NotGroup(**data: Any):

A criteria_tree node inverting its single child's state.

Arguments

  • child: The node to invert. pass and fail swap; unknown/na unchanged.
  • review_reason: See ColumnCriterion.review_reason.

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 node : Literal['not']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

ObservationCriterion

class ObservationCriterion(**data: Any):

An EHR observation code, optionally bounded by its own resolved value.

Arguments

  • node: Discriminator identifying this as an observation-type criteria_tree node.
  • value: An optional bound on the observation's own value (see CodeValueBound), or a list of bounds every one of which the same entry must satisfy. When set, a matching code entry additionally qualifies only if its value satisfies the bound(s). When unset, behaves exactly like CodeCriterion: presence/date/laterality only, value ignored.

A list is how a two-sided window is expressed, and expressing one as two sibling leaves under an AndGroup does not work. Every selector except CountSelector reduces to "at least one candidate entry qualifies" (see _build_code_filter), so two leaves ask whether some entry cleared the lower bound and whether some entry cleared the upper — satisfiable by two different readings on different dates. A patient whose study eye read 0.18 logMAR once and 1.40 logMAR another time passes both halves of a >= 0.30/<= 1.00 window without ever having had a reading inside it. Listing both bounds on one leaf checks them against a single entry, which is what "a measurement in this range" means.

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 node : Literal['observation']
  • value_bounds : list[CodeValueBound] - value, normalized to the list of bounds ANDed against one entry.

    Empty when no value bound is configured, so a caller can iterate unconditionally rather than branching on None.

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

OrGroup

class OrGroup(**data: Any):

A criteria_tree node requiring at least one child to pass.

Arguments

  • children: The child nodes; at least one required.
  • review_reason: See ColumnCriterion.review_reason. Collected whenever this node passes, even if an unflagged sibling child also passes: flagging more than the minimum set of passing nodes is intentional, since missing a genuinely lower-priority match costs more than an unnecessary review.

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 node : Literal['or']

Methods


model_post_init

def model_post_init(self: BaseModel, context: Any, /)> None:

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.

RelativeDate

class RelativeDate(**data: Any):

A date expressed relative to another instant — this many units before/after it.

Arguments

  • unit: The unit time is measured in.
  • time: How many of unit. Must be positive.
  • direction: Which way this points relative to the instant it's resolved against — "past" (before it) or "future" (after it). Defaults to "past".

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 direction : Literal['past', 'future']
  • static model_config
  • static time : int
  • static unit : Literal['d', 'm', 'y']

SubjectIdentity

class SubjectIdentity(**data: Any):

Identity of the subject evaluated on one row.

Every field is already known to criteria_matching while it evaluates the row. scan_id/study_date/laterality are None on the non-file-system (EHR) grain, where a row is a patient rather than a scan.

Arguments

  • bitfount_patient_id: The Bitfount patient ID, or None when the row has none. Absence is None rather than the empty string it used to be: an ID is derived from a name and a date of birth, and a name that cannot be reduced to a given and a family name yields no ID (see bitfount.federated.algorithms.ophthalmology.dataframe_generation_extensions.name_id_key). An empty string is a value, and values match each other — pandas joins two rows sharing "" as if they were one patient, and a consumer that forgets to check gets no complaint from the type checker. None makes the absence a distinct state the checker enforces at every point of use.
  • scan_id: The scan filename, or None on the EHR grain.
  • study_date: The scan study date (YYYYMMDD), or None.
  • laterality: The scan laterality, or None.
  • name: Display name from the row (a fallback; EHR prefers its own lookup).
  • mrns: Medical record number(s) found on the row.

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 | None
  • static laterality : str | None
  • static model_config
  • static mrns : list[str]
  • static name : str | None
  • static scan_id : str | None
  • static study_date : str | None

UnitBound

class UnitBound(**data: Any):

The unit a valueQuantity entry's own unit must equal, or lack.

A bare str | None cannot tell "don't care about unit" apart from "the entry must itself carry no unit": both would read as None. This object gives the two states distinct shapes instead: absent on CodeValueBound.unit means "don't care"; present, with value unset, means "the entry's own unit must be unset too."

Arguments

  • value: The exact unit the entry's own unit must equal, or None to require that the entry itself states no unit at all.

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 value : str | None