Skip to main content

bcva

Derive a canonical logMAR BCVA entry from the readings an EHR served.

Best-corrected visual acuity reaches criteria matching in whichever representation a site's EHR happens to use: an ETDRS letter count, a Snellen fraction, or a logMAR number under one of several unit spellings. A criterion's value bounds are ANDed against a single entry and shared by every candidate, so one leaf cannot hold a window per representation — a logMAR window would fail every reading at a letters-serving site, and fail it as a definite FAIL rather than as unknown.

This module absorbs that variance at the point the ehr_data row is built. For every BCVA observation whose scale can be established, it appends one derived entry stamped with DERIVED_CODE_SYSTEM / DERIVED_BCVA_LOGMAR_CODE and a logMAR reading. The served entries are left exactly as they were: a derived entry is an addition, never a rewrite, so nothing that reads the original observation sees a converted number in its place.

Scale is established from the observation's own code, then from its unit, and never from the magnitude of the number. 0.5 is a decimal visual acuity to one site (0.301 logMAR) and an already-logMAR reading to another, with nothing in the value to say which — the same ambiguity snellen_to_logmar refuses a bare number for. A reading whose scale cannot be established derives nothing and is reported, rather than being converted on a guess.

Laterality is carried, not inferred. The derived entry states one coding of its own, so it cannot also carry the source's displays; instead the source's resolved laterality is normalised into a SNOMED body-site code, the same encoding _bodysite_for gives a supplied row's eye column on the enrichment route. A derived entry therefore resolves to the same eye as the reading it came from, however that eye was stated. A bilateral reading produces one entry per eye, since no single site code says "both eyes" — the same grain the enrichment route stores one at.

Known limitation. Most of these LOINC codes come in a left/right pair, and observation_laterality_targets reads SNOMED body-site codes only (infer_laterality_targets_from_codes) — no LOINC. An EHR stating the eye only in its LOINC code, with no laterality in the display, text or bodySite, therefore yields an entry no side-scoped criterion can place. That is true of the served observation as much as of the derived one, so nothing here makes it worse; closing it needs a verified code-to-eye mapping for each pair, which is not asserted from the code numbers alone.

Module​

Functions​

with_derived_bcva​

def with_derived_bcva(    observations: list[ObservationRow] | None,    *,    patient_id: str | None = None,) ‑> list[dict[str, str | None | datetime.datetime | float | bool | int | dict[str, typing.Any] | list[dict[str, typing.Any]]]] | None:

Append a canonical logMAR entry per derivable BCVA observation.

None passes through as None rather than becoming a list. The two mean different things to the criteria — None is "not retrieved", resolving UNKNOWN, and [] is a confirmed negative — and _safe_observation_list exists to keep that distinction (see its docstring). An empty list passes through empty for the same reason: there is nothing to derive from, and nothing derived to add.

Refusals warn rather than reporting at info. On a backend that serves observations at all, the criterion reads a readable column, so a BCVA reading that derives nothing leaves the leaf with no qualifying candidate and a definite FAIL — the patient is excluded on a reading this code could not interpret. That has to be visible in the log.

Arguments

  • observations: The patient's served observation entries, or None when the fetch did not answer.
  • patient_id: The patient the readings belong to, for the log line only.

Returns The served entries followed by one derived entry per derivable BCVA reading, de-duplicated; observations unchanged when nothing derived.

Classes​

BodySiteEntry​

class BodySiteEntry(*args, **kwargs):

A serialised BodySite, as an observation's bodysite.

Variables​

  • static bodysite_coding_code : str | None
  • static bodysite_coding_display : str | None
  • static bodysite_coding_system : str | None
  • static text : str | None

CodingEntry​

class CodingEntry(*args, **kwargs):

One serialised Coding on an observation's own code.

asdict() emits every field of the dataclass, so all three keys are present on any entry that has a codings list at all.

Variables​

  • static code : str | None
  • static display : str | None
  • static system : str | None

ObservationEntry​

class ObservationEntry(*args, **kwargs):

A serialised Observation, as the ehr_data observation column holds.

total=False because a row is not guaranteed to carry every key: rows cached before codings existed have the flat code_system/code_code/ code_display keys instead (those are Observation properties now, so a freshly serialised entry has none of them), and _safe_observation_list copies whichever keys the entry it was given actually had.

Variables​

  • static code_code : str | None
  • static code_display : str | None
  • static code_system : str | None
  • static code_text : str | None

ObservationValueEntry​

class ObservationValueEntry(*args, **kwargs):

The keys this module reads from a serialised Observation.value.

value[x] is one of eleven variants (ObservationValue in bitfount.externals.ehr.types), each serialising to its own key set: value/unit for a Quantity, value alone for a ValueString, numerator/denominator for a Ratio. Rather than a union of all eleven — most of which cannot be an acuity — this states the four keys a BCVA reading can arrive under, and is total=False because which of them is present is exactly what varies.

Variables​

  • static unit : str | None
  • static value : float | int | str | bool | None

QuantityEntry​

class QuantityEntry(*args, **kwargs):

A serialised Quantity, as a Ratio's numerator or denominator.

asdict() emits every field of the dataclass, so all three keys are present on any entry carrying a Quantity at all. Only value is read here — a logMAR conversion needs the Snellen fraction's two numbers and nothing else.

Variables​

  • static comparator : str | None
  • static unit : str | None
  • static value : float | None