@cosyte/astm 0.0.22 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -858,7 +858,9 @@ declare function unpairedEscapeCharacter(position: AstmPosition): AstmRecordWarn
858
858
  declare function ambiguousValueSplit(position: AstmPosition): AstmRecordWarning;
859
859
  /**
860
860
  * Build an `ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG` warning. The flag is surfaced as
861
- * `undefined` (never coerced to `normal`); the warning carries only the position.
861
+ * `undefined` (never coerced to `normal`); the warning carries only the position
862
+ * and fixed prose naming the **same** vocabulary the interpreted flag reports,
863
+ * read from the one shared constant so the two cannot drift apart.
862
864
  *
863
865
  * @example
864
866
  * ```ts
@@ -870,6 +872,8 @@ declare function undefinedAbnormalFlag(position: AstmPosition): AstmRecordWarnin
870
872
  /**
871
873
  * Build an `ASTM_RECORD_UNDEFINED_RESULT_STATUS` warning. The status is surfaced
872
874
  * as `undefined` and, like every non-final status, never reads as active-final.
875
+ * The message carries the **same** attribution the interpreted status reports
876
+ * (there is no citable source for this letter set), from the one shared constant.
873
877
  *
874
878
  * @example
875
879
  * ```ts
@@ -1254,22 +1258,30 @@ declare function astmDateToLocalISO(d: AstmDate): string;
1254
1258
  * spec's component order is LOINC-slot / test-name / coding-scheme / local-code.
1255
1259
  * In the field the way real analyzers emit it:
1256
1260
  *
1257
- * - **component 1** is a LOINC slot: *almost always empty*; a few vendors place
1258
- * an inline LOINC when one exists;
1261
+ * - **component 1** is a LOINC slot: *almost always empty*, and the governing
1262
+ * mapping guide puts LOINC on the instrument wire explicitly out of scope, so
1263
+ * whatever arrives there is a wire value this library **does not vouch for**
1264
+ * and never treats as an identifier;
1259
1265
  * - **component 4** is the vendor/local code: the identifier that is actually
1260
- * present, and therefore the **primary** identifier here.
1266
+ * present, and therefore the **only** identifier a result is keyed on here.
1261
1267
  *
1262
1268
  * Following the sibling parsers' conservative posture, this module is a
1263
1269
  * **structural recognizer, not a dictionary**: it tags where a code came from
1264
1270
  * (its provenance) and surfaces every code verbatim. It does not look codes up,
1265
- * does not validate them, and bundles no LOINC/SNOMED tables.
1271
+ * does not validate them, and bundles no LOINC/SNOMED tables. In particular it
1272
+ * performs **no LOINC validation of any kind** and never decides what a
1273
+ * first-component value "looks like": `2345-7` and `Glucose` in component 1 are
1274
+ * treated identically, and the routing is positional throughout.
1266
1275
  */
1267
1276
  /** Where a Universal Test ID's usable identifier came from. */
1268
1277
  type UniversalTestIdProvenance =
1269
- /** Component 1 (the LOINC slot) is populated: a candidate LOINC, recognized not validated. */
1270
- "inline-loinc-candidate"
1271
1278
  /** Component 4 (the vendor/local code) carries the identifier. */
1272
- | "local-code"
1279
+ "local-code"
1280
+ /**
1281
+ * Component 1 is populated and there is no vendor/local code, so there is **no**
1282
+ * identifier: the value is surfaced unvalidated and is never keyed on.
1283
+ */
1284
+ | "unvalidated-wire-value-only"
1273
1285
  /** Only the test name (component 2) is present: no code. */
1274
1286
  | "name-only"
1275
1287
  /** Nothing usable in the field. */
