Skip to content

Safety and confidence

The validator compares Component, Property, Time, System, Scale, and Method when the report supplies the corresponding fact. It runs after every ordinary candidate-retrieval path and before ranking can emit a code.

Units are evidence, not identity

The raw OCR unit is preserved. UnitAssessment reports a canonical UCUM-like form and one of four states:

Do not write UnitAssessment.canonical back into the raw observation. For example, a fax U/L may yield canonical arbitrary-unit evidence {arb}/L; retain U/L in LabObservation.unit and display that printed surface to the reviewer. The mapper accepts {arb}/L for backward compatibility, but that tolerance is not permission to replace report text.

Gemini may optionally explain an unrecognized or recovered OCR unit, such as a lowercase l versus capital I in ulU/mL. That suggestion is advisory review metadata only. It must not overwrite LabObservation.unit, bypass unit/property validation, or become a registry mapping without an audited clinician correction.

State Meaning Mapping behavior
valid The printed surface parsed directly, including a report note such as (calc) that is stripped into annotations. Use it as property evidence.
recovered A bounded unit-grammar repair was verified, for example ulU/mL to u[IU]/mL or mgdL/ to mg/dL; the accepted surface is in recovered_surface. Use the canonical form while retaining raw text and repair trace.
missing No unit was extracted. Review by default; a confirmed ordinary panel component or exact clinician-approved template can proceed if all remaining safety facts agree.
unrecognized The surface could not be parsed. Keep likely candidates, return review/abstain diagnostics, and request correction. Do not label every candidate invalid.

The grammar covers every UCUM example unit of the pinned release (annotations, groups, bracket atoms, a leading solidus), the unit names, scale words and report notes laboratories print, and the release's own display forms; see OCR recovery for the list. Two additive fields (2026-09-17) carry the trace: recovered_surface is the accepted surface of a recovered repair, and annotations holds the non-dimensional notes read off the surface, both report notes (calc, by wt) and UCUM annotations (creat from mg/g{creat}). Annotations never change a dimension: cells/uL shares its dimension with 10*3/uL and /uL. A term whose LOINC example lists alternatives ([IU]/L;m[IU]/mL) accepts any of them.

Units help select the correct LOINC Property and sibling. A valid dimensional conflict remains a hard failure: mass cannot select a molar property, a molar unit cannot select a mass property, and a dimensional unit cannot select a ratio/fraction result. This boundary protects LDL cholesterol from LDL particle number and similar clinically distinct siblings.

Complete, incomplete, unreadable, missing

A printed unit is compared with the candidate's, and only a complete unit can contradict it (2026-09-24):

Comparison Example Validation
complete mg/dL, x1000/μL, 10^6 against a term whose own unit is 10*6 Dimension and property are checked; a conflict is a hard failure (LDL Size nm never files as a mass code).
incomplete 10^3 or x1000 where the candidate expects 10*3/uL Validated exactly like a missing unit (unit_partial when a unit is required).
unreadable a surface the grammar cannot read (two unreadable characters, an unknown atom) Reviews, unless the candidate is an exact clinician approval or a confirmed panel member, which may proceed as for a missing unit.
missing no unit Unchanged: a required unit reviews; the same approval and panel exception applies.

A term whose UCUM example units are all bare annotations ({INR}, {score}, {ratio}) needs no printed unit: by UCUM an annotation alone is unity, a result style, and an INR is never printed with a unit. An annotated dimensional unit (mg/g{creat}) is still a unit.

