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, orNonewhen 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.
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
bodysite : BodySiteEntry | None
- static
code_code : str | None
- static
code_display : str | None
- static
code_system : str | None
- static
code_text : str | None
- static
codings : list[CodingEntry]
- static
date : datetime.datetime | str | None
- static
value : ObservationValueEntry | 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
denominator : QuantityEntry | None
- static
numerator : QuantityEntry | None
- 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.