@@ -1289,13 +1301,17 @@ type UniversalTestIdProvenance =
1289
1301
  interface UniversalTestId {
1290
1302
  /** The field's components, verbatim and in order. */
1291
1303
  readonly components: readonly string[];
1292
- /** Component 1 when populated: a *candidate* LOINC (provenance only, never validated). */
1293
- readonly loincCandidate?: string;
1304
+ /**
1305
+ * Component 1 when populated, verbatim: a wire value this library **does not
1306
+ * vouch for**. It is not validated, is not reported as a LOINC or a LOINC
1307
+ * candidate, and is never the code a result is keyed on.
1308
+ */
1309
+ readonly unvalidatedWireValue?: string;
1294
1310
  /** Component 2: the test / battery name, when present. */
1295
1311
  readonly testName?: string;
1296
1312
  /** Component 3: the coding-scheme selector, when present. */
1297
1313
  readonly codingScheme?: string;
1298
- /** Component 4, the vendor/local code: the primary identifier when no inline LOINC is given. */
1314
+ /** Component 4, the vendor/local code: the only identifier a result is keyed on. */
1299
1315
  readonly localCode?: string;
1300
1316
  /** Where the primary identifier came from. */
1301
1317
  readonly provenance: UniversalTestIdProvenance;
@@ -1303,6 +1319,9 @@ interface UniversalTestId {
1303
1319
  /**
1304
1320
  * Recognize a Universal Test ID from a field's already-decoded components.
1305
1321
  *
1322
+ * The provenance is decided **positionally**, by which components are populated,
1323
+ * and never by what a value looks like.
1324
+ *
1306
1325
  * @param components - The component strings (escape-decoded), in order.
1307
1326
  * @returns The recognized, provenance-tagged Universal Test ID.
1308
1327
  * @example
@@ -1313,16 +1332,18 @@ interface UniversalTestId {
1313
1332
  */
1314
1333
  declare function recognizeUniversalTestId(components: readonly string[]): UniversalTestId;
1315
1334
  /**
1316
- * The primary code to key a result on: the inline LOINC candidate when a vendor
1317
- * supplied one, otherwise the local (vendor) code. Returns `undefined` when the
1318
- * field carries no code at all (name-only or empty): never a guess.
1335
+ * The primary code to key a result on: the local (vendor) code, and nothing else.
1336
+ * Returns `undefined` when the field carries no vendor/local code, **including**
1337
+ * when component 1 is populated: never a guess, and never a value this library
1338
+ * does not vouch for. Read `unvalidatedWireValue` for that value.
1319
1339
  *
1320
1340
  * @param u - A recognized Universal Test ID.
1321
- * @returns The primary code, or `undefined`.
1341
+ * @returns The vendor/local code, or `undefined`.
1322
1342
  * @example
1323
1343
  * ```ts
1324
1344
  * import { primaryCode, recognizeUniversalTestId } from "@cosyte/astm";
1325
- * primaryCode(recognizeUniversalTestId(["2345-7", "Glucose", "LN", "687"])); // "2345-7"
1345
+ * primaryCode(recognizeUniversalTestId(["2345-7", "Glucose", "LN", "687"])); // "687"
1346
+ * primaryCode(recognizeUniversalTestId(["Glucose", "", "", ""])); // undefined
1326
1347
  * ```
1327
1348
  */
1328
1349
  declare function primaryCode(u: UniversalTestId): string | undefined;
@@ -1837,6 +1858,127 @@ interface DefineAstmProfileOptions {
1837
1858
  readonly extends?: AstmProfile | readonly AstmProfile[];
1838
1859
  }
1839
1860
 
1861
+ /**
1862
+ * Vocabulary attribution for the code sets this library grades wire letters
1863
+ * against.
1864
+ *
1865
+ * An `R` record carries single letters whose meaning comes from a published
1866
+ * vocabulary, and the ASTM record grammar itself is frozen while those
1867
+ * vocabularies keep moving. Reporting a letter as "unrecognized" without saying
1868
+ * what it was compared against therefore hides a staleness: a consumer cannot
1869
+ * tell a code no published vocabulary defines from a code this library has
1870
+ * simply not caught up to.
1871
+ *
1872
+ * So every interpreted value carries an attribution, and the attribution has
1873
+ * exactly two shapes:
1874
+ *
1875
+ * - {@link NamedVocabulary}: a published code system, named by identifier and
1876
+ * version. **The version says what this library compared against; it is not a
1877
+ * claim about which version the sender meant.**
1878
+ * - {@link UnattributedVocabulary}: no citable published source. This is a
1879
+ * positive statement, deliberately distinguishable from an attribution that is
1880
+ * simply absent, and it never carries a code-system identifier.
1881
+ *
1882
+ * These constants are the single source of truth: the interpreted value and the
1883
+ * warning raised beside it both read them, so the two can never drift apart.
1884
+ */
1885
+ /**
1886
+ * A published code system this library graded a value against, named by
1887
+ * identifier and version.
1888
+ *
1889
+ * @example
1890
+ * ```ts
1891
+ * import { ABNORMAL_FLAG_VOCABULARY } from "@cosyte/astm";
1892
+ * ABNORMAL_FLAG_VOCABULARY.attributed; // true
1893
+ * ABNORMAL_FLAG_VOCABULARY.version; // the version compared against
1894
+ * ```
1895
+ */
1896
+ interface NamedVocabulary {
1897
+ /** Always `true`: a published source is cited. */
1898
+ readonly attributed: true;
1899
+ /** The code system's canonical identifier, verbatim from the published source. */
1900
+ readonly system: string;
1901
+ /**
1902
+ * The code system version this library compared against, verbatim from the
1903
+ * published source. **Not** a claim about the version the sender used.
1904
+ */
1905
+ readonly version: string;
1906
+ }
1907
+ /**
1908
+ * The explicit statement that a code set is bound by **no citable published
1909
+ * source**. It carries no identifier and no version, because inventing either
1910
+ * would be the guess this library exists to refuse.
1911
+ *
1912
+ * @example
1913
+ * ```ts
1914
+ * import { RESULT_STATUS_VOCABULARY } from "@cosyte/astm";
1915
+ * RESULT_STATUS_VOCABULARY.attributed; // false
1916
+ * RESULT_STATUS_VOCABULARY.reason; // fixed prose: why nothing is cited
1917
+ * ```
1918
+ */
1919
+ interface UnattributedVocabulary {
1920
+ /** Always `false`: no published source can be cited for this code set. */
1921
+ readonly attributed: false;
1922
+ /** Fixed prose saying why nothing is cited. Never a code-system identifier. */
1923
+ readonly reason: string;
1924
+ }
1925
+ /**
1926
+ * What an interpreted wire letter was graded against: either a named published
1927
+ * code system or an explicit "no citable source". Discriminate on
1928
+ * {@link NamedVocabulary.attributed}.
1929
+ */
1930
+ type VocabularyAttribution = NamedVocabulary | UnattributedVocabulary;
1931
+ /**
1932
+ * The vocabulary the `R` record's abnormal-flag letters (field 7) are graded
1933
+ * against: the HL7 v3 ObservationInterpretation code system, which carries the
1934
+ * concepts kept aligned with the HL7 v2 Table 0078 interpretation codes this
1935
+ * library has always recognized.
1936
+ *
1937
+ * The identifier and version are transcribed character for character from that
1938
+ * code system's published source of truth. **The version records what this
1939
+ * library compared against, not what the sender meant.**
1940
+ *
1941
+ * @example
1942
+ * ```ts
1943
+ * import { ABNORMAL_FLAG_VOCABULARY, interpretAbnormalFlag } from "@cosyte/astm";
1944
+ * interpretAbnormalFlag("HU").vocabulary === ABNORMAL_FLAG_VOCABULARY; // true
1945
+ * ```
1946
+ */
1947
+ declare const ABNORMAL_FLAG_VOCABULARY: NamedVocabulary;
1948
+ /**
1949
+ * The attribution for the `R` record's result-status letters (field 9): **none**.
1950
+ *
1951
+ * The letters this library models are the ones real analyzers send, but the
1952
+ * normative text that would bind them to a published code set is purchase-gated
1953
+ * and has not been read here. A neighbouring HL7 table exists whose letters
1954
+ * partly agree, and adopting it would be an assertion nothing in the public
1955
+ * record supports. So this library says the set is unattributed rather than
1956
+ * citing a source it cannot stand behind.
1957
+ *
1958
+ * @example
1959
+ * ```ts
1960
+ * import { interpretResultStatus } from "@cosyte/astm";
1961
+ * interpretResultStatus("F").vocabulary.attributed; // false
1962
+ * ```
1963
+ */
1964
+ declare const RESULT_STATUS_VOCABULARY: UnattributedVocabulary;
1965
+ /**
1966
+ * Render a vocabulary attribution as fixed prose for a warning message.
1967
+ *
1968
+ * The result is a function of the attribution constants alone: it embeds no
1969
+ * field text, no value, and no position, so a warning built with it stays
1970
+ * value-free.
1971
+ *
1972
+ * @param vocabulary - The attribution to describe.
1973
+ * @returns Fixed prose naming the code system and version, or the unattributed reason.
1974
+ * @example
1975
+ * ```ts
1976
+ * import { describeVocabulary, ABNORMAL_FLAG_VOCABULARY } from "@cosyte/astm";
1977
+ * describeVocabulary(ABNORMAL_FLAG_VOCABULARY); // "graded against <system> version <version>"
1978
+ * ```
1979
+ */
1980
+ declare function describeVocabulary(vocabulary: VocabularyAttribution): string;
1981
+
1840
1982
  /**
1841
1983
  * Safety-critical result semantics for the ASTM `R` (result) record.
1842
1984
  *
@@ -1844,9 +1986,9 @@ interface DefineAstmProfileOptions {
1844
1986
  * fields **raw**. This module turns those raw letters into modeled, **fail-safe**
1845
1987
  * semantics, under one rule: **never a confident wrong value.**
1846
1988
  *
1847
- * - An **abnormal flag** (field 7) is recognized against HL7 Table 0078; an
1848
- * unrecognized flag is surfaced as `undefined` (with a warning) and is **never
1849
- * coerced to `normal`**.
1989
+ * - An **abnormal flag** (field 7) is recognized against a **named** published
1990
+ * vocabulary; an unrecognized flag is surfaced as `undefined` (with a warning)
1991
+ * and is **never coerced to `normal`**.
1850
1992
  * - A **result status** (field 9) is modeled so a **correction (`C`)** or a
1851
1993
  * **cancellation (`X`)** can never read as an active-final result, and an
1852
1994
  * **absent** status is a typed `unspecified`: **never assumed `F` (final)**.
@@ -1854,54 +1996,91 @@ interface DefineAstmProfileOptions {
1854
1996
  * pair; an unparseable range is surfaced **verbatim**, a bound is **never
1855
1997
  * fabricated**.
1856
1998
  *
1999
+ * Every interpreted flag and status also carries the **vocabulary it was graded
2000
+ * against**, recognized or not, so an unknown code is distinguishable from one
2001
+ * this library has not caught up to. The status set's attribution is the
2002
+ * explicit "no citable published source": see `../common/vocabulary.js`.
2003
+ *
1857
2004
  * Every bound and code is surfaced verbatim; nothing is collapsed, reconciled,
1858
2005
  * or converted. Units are handled at the record layer (a numeric value without
1859
2006
  * units warns; units are never defaulted, guessed, or converted, see
1860
2007
  * `./parse.ts`).
1861
2008
  */
2009
+
2010
+ /**
2011
+ * The recognized abnormal-flag letters, graded against
2012
+ * {@link ABNORMAL_FLAG_VOCABULARY} (the flag *value* set, a published fact set,
2013
+ * not CLSI prose). That code system carries the concepts kept aligned with the
2014
+ * HL7 v2 Table 0078 interpretation codes. Read in the context of an `R` record's
2015
+ * field 7: here `S`/`R`/`I` are the microbiology susceptibility codes and `R` is
2016
+ * **not** the repeat delimiter.
2017
+ *
2018
+ * Recognition is **exact match** after the surrounding whitespace is trimmed:
2019
+ * letter case is never folded, so `hu` is unrecognized and stays that way.
2020
+ */
2021
+ type AbnormalFlagCode = "L" | "H" | "LL" | "HH" | "<" | ">" | "N" | "A" | "AA" | "U" | "D" | "B" | "W" | "S" | "R" | "I" | "HU" | "LU";
1862
2022
  /**
1863
- * The recognized abnormal-flag letters, from **HL7 v2.2 Table 0078** (the flag
1864
- * *value* set, a published fact set, not CLSI prose). Read in the context of an
1865
- * `R` record's field 7: here `S`/`R`/`I` are the microbiology susceptibility
1866
- * codes and `R` is **not** the repeat delimiter.
2023
+ * The modeled meaning of a recognized {@link AbnormalFlagCode}, plus the
2024
+ * fail-safe sentinel `undefined` (a flag was present but is not in the graded
2025
+ * vocabulary), which is **never** collapsed to `normal`.
1867
2026
  */
1868
- type AbnormalFlagCode = "L" | "H" | "LL" | "HH" | "<" | ">" | "N" | "A" | "AA" | "U" | "D" | "B" | "W" | "S" | "R" | "I";
2027
+ type AbnormalFlagMeaning = "below-normal" | "above-normal" | "critically-below-normal" | "critically-above-normal" | "below-scale" | "above-scale" | "normal" | "abnormal" | "very-abnormal" | "significant-change-up" | "significant-change-down" | "better" | "worse" | "susceptible" | "resistant" | "intermediate" | "significantly-high" | "significantly-low" | "undefined";
1869
2028
  /**
1870
- * The modeled meaning of a recognized {@link AbnormalFlagCode}, plus the two
1871
- * fail-safe sentinels: `undefined` (a flag was present but is not in Table 0078),
1872
- * which is **never** collapsed to `normal`.
2029
+ * Every abnormal-flag letter this library recognizes, in declaration order. A
2030
+ * letter outside this list is surfaced verbatim as unrecognized, never guessed
2031
+ * into a meaning.
2032
+ *
2033
+ * @example
2034
+ * ```ts
2035
+ * import { ABNORMAL_FLAG_CODES } from "@cosyte/astm";
2036
+ * ABNORMAL_FLAG_CODES.includes("HU"); // true
2037
+ * ```
1873
2038
  */
1874
- type AbnormalFlagMeaning = "below-normal" | "above-normal" | "critically-below-normal" | "critically-above-normal" | "below-scale" | "above-scale" | "normal" | "abnormal" | "very-abnormal" | "significant-change-up" | "significant-change-down" | "better" | "worse" | "susceptible" | "resistant" | "intermediate" | "undefined";
2039
+ declare const ABNORMAL_FLAG_CODES: readonly AbnormalFlagCode[];
1875
2040
  /**
1876
2041
  * A recognized (or explicitly *un*recognized) abnormal flag. The raw field text
1877
2042
  * is always preserved; `recognized` is `false` and `meaning` is `"undefined"`
1878
- * for any letter outside Table 0078, the flag is surfaced, never dropped, and
1879
- * **never coerced to `normal`**.
2043
+ * for any letter outside the graded vocabulary, the flag is surfaced, never
2044
+ * dropped, and **never coerced to `normal`**.
2045
+ *
2046
+ * `vocabulary` is present either way, so a consumer holding an unrecognized flag
2047
+ * can tell a code no published vocabulary defines from one this library has not
2048
+ * caught up to.
1880
2049
  *
1881
2050
  * @example
1882
2051
  * ```ts
1883
2052
  * import { interpretAbnormalFlag } from "@cosyte/astm";
1884
2053
  * const f = interpretAbnormalFlag("HH");
1885
- * f.meaning; // "critically-above-normal"
1886
- * f.recognized; // true
2054
+ * f.meaning; // "critically-above-normal"
2055
+ * f.recognized; // true
2056
+ * f.vocabulary.version; // the code system version compared against
1887
2057
  * ```
1888
2058
  */
1889
2059
  interface AbnormalFlag {
1890
2060
  /** The verbatim field text, exactly as received. */
1891
2061
  readonly raw: string;
1892
- /** The Table 0078 code, present only when the raw text is a recognized flag. */
2062
+ /** The recognized code, present only when the raw text is a recognized flag. */
1893
2063
  readonly code?: AbnormalFlagCode;
1894
2064
  /** The modeled meaning; `"undefined"` (never `"normal"`) for an unrecognized flag. */
1895
2065
  readonly meaning: AbnormalFlagMeaning;
1896
- /** Whether the raw text matched a Table 0078 flag. */
2066
+ /** Whether the raw text matched a flag in the graded vocabulary. */
1897
2067
  readonly recognized: boolean;
2068
+ /**
2069
+ * The published code system this flag was graded against, named by identifier
2070
+ * and version. Always present, recognized or not. The version says what this
2071
+ * library compared against, **not** what the sender meant.
2072
+ */
2073
+ readonly vocabulary: NamedVocabulary;
1898
2074
  }
1899
2075
  /**
1900
- * Interpret an `R`-record abnormal-flag field (field 7) against HL7 Table 0078.
2076
+ * Interpret an `R`-record abnormal-flag field (field 7) against
2077
+ * {@link ABNORMAL_FLAG_VOCABULARY}.
1901
2078
  *
1902
2079
  * Leading/trailing whitespace is ignored for the lookup but the `raw` text is
1903
- * preserved verbatim. An unrecognized flag yields `{ recognized: false, meaning:
1904
- * "undefined" }`, surfaced, never dropped, and **never `"normal"`**.
2080
+ * preserved verbatim. Matching is otherwise **exact**: letter case is never
2081
+ * folded. An unrecognized flag yields `{ recognized: false, meaning:
2082
+ * "undefined" }`, surfaced, never dropped, and **never `"normal"`**, and still
2083
+ * carries the vocabulary it was graded against.
1905
2084
  *
1906
2085
  * @param raw - The verbatim field-7 text.
1907
2086
  * @returns The interpreted flag.
@@ -1909,6 +2088,7 @@ interface AbnormalFlag {
1909
2088
  * ```ts
1910
2089
  * import { interpretAbnormalFlag } from "@cosyte/astm";
1911
2090
  * interpretAbnormalFlag("U").meaning; // "significant-change-up"
2091
+ * interpretAbnormalFlag("HU").meaning; // "significantly-high"
1912
2092
  * interpretAbnormalFlag("ZZ").meaning; // "undefined" (never "normal")
1913
2093
  * ```
1914
2094
  */
@@ -1917,6 +2097,10 @@ declare function interpretAbnormalFlag(raw: string): AbnormalFlag;
1917
2097
  * The recognized result-status letters (`R`-record field 9). The clinically
1918
2098
  * load-bearing members are **`C` (correction, supersedes a previously
1919
2099
  * transmitted value)** and **`X` (cannot be done / cancelled)**.
2100
+ *
2101
+ * This set is bound by **no citable published source**: see
2102
+ * {@link RESULT_STATUS_VOCABULARY}, which every interpreted status carries. As
2103
+ * with the flags, matching is exact after trimming; letter case is never folded.
1920
2104
  */
1921
2105
  type ResultStatusCode = "F" | "C" | "P" | "R" | "S" | "I" | "X";
1922
2106
  /**
@@ -1937,12 +2121,17 @@ type ResultStatusMeaning = "final" | "correction" | "preliminary" | "previously-
1937
2121
  * transmitted one.
1938
2122
  * - **`cancelled`** is `true` for `X`: the result cannot be done / was cancelled.
1939
2123
  *
2124
+ * `vocabulary` is always the explicit "no citable published source": this letter
2125
+ * set is modeled from what analyzers send, not from a vocabulary this library
2126
+ * can name.
2127
+ *
1940
2128
  * @example
1941
2129
  * ```ts
1942
2130
  * import { interpretResultStatus } from "@cosyte/astm";
1943
- * interpretResultStatus("C").isActiveFinal; // false (a correction is not active-final)
1944
- * interpretResultStatus("X").cancelled; // true
1945
- * interpretResultStatus(undefined).meaning; // "unspecified" (never "final")
2131
+ * interpretResultStatus("C").isActiveFinal; // false (a correction is not active-final)
2132
+ * interpretResultStatus("X").cancelled; // true
2133
+ * interpretResultStatus(undefined).meaning; // "unspecified" (never "final")
2134
+ * interpretResultStatus("F").vocabulary.attributed; // false: nothing is cited
1946
2135
  * ```
1947
2136
  */
1948
2137
  interface ResultStatus {
@@ -1960,6 +2149,12 @@ interface ResultStatus {
1960
2149
  readonly supersedes: boolean;
1961
2150
  /** `true` for `X`: the result cannot be done / was cancelled. */
1962
2151
  readonly cancelled: boolean;
2152
+ /**
2153
+ * The explicit statement that this letter set is bound by **no citable
2154
+ * published source**. Always present and always unattributed, so an absent
2155
+ * attribution and an unattributable one never look alike.
2156
+ */
2157
+ readonly vocabulary: UnattributedVocabulary;
1963
2158
  }
1964
2159
  /**
1965
2160
  * Interpret an `R`-record result-status field (field 9).
@@ -1969,14 +2164,18 @@ interface ResultStatus {
1969
2164
  * (recognized `false`). In every non-`F` case `isActiveFinal` is `false`, so a
1970
2165
  * correction, a cancellation, or an unknown status can never read as current.
1971
2166
  *
2167
+ * Every returned status carries {@link RESULT_STATUS_VOCABULARY}: the explicit
2168
+ * statement that no published source binding this letter set can be cited.
2169
+ *
1972
2170
  * @param raw - The verbatim field-9 text, or `undefined`/empty when absent.
1973
2171
  * @returns The modeled status.
1974
2172
  * @example
1975
2173
  * ```ts
1976
2174
  * import { interpretResultStatus } from "@cosyte/astm";
1977
2175
  * const s = interpretResultStatus("F");
1978
- * s.isActiveFinal; // true
1979
- * s.supersedes; // false
2176
+ * s.isActiveFinal; // true
2177
+ * s.supersedes; // false
2178
+ * s.vocabulary.attributed; // false: nothing is cited for this set
1980
2179
  * ```
1981
2180
  */
1982
2181
  declare function interpretResultStatus(raw: string | undefined): ResultStatus;
@@ -4285,6 +4484,183 @@ declare function decodeEscapes(leaf: string, d: Delimiters, onUnknown?: UnknownE
4285
4484
  */
4286
4485
  declare function splitEscapeAware(text: string, delimiter: string, escape: string, onAmbiguousAlignment?: AmbiguousAlignmentSink, onShiftedFields?: ShiftedFieldsSink, onTruncatedField?: TruncatedFieldSink, onShiftedComponents?: ShiftedComponentsSink): string[];
4287
4486
 
4487
+ /**
4488
+ * The shared `@cosyte/*` date conversion surface, over {@link AstmDate}.
4489
+ *
4490
+ * `toObject`, `toISO` and `toDate` carry the same names, the same return shapes
4491
+ * and the same timezone rule in every `@cosyte` parser, so moving between two of
4492
+ * them costs nothing to relearn. They are **additive**: {@link astmDateToLocalISO}
4493
+ * and {@link parseAstmDate} are untouched, and `toISO` renders exactly the string
4494
+ * `astmDateToLocalISO` renders for every value `parseAstmDate` produces.
4495
+ *
4496
+ * **ASTM is the strictest case for the rule these functions exist for.** An ASTM
4497
+ * timestamp models **no timezone at all**: the value is local to the instrument
4498
+ * that wrote it. So an ASTM value is never an absolute instant on its own, and
4499
+ * {@link toDate} answers `undefined` until the caller says which zone the
4500
+ * instrument was in (`assumeOffsetMinutes`). The host machine's zone is never
4501
+ * read and UTC is never assumed: either guess shifts a date of birth by a day in
4502
+ * every negative-offset zone, silently, and nothing throws to say so.
4503
+ *
4504
+ * Nothing here fabricates a component either. A value stated to day precision
4505
+ * converts to a day, never to a fabricated midnight, and the components a value
4506
+ * did not state are **absent** from {@link DateParts} rather than zero-filled.
4507
+ *
4508
+ * **Nor does anything here normalise a component that is out of calendar range.**
4509
+ * `parseAstmDate` is lenient by design and reads `19881301` into month 13, so the
4510
+ * conversions are where that has to be answered. All three refuse such a value
4511
+ * (`undefined`, never a throw), because the alternative is what a JS `Date` does
4512
+ * with it: month 13 rolls into January of the following year and comes back as a
4513
+ * confident, wrong instant that no warning marks. A date of birth is the case
4514
+ * that decided it.
4515
+ */
4516
+
4517
+ /**
4518
+ * The calendar components a parsed value actually stated: the shared return
4519
+ * shape of {@link toObject} across every `@cosyte` parser.
4520
+ *
4521
+ * Every value is a `number` and `month` is **spec-native 1 to 12**, never the JS
4522
+ * `Date` 0 to 11. A component the value did not state is **absent**: the key is
4523
+ * not present at all, rather than present and `undefined`, so `Object.keys()` of
4524
+ * the result is exactly the set of stated components and the value's precision is
4525
+ * recoverable from it. There is no `precision`, `raw`, `valid` or `truncated` key:
4526
+ * this is the calendar reading, not the parse record.
4527
+ *
4528
+ * Deleting `offsetMinutes` leaves an object `Temporal.PlainDateTime.from` and
4529
+ * luxon's `DateTime.fromObject` accept with no key rename and no value
4530
+ * adjustment, which is why the keys are singular and the month is 1-based.
4531
+ * Neither library is a dependency of this package: the shape is the interop, not
4532
+ * an import.
4533
+ *
4534
+ * `millisecond` and `offsetMinutes` belong to the shared shape and are **never
4535
+ * populated by this package**: an ASTM timestamp carries no timezone, and this
4536
+ * parser retains no fractional-second component (the digits stay in
4537
+ * {@link AstmDate.raw}). They are declared so that code written over two
4538
+ * `@cosyte` parsers reads one shape rather than two.
4539
+ */
4540
+ interface DateParts {
4541
+ /** Four-digit calendar year, exactly as stated (`50` is the year 50, not 1950). */
4542
+ readonly year?: number;
4543
+ /** Calendar month, **1 to 12**, as the standard states it. */
4544
+ readonly month?: number;
4545
+ /** Day of month, 1 to 31. */
4546
+ readonly day?: number;
4547
+ /** Hour of day, 0 to 23. */
4548
+ readonly hour?: number;
4549
+ /** Minute of hour, 0 to 59. */
4550
+ readonly minute?: number;
4551
+ /** Second of minute, 0 to 59. */
4552
+ readonly second?: number;
4553
+ /** Millisecond. Never populated here: this parser retains no fractional second. */
4554
+ readonly millisecond?: number;
4555
+ /** Signed minutes east of UTC. Never populated here: ASTM states no offset. */
4556
+ readonly offsetMinutes?: number;
4557
+ }
4558
+ /** The options {@link toDate} accepts. Carries `assumeOffsetMinutes` and nothing else. */
4559
+ interface ToDateOptions {
4560
+ /**
4561
+ * The zone to read an offset-less value in, as signed minutes east of UTC
4562
+ * (`-300` is UTC-05:00). Supplying `0` means "treat this value as UTC", which
4563
+ * is a decision only the caller can make: an ASTM value states no offset, so
4564
+ * without this option {@link toDate} returns `undefined` rather than guessing.
4565
+ */
4566
+ readonly assumeOffsetMinutes?: number;
4567
+ }
4568
+ /**
4569
+ * Read a parsed ASTM date as the shared {@link DateParts} object: only the
4570
+ * components the value stated, frozen, with a spec-native 1-to-12 month.
4571
+ *
4572
+ * Returns `undefined` for `undefined`, for `null`, for a value that stated no
4573
+ * component at all, and for one whose stated components are not a real calendar
4574
+ * date (a month outside 1 to 12, a day past the end of that month, an hour past
4575
+ * 23, a minute or second past 59). Never throws, whatever it is handed. The
4576
+ * truncation flag, the precision and the raw digit run are deliberately not
4577
+ * carried over: a dangling half-component was never a complete component, so it
4578
+ * appears in no field here.
4579
+ *
4580
+ * The out-of-range refusal is total, not per component: `"19881301"` answers
4581
+ * `undefined`, not `{ year: 1988 }`, because a year-precision date of birth is
4582
+ * not what that instrument sent either.
4583
+ *
4584
+ * @param value - A parsed date, or the `undefined` {@link parseAstmDate} returns.
4585
+ * @returns The stated components, frozen, or `undefined`.
4586
+ * @example
4587
+ * ```ts
4588
+ * import { parseAstmDate, toObject } from "@cosyte/astm";
4589
+ * toObject(parseAstmDate("20240315")); // { year: 2024, month: 3, day: 15 }
4590
+ * Object.keys(toObject(parseAstmDate("2024")) ?? {}); // ["year"]
4591
+ * toObject(parseAstmDate("19881301")); // undefined: there is no month 13
4592
+ * ```
4593
+ */
4594
+ declare function toObject(value: AstmDate | null | undefined): DateParts | undefined;
4595
+ /**
4596
+ * Render a parsed ASTM date as ISO-8601, truncated to the precision it stated
4597
+ * and **never** padded out to a precision it did not.
4598
+ *
4599
+ * No `Z` and no offset is ever appended, because ASTM states none and appending
4600
+ * one would fabricate the instrument's zone. The string is deliberately
4601
+ * zone-less. It is identical to what {@link astmDateToLocalISO} returns for every
4602
+ * value {@link parseAstmDate} produces **whose components are a real calendar
4603
+ * date**; the shared name is the portable route and the repo-native name is
4604
+ * unchanged. On a run that is not, the two differ on purpose and only in one
4605
+ * direction: `astmDateToLocalISO` still renders the digits it was given
4606
+ * (`"1988-13-01"`), because that is its pinned behaviour, and `toISO` answers
4607
+ * `undefined` rather than emit a string no ISO-8601 reader accepts.
4608
+ *
4609
+ * Returns `undefined` for `undefined`, for `null`, for a value stating no
4610
+ * component, for one whose components are not a real calendar date, and for one
4611
+ * stating no year: an ASTM timestamp opens with a mandatory four-digit year, so a
4612
+ * yearless value is not one this parser reads. Never throws.
4613
+ *
4614
+ * @param value - A parsed date, or the `undefined` {@link parseAstmDate} returns.
4615
+ * @returns e.g. `"2024-03-15T09:30"` (minute precision) or `"2024-03"` (month).
4616
+ * @example
4617
+ * ```ts
4618
+ * import { parseAstmDate, toISO } from "@cosyte/astm";
4619
+ * toISO(parseAstmDate("202403150930")); // "2024-03-15T09:30"
4620
+ * toISO(parseAstmDate("not a date")); // undefined
4621
+ * toISO(parseAstmDate("20240230")); // undefined: February has no 30th
4622
+ * ```
4623
+ */
4624
+ declare function toISO(value: AstmDate | null | undefined): string | undefined;
4625
+ /**
4626
+ * Convert a parsed ASTM date to an absolute-instant JS `Date`, **only** when the
4627
+ * caller has made the zone determinate.
4628
+ *
4629
+ * ASTM carries no timezone, so `toDate` returns `undefined` unless
4630
+ * `options.assumeOffsetMinutes` says which zone the instrument was in. The host
4631
+ * machine's zone is never read and UTC is never assumed. A value stating no year
4632
+ * is never an instant either, and neither is one whose components are not a real
4633
+ * calendar date: month 13 becomes January of the next year in a JS `Date`, and
4634
+ * that silent roll-over is the reason the range is checked before the instant is
4635
+ * built rather than after.
4636
+ *
4637
+ * An `assumeOffsetMinutes` that names no usable zone is refused the same way,
4638
+ * with `undefined`: one that is not a finite number (`NaN`, either infinity),
4639
+ * and one so large that applying it puts the result outside the range a JS
4640
+ * `Date` represents. **The answer is never an `Invalid Date`.** Such a `Date`
4641
+ * satisfies this signature and defeats it, because a caller cannot tell it from
4642
+ * a real instant without testing `getTime()` for `NaN`, and `toISOString()` on
4643
+ * it throws.
4644
+ *
4645
+ * Components below the stated precision fill to their lowest legal value (month
4646
+ * and day to 1, time to 0) **for instant construction only**: the value itself is
4647
+ * unchanged, and {@link toObject} and {@link toISO} report exactly what they
4648
+ * reported before the call.
4649
+ *
4650
+ * @param value - A parsed date, or the `undefined` {@link parseAstmDate} returns.
4651
+ * @param options - The zone assumption to apply, as signed minutes east of UTC.
4652
+ * @returns The instant, or `undefined` when the zone or the year is unstated.
4653
+ * @example
4654
+ * ```ts
4655
+ * import { parseAstmDate, toDate } from "@cosyte/astm";
4656
+ * const d = parseAstmDate("20240315");
4657
+ * toDate(d); // undefined: no zone was stated
4658
+ * toDate(d, { assumeOffsetMinutes: -300 }); // 2024-03-15T05:00:00.000Z
4659
+ * toDate(parseAstmDate("19881301"), { assumeOffsetMinutes: 0 }); // undefined, not 1989
4660
+ * ```
4661
+ */
4662
+ declare function toDate(value: AstmDate | null | undefined, options?: ToDateOptions): Date | undefined;
4663
+
4288
4664
  /**
4289
4665
  * Deep-freeze helper for the immutable ASTM model.
4290
4666
  *
@@ -5092,6 +5468,207 @@ declare const astmProfiles: {
5092
5468
  readonly referenceCorpus: typeof referenceCorpus;
5093
5469
  };
5094
5470
 
5471
+ /**
5472
+ * The **terminology** warning registry (`ASTM_LIVD_*`) for the LIVD-aware LOINC
5473
+ * recognition layer: the fourth of the package's registries,
5474
+ * alongside the record layer's `ASTM_RECORD_*`, the frame codec's `ASTM_FRAME_*`,
5475
+ * and the protocol reducer's `ASTM_LTP_*`.
5476
+ *
5477
+ * A terminology warning is the record of a **safe non-mapping**: a reported
5478
+ * vendor test code that a consumer-supplied LIVD catalog did not map to a single
5479
+ * LOINC. It never carries a LOINC the catalog did not vouch for: the whole point
5480
+ * of the layer is that an unmapped or ambiguous code is surfaced as *unmapped*,
5481
+ * **never a guessed LOINC** (mis-identifying a test is safety-critical). Every
5482
+ * warning carries a stable code and a message that is a **constant**, and
5483
+ * **never** a field value, a reported code or any text the consumer stored in
5484
+ * their catalog. Consumers compare `warning.code ===
5485
+ * LIVD_WARNING_CODES.<CODE>`; renaming a code is a **breaking change**.
5486
+ *
5487
+ * **Two shapes, by what the warning is about.** A warning about a RECORD carries
5488
+ * positional context ({@link AstmPosition}) and is an {@link AstmLivdWarning}: one
5489
+ * per unmapped or ambiguous code, produced while a message is annotated. A warning
5490
+ * about the CATALOG ITSELF carries no position, because no record is implicated: it
5491
+ * is an {@link AstmLivdCatalogWarning}, raised once where the catalog is defined,
5492
+ * and it names the catalog through a structured field
5493
+ * ({@link LivdCatalogIdentity}) rather than through its message text, so a consumer
5494
+ * who logs only the message can never emit text they stored in their catalog.
5495
+ *
5496
+ * These codes live in their own registry and are deliberately **not** part of the
5497
+ * profile safety gate's universe (`ALL_ASTM_WARNING_CODES`): a LIVD non-mapping is
5498
+ * a post-parse advisory produced by the opt-in {@link applyLivd} helper, not a
5499
+ * parse-time deviation a vendor profile could ever "tolerate". A profile referencing
5500
+ * one is rejected as an unknown code: correctly, since there is nothing here to
5501
+ * quiet.
5502
+ */
5503
+
5504
+ /**
5505
+ * Stable string codes for every terminology (LIVD) warning. `key === value` so
5506
+ * `Object.values(...)` yields a stable snapshot set.
5507
+ *
5508
+ * @example
5509
+ * ```ts
5510
+ * import { LIVD_WARNING_CODES } from "@cosyte/astm";
5511
+ * LIVD_WARNING_CODES.ASTM_LIVD_UNMAPPED_CODE; // "ASTM_LIVD_UNMAPPED_CODE"
5512
+ * ```
5513
+ */
5514
+ declare const LIVD_WARNING_CODES: {
5515
+ /**
5516
+ * A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held **no**
5517
+ * entry for it. The code stays surfaced verbatim and the mapping is `unmapped`: a LOINC is
5518
+ * **never** guessed. Purely advisory: the raw code and value are untouched.
5519
+ */
5520
+ readonly ASTM_LIVD_UNMAPPED_CODE: "ASTM_LIVD_UNMAPPED_CODE";
5521
+ /**
5522
+ * A vendor/local test code matched **more than one distinct LOINC** in the catalog (e.g. the same
5523
+ * transmission code used by two devices for different analytes). The mapping is `ambiguous` and the
5524
+ * candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
5525
+ * fail-safe, since guessing wrong mis-identifies the test.
5526
+ */
5527
+ readonly ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING";
5528
+ /**
5529
+ * A consumer-supplied LIVD catalog was defined **without a LOINC version**, so a mapping it
5530
+ * produces cannot say which version of LOINC it was made against. Advisory and **never a
5531
+ * refusal**: the catalog is still built, still indexed and still answers exactly as it would
5532
+ * have. Raised **once, where the catalog is defined**, and never per record: it is a fact about
5533
+ * the catalog rather than about any message, so it never joins the per-record warning stream.
5534
+ */
5535
+ readonly ASTM_LIVD_CATALOG_NO_LOINC_VERSION: "ASTM_LIVD_CATALOG_NO_LOINC_VERSION";
5536
+ };
5537
+ /**
5538
+ * Discriminant type for every code in {@link LIVD_WARNING_CODES}, the per-record
5539
+ * codes and the catalog-level one alike. Narrowing by this code lets consumers
5540
+ * write exhaustive `switch` blocks against the registry. It tracks the registry
5541
+ * exactly, so `Object.values(LIVD_WARNING_CODES)` stays a snapshot of this type.
5542
+ */
5543
+ type LivdWarningCode = (typeof LIVD_WARNING_CODES)[keyof typeof LIVD_WARNING_CODES];
5544
+ /**
5545
+ * A single terminology (LIVD) warning: a stable code, a value-free human-readable
5546
+ * message, and positional context. Never carries a test code, a value, or a LOINC.
5547
+ *
5548
+ * @example
5549
+ * ```ts
5550
+ * import type { AstmLivdWarning } from "@cosyte/astm";
5551
+ * const w: AstmLivdWarning = {
5552
+ * code: "ASTM_LIVD_UNMAPPED_CODE",
5553
+ * message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
5554
+ * position: { recordIndex: 3, recordType: "R" },
5555
+ * };
5556
+ * ```
5557
+ */
5558
+ interface AstmLivdWarning {
5559
+ readonly code: LivdWarningCode;
5560
+ /** Human-readable detail for logs. Never contains a test code, a value, or a LOINC. */
5561
+ readonly message: string;
5562
+ readonly position: AstmPosition;
5563
+ }
5564
+ /**
5565
+ * Build an `ASTM_LIVD_UNMAPPED_CODE` warning. The reported code stays verbatim and
5566
+ * the mapping is `unmapped`; no LOINC is fabricated.
5567
+ *
5568
+ * @param position - Where the unmapped code was seen (record ordinal + type; never the code).
5569
+ * @returns The warning.
5570
+ * @example
5571
+ * ```ts
5572
+ * import { livdUnmappedCode } from "@cosyte/astm";
5573
+ * livdUnmappedCode({ recordIndex: 3, recordType: "R" });
5574
+ * ```
5575
+ */
5576
+ declare function livdUnmappedCode(position: AstmPosition): AstmLivdWarning;
5577
+ /**
5578
+ * Build an `ASTM_LIVD_AMBIGUOUS_MAPPING` warning. The code matched multiple distinct
5579
+ * LOINCs; none is chosen (refusing to pick is the fail-safe).
5580
+ *
5581
+ * @param position - Where the ambiguous code was seen (record ordinal + type; never the code).
5582
+ * @returns The warning.
5583
+ * @example
5584
+ * ```ts
5585
+ * import { livdAmbiguousMapping } from "@cosyte/astm";
5586
+ * livdAmbiguousMapping({ recordIndex: 4, recordType: "O" });
5587
+ * ```
5588
+ */
5589
+ declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
5590
+ /**
5591
+ * What a LIVD catalog declared about its own identity: the two publication elements
5592
+ * that name a publication, or the explicit statement that it declared neither.
5593
+ *
5594
+ * The same two shapes the rest of this package uses for attribution: a named source,
5595
+ * or a positive "nothing citable", never an absent field and never an invented name.
5596
+ * Discriminate on `declared`.
5597
+ *
5598
+ * @example
5599
+ * ```ts
5600
+ * import type { LivdCatalogIdentity } from "@cosyte/astm";
5601
+ * const named: LivdCatalogIdentity = { declared: true, publisher: "Example Diagnostics" };
5602
+ * ```
5603
+ */
5604
+ type LivdCatalogIdentity = {
5605
+ /** Always `true`: the catalog declared at least one of the two elements below. */
5606
+ readonly declared: true;
5607
+ /** The **Publisher** the catalog declared, verbatim, when it declared one. */
5608
+ readonly publisher?: string;
5609
+ /** The **Publication Version ID** the catalog declared, verbatim, when it declared one. */
5610
+ readonly publicationVersion?: string;
5611
+ } | {
5612
+ /** Always `false`: the catalog declared neither a publisher nor a publication version. */
5613
+ readonly declared: false;
5614
+ /** Fixed prose saying that no identity was declared. Never an invented or indexed name. */
5615
+ readonly reason: string;
5616
+ };
5617
+ /**
5618
+ * The identity reported for a catalog that declared neither a publisher nor a
5619
+ * publication version: a positive statement that it declared none.
5620
+ *
5621
+ * It exists so that case is said rather than left to an absent field, and so nothing
5622
+ * has to invent a name, an ordinal or a position to stand in for one. Frozen.
5623
+ *
5624
+ * @example
5625
+ * ```ts
5626
+ * import { LIVD_CATALOG_IDENTITY_UNDECLARED } from "@cosyte/astm";
5627
+ * LIVD_CATALOG_IDENTITY_UNDECLARED.declared; // false
5628
+ * ```
5629
+ */
5630
+ declare const LIVD_CATALOG_IDENTITY_UNDECLARED: LivdCatalogIdentity;
5631
+ /**
5632
+ * A catalog-level terminology warning: a stable code, a value-free constant message,
5633
+ * and the catalog's own declared identity. It carries no position, because no record
5634
+ * is implicated, and it is never produced while a message is annotated.
5635
+ *
5636
+ * @example
5637
+ * ```ts
5638
+ * import type { AstmLivdCatalogWarning } from "@cosyte/astm";
5639
+ * const w: AstmLivdCatalogWarning = {
5640
+ * code: "ASTM_LIVD_CATALOG_NO_LOINC_VERSION",
5641
+ * message: "...",
5642
+ * catalog: { declared: true, publisher: "Example Diagnostics", publicationVersion: "2026-01" },
5643
+ * };
5644
+ * ```
5645
+ */
5646
+ interface AstmLivdCatalogWarning {
5647
+ /** Always `ASTM_LIVD_CATALOG_NO_LOINC_VERSION`. */
5648
+ readonly code: typeof LIVD_WARNING_CODES.ASTM_LIVD_CATALOG_NO_LOINC_VERSION;
5649
+ /**
5650
+ * Human-readable detail for logs. A **constant**: never a test code, a value, a LOINC, or any
5651
+ * text the consumer supplied. The catalog is named by the `catalog` field, not by this string.
5652
+ */
5653
+ readonly message: string;
5654
+ /** What the catalog declared about itself, or the positive statement that it declared none. */
5655
+ readonly catalog: LivdCatalogIdentity;
5656
+ }
5657
+ /**
5658
+ * Build an `ASTM_LIVD_CATALOG_NO_LOINC_VERSION` warning. Advisory: the catalog it
5659
+ * describes is still built and still answers every lookup exactly as it would have.
5660
+ *
5661
+ * @param catalog - What the catalog declared about its identity; pass
5662
+ * {@link LIVD_CATALOG_IDENTITY_UNDECLARED} where it declared none.
5663
+ * @returns The warning.
5664
+ * @example
5665
+ * ```ts
5666
+ * import { livdCatalogMissingLoincVersion, LIVD_CATALOG_IDENTITY_UNDECLARED } from "@cosyte/astm";
5667
+ * livdCatalogMissingLoincVersion(LIVD_CATALOG_IDENTITY_UNDECLARED);
5668
+ * ```
5669
+ */
5670
+ declare function livdCatalogMissingLoincVersion(catalog: LivdCatalogIdentity): AstmLivdCatalogWarning;
5671
+
5095
5672
  /**
5096
5673
  * The **consumer-supplied** LIVD catalog: a vendor-test-code → LOINC index built
5097
5674
  * from an IICC LIVD ("LOINC to Vendor IVD") mapping the consumer provides.
@@ -5122,7 +5699,55 @@ declare const astmProfiles: {
5122
5699
  * conflicting entries *within* one catalog are caught and surfaced as `ambiguous`
5123
5700
  * (never resolved to a guess), but cross-device code reuse the catalog cannot see is
5124
5701
  * a catalog-hygiene concern the consumer owns.
5702
+ *
5703
+ * **One vendor analyte code to many LOINCs is the ORDINARY case, and the units are
5704
+ * what tell them apart.** The mapping guide this catalog format comes from describes
5705
+ * the relationship as many-to-many by design, and its own worked examples are the
5706
+ * commonest chemistry analytes: a serum glucose maps to one LOINC as a mass
5707
+ * concentration and to another as a substance concentration, and a urine analyte maps
5708
+ * to one LOINC at `mmol/L` (a spot specimen) and to another at `mmol/(24.h)` (a 24
5709
+ * hour collection). The guide's remedy is to define a mapping per unit, so
5710
+ * {@link LivdEntry.representativeUnit} carries that unit and
5711
+ * {@link LivdCatalog.lookup} compares it with the units the `R` record reported.
5712
+ *
5713
+ * **That comparison is VERBATIM and CASE SENSITIVE, and it is not UCUM.** Nothing is
5714
+ * normalized, converted, case folded or scaled on either side: a catalog saying
5715
+ * `mg/dL` does not match a feed saying `MG/DL`, and a catalog saying `mg/L` never
5716
+ * matches `ug/dL` however convertible the two are. UCUM defines a case-insensitive
5717
+ * variant of every terminal symbol and requires a program claiming full conformance
5718
+ * to compare unit expressions by their SEMANTICS, so a verbatim string comparison is
5719
+ * a deliberately limited choice rather than UCUM conformance. It is the only
5720
+ * comparison that cannot invent an equivalence, and because a consumer could
5721
+ * otherwise read a matched unit as a conformance claim this package does not make,
5722
+ * every unit-selected answer states what the comparison was
5723
+ * ({@link LivdUnitComparison}).
5724
+ *
5725
+ * **A catalog can also say where the mapping came from, and none of it is checked.**
5726
+ * The mapping guide defines four elements about the PUBLICATION rather than about any
5727
+ * row: who published it, which publication version it is, which LOINC version the
5728
+ * mapping was made against, and the attribution statement the LOINC license requires.
5729
+ * {@link LivdPublication} carries all four, every one optional, every one preserved
5730
+ * verbatim, and **not one of them validated**: this package performs no LOINC
5731
+ * validation of any kind, so a LOINC version is stored and surfaced exactly as
5732
+ * supplied and is never parsed, ranged, formatted or compared. Carrying an
5733
+ * attribution statement is not discharging the obligation to make it: the terminology
5734
+ * data, and its license, stay the consumer's.
5735
+ *
5736
+ * **A catalog with no LOINC version is built, and says so.** A mapping whose LOINC
5737
+ * version is unrecorded cannot be reproduced later, so defining a catalog without one
5738
+ * surfaces a value-free `ASTM_LIVD_CATALOG_NO_LOINC_VERSION` warning on
5739
+ * {@link LivdCatalog.warnings}. It is a nudge and never a refusal: the catalog is
5740
+ * built, every lookup answers exactly as it would have, and no per-record warning
5741
+ * moves.
5742
+ *
5743
+ * **The three LIVD attributes are carried verbatim and only one of them is matched
5744
+ * on.** {@link LivdEntry.vendorSpecimenDescription} and
5745
+ * {@link LivdEntry.vendorResultDescription} are free text the guide says is there to
5746
+ * help a laboratory choose by hand and is "not intended to be parsed by an IVD
5747
+ * Software System that automates the mapping": they are stored and surfaced, never
5748
+ * matched on, never normalized. Only the representative unit ever selects.
5125
5749
  */
5750
+
5126
5751
  /**
5127
5752
  * One LIVD mapping row a consumer supplies: a vendor test code and the LOINC it
5128
5753
  * maps to, plus optional human-readable / provenance fields. Modeled on the IICC
@@ -5155,29 +5780,232 @@ interface LivdEntry {
5155
5780
  readonly manufacturer?: string;
5156
5781
  /** The device **Model**, when the catalog scopes the mapping to a device (optional provenance). */
5157
5782
  readonly model?: string;
5783
+ /**
5784
+ * The **Vendor Specimen Description**: the vendor's own human-readable text for the specimen this
5785
+ * mapping is for, such as `"Serum or Plasma"`. Carried **verbatim** (never trimmed, case folded or
5786
+ * normalized) and surfaced for a human to read. **Never matched on**: the mapping guide states this
5787
+ * text is not intended to be parsed by software that automates the mapping, so choosing on it would
5788
+ * be a string guess. Optional, so an existing catalog keeps working unchanged.
5789
+ */
5790
+ readonly vendorSpecimenDescription?: string;
5791
+ /**
5792
+ * The **Vendor Result Description**: the vendor's own human-readable text for the result this
5793
+ * mapping produces (binary, ordinal, nominal, or a numeric result with its unit). Carried
5794
+ * **verbatim** and surfaced for a human to read; **never matched on**, for the same reason as
5795
+ * {@link LivdEntry.vendorSpecimenDescription}. Optional.
5796
+ */
5797
+ readonly vendorResultDescription?: string;
5798
+ /**
5799
+ * The **representative unit of measure** for this mapping, preferably a UCUM unit (e.g. `"mg/dL"`,
5800
+ * `"mmol/L"`, `"mmol/(24.h)"`). This is the **one** attribute a lookup ever selects on: when a
5801
+ * vendor analyte code carries several candidate LOINCs, the candidate whose representative unit is
5802
+ * **exactly equal** to the units the `R` record reported is chosen, compared verbatim and case
5803
+ * sensitively with no normalization, conversion or scale factor on either side.
5804
+ *
5805
+ * Absent, empty or whitespace only means **not unit qualified**: such a candidate is never selected
5806
+ * by a unit comparison, though it is still surfaced among an ambiguous answer's candidates.
5807
+ * Optional, so an existing catalog keeps working unchanged.
5808
+ */
5809
+ readonly representativeUnit?: string;
5810
+ }
5811
+ /**
5812
+ * How a candidate was selected on its unit, stated on the answer so a consumer cannot
5813
+ * read a matched unit as a UCUM conformance claim this package does not make.
5814
+ *
5815
+ * Present on a {@link LivdLookup} **only** when a unit comparison actually chose
5816
+ * between candidate LOINCs. Its whole content is a disclosure: the comparison was a
5817
+ * verbatim, case-sensitive string equality, and **not** a UCUM semantic comparison.
5818
+ * Nothing was normalized, case folded, scaled or converted on either side.
5819
+ *
5820
+ * @example
5821
+ * ```ts
5822
+ * import type { LivdUnitComparison } from "@cosyte/astm";
5823
+ * const c: LivdUnitComparison = {
5824
+ * comparison: "verbatim-case-sensitive",
5825
+ * ucumSemantic: false,
5826
+ * reportedUnits: "mg/dL",
5827
+ * representativeUnit: "mg/dL",
5828
+ * note: "...",
5829
+ * };
5830
+ * ```
5831
+ */
5832
+ interface LivdUnitComparison {
5833
+ /** Always `"verbatim-case-sensitive"`: an exact string equality, nothing normalized. */
5834
+ readonly comparison: "verbatim-case-sensitive";
5835
+ /** Always `false`. This package does not compare unit expressions by their semantics. */
5836
+ readonly ucumSemantic: false;
5837
+ /** The units the record reported, verbatim. */
5838
+ readonly reportedUnits: string;
5839
+ /** The chosen candidate's representative unit, verbatim. Byte-identical to `reportedUnits`. */
5840
+ readonly representativeUnit: string;
5841
+ /** A human-readable restatement of the two facts above, for a log or a review screen. */
5842
+ readonly note: string;
5843
+ }
5844
+ /**
5845
+ * One candidate mapping behind an `ambiguous` answer: the LOINC plus the LIVD
5846
+ * attributes that tell candidates apart, so a human can choose what this package
5847
+ * refuses to guess.
5848
+ *
5849
+ * One per catalog row for the vendor code, in catalog order, so a LOINC appearing in
5850
+ * two rows appears twice. Every candidate is surfaced, including one that is not unit
5851
+ * qualified: it can never be selected on a unit, but hiding it would hide a mapping
5852
+ * the consumer's own catalog holds.
5853
+ *
5854
+ * @example
5855
+ * ```ts
5856
+ * import type { LivdCandidate } from "@cosyte/astm";
5857
+ * const c: LivdCandidate = {
5858
+ * loinc: "2345-7",
5859
+ * vendorSpecimenDescription: "Serum or Plasma",
5860
+ * representativeUnit: "mg/dL",
5861
+ * unitQualified: true,
5862
+ * };
5863
+ * ```
5864
+ */
5865
+ interface LivdCandidate {
5866
+ /** The candidate LOINC, verbatim from the catalog row. Never validated. */
5867
+ readonly loinc: string;
5868
+ /** The row's LOINC Long Common Name, when supplied. */
5869
+ readonly loincLongName?: string;
5870
+ /** The row's Vendor Specimen Description, verbatim, when supplied. Never matched on. */
5871
+ readonly vendorSpecimenDescription?: string;
5872
+ /** The row's Vendor Result Description, verbatim, when supplied. Never matched on. */
5873
+ readonly vendorResultDescription?: string;
5874
+ /** The row's representative unit, verbatim, when supplied. */
5875
+ readonly representativeUnit?: string;
5876
+ /**
5877
+ * `false` when the representative unit is absent, empty or whitespace only. Such a candidate is
5878
+ * **never** selected by a unit comparison against any reported units, and is surfaced here anyway.
5879
+ */
5880
+ readonly unitQualified: boolean;
5158
5881
  }
5882
+ /**
5883
+ * Why a code carrying several candidate LOINCs stayed `ambiguous` after the unit
5884
+ * comparison ran, or why the comparison could not run at all.
5885
+ *
5886
+ * - `no-reported-units`: the record reported no usable units (absent, empty or whitespace only), so
5887
+ * nothing could be compared. A unit-qualified candidate is never chosen in this case.
5888
+ * - `no-candidate-matched-units`: units were reported and **no** unit-qualified candidate's
5889
+ * representative unit was exactly equal to them.
5890
+ * - `multiple-candidates-matched-units`: units were reported and **more than one distinct** candidate
5891
+ * LOINC matched them exactly. Picking one would be a guess.
5892
+ *
5893
+ * Every one of them surfaces every candidate and chooses no LOINC.
5894
+ */
5895
+ type LivdAmbiguityReason = "no-reported-units" | "no-candidate-matched-units" | "multiple-candidates-matched-units";
5159
5896
  /**
5160
5897
  * The outcome of looking a vendor code up in a {@link LivdCatalog}. A distinct
5161
5898
  * value per safe disposition: a hit is `mapped`; a miss is `unmapped`; a code that
5162
- * matched more than one **distinct** LOINC is `ambiguous` with the candidates
5163
- * surfaced but **none chosen**. There is deliberately no "guessed" case.
5899
+ * matched more than one **distinct** LOINC and that the reported units did not
5900
+ * settle is `ambiguous` with the candidates surfaced but **none chosen**. There is
5901
+ * deliberately no "guessed" case.
5902
+ *
5903
+ * **The added fields are all optional, and a catalog carrying none of the three LIVD
5904
+ * attributes answers byte-identically to how it answered before they existed.** They
5905
+ * appear only where the consumer's own catalog rows supply something to put in them.
5164
5906
  */
5165
5907
  type LivdLookup =
5166
- /** Exactly one LOINC (one entry, or several entries that all agree on the same LOINC). */
5908
+ /**
5909
+ * Exactly one LOINC: one entry, several entries that all agree on the same LOINC, or several
5910
+ * candidates of which exactly one had a representative unit equal to the units the record
5911
+ * reported (and then, and only then, `unitComparison` says how that comparison was made).
5912
+ */
5167
5913
  {
5168
5914
  readonly status: "mapped";
5169
5915
  readonly loinc: string;
5170
5916
  readonly loincLongName?: string;
5917
+ /** The chosen row's Vendor Specimen Description, verbatim, when supplied. Never matched on. */
5918
+ readonly vendorSpecimenDescription?: string;
5919
+ /** The chosen row's Vendor Result Description, verbatim, when supplied. Never matched on. */
5920
+ readonly vendorResultDescription?: string;
5921
+ /**
5922
+ * The chosen row's representative unit, verbatim, when supplied.
5923
+ *
5924
+ * **This is provenance about the CATALOG ROW, not a restatement of what the record
5925
+ * reported.** Where a vendor code carried a single candidate LOINC across several rows
5926
+ * that spell the unit differently, the answer takes the FIRST row's attributes (exactly
5927
+ * as it has always taken the first row's `loincLongName`), so this can name a unit the
5928
+ * record did not report. Only {@link LivdUnitComparison} asserts the two were equal, and
5929
+ * it is present only where a unit actually chose between candidates.
5930
+ */
5931
+ readonly representativeUnit?: string;
5932
+ /**
5933
+ * Present **if and only if** a unit comparison chose this LOINC from more than one candidate.
5934
+ * Absent where the code carried a single candidate LOINC, which is answered whether or not the
5935
+ * units agree: nothing was selected on a unit there, so claiming it was would be false.
5936
+ */
5937
+ readonly unitComparison?: LivdUnitComparison;
5171
5938
  }
5172
5939
  /** No entry for this code: a miss. The code stays verbatim; no LOINC is invented. */
5173
5940
  | {
5174
5941
  readonly status: "unmapped";
5175
5942
  }
5176
- /** More than one distinct LOINC: surfaced for inspection, never resolved to one. */
5943
+ /** More than one distinct LOINC, unsettled: surfaced for inspection, never resolved to one. */
5177
5944
  | {
5178
5945
  readonly status: "ambiguous";
5946
+ /** Every distinct candidate LOINC, deduplicated, in first-seen catalog order. */
5179
5947
  readonly candidates: readonly string[];
5948
+ /**
5949
+ * Every candidate row with its LIVD attributes, one per catalog row (so a LOINC held by two
5950
+ * rows appears twice), including rows that are not unit qualified. Present only where at least
5951
+ * one row for this vendor code carries one of the three LIVD attributes.
5952
+ */
5953
+ readonly candidateDetails?: readonly LivdCandidate[];
5954
+ /**
5955
+ * Why the unit comparison did not settle it. Present under the same condition as
5956
+ * `candidateDetails`: a catalog carrying none of the LIVD attributes answers exactly as it did
5957
+ * before this field existed.
5958
+ */
5959
+ readonly reason?: LivdAmbiguityReason;
5180
5960
  };
5961
+ /**
5962
+ * The publication-level metadata a consumer declares about the LIVD publication
5963
+ * their catalog was built from: what the mapping guide defines about the publication
5964
+ * rather than about any one row.
5965
+ *
5966
+ * Every field is **optional** and every declared value is preserved **verbatim**:
5967
+ * nothing is trimmed, case folded, reordered, reformatted, defaulted or validated,
5968
+ * and no value is ever rejected, corrected or warned about on account of its content
5969
+ * or its shape. A value that is absent, empty or whitespace only is one case, **no
5970
+ * value declared**, which is the only inspection any of these strings gets.
5971
+ *
5972
+ * **None of it is checked, and none of it is a claim by this package.** This library
5973
+ * is not the LOINC licensee and performs no LOINC validation of any kind, so it
5974
+ * cannot tell a real LOINC version from a typed one and does not try.
5975
+ *
5976
+ * @example
5977
+ * ```ts
5978
+ * import type { LivdPublication } from "@cosyte/astm";
5979
+ * const p: LivdPublication = { publisher: "Example Diagnostics", loincVersion: "2.78" };
5980
+ * ```
5981
+ */
5982
+ interface LivdPublication {
5983
+ /** The **Publisher**: the entity publishing the mapping information. Verbatim, never checked. */
5984
+ readonly publisher?: string;
5985
+ /**
5986
+ * The **Publication Version ID**: human-readable information the vendor provides that tells one
5987
+ * LIVD publication version from another. Verbatim, never parsed and never ordered against another.
5988
+ */
5989
+ readonly publicationVersion?: string;
5990
+ /**
5991
+ * The **LOINC Version ID**: the version of LOINC the mapping was made against. Carried verbatim
5992
+ * onto every annotation this catalog produces ({@link LivdAnnotation.catalogLoincVersion}), so a
5993
+ * mapping can be reproduced later. It is **provenance the consumer declared**, never a statement
5994
+ * that any LOINC was checked against it: nothing here validates a LOINC or a LOINC version.
5995
+ *
5996
+ * Declaring none is allowed and surfaces a value-free warning on {@link LivdCatalog.warnings};
5997
+ * the catalog is still built and every answer is unchanged.
5998
+ */
5999
+ readonly loincVersion?: string;
6000
+ /**
6001
+ * The **LOINCCopyright**: the attribution statement the LOINC license requires beside content
6002
+ * that carries LOINC codes. Stored verbatim so it travels with the mapping it applies to.
6003
+ *
6004
+ * **Carrying it is not discharging it.** The obligation is the consumer's, this package supplies
6005
+ * no statement on anyone's behalf, and it checks neither the presence nor the wording of one.
6006
+ */
6007
+ readonly loincCopyright?: string;
6008
+ }
5181
6009
  /**
5182
6010
  * An immutable, consumer-supplied LIVD catalog. Look a vendor code up with
5183
6011
  * {@link LivdCatalog.lookup}; the catalog **never** picks between conflicting
@@ -5186,15 +6014,45 @@ type LivdLookup =
5186
6014
  interface LivdCatalog {
5187
6015
  /** The number of distinct vendor codes indexed (not the number of input rows). */
5188
6016
  readonly size: number;
6017
+ /**
6018
+ * The publication-level metadata the consumer declared, each value **verbatim**. Absent where
6019
+ * the catalog declared none, and each field inside it absent where that value was not declared:
6020
+ * a blank is never stored as a blank, and nothing is ever defaulted.
6021
+ *
6022
+ * Optional on the interface, so a catalog a consumer implements by hand needs no metadata and
6023
+ * stays source compatible.
6024
+ */
6025
+ readonly publication?: LivdPublication;
6026
+ /**
6027
+ * The value-free warnings raised where this catalog was **defined**, in the order they were
6028
+ * raised. {@link defineLivdCatalog} always sets it, empty where it had nothing to say.
6029
+ *
6030
+ * These are facts about the CATALOG, so they never join the per-record warning stream
6031
+ * {@link applyLivd} returns and they implicate no record. Optional on the interface, so a
6032
+ * hand-implemented catalog that offers none stays source compatible.
6033
+ */
6034
+ readonly warnings?: readonly AstmLivdCatalogWarning[];
5189
6035
  /**
5190
6036
  * Look a vendor code up, verbatim (exact, case-sensitive). Returns `mapped` on a
5191
6037
  * single-LOINC hit, `unmapped` on a miss, and `ambiguous` when the code carries
5192
- * more than one distinct LOINC: never a guess.
6038
+ * more than one distinct LOINC that the reported units did not settle: never a
6039
+ * guess.
6040
+ *
6041
+ * `reportedUnits` are the units the `R` record carried, verbatim. They are compared
6042
+ * with each candidate's {@link LivdEntry.representativeUnit} by exact,
6043
+ * case-sensitive string equality and by nothing else. Omitting them, or passing an
6044
+ * empty or whitespace-only string, means **no units were reported**: no
6045
+ * unit-qualified candidate is chosen and a code carrying several candidates stays
6046
+ * `ambiguous`. A code carrying exactly one candidate LOINC is answered either way.
6047
+ *
6048
+ * Implementing this interface by hand stays source compatible: a `lookup` declared
6049
+ * with the vendor code alone simply ignores the units, and answers as it always did.
5193
6050
  *
5194
6051
  * @param vendorCode - The reported vendor/local test code.
6052
+ * @param reportedUnits - The units the record reported, verbatim, when it reported any.
5195
6053
  * @returns The lookup outcome.
5196
6054
  */
5197
- lookup(vendorCode: string): LivdLookup;
6055
+ lookup(vendorCode: string, reportedUnits?: string): LivdLookup;
5198
6056
  }
5199
6057
  /**
5200
6058
  * Build a {@link LivdCatalog} from LIVD entries a consumer supplies. Entries are
@@ -5202,13 +6060,26 @@ interface LivdCatalog {
5202
6060
  * code:
5203
6061
  *
5204
6062
  * - all agreeing on the same `loinc` → a single `mapped` result (the first entry's
5205
- * optional `loincLongName` is kept);
5206
- * - disagreeing (two distinct LOINCs) → an `ambiguous` result carrying every
5207
- * distinct candidate, and **no** choice between them.
6063
+ * optional `loincLongName` and LIVD attributes are kept), whether or not the
6064
+ * reported units equal that entry's representative unit;
6065
+ * - disagreeing (two distinct LOINCs) → the reported units decide, by exact
6066
+ * case-sensitive equality against each row's
6067
+ * {@link LivdEntry.representativeUnit}. Exactly one distinct LOINC matching is
6068
+ * `mapped`, and the answer states that the comparison was verbatim. None matching,
6069
+ * more than one matching, or no units reported is an `ambiguous` result carrying
6070
+ * every distinct candidate and **no** choice between them.
6071
+ *
6072
+ * Publication-level metadata is supplied beside the rows, and every element of it is
6073
+ * optional, so the single-argument call keeps working exactly as it did. Each declared
6074
+ * value is preserved verbatim and readable back off {@link LivdCatalog.publication};
6075
+ * none of it is validated. Declaring no LOINC version is allowed and puts a value-free
6076
+ * {@link AstmLivdCatalogWarning} on {@link LivdCatalog.warnings}: a nudge, never a
6077
+ * refusal, and no lookup answers differently because of it.
5208
6078
  *
5209
6079
  * The returned catalog is deeply frozen; nothing is mutated after construction.
5210
6080
  *
5211
6081
  * @param entries - The consumer's LIVD mapping rows.
6082
+ * @param publication - The consumer's publication-level metadata, when they declare any.
5212
6083
  * @returns An immutable catalog.
5213
6084
  * @example
5214
6085
  * ```ts
@@ -5220,108 +6091,20 @@ interface LivdCatalog {
5220
6091
  * catalog.lookup("687"); // { status: "mapped", loinc: "1920-8", loincLongName: "AST" }
5221
6092
  * catalog.lookup("999"); // { status: "unmapped" }
5222
6093
  * ```
5223
- */
5224
- declare function defineLivdCatalog(entries: readonly LivdEntry[]): LivdCatalog;
5225
-
5226
- /**
5227
- * The **terminology** warning registry (`ASTM_LIVD_*`) for the LIVD-aware LOINC
5228
- * recognition layer: the fourth of the package's registries,
5229
- * alongside the record layer's `ASTM_RECORD_*`, the frame codec's `ASTM_FRAME_*`,
5230
- * and the protocol reducer's `ASTM_LTP_*`.
5231
- *
5232
- * A terminology warning is the record of a **safe non-mapping**: a reported
5233
- * vendor test code that a consumer-supplied LIVD catalog did not map to a single
5234
- * LOINC. It never carries a LOINC the catalog did not vouch for: the whole point
5235
- * of the layer is that an unmapped or ambiguous code is surfaced as *unmapped*,
5236
- * **never a guessed LOINC** (mis-identifying a test is safety-critical). Every
5237
- * warning carries a stable code plus positional context ({@link AstmPosition}) and
5238
- * **never** a field value. Consumers compare `warning.code ===
5239
- * LIVD_WARNING_CODES.<CODE>`; renaming a code is a **breaking change**.
5240
- *
5241
- * These codes live in their own registry and are deliberately **not** part of the
5242
- * profile safety gate's universe (`ALL_ASTM_WARNING_CODES`): a LIVD non-mapping is
5243
- * a post-parse advisory produced by the opt-in {@link applyLivd} helper, not a
5244
- * parse-time deviation a vendor profile could ever "tolerate". A profile referencing
5245
- * one is rejected as an unknown code: correctly, since there is nothing here to
5246
- * quiet.
5247
- */
5248
-
5249
- /**
5250
- * Stable string codes for every terminology (LIVD) warning. `key === value` so
5251
- * `Object.values(...)` yields a stable snapshot set.
5252
- *
5253
- * @example
5254
- * ```ts
5255
- * import { LIVD_WARNING_CODES } from "@cosyte/astm";
5256
- * LIVD_WARNING_CODES.ASTM_LIVD_UNMAPPED_CODE; // "ASTM_LIVD_UNMAPPED_CODE"
5257
- * ```
5258
- */
5259
- declare const LIVD_WARNING_CODES: {
5260
- /**
5261
- * A record carried a vendor/local test code, but the consumer-supplied LIVD catalog held **no**
5262
- * entry for it. The code stays surfaced verbatim and the mapping is `unmapped`: a LOINC is
5263
- * **never** guessed. Purely advisory: the raw code and value are untouched.
5264
- */
5265
- readonly ASTM_LIVD_UNMAPPED_CODE: "ASTM_LIVD_UNMAPPED_CODE";
5266
- /**
5267
- * A vendor/local test code matched **more than one distinct LOINC** in the catalog (e.g. the same
5268
- * transmission code used by two devices for different analytes). The mapping is `ambiguous` and the
5269
- * candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
5270
- * fail-safe, since guessing wrong mis-identifies the test.
5271
- */
5272
- readonly ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING";
5273
- };
5274
- /**
5275
- * Discriminant type for {@link AstmLivdWarning.code}. Narrowing by this code lets
5276
- * consumers write exhaustive `switch` blocks against {@link LIVD_WARNING_CODES}.
5277
- */
5278
- type LivdWarningCode = (typeof LIVD_WARNING_CODES)[keyof typeof LIVD_WARNING_CODES];
5279
- /**
5280
- * A single terminology (LIVD) warning: a stable code, a value-free human-readable
5281
- * message, and positional context. Never carries a test code, a value, or a LOINC.
5282
- *
5283
6094
  * @example
5284
6095
  * ```ts
5285
- * import type { AstmLivdWarning } from "@cosyte/astm";
5286
- * const w: AstmLivdWarning = {
5287
- * code: "ASTM_LIVD_UNMAPPED_CODE",
5288
- * message: "Reported test code had no LIVD mapping, surfaced unmapped, never a guessed LOINC.",
5289
- * position: { recordIndex: 3, recordType: "R" },
5290
- * };
5291
- * ```
5292
- */
5293
- interface AstmLivdWarning {
5294
- readonly code: LivdWarningCode;
5295
- /** Human-readable detail for logs. Never contains a test code, a value, or a LOINC. */
5296
- readonly message: string;
5297
- readonly position: AstmPosition;
5298
- }
5299
- /**
5300
- * Build an `ASTM_LIVD_UNMAPPED_CODE` warning. The reported code stays verbatim and
5301
- * the mapping is `unmapped`; no LOINC is fabricated.
5302
- *
5303
- * @param position - Where the unmapped code was seen (record ordinal + type; never the code).
5304
- * @returns The warning.
5305
- * @example
5306
- * ```ts
5307
- * import { livdUnmappedCode } from "@cosyte/astm";
5308
- * livdUnmappedCode({ recordIndex: 3, recordType: "R" });
5309
- * ```
5310
- */
5311
- declare function livdUnmappedCode(position: AstmPosition): AstmLivdWarning;
5312
- /**
5313
- * Build an `ASTM_LIVD_AMBIGUOUS_MAPPING` warning. The code matched multiple distinct
5314
- * LOINCs; none is chosen (refusing to pick is the fail-safe).
5315
- *
5316
- * @param position - Where the ambiguous code was seen (record ordinal + type; never the code).
5317
- * @returns The warning.
5318
- * @example
5319
- * ```ts
5320
- * import { livdAmbiguousMapping } from "@cosyte/astm";
5321
- * livdAmbiguousMapping({ recordIndex: 4, recordType: "O" });
6096
+ * import { defineLivdCatalog } from "@cosyte/astm";
6097
+ * // One vendor analyte code, two LOINCs, told apart by their representative unit.
6098
+ * const glucose = defineLivdCatalog([
6099
+ * { vendorCode: "GLU", loinc: "2345-7", representativeUnit: "mg/dL" },
6100
+ * { vendorCode: "GLU", loinc: "15074-8", representativeUnit: "mmol/L" },
6101
+ * ]);
6102
+ * glucose.lookup("GLU", "mmol/L").status; // "mapped": 15074-8, on a verbatim unit match
6103
+ * glucose.lookup("GLU", "MMOL/L").status; // "ambiguous": the comparison is case sensitive
6104
+ * glucose.lookup("GLU").status; // "ambiguous": no units reported, so nothing to compare
5322
6105
  * ```
5323
6106
  */
5324
- declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
6107
+ declare function defineLivdCatalog(entries: readonly LivdEntry[], publication?: LivdPublication): LivdCatalog;
5325
6108
 
5326
6109
  /**
5327
6110
  * The **additive, advisory** LIVD annotation layer. {@link applyLivd}
@@ -5331,24 +6114,52 @@ declare function livdAmbiguousMapping(position: AstmPosition): AstmLivdWarning;
5331
6114
  * annotation, not a rewrite: the wire stays exactly as parsed.
5332
6115
  *
5333
6116
  * **The never-fabricate rule (the safety-critical point).** A vendor code with no
5334
- * single LIVD mapping surfaces as a typed `unmapped`/`ambiguous`/`no-code`
5335
- * annotation: **never** a guessed LOINC. A wrong LOINC mis-identifies a test, so
5336
- * the layer only ever reports a LOINC the wire itself carried (an inline LOINC
5337
- * candidate) or one the consumer's catalog vouched for (labeled `derived`), and
5338
- * otherwise reports nothing but the miss.
6117
+ * single LIVD mapping surfaces as a typed `unmapped`/`ambiguous`/`no-vendor-code`/
6118
+ * `no-code` annotation: **never** a guessed LOINC. A wrong LOINC mis-identifies a
6119
+ * test, so the only LOINC this layer ever reports is one the consumer's catalog
6120
+ * vouched for (labeled `derived`), and otherwise it reports nothing but the miss.
6121
+ *
6122
+ * **The wire never answers the analyte-identity question.** The catalog is
6123
+ * consulted whenever a vendor/local code is present, and it is consulted with that
6124
+ * code alone. A populated component 1 rides along verbatim as
6125
+ * {@link LivdAnnotation.unvalidatedWireValue}, is never validated, and is never
6126
+ * reported as a LOINC. Where it differs from what the catalog vouched for, the
6127
+ * difference is reported as its own fact
6128
+ * ({@link LivdAnnotation.wireValueDisagreesWithCatalog}) and is **never resolved**:
6129
+ * both values are surfaced, and nothing here picks between them.
6130
+ *
6131
+ * **The units DO answer, and only for choosing between the catalog's own
6132
+ * candidates.** One vendor analyte code legitimately maps to several LOINCs that
6133
+ * differ by reporting unit, so the catalog is consulted with the vendor code AND with
6134
+ * the units field 5 of the `R` record carried, verbatim. That comparison is exact and
6135
+ * case sensitive, never a UCUM semantic comparison, and a unit-selected answer says
6136
+ * so on its output. It only ever narrows the candidate list the consumer's own
6137
+ * catalog produced: it can never introduce a LOINC, never overrides the catalog, and
6138
+ * a record with no readable units simply leaves the ambiguity standing.
6139
+ *
6140
+ * **The catalog's declared LOINC version rides on every annotation.** Where the
6141
+ * consumer declared one, {@link LivdAnnotation.catalogLoincVersion} carries it
6142
+ * verbatim on every annotation that catalog produced, whatever the outcome of the
6143
+ * lookup, so a mapping says which version of LOINC it was made against and can be
6144
+ * reproduced. It is provenance the CONSUMER declared and nothing else: no LOINC is
6145
+ * validated against it, or against anything, here or anywhere in this package.
5339
6146
  */
5340
6147
 
5341
6148
  /**
5342
- * The outcome of annotating one record's Universal Test ID against a LIVD catalog.
5343
- * A distinct case per disposition; there is no case in which a LOINC is guessed.
6149
+ * The outcome of looking one record's Universal Test ID up in a LIVD catalog: a
6150
+ * closed, mutually exclusive discriminant. There is no case in which a LOINC is
6151
+ * guessed, and no case in which a value the wire carried is reported as a LOINC.
5344
6152
  *
5345
6153
  * - `mapped`, the vendor/local code resolved to a single LOINC **via the catalog**
5346
6154
  * (labeled `derived: true`, `source: "livd"`).
5347
- * - `inline-loinc`, the wire itself carried a LOINC in the Universal Test ID's slot
5348
- * (component 1); surfaced `source: "wire"`, **not** derived and **not** validated.
5349
- * - `unmapped`: a vendor/local code with no catalog entry.
6155
+ * - `unmapped`: a vendor/local code was looked up and the catalog held no entry
6156
+ * for it (a hit whose LOINC is a zero-length string is reported here too: an
6157
+ * empty LOINC is not an answer).
5350
6158
  * - `ambiguous`: a vendor/local code matching more than one distinct LOINC; the
5351
6159
  * candidates are surfaced but **none is chosen**.
6160
+ * - `no-vendor-code`: component 1 is populated and there is no vendor/local code,
6161
+ * so nothing was looked up. The wire value is surfaced unvalidated, and is never
6162
+ * used as a lookup key: the catalog is keyed on the vendor transmission code.
5352
6163
  * - `no-code`: the record carried no usable test code at all (name-only/empty), so
5353
6164
  * there was nothing to map.
5354
6165
  */
@@ -5356,24 +6167,38 @@ type LivdMapping = {
5356
6167
  readonly status: "mapped";
5357
6168
  readonly loinc: string;
5358
6169
  readonly loincLongName?: string;
6170
+ /** The chosen catalog row's Vendor Specimen Description, verbatim, when supplied. */
6171
+ readonly vendorSpecimenDescription?: string;
6172
+ /** The chosen catalog row's Vendor Result Description, verbatim, when supplied. */
6173
+ readonly vendorResultDescription?: string;
6174
+ /** The chosen catalog row's representative unit, verbatim, when supplied. */
6175
+ readonly representativeUnit?: string;
6176
+ /**
6177
+ * Present **if and only if** the record's units chose this LOINC from more than one candidate,
6178
+ * and it states what that comparison was: verbatim and case sensitive, not UCUM semantic.
6179
+ */
6180
+ readonly unitComparison?: LivdUnitComparison;
5359
6181
  readonly source: "livd";
5360
6182
  readonly derived: true;
5361
- } | {
5362
- readonly status: "inline-loinc";
5363
- readonly loinc: string;
5364
- readonly source: "wire";
5365
6183
  } | {
5366
6184
  readonly status: "unmapped";
5367
6185
  } | {
5368
6186
  readonly status: "ambiguous";
5369
6187
  readonly candidates: readonly string[];
6188
+ /** Every candidate row with its LIVD attributes, when the catalog supplied any. */
6189
+ readonly candidateDetails?: readonly LivdCandidate[];
6190
+ /** Why the units did not settle it, when the catalog supplied the attributes to say. */
6191
+ readonly reason?: LivdAmbiguityReason;
6192
+ } | {
6193
+ readonly status: "no-vendor-code";
5370
6194
  } | {
5371
6195
  readonly status: "no-code";
5372
6196
  };
5373
6197
  /**
5374
- * One record's LIVD annotation, which record, the code that was looked up
5375
- * (verbatim), how it was recognized, and the mapping outcome. Additive: it points
5376
- * at the record by index and never replaces it.
6198
+ * One record's LIVD annotation: which record, the code the catalog was consulted
6199
+ * with (verbatim), how it was recognized, the lookup outcome, and the two facts
6200
+ * about a populated component 1 that ride alongside **any** outcome. Additive: it
6201
+ * points at the record by index and never replaces it.
5377
6202
  *
5378
6203
  * @example
5379
6204
  * ```ts
@@ -5382,6 +6207,8 @@ type LivdMapping = {
5382
6207
  * recordIndex: 3,
5383
6208
  * recordType: "R",
5384
6209
  * reportedCode: "687",
6210
+ * unvalidatedWireValue: "Glucose",
6211
+ * wireValueDisagreesWithCatalog: true,
5385
6212
  * provenance: "local-code",
5386
6213
  * mapping: { status: "mapped", loinc: "1920-8", source: "livd", derived: true },
5387
6214
  * };
@@ -5392,12 +6219,47 @@ interface LivdAnnotation {
5392
6219
  readonly recordIndex: number;
5393
6220
  /** The annotated record's type. */
5394
6221
  readonly recordType: "R" | "O";
5395
- /** The reported primary code that was looked up, verbatim; absent when the record carried no code. */
6222
+ /**
6223
+ * The vendor/local code the catalog was consulted with, verbatim. Absent when no
6224
+ * vendor/local code was present, because then nothing was looked up. It is never
6225
+ * a component 1 value.
6226
+ */
5396
6227
  readonly reportedCode?: string;
5397
- /** How the reported code was recognized in the Universal Test ID (provenance only, never a lookup). */
6228
+ /**
6229
+ * Component 1 when populated, verbatim: a wire value this library **does not
6230
+ * vouch for**, carried on every outcome and never validated, never promoted to a
6231
+ * LOINC, and never used as a lookup key. Absent when component 1 is empty.
6232
+ */
6233
+ readonly unvalidatedWireValue?: string;
6234
+ /**
6235
+ * `true` if and only if the catalog vouched for exactly one LOINC for the
6236
+ * vendor/local code, component 1 is populated, and that value is not
6237
+ * byte-identical to that LOINC. `false` in **every** other case, including where
6238
+ * the catalog vouched for no single LOINC (a miss, an ambiguity, no vendor code,
6239
+ * no code at all): asserting a disagreement there would claim the catalog spoke
6240
+ * about a code it never spoke about.
6241
+ *
6242
+ * It reports the difference and **nothing else**. Neither value is marked
6243
+ * correct, neither is suppressed, and no field says the difference was settled.
6244
+ */
6245
+ readonly wireValueDisagreesWithCatalog: boolean;
6246
+ /** How the identifier was recognized in the Universal Test ID (provenance only, never a lookup). */
5398
6247
  readonly provenance: UniversalTestIdProvenance;
5399
- /** The mapping outcome: never a guessed LOINC. */
6248
+ /** The lookup outcome: never a guessed LOINC. */
5400
6249
  readonly mapping: LivdMapping;
6250
+ /**
6251
+ * The **LOINC version the catalog declared**, verbatim, carried on **every** annotation that
6252
+ * catalog produced, whatever the outcome of the lookup. It is what makes a mapping reproducible:
6253
+ * the reader can see which version of LOINC the consumer's catalog says it was built against.
6254
+ *
6255
+ * **It asserts nothing about this LOINC.** It is provenance about the CATALOG, not a claim that
6256
+ * any LOINC was checked, found, or validated against that version: this package performs no
6257
+ * LOINC validation of any kind, and nothing here reads the string beyond carrying it.
6258
+ *
6259
+ * Absent when the catalog declared no LOINC version (including a catalog a consumer implemented
6260
+ * by hand, which declares none), never a default, a placeholder or an empty string.
6261
+ */
6262
+ readonly catalogLoincVersion?: string;
5401
6263
  }
5402
6264
  /**
5403
6265
  * The result of {@link applyLivd}: the per-record annotations (one per `R`/`O`
@@ -5407,7 +6269,15 @@ interface LivdAnnotation {
5407
6269
  interface LivdResult {
5408
6270
  /** One annotation per `R`/`O` record, in wire order. */
5409
6271
  readonly annotations: readonly LivdAnnotation[];
5410
- /** A value-free warning per `unmapped`/`ambiguous` code: never per `mapped`/`inline-loinc`/`no-code`. */
6272
+ /**
6273
+ * A value-free warning per `unmapped`/`ambiguous` code: never per `mapped`, and
6274
+ * never per `no-vendor-code`/`no-code`, where no lookup happened at all.
6275
+ *
6276
+ * Per-record only. A warning about the CATALOG, such as one declaring no LOINC
6277
+ * version, is raised where the catalog is defined and stays on
6278
+ * {@link LivdCatalog.warnings}: it implicates no record, so it never joins this
6279
+ * stream and never changes its codes, its order or its length.
6280
+ */
5411
6281
  readonly warnings: readonly AstmLivdWarning[];
5412
6282
  }