A dimensional unit beside a number is a quantity, so a candidate whose Property never carries a dimension (PrThr, Prid, Imp, Type, Find, ID, Titr, Score; LOINC's property axis, closed) is rejected (dimensional unit conflicts with a presence, identity, titer or score property, 2026-09-25): C-Pep in ng/mL had tied with an allergen-mix [Presence] term whose Latin names explained its letters. An answer word beside a stray unit (Negative mg/dL) is not this rule's business: the value's shape says what kind of result was printed.

A ratio unit says what it relates (2026-09-25; units.ratio_basis, units.denominator_annotation). The dimensions of mg/g and mmol/mol both cancel, but the atoms on each side of the solidus do not: a mass ratio selects MRto and rejects SRto (mass ratio unit conflicts with a molar ratio property), a molar ratio the reverse. An annotation on the denominator (mg/g creat, which the release itself writes as mg/g{creat} on 1,434 example units) names the denominator analyte: a slashed Component whose denominator it does not name (creat begins Creatinine, not Globulin) and whose own examples do not carry it is another ratio (unit names a denominator the candidate's component does not have). ALBUMIN/CREATININE RATIO, RANDOM URINE in mg/g creat had sat 0.005 above the albumin/globulin ratio and the molar ACR term. The annotation also names the denominator through a release name of the term that is not a word of its numerator (WBC for Leukocytes under Erythrocytes.nucleated/Leukocytes), singular and plural alike (WBCs; safety._denominator_named, 2026-10-05): NUCLEATED RBCS printed /100 WBC had lost every ratio term, the release's own example /100{WBCs} included. Read from the unit and the candidate's own Component, examples and release names, never from an analyte list.

Units are supporting evidence, never a reason to abstain (the practice's decision, 2026-09-24; config/axis_safety_policy.json 2026.09.24.1 sets missing_unit_outcome and unrecognized_unit_outcome to accept). A missing, magnitude-only or unreadable unit never blocks a candidate; a mapped row that went without the unit its code would need carries review_flags (unit_missing, unit_incomplete, unit_unreadable). Where the unit is what would pick between siblings (Glucose in mg/dL is MCnc, in mmol/L SCnc), the practice's approved Property for that analyte and material decides (meaning_groups[].practice_property); with none, or two, the margin decides as usual. A complete unit that contradicts a candidate still rejects it: that selects the right sibling, it does not abstain the row. Publication still requires a unit a clinician types to parse (it becomes the approval's requirement); an approval without a unit publishes with a warning.

Ratios need no unit: the units cancel, and laboratories print a ratio bare, as ratio, as mg/mg or g/g. All of these are accepted against every ratio style in the release ({ratio}, mg/mg{creat}, g/g{creat}, a blank example); a dimensional unit (mg/dL) on a ratio row still rejects the ratio term. Which Properties are dimensionless is read from LOINC's property codes: ratios end in Rto, fractions in Fr, relative quantities start with Rel, plus Ratio, Prct and ARat. Rates (MRat, SRat: mass or substance per time) are dimensional, so mg/d and mg/24 h are units of a 24-hour urine term, never a conflict. A discriminator word is observed when a row word the term's own release names use contains it: MICROALBUMIN/CREATININE RATIO names the albumin of 14959-1 (which LOINC names "Microalbumin/Creatinine"); Prealbumin does not. Every Component word is observed when the row prints the term's own long-name head, the release's clinical wording of the Component (_name_head_covers, 2026-09-25): Direct Bilirubin names Bilirubin.direct (1968-7), whose Component Bilirubin.glucuronidated+Bilirubin.albumin bound holds the albumin discriminator; Bilirubin alone prints no head and is still rejected there.

Under the configurable review policy, a candidate held back only for its unit ranks with the survivors and is never filed; when it leads, the row abstains naming it (unit_missing), stages.unit_review_candidate_count.

"Magnitude only" is what was printed (units.magnitude_only_unit), not a verdict: the release itself prints 10*6 as the complete unit of counts in millions. When an approval or confirmed member proceeds on an incomplete or unreadable unit, or on a unit whose prefix was unreadable, the mapped result carries review_flags (unit_incomplete, unit_unreadable, unit_prefix_unreadable) so the main project files it for LOINC review; a mapped row's reasons are emptied, so this field is the one that survives. When such an approval cannot proceed (a source-scoped or legacy entry), its blocked outcome is approved_candidate_unit_review with "needs review", not "was rejected".

The grammar reads notation compositionally rather than from a list of printed forms: a leading multiplication sign before a number is only a sign (x1000/uL is 1000/uL); 10 followed by ^, *, E or a parenthesised integer is a power of ten (10E3, x10(9)/L); a bare 1.73 denominator is the body-surface-area convention (mL/min/1.73 reads as mL/min/1.73.m2, the eGFR dimension); an arbitrary unit may lose its brackets (arb'U/mL). Control, format and private-use characters become one visible placeholder; a single placeholder before an atom that takes SI prefixes is an unknown prefix (10^3/<?>L keeps the volume dimension and is recovered), two placeholders stay unreadable. The force-exact result-style guard treats a style no unit establishes (absent, unreadable, magnitude-only) as unknown, so a certified WBC force entry applies to x1000/μL or 10^3; only a parsed contradicting style (a percent) keeps it off.

A printed % never matches a term whose units are only UCUM annotations ({titer}, {ratio}, {index}). Both are dimensionless, so the dimension check alone passed them, but a percent is a fraction and a titer, ratio or index is not: %NEUT reached neutralizing-antibody titer terms through their Neut alias. This is a hard failure (percent unit conflicts with the candidate's titer, ratio or index result style), read from the candidate's own example units, not from a list of analytes.

Unitless LOINC result styles

A blank report unit is valid when the selected active LOINC term itself has a non-dimensional official example token, currently {ratio} or {titer}. For example, total-cholesterol/HDL cholesterol ratio (9830-1) may be reported as 4.5 without a physical unit. The mapper therefore does not reject that row solely because UNITSREQUIRED=Y.

This is not a broad ratio exemption. A ratio term whose LOINC example is a dimensional surface such as mg/g still requires an observed compatible unit. The editable values live in axis_safety_policy.json -> unit_policy.unitless_example_tokens; changing them requires clinical review, regression tests, and a new artifact release.

A second, narrower exemption (policy 2026.09.17.1, unit_policy.unitless_property_codes = Rto, MRto, SRto, CRto, DRto, NRto) covers a ratio-of-like-quantities property whose term documents no example unit at all, such as the GGT/AST ratio (2325-9, CRto, UNITSREQUIRED=Y): the printed number is a bare ratio and a blank unit does not block it. A ratio term with mg/g keeps its requirement, ArVRat (eGFR) is never in the list, and a dimensional unit against any ratio property remains a hard failure. Without the policy file the set is empty and nothing changes. The review contract applies the same rule to approvals with unit=null.

A row whose value has no number (Negative, None seen, 2+), no explicit scale and no unit is treated as non-quantitative (value_shape fact): it conflicts only with Qn candidates and never asks for a unit. A printed unit is itself evidence of a quantitative result style, so NONE SEEN with /HPF keeps its field-count terms (the first production run of this rule had rejected every candidate of such rows). No result vocabulary is consulted, so an unseen word behaves like a seen one.

The editable release-controlled files are:

  • config/unit_surface_rules.json: grammar-level OCR surface repairs.
  • config/unit_release_vocabulary.json: generated from the pinned release by tools/export_example_units.py, never hand-edited: the UCUM annotation tokens the release uses, the display words LOINC pairs with them (creatinine -> creat), and the display forms the grammar cannot read aligned to their terms' UCUM example (arb U/mL -> [arb'U]/mL). The grammar always comes first; a pairing that shares no word with its UCUM example (SD, Da) is left out so such a printed unit goes to review.
  • config/axis_safety_policy.json: unit policy plus reusable axis safety vocabulary.
  • config/axis_context_aliases.json: context and method-family equivalences such as ImmunoBlot and LOINC IB.

These files are not an unbounded per-analyte alias list. Increment their version, run tests, run deployment canaries, and deploy them with the pinned registry artifact.

Scientific count surfaces such as x10E3/uL and x10E6/uL are normalized by the unit grammar. ratio is a non-dimensional result-style surface and can only support a compatible ratio/fraction term. These are grammar rules, not analyte-specific maps. See OCR recovery contract.

The printed value

The value cell is a fact of the row, read by grammar (result_style.value_shape, 2026-09-25): number (4.5, 1,234, 5.7 H), comparator (<40), titer (1:64, <1:64), range (2-5), grade (2+), answer (one to three words without a digit: Negative, None seen, Pattern A), pointer (See note), status (TNP, Not reportable), sentence (four or more words or sentence punctuation), or nothing (O.9 is no number; Positive 1:80 is mixed). Only the pointer words, the status phrases and the degree words of an ordinal answer are a vocabulary, versioned in config/axis_safety_policy.json (value_policy, 2026.10.05.1); every other shape is grammar, so an unseen answer word behaves like a seen one. An answer whose first word is a degree word (NONE SEEN, FEW, NEGATIVE, NOT DETECTED; result_style.ordinal_answer, 2026-10-05) states a presence or an amount, never a type: Crystals [type] (Prid, nominal) then joins Crystals [Presence] as its sibling and the presence term is the answer, while a name outside the vocabulary (CALCIUM OXALATE) keeps the type term apart for a reviewer. CRYSTALS NONE SEEN had tied the two by 0.003 on every urinalysis with microscopy.

What each shape decides: a pointer or a status is no result, and the row abstains before retrieval with value_not_a_result by every path (fast path and force-exact included; production had filed See note under a microscopy term and TNP as measurements; the application filters such cells first and the mapper is the second net; a result_kind the application sends is honoured the same way, anything but result). A titer is a Property fact (Titr) unless a dimensional unit contradicts it, and a candidate that is not a titer result is rejected (value is a titer; candidate is not a titer result): <1:64 keeps Ehrlichia chaffeensis IgG Ab [Titer] and rejects its [Presence] and [Units/volume] siblings, which retrieval had left within 0.003 of each other. A sentence fits only a narrative or document term. An answer word or a grade establishes the non-quantitative scale as before (Negative keeps the presence term and rejects the Qn siblings); with an answer word and no unit a semi-quantitative titer sibling groups with the presence term and ranks after it. A bare number, comparator or range with no unit is evidence only (universal_axis_facts.value) and never a gate: index-reported presence tests print bare numbers.

An approval of the row's surface rejected on the value's shape (a titer, a sentence, or a Scale the value contradicts) is inapplicable, not blocked: it was made for rows printing another kind of value. Urine GLUCOSE NEGATIVE under the serum approval 2345-7 therefore ranks the test-strip presence term instead of stopping on the approval (inapplicable_approved_candidates).

Specimen and panel context

specimen is the analytical material; collection_specimen records how the sample was drawn. Blood, Venous may be compatible with blood-derived serum/plasma results and rules out physically disjoint urine/CSF candidates, but it must not claim that every test was analyzed as whole blood or serum/plasma. A word that names a collection device rather than a material (swab, brush, scraping, aspirate, cup, kit; specimens.material_words, 2026-10-05) states no analytical specimen: a row printed Swab rejects no System, while Vaginal swab keeps its site and still rejects a urine term. A report's either/or heading (... MALE UR/SWAB) had reached the mapper as specimen: Swab and rejected every NAA term of a Trichomonas row.

An exact recognized report heading may confirm a component's membership in an official LOINC panel. If the row lacks an analytical specimen, the clinician-approved panel default can then supply that context. An explicit row specimen wins, and a contradiction of a printed row fact is a hard failure. The parent panel LOINC is never emitted for an individual result row. See Panel context.

A specimen carries its provenance (LabObservation.specimen_source, 2026-09-24): row (the row's own cell), section_heading (the heading above it) or report (a document-level phrase). Two conflicts are soft evidence to confirm, never a hard rejection: a report-level specimen that contradicts a candidate's System (a document-level Ser/Plas stamped on a CBC section), and a row specimen that contradicts a confirmed panel member whose reviewed panel default agrees with the candidate (Serum stamped on a hospital's CBC row; the eosinophil-count approval stays named for review instead of stepping aside for serum proteins). The second is soft only while the specimen is the only contradiction (2026-10-06): for an approval another printed fact already refused, the mapper asks the specimen gate with printed_decides and the row's or section's specimen outranks the member default, so the urinalysis microscopy rows that run onto a page without their heading and reach the CBC heading with Urine printed beside them (their /HPF refuses the blood count) set the blood-count approval aside as inapplicable. Both soft conflicts review with specimen is soft evidence and conflicts with the candidate system; confirm the specimen, and an approval blocked that way is named for review rather than set aside as inapplicable. A report-level specimen is also only a non-explicit retrieval hint, so it cannot steer candidate retrieval to another System (serum IgE terms for a blood count).

An approval of the row's surface whose System contradicts a trusted heading's default specimen, while a confirmed member of that heading survived validation for the row, was made for another context and is inapplicable (2026-09-25; Mapper._without_panel_default_approvals): under URINALYSIS REFLEX the serum approval of GLUCOSE steps aside and the urine test-strip member leads, whether the value is NEGATIVE or 97 mg/dL. Only a trusted heading with hard defaults speaks (never a hint or sibling inference), only when the row prints no specimen of its own (a report-level phrase is not the row's, so it does not keep the heading out), and only when the heading has a surviving member for the analyte: a non-member row under the wrong heading (hemoglobin A1c under a chemistry panel) keeps its approval. Since 2026-10-02 the rule also covers an approval another gate rejected first: under URINALYSIS, COMPLETE the practice's blood-count approval of RBC (789-8) is rejected on /HPF before the specimen gate runs; it is inapplicable, not blocked, and the urine sediment member 13945-1 files.

Evidence about the material ranks row cell, then section heading, then document. A confirmed member's reviewed panel default therefore replaces a report-level specimen outright (specimen_source: confirmed_panel_default): the CBC rows under their own trusted heading are whole blood whatever the document header says. And a specimen that is not printed on the row (a report-level phrase, or a memberless panel's soft default) never blocks an approval of the row's own surface: the approved code maps with review_flags: ["specimen_unconfirmed"]. A specimen the row itself prints still decides.

When an official panel component has passed normal safety validation, it gets a strong ranking preference over non-member siblings. That allows CMP Globulin to select its documented calculated component rather than an unrelated serum, plasma, urine, or body-fluid globulin sibling. Panel membership remains ranking evidence, not a validation bypass.

Method and timing

Method tags are safety context, not decoration. Calculated LDL and LDL Direct must not silently choose one another's method-specific sibling. Conversely, a missing method does not reject a generic method-unspecified LOINC. The mapper uses a method-specific LOINC only when the method is explicit in the row or supported by reviewed source evidence. Method families come from the reviewed axis_context_aliases.json; a multi-word LOINC method that contains a family's own word of five or more letters belongs to that family, so Creatinine-based formula (CKD-EPI 2021) is a calculated method and a row that says calc fits it. A parenthesised qualifier after a method names the formula or the instrument, not the method (2026-10-07): Calc (NIH) is a calculation and IA (Roche) an immunoassay, unless the qualifier names a family itself; two words of which one is a family's own alias of three or more letters and the other names nothing (calc nih once the parentheses are gone) read the same way, while two-letter aliases (if, ia, ib) stay out of sentences. LDL Chol Calc (NIH) had failed every calculated term, the practice's approval included. For an approval of the row's own surface a printed method whose every word the approved alias carries is already judged (Mapper._validation_view): the application read it out of the name, and the clinician judged the name whole. A sentence that contains a family's multi-word alias as a phrase belongs to that family too (2026-10-05): a report's methodology footnote Roche Cobas 6800/8800 systems using real-time Polymerase Chain Reaction (PCR) is the PCR family and fits Probe.amp.tar, while the instrument words alone name no method; TMA, transcription mediated amplification and nucleic acid amplification (test) are reviewed surfaces of the same family (axis_context_aliases.json 2026.10.05.1). A formula name printed in the row (CKD-EPI, CKD-EPI 2021, MDRD; reviewed method surfaces of axis_context_aliases.json 2026.10.05.1 with their own canonicals) is the row's method, so a cystatin C formula fails it while both CKD-EPI equations fit; beside such a name the calculated family's manner word yields (Estimated GFR (MDRD) is one clue, context._formula_over_manner), while LDL Direct calculated keeps both clues and its conflict. A year contradicts only a printed year (2026-09-25): CKD-EPI 2009 rejects the 2021 term, while a row that names the formula without its year (CKD-EPI) fits every year of it. Production had rejected the approved 2021 term for such a row and filed the 2009 one.

Siblings that differ only in a method the row does not print are one unresolved meaning group (meaning_groups[].unresolved: "method_variant", 2026-09-25): the 2009 and 2021 CKD-EPI equations, calculated and direct LDL, the three stool O&P methods. With no printed method they are unresolved unless the practice approved the analyte in exactly one of their methods (the existing rule) or, under a trusted heading, exactly one of them is a confirmed member of the heading's record (the urinalysis record names the test-strip specific gravity, not the automated strip; the record is the practice's statement of the method the panel reports); with a printed method that fits several of them (CKD-EPI) they are unresolved when the top two tie on every ordering key before LOINC's common rank (approval of the row's surface, printed axis matches, the practice's approved targets, panel membership). The mapper then abstains with method_variant_ambiguity, naming the leader and its variants, instead of letting the margin between them file one; a reviewed registry leader is exempt (the reviewer chose the variant for that name). So Est GFR (CKD-EPI) files the approved 2021 term and reviews with no approval, and the expanded Estimated GFR (Non-African American) reviews under a practice that approves two eGFR methods until the row's method column says which.

What decides inside a group when the row prints no method is the practice's policy (practice_policy.method_default, 2026-09-29). With practice_then_method_less, the order is: an approval of the row's own question; a confirmed member of the row's trusted panel; the practice's approved code for the analyte; LOINC's method-less term; the practice's single approved method where no method-less term exists; otherwise the row reviews. method_less puts the method-less term before the panel member and the practice's code. ask lets neither a method-less term nor the practice's method decide: the group is unresolved unless the panel record names the method.

Retrieval may return the method-specific siblings alone. The mapper then looks the method-less sibling up (axis_siblings.AxisSiblingIndex: the same Component, Property, Time, System family and Scale, no Method; active result terms only; built once from the terms the catalog holds) and validates it like any candidate (Mapper._method_less_siblings, source sibling_completion, evidence completes). Nothing is added when the row prints a method, when a method-less survivor is already there, or under ask. method_variant_ambiguity therefore remains where LOINC has no method-less sibling and the practice approved no single variant (fibrinogen by coagulation assay or derived, the Lyme immunoassay and immunoblot interpretations): a real question, asked once. A filing the policy made is recorded in stages.policy_defaults as {axis: "method", choice: "method_less", code} (completed: true when the term was looked up); it carries no review flag, because the method-less term is the general reading. The other filing the policy makes, the code the practice approved for the analyte over its method-less sibling (practice_then_method_less), is recorded the same way as {axis: "method", choice: "practice_method", code} (2026-10-05): the automated immature-granulocyte count over LOINC's method-less count. Nothing is recorded when the row printed the method.

A row that states a total names the whole component. LOINC writes a sub-fraction as Component.qualifier (Thyroxine.free, Bilirubin.direct, Cholesterol.in LDL), so a candidate whose dotted sub-part the row does not mention is hard-rejected (row states a total; candidate is an unstated sub-fraction of the component): T4 (THYROXINE), TOTAL keeps Thyroxine and loses Thyroxine.free; Bilirubin, Total keeps Bilirubin.total and loses Bilirubin.direct; a ratio such as Cholesterol.total/Cholesterol.in HDL passes for Total Cholesterol/HDL ratio because every sub-part is in the row. Grammar only: LOINC's dot and the English word, no analyte list. A row without the word stays ambiguous on purpose. One reading was added for Release A (0.3.5, 2026-10-08): LOINC writes a lipoprotein fraction as the dotted sub-part (Lipoprotein.alpha is HDL, Lipoprotein.beta.subparticle the LDL particles) and says HDL/LDL only in the term's short and related names, so a row that prints one of the policy's fraction acronyms (lipoprotein_fraction_acronyms) which the term's release names carry has named that sub-part: HDL-P (total) states the total of the alpha fraction and files the practice's 49748-7; T4 (THYROXINE), TOTAL prints no such acronym and still loses Thyroxine.free.

A printed unit reckoned per volume, per mass or per time contradicts a Property reckoned per another (0.3.6, 2026-10-09, safety._property_dimension_conflict): LOINC's Property grammar says a concentration (…Cnc) is per volume, a content (…Cnt) per mass and a rate (…Rat) per time, and the unit's dimension says what the print is per. It decides only where the term's own example units do not: Lead [Mass/mass] in Specimen (MCnt, no example unit) had held the margin against the venous-blood lead term for a row in ug/dL, and Albumin [Mass/time] in Urine collected for unspecified duration (MRat) against the urine albumin in mg/dL, which the time gate rightly allows for a random collection. A dimensionless print (mg/g, %, a per-100 count) says nothing here; a per-100 print's apostrophe (/100 WBC'S) is unit noise removed by the surface rules (unit_surface_rules.json 2026.10.09.1), so the print reads as the per-100-leukocytes count and contradicts a count per volume.

A method clue read out of the row name is void for a candidate whose own component contains every word of it. LOINC's method axis holds questionnaire and assay names that are ordinary words elsewhere (CAST is a survey instrument), so the catalog-aware context extractor reads CAST inside HYALINE CAST as a method; against the sediment term Hyaline casts that word names the analyte, singular or plural, and is not a method conflict (observed method word names the candidate component). A real method word (Immunoassay) still conflicts. The rule is derived from the candidate's component text, never from a list of analyte names, and applies in both the validator and universal signature grouping.

A method or time clue may not consume the row's last analyte word. The catalog-aware extractor reads LOINC Part names in the row, and some are also the analyte's own abbreviation: Neut is the neutralization method, PT a time aspect. When the clues of every axis together cover every word of the row, the leading word is the analyte (lab names put it first), and the method and time clues on it are released (context._release_analyte_clues): Neut % (Auto) keeps Auto as its method and Neut as its analyte, and a bare PT or INR states neither a method nor a time. LDL Chol Calc keeps Calculated, because its analyte words are not clues.

A method or time clue that LOINC itself uses to name a retrieved candidate's analyte is void for the row (context.release_candidate_clues, 2026-09-25). Direct Bilirubin reads direct as the method Direct assay; production therefore hard-rejected every methodless term of the analyte LOINC names Bilirubin.direct (1968-7) and filed an amniotic-fluid total bilirubin measured by direct spectrophotometry (12476-8). The release runs after retrieval, against the retrieved terms: a clue whose words are all analyte-head words of a term (grounding.analyte_head_words: the Component and the long name's head, never related names, which carry Calc and Dir) that also names another word of the row is dropped, the facts and the universal candidates are rebuilt without it, and stages.released_clues[{axis, value, span, named_by}] records it. LDL Direct keeps its method (the bilirubin term names no other word of that row); LDL Chol Calc keeps Calculated. The registry fast path applies the same release against the approved term alone, so an approval of Bilirubin, Direct files there.

Fasting is generally pre-analytic context, not a replacement for a 24-hour LOINC Time axis. Timing words are normalized to LOINC time aspects first (time_surface_code: random, spot, point in time -> Pt; 24 hour, 24 hr, timed urine, any <n> hour -> <n>H; fasting, trough, post challenge pass through). An explicit duration conflicts with a point-in-time candidate and with a different duration; an explicit point-in-time collection (random) conflicts with every timed sibling, so Albumin, Urine collected at random no longer ties with its 24-hour term. A component acronym of the form X/Y in the row name (A/G Ratio) counts as observing both halves of a candidate's Albumin/Globulin component, so the albumin discriminator is not an unobserved qualifier there. Qualitative, ordinal, titer, and numeric result styles are compared against the candidate Scale and Property rather than treated as ordinary units. A report scale word is normalized to its LOINC scale code before that comparison (ordinal -> Ord, nominal -> Nom, qualitative -> Ord, quantitative -> Qn, semi-quantitative -> SemiQn, narrative -> Nar; the reviewed scale entries of config/axis_context_aliases.json), so a consumer that labels a test-strip result ordinal no longer conflicts with an Ord presence term. A word LOINC does not use is no scale at all (2026-09-25; scale="%" had hard-rejected every candidate of Lymph % (Auto) as a text mismatch): an unparseable scale is absent, never a contradiction. A consumer's scale label is its classification of the printed value, never a printed fact (no report prints "ordinal"), so it contradicts a candidate only across the boundary the value itself proves, a number against a word: within Ord, Nom, SemiQn, Nar and Doc the label decides nothing (2026-09-25; an Ord label had made the practice's approved [Type] term for LDL PATTERN, Scale Nom, inapplicable and the row a case), and the value's own shape keeps the finer distinctions (a sentence fits only Nar/Doc, a titer only Titr, see the value grammar below). The remaining property-specific relaxation (Qn/SemiQn for titers) applies to the codes. The validator, the universal index and the ranking axis matches share this one rule (scale_surface_code, _scale_surfaces_compatible).

Name heuristics and clinician approvals

Some checks infer from the printed name whether a candidate is the named analyte: antibody isotype, organism family and species, a component or system qualifier the row does not print, a row that states a total, an unobserved component discriminator, high versus low density, and a lipoprotein fraction acronym (LDL, HDL, VLDL). They exist to keep an IgG row off an IgM code and an HDL row off an LDL code. They are heuristics about naming, and LOINC names are not uniform: the LDL particle terms are filed as Lipoprotein.beta.subparticle and say LDL only in their short and related names. The acronym check therefore reads every release name (component, long common name, short, consumer and related names), as the density check does.

A clinician approval of the row's own surface is the name judgement. When the candidate is such an approval (exact_for_row with clinician authority, on the full pipeline and on the registry fast path alike), the name heuristics are recorded (deferred_name_conflict, and a reason name heuristic deferred to the clinician approval) and skipped. Every physical gate still applies: time, unit dimension and property, specimen, method and scale. Publication is where the heuristics check the clinician's choice, so validate_review_decision keeps them as errors (an IgG code for a row printed IgM is refused); once published, the approval is not second-guessed by a naming rule.

The same judgement covers the facts read out of the name. For an approval of the row's own surface, the validator sees the row's own columns (specimen, method, time), not the specimen, method or time words parsed from the approved name: Blood in White Blood Cells (WBC), Stool, Direct in Direct HDL, Concentration in Mean Corpuscular Hemoglobin Concentration were already judged by the clinician (Mapper._validation_view, full pipeline and fast path alike). Context conflicts read from the name (Plasma or Serum gives two specimen clues) do not override such an approval either. A specimen, method or time the row prints in its own column still decides. A duration printed in the name (Calcium, 24Hr Urine) states the time of a candidate whose own time aspect is that duration; it never contradicts one, because a number in a name can also be a challenge time (Glucose 2 hr post), which LOINC files inside a point-in-time Component.

Siblings that name one analyte

LOINC often has several terms for one reported analyte that differ only in an axis the report does not print: Hepatitis C virus Ab in Ser, Ser/Plas and Ser/Plas/Bld; a method-specific and a methodless term; the general Lipoprotein.beta.subparticle size and its .medium subparticle. Between such siblings the confidence margin measured only retrieval noise, so the row reviewed although every survivor named the same analyte (four hepatitis C terms scored 0.938 each in production). After ranking, the mapper therefore groups the survivors by meaning (meaning.MeaningGrouper) and takes the margin between groups:

  • Key. Component root, Property, Scale, Time and System family (serum and plasma variants, PPP and PRP are one family). Under a panel trusted for ranking, a confirmed member and its non-member twin in another System family are one group.
  • Component root. A term that adds a dotted qualifier to another surviving term's Component (a ratio keeps its denominator: Lymphocytes.variant/Leukocytes extends Lymphocytes/Leukocytes) joins that term's group when the row prints neither the qualifier's words nor a word only the qualified term's LOINC names carry. An abbreviation of a qualifier word counts as printed (Imm for immature, Bands for band form); a synonym from the qualified term's names must be the row's own word (Atypical for variant), because a row word such as lymph begins many release words that do not name the qualifier. A base that asserts a method of its own is not the general reading. An isotype the row prints is a fact (2026-10-05): a term that adds an isotype or a numbered partner the row does not print (Ab.IgG+IgM for a row printing IgM, virus 1+2 for a row printing 1; meaning._pair_qualifier) is an unprinted extension of the row's term and joins its group after it, and a term that drops the printed isotype (HSV 2 Ab for HSV 2 Ab, IgG) is the general term of a narrower printed analyte and joins the isotype term's group after it, because LOINC's isotype-less antibody term means any class, which the print contradicts (_printed_isotype_extension). HEPATITIS A IgM, Herpes Simplex 1 Ab, IgG and Herpes Simplex 2 Ab, IgG had reviewed on margins of 0.055-0.07 between such pairs.
  • Material the practice uses. When the row states no specimen, terms that differ only in System join when the practice's clinicians approved that analyte (Component and Property) in exactly one material family, and a term of that family wins: Lymphs % meets Lymphocytes/Leukocytes in blood, body fluid and synovial fluid, and the practice approved blood only. Two approved materials (serum and urine osmolality) keep the Systems apart for review; the group records practice_system.
  • A count per microscopy field. /HPF and /LPF are a urine sediment examination by that method (2026-10-07): urine and urine sediment are one material, the field microscopy sibling is the printed method the group prefers (in a general group too), and Component roots fold singular and plural (Erythrocyte and Erythrocytes are one analyte). Urinalysis rows that reached the mapper under the CBC heading had reviewed between the sediment count by microscopy and Erythrocyte [#/area] in Urine by Computer assisted; they file 13945-1 / 5821-4 wherever the heading landed. A printed other material (stool, sputum, a wet preparation) keeps its own terms: the rule never files a urine sediment code over it. The unit gate reads the same fact: a count per field is not a concentration, so a ...Cnc term with no example units (Leukocyte esterase [Units/volume] in Urine, which had outranked the sediment count) is rejected (safety._field_count_conflict).
  • Scale on a printed number. A number printed with a dimensional unit is a quantitative result (2026-10-06): a semi-quantitative sibling, the test strip with a detection limit the row never prints (Albumin [Mass/volume] in Urine by Detection limit <= 20 mg/L test strip), joins the quantitative method-less term's group and ranks after it. Albumin, Urine printed 12 mg/L had reviewed at 0.005 between the two.
  • Method. With no printed method, a method-specific term joins only a group that has a methodless term, or a set of method-only siblings the practice approved in exactly one of their methods (stool O&P by light microscopy; practice_method). Calculated and direct LDL, each asserting a method the row did not print, stay two groups and the row reviews.
  • Winner. Chosen by evidence, never by a list: a code a clinician approved for this row's own surface, then the general term over an unprinted qualifier, then the practice's material, then an axis the row printed and the candidate matches, then a code the practice approved for any surface, then panel membership, then no unprinted method, then LOINC's common-test rank.

The winner keeps the score of its group's best member and records the others (meaning_group_alternatives); stages.meaning_groups lists every group of more than one term, and the alternatives stay in candidates. A different Component, Property, Scale or Time is always a different group, so a genuine ambiguity still reviews, and 0.82/0.08 are unchanged.

A row that states a relation between analytes (X/Y between words, or the words ratio, index, per, vs: BUN/CREAT, Albumin Creatinine Ratio) is not one analyte's quantity. A candidate that is not itself a relation (no slashed Component, no ratio, fraction or relative Property, no relation word) is rejected (row states a relation; candidate is a single analyte), and a plain registry alias contained in such a row (BUN in BUN/CREAT) is not retrieved. Grammar only: HIV 1/2 Ab (a numbered pair), Lyme IgG/IgM Ab (names that differ only in their last character enumerate), a slash inside parentheses (RISK RATIO (CHOL/HDL) still says ratio) and a slash inside a reviewed method surface (INSULIN, INTACT, LC/MS/MS, GC-MS; safety.relation_stated passes the multi-word method aliases of axis_context_aliases.json, version 2026.09.25.1, to states_relation) state no relation (2026-09-25; the LC/MS/MS rows had rejected every candidate as "a single analyte"). Like the other name heuristics, it is deferred for an approval of the row's own surface. Whether the two sides of a surviving pair name one analyte is the catalog's reading, not the grammar's (2026-10-08, grounding.slash_enumerates, read once per row against its candidates and passed to the validator as relation): a side is the whole text on its side of the slash with the relation words removed (normalization.slash_sides; ARACHIDONIC ACID and EPA of ARACHIDONIC ACID/EPA RATIO), and a side names a candidate term when the term explains every word of it. SGPT/ALT and SGOT/AST are two names of one enzyme — terms named by both sides exist (SGPT is among the related names of every alanine aminotransferase term) and the sides do not each have an analyte of their own; a stray a short side also reaches (ALT is among the names of a nucleotide term) is not an analyte of the row's — and ABO/Rh is the pair LOINC writes as the Component ABO & Rh group; such a slash enumerates, the single-analyte terms are not refused, and the row records stages.slash_enumeration. ARACHIDONIC ACID/EPA names the arachidonate terms on one side and the eicosapentaenoate terms on the other, so it relates them; CHOL/HDL names Cholesterol.in HDL on both sides but HDL is its sub-part, a denominator, so the relation stands; a ratio term counts for nothing, nor does a side of fewer than three letters (Na/K). A hospital's CMP had rejected every alanine and aspartate aminotransferase term, the practice's approvals ALT (SGPT) and AST (SGOT) among them.

The mirror (2026-10-05): a ratio of two analytes needs both named. A candidate whose Component is slashed and whose Property is a typed ratio (MRto, SRto, NRto: a ratio of two measured quantities) is rejected for a row that states no relation in its name, prints no ratio unit (no mass or substance ratio, no denominator annotation; % is a fraction of a whole, not a ratio of two analytes) and does not name every side of the Component by that side's own words (grounding.unnamed_relation_sides: 6 against 3, albumin against creatinine; digits count, because LOINC tells omega 6 from omega 3 by them): row names one analyte; candidate is a ratio of two. OMEGA-3 TOTAL and OMEGA-6 TOTAL (% by wt) had filed the omega 6/omega 3 molar ratio 48382-6 outright, the only OmegaCheck member that survived, whose Component contains the row's words; a GLOBULIN row no longer carries the albumin/globulin ratio. LOINC's fractions (...Fr: Hemoglobin A1c/Hemoglobin.total, Lymphocytes/ Leukocytes) and its plain Ratio (Erythrocytes.nucleated/Leukocytes, printed per 100 cells) relate an analyte to a reference population and are not read here; OMEGA-6/OMEGA-3 RATIO, CHOL/HDLC RATIO, ALBUMIN/GLOBULIN RATIO state the relation, ALBUMIN, URINE in mg/g states it through its unit, and an approval of the row's own surface is deferred to as above.

A registry alias contained in a longer row keeps its authority only when every word the row adds is one of the target's own release words (the same word, or one abbreviating the other: Plt Count, VLDL Cholesterol Cal, Glucose, Fasting). Otherwise the extra words may change the analyte (LDL PATTERN, Nucleated RBC %, Small LDL-P): the hit is ordinary retrieval (registry_match: contained_unexplained, score 0.75, below acceptance, no registry tier or bonus), so containment alone never files.

With no material known (no specimen printed, no trusted heading with a hard default) and no approval of the analyte in any material, System siblings of one analyte join one group and the default material wins (2026-09-25; MeaningGrouper._default_material_keys), so a practice that files in serum reads Alpha-1-Globulins as its serum electrophoresis fraction rather than the urine, body-fluid or synovial term that tied it at 0.900. Since 2026-09-29 the default is per test family (Mapper._practice_class_defaults): for each LOINC class, the material most of the practice's published approvals of that class are in, when the class has at least practice_policy.material_default.min_codes approved codes (5) and that material holds at least min_share of them (0.70); urine sediment counts as urine. On the 2026-09-25 registry: chemistry, serology and microbiology read in serum or plasma, hematology in blood, urinalysis in urine; toxicology has no material with the share and coagulation and blood bank too few approvals, so their rows ask. The default of a group is the one material among its siblings that is the default of its own class; two such materials (a chemistry term in serum beside a urinalysis term in urine) leave the Systems apart. The material the panel records that name the row agree on comes first, as before. The default must be among the siblings; otherwise the Systems stay apart for review. The filing is recorded in stages.policy_defaults as {axis: "system", choice, code, class}. An analyte the practice approved somewhere keeps the practice rule above (one material decides, two stay apart). The mapped row carries review_flags: ["specimen_unprinted"]: the code is the practice's reading, the material was never printed. PEP, SPEP, UPEP and protein electrophoresis are reviewed surfaces of the electrophoresis method (axis_context_aliases.json 2026.10.05.1), so Albumin (PEP) keeps the electrophoresis term over the plain albumin; a clue that would leave the name only single-character words is released (C-Pep is C-peptide, not an electrophoresis of C).

The same reading for a row that prints no unit at all (2026-10-08, Dr Tro's confirmed answers, practice_policy version 2026.10.08.1). The Property siblings of an analyte the practice never approved in any Property for that material join under the practice's property default (property_default: MCnc; MeaningGrouper._default_property_keys): a unit-less urine creatinine reads as the mass concentration, not the molar one. A unit that is printed but incomplete is not "no unit at all" and the siblings stay apart as before. In the urine microscopy family (System urine or urine sediment, a microscopy Method — read from LOINC's axes, meaning.microscopy_method) a bare number reads as the count per field and a degree word (FEW, MODERATE; value_policy.ordinal_answers) as the presence term (scale_default.urine_microscopy): the presence twin folds into the count on a number, the count into the presence term on a word; a type name (CALCIUM OXALATE) is no degree word and keeps the type term apart, and an approval of the row's own surface files before any of this. For the Property, an approval of the analyte in any Property for that material keeps the practice's own rule (_practice_property_keys), which since the same day counts only the approved Properties among the survivors: the timed urine creatinine the practice approved beside the random one is no sibling of a point-in-time row, so the random row reads as the approved mass concentration. A microscopy result with no specimen and no trusted heading reads in the practice's microscopy material (method:microscopy beside the per-class defaults in Mapper._practice_class_defaults: the material most of the practice's approved microscopy terms are in, urine) even where the panel records that name the row disagree (the CBC's blood count is not what a degree word reports); urine and urine sediment are one material and the microscopic examination is the method the group prefers, as on a field unit; the row is flagged specimen_unprinted like any default material, and a printed other material (stool, sputum) keeps its own terms. Each filing is recorded in stages.policy_defaults ({axis: "property", choice} — the default's Property, or practice_property with the approved one — {axis: "scale", choice: count_per_field | presence}) and a row read by a Property rule or the count reading files with review_flags: ["unit_missing"] instead of waiting for a unit: the decision is the practice's, the unit was never printed. Two readings of the value's shape go with it, grammar from LOINC's Property and Scale axes: on a degree word a morphology term (Erythrocyte [Morphology], Morph) reads as presence the way a type does and ranks after the presence term; on a bare number the word siblings of an analyte — an interpretation, a type, a morphology (a nominal Scale) — join its one quantitative group as alternatives (MeaningGrouper._fold_word_terms), so Creatinine [Interpretation] in Urine no longer holds the margin against the mass concentration a unit-less Creatinine, Urine prints a number for, and the test strip's semi-quantitative twin folds into the quantitative term on a bare number as it does beside a unit (Dr Tro's U4: the plain term, never the strip or detection-limit variant) — unless the row's trusted panel names the strip term as its member, as the urinalysis record names the test-strip specific gravity (2026-09-29): the practice's record decides. A presence term keeps its own group on a number, as decided on 2026-09-25: index-reported presence tests print bare numbers; on a degree word the urine strip's semi-quantitative count reads as presence with the microscopy count and ranks after the microscopy presence term. One guard closes the material question (Mapper._open_material_outcome): a microscopy result with no material printed whose leader is a microscopic examination in another material reviews naming it — WBC MODERATE with nothing else printed had filed the stool leukocytes once the blood morphology terms stopped holding the margin, because the urine term was not among the candidates and nothing may stand in for the default. A serology row printing Negative is a degree word too; its serum term is no microscopic examination and is its own answer, with its class default as before.

A challenge state the row does not print (C peptide^post CFst, the fasting C-peptide) extends its analyte the way a dotted qualifier does and joins the general term's group; a timed challenge (Glucose^2H post dose glucose) carries a number the row would have printed and stays its own analyte (2026-09-25). C-Pep had abstained 0.072 from its fasting sibling.

A trailing word after the analyte, which LOINC then names in the singular (Erythrocyte clumps, Erythrocyte casts, Leukocyte clumps over Erythrocytes / Leukocytes), is an unprinted qualifier in the same way (2026-10-02, meaning._trailing_qualifier): when the row prints neither the word nor a name only the narrower term carries, that term joins the count's meaning group and ranks after it. A urinalysis RBC printed /HPF under a heading that also prints Urine had tied the count 13945-1 with the clumps term 58449-0 by 0.026; a row printing RBC Clumps, or LE for leukocyte esterase, keeps the narrower term apart.

A A+B+C compound of named analytes that adds analytes the row does not print (Basophils+Eosinophils+Monocytes/Leukocytes against Mono % (Auto)) is an unprinted extension of the single analyte's term, and a dotted Property (NFr.DF, a pure number fraction) is a unit convention on its family (NFr), so both join the general term's meaning group and rank after it: the general term, then the practice's approved code, then the undotted Property, then common rank (2026-09-25; meaning._compound_qualifier, _property_root). A numbered pair (HIV 1+2 Ab) is never split, and a row that prints the compound's analytes (Baso+Eos+Mono %) keeps the compound apart. Mono % (Auto) had tied three ways at 0.900.

The row must name what is filed

Embedding similarity can rank a term whose names share nothing with the row: Baso (Absolute) scored the basil IgE term above the basophil count the row abbreviates. A leader the row's own words do not name is therefore never filed while a candidate the row does name survives; the row abstains with unnamed_top_candidate, naming that candidate (grounding.row_names_term). A row names a term when one of its words (not an axis word of the term) is one of the term's analyte words, from its Component, long-name head, consumer, short and related names, or abbreviates one (Baso -> basophil), or is a near spelling of a long one. Only the row abbreviates: the basil term's Abs (antibodies) never names a row printing Absolute. An abbreviation is the start of a word at least two letters longer (2026-10-05): the start of a token one letter longer is another token (rpr inside the peach allergen's rPru p, TSH inside TSHR), and a compacted related phrase matches whole, never by its start. A test printed by its method (RPR, VDRL, FTA-ABS) names the terms that carry that Method (grounding._names_and_axis): when every row word left after the term's other axis words is a word of the Method, the Method's words are names, not axis words. RPR NONREACTIVE had reviewed on three peach-allergen terms it "named" by that substring while the reagin terms it meant were named by nothing. A one-letter token followed by a word the term explains is the initial of a name word (M. genitalium for Mycoplasma genitalium; a trailing D against a B12 term stays unexplained). OCR confusions (I/l/1, O/0, rn/m) are folded and related names also count compacted (Hgb A1c = HgbA1c). A row of two letters or fewer carries no lexical signal. Approvals and reviewed registry hits are exempt, and a leader with no named competitor is unaffected, because some abbreviations appear in no LOINC name (RDW, MPV, ANC): measured on the practice's 743 approved surfaces, 14 would not ground, all such abbreviations.

A group winner whose names explain strictly more of the row's words than the leader's do takes the lead (Mapper._coverage_order, grounding.row_word_coverage, 2026-09-25). Between different component roots the margin is retrieval noise: Direct Bilirubin ranked total bilirubin (its names explain bilirubin) over Bilirubin.direct (bilirubin and direct); INSULIN RESISTANCE SCORE ranked the contained Insulin approval over the score term; Babesia duncani WA1 IgG ranked isotype-less antibody terms over the IgG term. Coverage is the row's content words explained by the term's analyte names, matched as row_names_term matches them; a term's axis words never count, nor do the spans of the row's kept axis clues (a method or specimen word is not an analyte word). The new leader keeps its own score, so an unexplained margin still reviews; the winners it strictly covers become alternatives and leave the margin; a reviewed registry leader is never displaced (the clinician named the code); equal coverage is untouched. stages.coverage_dominance records leader, displaced and each winner's coverage.

Outcomes and diagnostics

The confidence policy can return:

  • mapped: a safe candidate passed confidence and margin gates.
  • abstain: evidence is ambiguous, incomplete, or needs correction.
  • invalid_candidate: candidates existed but every one had a hard safety conflict.
  • no_candidate: no usable candidate was generated.
  • force_mapped: a separately governed, exact-lossless operational exception.

Every result includes bounded diagnostics, provenance.stages.candidate_decision_trace, and a single provenance.stages.primary_outcome. The main project should show the primary outcome first, then candidate diagnostics, so an unrelated rejected sibling is not presented as the reason a routine row abstained.

A blocked approved code stays the outcome

When a clinician-approved entry matched the row's own surface (registry authority with exact_for_row) but its candidate did not survive validation, the approved code is the outcome, not the noise that survived. The row abstains even when an unrelated survivor clears both thresholds, reasons names the rejection (the clinician-approved code 27353-2 was rejected: registry required_specimen needs an explicit analytical specimen), and primary_outcome is built from the blocked candidate:

  • missing_required_clinical_fact when a registry constraint failed (specimen, method, class, property, panel): the report lacks a fact the approval requires. Estimated Average Glucose approved for Bld on a row that states no specimen abstains on 27353-2 instead of ranking an ethyl glucuronide term. The constraint decides whether the entry applies, never whether the code exists (2026-09-25): an entry the row's context does not satisfy is inapplicable, its authority, match and constraints leave the candidate, and the code goes on with the score its retrieval layers gave it (registry_entry_out_of_scope in its evidence) or leaves when nothing else proposed it (trace outcome inapplicable). Before, the gate hard-rejected the code: a bare Iron Saturation row, which only contains the % SATURATION alias approved under the iron heading, lost 2502-3 and filed the molar twin 14801-5 at 0.998; it now ties the two fractions and reviews.
  • approved_candidate_rejected when the validator rejected the approved code for any other reason (an OCR-damaged unit such as m/min/1.73m2 on EGFR). The candidate-level diagnostic (unit_dimension_mismatch) is kept beside it so the main project's unit repair can still act, and candidate_display carries code - long common name for the review page.

primary_outcome.candidate_code may therefore name a code that is absent from candidates. When nothing survived, the status stays invalid_candidate/abstain as the policy decided, with the same primary outcome.

An approval is only blocked when it applies to the row. When the approved code's System contradicts a printed fact, the approval was made for another context and blocks nothing: a printed analytical specimen or collection source (GLUCOSE approved for serum on a urine row of a urinalysis panel), a trusted panel's default specimen, or the entry's own required_specimen against a printed specimen. Such an approval is recorded in stages.inapplicable_approved_candidates and its registry_evidence item carries applicable: false with not_applicable_reason, so a consumer does not route it to a unit repair; the row's survivors are ranked as usual. The rule does not depend on which gate rejected the code first: the validator stops at the first gate that rejects, and the specimen gate comes after the unit gate, so the mapper asks the specimen gate on its own (safety.material_conflict, the validator's own specimen logic) for every rejected approval of the row's surface (2026-10-02). A urinalysis RBC printed /HPF with Urine on the row, in its section heading or as the collection source does not stay blocked on the blood count's unit. A report-level phrase stays soft evidence and keeps the block. A missing fact (no specimen where Bld is required, no heading where a panel is required) or a unit rejection with no contradicting material still blocks, with one exception (2026-10-07, Mapper._made_for_another_row): a count per microscopy field (/HPF, /LPF) refused against a code whose units are per volume is another kind of result, not a convertible unit, so the approval is inapplicable whatever the heading or the specimen printed; a unit of the code's own dimension (mmol/L for mg/dL) still blocks and names it. A contained or compact-form registry hit is not an approval of the row's surface and never triggers the rule, and a code approved under two entries that are both exact for the row (mg/dL universally, mmol/L for one laboratory) is validated against the union of their units before it can be called blocked; a contained entry neither adds nor relaxes a unit, so the full pipeline and the registry fast path stay equivalent. If any clinician-authority candidate survives, nothing changes.

Force-exact exception

force_mapped is intentionally separate from a clinician-approved mapping. It is allowed only for a verified active Observation/Both LOINC target and an exact lossless label match. It bypasses unit and six-axis checks by explicit policy, is auditable in provenance, is excluded from ordinary accuracy metrics, and is blocked from Elation by default. It must be created only through the restricted server-side publication action, never a general staff/clinician UI.

An override may additionally be source-scoped only when a documented assay requires it. A source-scoped exact override must match the incoming source laboratory or source test identifier; it never prevents the source-neutral mapping path from running for another laboratory. The OCR-recovery bootstrap contains three Labcorp-specific examples and otherwise uses source-neutral canonical identities. See OCR recovery contract.