5413
6283
  /**
@@ -5435,9 +6305,14 @@ declare function lookupLivdForRecord(record: ResultRecord | OrderRecord, catalog
5435
6305
  *
5436
6306
  * Fail-safe: with no matching entry a code is `unmapped` (+ an
5437
6307
  * `ASTM_LIVD_UNMAPPED_CODE` warning); with a conflicting mapping it is `ambiguous`
5438
- * (+ an `ASTM_LIVD_AMBIGUOUS_MAPPING` warning); a LOINC is **never** guessed. When
5439
- * the wire already carried an inline LOINC candidate, it is surfaced from the wire
5440
- * (never overwritten by the catalog).
6308
+ * (+ an `ASTM_LIVD_AMBIGUOUS_MAPPING` warning); a LOINC is **never** guessed. A
6309
+ * populated component 1 never short-circuits the lookup and is never reported as a
6310
+ * LOINC: it is carried verbatim as an unvalidated wire value, and where it differs
6311
+ * from the catalog's answer the difference is reported and left unresolved.
6312
+ *
6313
+ * A catalog is code a consumer supplies, so a lookup that throws propagates to the
6314
+ * caller unchanged: a consumer's own failure is never reported as a catalog miss,
6315
+ * and no partially annotated result is returned.
5441
6316
  *
5442
6317
  * @param msg - A parsed ASTM message.
5443
6318
  * @param catalog - The consumer-supplied LIVD catalog (build with {@link defineLivdCatalog}).
@@ -5445,10 +6320,12 @@ declare function lookupLivdForRecord(record: ResultRecord | OrderRecord, catalog
5445
6320
  * @example
5446
6321
  * ```ts
5447
6322
  * import { parseAstmRecords, defineLivdCatalog, applyLivd } from "@cosyte/astm";
5448
- * const msg = parseAstmRecords("H|\\^&\rR|1|^^^687|28.6|U/L||N||F\rL|1\r");
6323
+ * const msg = parseAstmRecords("H|\\^&\rR|1|Glucose^^^687|28.6|U/L||N||F\rL|1\r");
5449
6324
  * const catalog = defineLivdCatalog([{ vendorCode: "687", loinc: "1920-8", loincLongName: "AST" }]);
5450
- * const { annotations } = applyLivd(msg, catalog);
5451
- * annotations[0]?.mapping; // { status: "mapped", loinc: "1920-8", loincLongName: "AST", source: "livd", derived: true }
6325
+ * const [a] = applyLivd(msg, catalog).annotations;
6326
+ * a?.mapping; // { status: "mapped", loinc: "1920-8", loincLongName: "AST", source: "livd", derived: true }
6327
+ * a?.unvalidatedWireValue; // "Glucose": carried verbatim, vouched for by nothing
6328
+ * a?.wireValueDisagreesWithCatalog; // true: reported, never resolved
5452
6329
  * ```
5453
6330
  */
5454
6331
  declare function applyLivd(msg: AstmMessage, catalog: LivdCatalog): LivdResult;
@@ -6271,4 +7148,4 @@ declare class AstmFrameStrictError extends Error {
6271
7148
  */
6272
7149
  declare const VERSION: string;
6273
7150
 
6274
- export { ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousAlignmentSink, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type DelimiterDeclarationFault, type DelimiterReadResult, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAnnotation, type LivdCatalog, type LivdEntry, type LivdLookup, type LivdMapping, type LivdResult, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, type ShiftedComponentsSink, type ShiftedFieldsSink, type SwallowedDelimiterSink, TOLERABLE_CODES, type TerminatorRecord, type TruncatedFieldSink, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, WARNING_CODES, type WarningCode, alignmentShiftedComponents, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };
7151
+ export { ABNORMAL_FLAG_CODES, ABNORMAL_FLAG_VOCABULARY, ALL_ASTM_WARNING_CODES, AMBIGUOUS_CODES, ACK as ASTM_ACK, ENQ as ASTM_ENQ, EOT as ASTM_EOT, NAK as ASTM_NAK, type AbnormalFlag, type AbnormalFlagCode, type AbnormalFlagMeaning, type AmbiguousAlignmentSink, type AmbiguousCode, type AnyAstmWarningCode, AstmAmbiguousStreamError, type AstmDate, type AstmDatePrecision, type AstmField, type AstmFrame, AstmFrameEncodeError, type AstmFrameEncodeErrorCode, type AstmFramePosition, AstmFrameStrictError, type AstmFrameWarning, type AstmFraming, type AstmLivdCatalogWarning, type AstmLivdWarning, type AstmLtpWarning, type AstmMessage, type AstmMessageClassification, type AstmMessageKind, AstmParseError, type AstmParseOptions, type AstmPosition, type AstmProfile, AstmProfileDefinitionError, type AstmProfileProvenance, type AstmQuirkMatch, type AstmQuirkTolerance, type AstmRecord, type AstmRecordInput, type AstmRecordWarning, AstmSerializeError, type AstmSerializeErrorCode, type AstmStreamMessage, AstmStrictError, CANONICAL_DELIMITERS, type CommentInput, type CommentRecord, type ComposeFramesOptions, type DateParts, type DecodeAstmFramesResult, type DefineAstmProfileOptions, type DelimiterDeclarationFault, type DelimiterReadResult, type Delimiters, type DetectFramingOptions, type DetectFramingResult, FATAL_CODES, FRAME_WARNING_CODES, type FatalCode, type FrameChecksum, type FrameOptions, type FrameTerminator, type FrameWarningCode, type FramedAstmResult, type HeaderInput, type HeaderRecord, LIVD_CATALOG_IDENTITY_UNDECLARED, LIVD_WARNING_CODES, LTP_WARNING_CODES, type LivdAmbiguityReason, type LivdAnnotation, type LivdCandidate, type LivdCatalog, type LivdCatalogIdentity, type LivdEntry, type LivdLookup, type LivdMapping, type LivdPublication, type LivdResult, type LivdUnitComparison, type LivdWarningCode, type LtpAction, type LtpEvent, type LtpPhase, type LtpState, type LtpTransition, type LtpWarningCode, type ManufacturerRecord, type MessageInput, type NamedVocabulary, type OrderInput, type OrderRecord, type PatientInput, type PatientName, type PatientNameInput, type PatientRecord, type QueryInput, type QueryRecord, RESULT_STATUS_VOCABULARY, type ReferenceRange, type ReferenceRangeKind, type ResultInput, type ResultRecord, type ResultStatus, type ResultStatusCode, type ResultStatusMeaning, SAFETY_CRITICAL_CODES, type ScientificRecord, type ShiftedComponentsSink, type ShiftedFieldsSink, type SwallowedDelimiterSink, TOLERABLE_CODES, type TerminatorRecord, type ToDateOptions, type TruncatedFieldSink, type UnattributedVocabulary, type UniversalTestId, type UniversalTestIdProvenance, type UnknownEscapeSink, type UnpairedEscapeSink, type UnsupportedRecord, VERSION, type VerbatimInput, type VocabularyAttribution, WARNING_CODES, type WarningCode, alignmentShiftedComponents, alignmentShiftedFields, alignmentTruncatedField, ambiguousEscapeAlignment, ambiguousMessageKind, ambiguousValueSplit, applyAstmProfile, applyAstmProfileToWarnings, applyLivd, astmDateToLocalISO, astmProfiles, attachComments, buildAstmMessage, classifyMessage, comments, commentsFor, composeAstmFrames, computeChecksum, decodeAstmFrames, decodeEscapes, deepFreeze, defineAstmProfile, defineLivdCatalog, delimiterRoleCollision, delimiterSwallowedByEscape, delimitersRedeclared, describeVocabulary, detectFraming, encodeComponent, fieldScalar, fieldsUnseparated, frameBadChecksum, frameOversize, frameSequenceGap, frameUnterminated, getAstmProfile, getDefaultAstmProfile, hasCollidingRoles, interpretAbnormalFlag, interpretResultStatus, isNonStandard, isSafetyCriticalCode, listAstmProfiles, livdAmbiguousMapping, livdCatalogMissingLoincVersion, livdUnmappedCode, lookupLivdForRecord, ltpAmbiguousTransport, ltpFrameRejected, ltpInitialState, ltpReduce, ltpUnexpectedEvent, messages, nonStandardDelimiters, orders, orphanComment, parseAstmDate, parseAstmRecords, parseChecksumHex, parseFramedAstm, parseReferenceRange, partialTimestamp, patient, primaryCode, profileQuirkApplied, query, readDelimiterDeclaration, readDelimiters, recognizeUniversalTestId, resolveProfileTransport, results, serializeAstmRecord, serializeAstmRecords, serializeField, serializeFramedAstm, setDefaultAstmProfile, splitEscapeAware, toChecksumHex, toDate, toISO, toObject, tokenizeHeader, tokenizeRecord, undefinedAbnormalFlag, undefinedResultStatus, uninterpretedQueryStatus, unitsAbsent, unknownEscapeSequence, unknownRecordType, unpairedEscapeCharacter, unparseableReferenceRange, unreadableRedeclaration };