@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.cjs CHANGED
@@ -102,13 +102,13 @@ function parseAstmDate(raw) {
102
102
  return { ...base, month, day, hour, minute, second, precision: "second" };
103
103
  }
104
104
  function astmDateToLocalISO(d) {
105
- const p2 = (n) => String(n).padStart(2, "0");
105
+ const p22 = (n) => String(n).padStart(2, "0");
106
106
  let out = String(d.year).padStart(4, "0");
107
- if (d.month !== void 0) out += `-${p2(d.month)}`;
108
- if (d.day !== void 0) out += `-${p2(d.day)}`;
109
- if (d.hour !== void 0) out += `T${p2(d.hour)}`;
110
- if (d.minute !== void 0) out += `:${p2(d.minute)}`;
111
- if (d.second !== void 0) out += `:${p2(d.second)}`;
107
+ if (d.month !== void 0) out += `-${p22(d.month)}`;
108
+ if (d.day !== void 0) out += `-${p22(d.day)}`;
109
+ if (d.hour !== void 0) out += `T${p22(d.hour)}`;
110
+ if (d.minute !== void 0) out += `:${p22(d.minute)}`;
111
+ if (d.second !== void 0) out += `:${p22(d.second)}`;
112
112
  return out;
113
113
  }
114
114
 
@@ -118,26 +118,40 @@ function recognizeUniversalTestId(components) {
118
118
  const c = components[i];
119
119
  return c !== void 0 && c.length > 0 ? c : void 0;
120
120
  };
121
- const loincCandidate = nonEmpty(0);
121
+ const unvalidatedWireValue = nonEmpty(0);
122
122
  const testName = nonEmpty(1);
123
123
  const codingScheme = nonEmpty(2);
124
124
  const localCode = nonEmpty(3);
125
125
  let provenance;
126
- if (loincCandidate !== void 0) provenance = "inline-loinc-candidate";
127
- else if (localCode !== void 0) provenance = "local-code";
126
+ if (localCode !== void 0) provenance = "local-code";
127
+ else if (unvalidatedWireValue !== void 0) provenance = "unvalidated-wire-value-only";
128
128
  else if (testName !== void 0) provenance = "name-only";
129
129
  else provenance = "empty";
130
130
  const base = { components, provenance };
131
131
  return {
132
132
  ...base,
133
- ...loincCandidate !== void 0 ? { loincCandidate } : {},
133
+ ...unvalidatedWireValue !== void 0 ? { unvalidatedWireValue } : {},
134
134
  ...testName !== void 0 ? { testName } : {},
135
135
  ...codingScheme !== void 0 ? { codingScheme } : {},
136
136
  ...localCode !== void 0 ? { localCode } : {}
137
137
  };
138
138
  }
139
139
  function primaryCode(u) {
140
- return u.loincCandidate ?? u.localCode;
140
+ return u.localCode;
141
+ }
142
+
143
+ // src/common/vocabulary.ts
144
+ var ABNORMAL_FLAG_VOCABULARY = Object.freeze({
145
+ attributed: true,
146
+ system: "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
147
+ version: "4.0.0"
148
+ });
149
+ var RESULT_STATUS_VOCABULARY = Object.freeze({
150
+ attributed: false,
151
+ reason: "The result-status letter set is graded against no citable published source: the normative text that would bind it is purchase-gated and unread here, and no neighbouring code system is assumed in its place."
152
+ });
153
+ function describeVocabulary(vocabulary) {
154
+ return vocabulary.attributed ? `graded against ${vocabulary.system} version ${vocabulary.version}` : vocabulary.reason;
141
155
  }
142
156
 
143
157
  // src/common/warnings.ts
@@ -747,14 +761,14 @@ function ambiguousValueSplit(position) {
747
761
  function undefinedAbnormalFlag(position) {
748
762
  return {
749
763
  code: WARNING_CODES.ASTM_RECORD_UNDEFINED_ABNORMAL_FLAG,
750
- message: "Abnormal flag is not in HL7 Table 0078, surfaced as undefined, never as normal.",
764
+ message: `Abnormal flag is not a code in the vocabulary it was ${describeVocabulary(ABNORMAL_FLAG_VOCABULARY)}, surfaced as undefined, never as normal.`,
751
765
  position
752
766
  };
753
767
  }
754
768
  function undefinedResultStatus(position) {
755
769
  return {
756
770
  code: WARNING_CODES.ASTM_RECORD_UNDEFINED_RESULT_STATUS,
757
- message: "Result status is not a recognized status letter, surfaced as undefined.",
771
+ message: `Result status is not a recognized status letter, surfaced as undefined. ${describeVocabulary(RESULT_STATUS_VOCABULARY)}`,
758
772
  position
759
773
  };
760
774
  }
@@ -828,14 +842,25 @@ var ABNORMAL_FLAG_MEANINGS = {
828
842
  W: "worse",
829
843
  S: "susceptible",
830
844
  R: "resistant",
831
- I: "intermediate"
845
+ I: "intermediate",
846
+ HU: "significantly-high",
847
+ LU: "significantly-low"
832
848
  };
849
+ var ABNORMAL_FLAG_CODES = Object.freeze(
850
+ Object.keys(ABNORMAL_FLAG_MEANINGS)
851
+ );
833
852
  function interpretAbnormalFlag(raw) {
834
853
  const key = raw.trim();
835
854
  if (isAbnormalFlagCode(key)) {
836
- return { raw, code: key, meaning: ABNORMAL_FLAG_MEANINGS[key], recognized: true };
855
+ return {
856
+ raw,
857
+ code: key,
858
+ meaning: ABNORMAL_FLAG_MEANINGS[key],
859
+ recognized: true,
860
+ vocabulary: ABNORMAL_FLAG_VOCABULARY
861
+ };
837
862
  }
838
- return { raw, meaning: "undefined", recognized: false };
863
+ return { raw, meaning: "undefined", recognized: false, vocabulary: ABNORMAL_FLAG_VOCABULARY };
839
864
  }
840
865
  function isAbnormalFlagCode(value) {
841
866
  return Object.prototype.hasOwnProperty.call(ABNORMAL_FLAG_MEANINGS, value);
@@ -856,7 +881,8 @@ function interpretResultStatus(raw) {
856
881
  recognized: false,
857
882
  isActiveFinal: false,
858
883
  supersedes: false,
859
- cancelled: false
884
+ cancelled: false,
885
+ vocabulary: RESULT_STATUS_VOCABULARY
860
886
  };
861
887
  }
862
888
  const key = raw.trim();
@@ -868,7 +894,8 @@ function interpretResultStatus(raw) {
868
894
  recognized: true,
869
895
  isActiveFinal: key === "F",
870
896
  supersedes: key === "C",
871
- cancelled: key === "X"
897
+ cancelled: key === "X",
898
+ vocabulary: RESULT_STATUS_VOCABULARY
872
899
  };
873
900
  }
874
901
  return {
@@ -877,7 +904,8 @@ function interpretResultStatus(raw) {
877
904
  recognized: false,
878
905
  isActiveFinal: false,
879
906
  supersedes: false,
880
- cancelled: false
907
+ cancelled: false,
908
+ vocabulary: RESULT_STATUS_VOCABULARY
881
909
  };
882
910
  }
883
911
  function isResultStatusCode(value) {
@@ -1802,10 +1830,10 @@ function buildResult(recordIndex, fields, warnings) {
1802
1830
  13,
1803
1831
  warnings
1804
1832
  );
1805
- const ambiguous = valueField !== void 0 && valueField.components.length > 1;
1806
- const valueComponents = ambiguous ? valueField.components : void 0;
1807
- const value = ambiguous ? valueField.raw : fieldScalar(valueField);
1808
- if (ambiguous) {
1833
+ const ambiguous2 = valueField !== void 0 && valueField.components.length > 1;
1834
+ const valueComponents = ambiguous2 ? valueField.components : void 0;
1835
+ const value = ambiguous2 ? valueField.raw : fieldScalar(valueField);
1836
+ if (ambiguous2) {
1809
1837
  warnings.push(ambiguousValueSplit({ recordIndex, recordType: "R", fieldIndex: 4 }));
1810
1838
  }
1811
1839
  const units = fieldScalar(astmField(fields, 5));
@@ -2309,41 +2337,77 @@ function query(msg) {
2309
2337
  return msg.records.filter((r) => r.type === "Q");
2310
2338
  }
2311
2339
 
2340
+ // src/common/date-conversion.ts
2341
+ function isStated(component) {
2342
+ return typeof component === "number" && Number.isFinite(component);
2343
+ }
2344
+ function p2(n) {
2345
+ return String(n).padStart(2, "0");
2346
+ }
2347
+ function isLeapYear(year) {
2348
+ return year % 4 === 0 && year % 100 !== 0 || year % 400 === 0;
2349
+ }
2350
+ function longestDay(year, month) {
2351
+ if (month === 2) return year === void 0 || isLeapYear(year) ? 29 : 28;
2352
+ return month === 4 || month === 6 || month === 9 || month === 11 ? 30 : 31;
2353
+ }
2354
+ function inRange(component, low, high) {
2355
+ if (component === void 0) return true;
2356
+ return Number.isInteger(component) && component >= low && component <= high;
2357
+ }
2358
+ function statesARealCalendarDate(parts) {
2359
+ return inRange(parts.year, 0, 9999) && inRange(parts.month, 1, 12) && inRange(parts.day, 1, parts.month === void 0 ? 31 : longestDay(parts.year, parts.month)) && inRange(parts.hour, 0, 23) && inRange(parts.minute, 0, 59) && // 60 is refused with the rest: a leap second is legal in ISO-8601 prose and
2360
+ // is an Invalid Date to every JS reader, so accepting it here would hand out
2361
+ // a string the next hop cannot read.
2362
+ inRange(parts.second, 0, 59);
2363
+ }
2364
+ function toObject(value) {
2365
+ if (value === null || value === void 0) return void 0;
2366
+ const parts = {};
2367
+ if (isStated(value.year)) parts.year = value.year;
2368
+ if (isStated(value.month)) parts.month = value.month;
2369
+ if (isStated(value.day)) parts.day = value.day;
2370
+ if (isStated(value.hour)) parts.hour = value.hour;
2371
+ if (isStated(value.minute)) parts.minute = value.minute;
2372
+ if (isStated(value.second)) parts.second = value.second;
2373
+ if (Object.keys(parts).length === 0) return void 0;
2374
+ if (!statesARealCalendarDate(parts)) return void 0;
2375
+ return Object.freeze(parts);
2376
+ }
2377
+ function toISO(value) {
2378
+ const parts = toObject(value);
2379
+ if (parts === void 0 || parts.year === void 0) return void 0;
2380
+ let out = String(parts.year).padStart(4, "0");
2381
+ if (parts.month === void 0) return out;
2382
+ out += `-${p2(parts.month)}`;
2383
+ if (parts.day === void 0) return out;
2384
+ out += `-${p2(parts.day)}`;
2385
+ if (parts.hour === void 0) return out;
2386
+ out += `T${p2(parts.hour)}`;
2387
+ if (parts.minute === void 0) return out;
2388
+ out += `:${p2(parts.minute)}`;
2389
+ if (parts.second === void 0) return out;
2390
+ out += `:${p2(parts.second)}`;
2391
+ return out;
2392
+ }
2393
+ function toDate(value, options) {
2394
+ const parts = toObject(value);
2395
+ if (parts === void 0 || parts.year === void 0) return void 0;
2396
+ const offsetMinutes = options?.assumeOffsetMinutes;
2397
+ if (!isStated(offsetMinutes)) return void 0;
2398
+ const instant = /* @__PURE__ */ new Date(0);
2399
+ instant.setUTCFullYear(parts.year, (parts.month ?? 1) - 1, parts.day ?? 1);
2400
+ instant.setUTCHours(parts.hour ?? 0, parts.minute ?? 0, parts.second ?? 0, 0);
2401
+ const shifted = new Date(instant.getTime() - offsetMinutes * 6e4);
2402
+ return Number.isNaN(shifted.getTime()) ? void 0 : shifted;
2403
+ }
2404
+
2312
2405
  // src/profiles/index.ts
2313
2406
  var astmProfiles = Object.freeze({
2314
2407
  default: defaultProfile,
2315
2408
  referenceCorpus
2316
2409
  });
2317
2410
 
2318
- // src/terminology/catalog.ts
2319
- function defineLivdCatalog(entries) {
2320
- const index = /* @__PURE__ */ new Map();
2321
- for (const entry of entries) {
2322
- const bucket = index.get(entry.vendorCode);
2323
- if (bucket) bucket.push(entry);
2324
- else index.set(entry.vendorCode, [entry]);
2325
- }
2326
- const catalog = {
2327
- size: index.size,
2328
- lookup(vendorCode) {
2329
- const bucket = index.get(vendorCode);
2330
- if (!bucket || bucket.length === 0) return { status: "unmapped" };
2331
- const distinct = [...new Set(bucket.map((e) => e.loinc))];
2332
- if (distinct.length > 1) {
2333
- return deepFreeze({ status: "ambiguous", candidates: distinct });
2334
- }
2335
- const [first] = bucket;
2336
- if (first === void 0) return { status: "unmapped" };
2337
- return deepFreeze({
2338
- status: "mapped",
2339
- loinc: first.loinc,
2340
- ...first.loincLongName !== void 0 ? { loincLongName: first.loincLongName } : {}
2341
- });
2342
- }
2343
- };
2344
- return Object.freeze(catalog);
2345
- }
2346
-
2347
2411
  // src/terminology/warnings.ts
2348
2412
  var LIVD_WARNING_CODES = {
2349
2413
  /**
@@ -2358,7 +2422,15 @@ var LIVD_WARNING_CODES = {
2358
2422
  * candidate LOINCs are surfaced for inspection, but **none is chosen**: refusing to pick is the
2359
2423
  * fail-safe, since guessing wrong mis-identifies the test.
2360
2424
  */
2361
- ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING"
2425
+ ASTM_LIVD_AMBIGUOUS_MAPPING: "ASTM_LIVD_AMBIGUOUS_MAPPING",
2426
+ /**
2427
+ * A consumer-supplied LIVD catalog was defined **without a LOINC version**, so a mapping it
2428
+ * produces cannot say which version of LOINC it was made against. Advisory and **never a
2429
+ * refusal**: the catalog is still built, still indexed and still answers exactly as it would
2430
+ * have. Raised **once, where the catalog is defined**, and never per record: it is a fact about
2431
+ * the catalog rather than about any message, so it never joins the per-record warning stream.
2432
+ */
2433
+ ASTM_LIVD_CATALOG_NO_LOINC_VERSION: "ASTM_LIVD_CATALOG_NO_LOINC_VERSION"
2362
2434
  };
2363
2435
  function livdUnmappedCode(position) {
2364
2436
  return {
@@ -2374,44 +2446,208 @@ function livdAmbiguousMapping(position) {
2374
2446
  position
2375
2447
  };
2376
2448
  }
2449
+ var LIVD_CATALOG_IDENTITY_UNDECLARED = Object.freeze({
2450
+ declared: false,
2451
+ reason: "The catalog declared neither a publisher nor a publication version, so it declared no identity at all. Nothing stands in for one here: no name is invented, no ordinal or position is substituted, and no value from the catalog's rows is borrowed to label it."
2452
+ });
2453
+ var CATALOG_NO_LOINC_VERSION_MESSAGE = "The LIVD catalog declared no LOINC version, so an annotation it produces cannot state which version of LOINC the mapping was made against. The catalog is built and usable, and nothing about a lookup changes. What the catalog declared about itself is on this warning's catalog field, never in this message.";
2454
+ function livdCatalogMissingLoincVersion(catalog) {
2455
+ return {
2456
+ code: LIVD_WARNING_CODES.ASTM_LIVD_CATALOG_NO_LOINC_VERSION,
2457
+ message: CATALOG_NO_LOINC_VERSION_MESSAGE,
2458
+ catalog
2459
+ };
2460
+ }
2461
+
2462
+ // src/terminology/catalog.ts
2463
+ var VERBATIM_UNIT_NOTE = "Selected by comparing the units the record reported with the catalog entry's representative unit verbatim and case sensitively (exact string equality). This is NOT a UCUM semantic comparison: neither side was normalized, case folded, scaled or converted, and this package does not claim full UCUM conformance.";
2464
+ function declaredValue(value) {
2465
+ return value === void 0 || value.trim() === "" ? void 0 : value;
2466
+ }
2467
+ function declaredPublication(publication) {
2468
+ const publisher = declaredValue(publication?.publisher);
2469
+ const publicationVersion = declaredValue(publication?.publicationVersion);
2470
+ const loincVersion = declaredValue(publication?.loincVersion);
2471
+ const loincCopyright = declaredValue(publication?.loincCopyright);
2472
+ const declared = {
2473
+ ...publisher !== void 0 ? { publisher } : {},
2474
+ ...publicationVersion !== void 0 ? { publicationVersion } : {},
2475
+ ...loincVersion !== void 0 ? { loincVersion } : {},
2476
+ ...loincCopyright !== void 0 ? { loincCopyright } : {}
2477
+ };
2478
+ return Object.keys(declared).length === 0 ? void 0 : declared;
2479
+ }
2480
+ function identityOf(publication) {
2481
+ const publisher = publication?.publisher;
2482
+ const publicationVersion = publication?.publicationVersion;
2483
+ if (publisher === void 0 && publicationVersion === void 0) {
2484
+ return LIVD_CATALOG_IDENTITY_UNDECLARED;
2485
+ }
2486
+ return {
2487
+ declared: true,
2488
+ ...publisher !== void 0 ? { publisher } : {},
2489
+ ...publicationVersion !== void 0 ? { publicationVersion } : {}
2490
+ };
2491
+ }
2492
+ function definitionWarnings(publication) {
2493
+ if (publication?.loincVersion !== void 0) return [];
2494
+ return [livdCatalogMissingLoincVersion(identityOf(publication))];
2495
+ }
2496
+ function carriesLivdAttributes(entry) {
2497
+ return entry.vendorSpecimenDescription !== void 0 || entry.vendorResultDescription !== void 0 || entry.representativeUnit !== void 0;
2498
+ }
2499
+ function isUnitQualified(entry) {
2500
+ const unit = entry.representativeUnit;
2501
+ return unit !== void 0 && unit.trim() !== "";
2502
+ }
2503
+ function usableUnits(reportedUnits) {
2504
+ if (reportedUnits === void 0 || reportedUnits.trim() === "") return void 0;
2505
+ return reportedUnits;
2506
+ }
2507
+ function candidateOf(entry) {
2508
+ return {
2509
+ loinc: entry.loinc,
2510
+ ...entry.loincLongName !== void 0 ? { loincLongName: entry.loincLongName } : {},
2511
+ ...entry.vendorSpecimenDescription !== void 0 ? { vendorSpecimenDescription: entry.vendorSpecimenDescription } : {},
2512
+ ...entry.vendorResultDescription !== void 0 ? { vendorResultDescription: entry.vendorResultDescription } : {},
2513
+ ...entry.representativeUnit !== void 0 ? { representativeUnit: entry.representativeUnit } : {},
2514
+ unitQualified: isUnitQualified(entry)
2515
+ };
2516
+ }
2517
+ function unitComparisonOf(matchedUnits) {
2518
+ return {
2519
+ comparison: "verbatim-case-sensitive",
2520
+ ucumSemantic: false,
2521
+ reportedUnits: matchedUnits,
2522
+ representativeUnit: matchedUnits,
2523
+ note: VERBATIM_UNIT_NOTE
2524
+ };
2525
+ }
2526
+ function mappedFrom(entry, unitComparison) {
2527
+ return {
2528
+ status: "mapped",
2529
+ loinc: entry.loinc,
2530
+ ...entry.loincLongName !== void 0 ? { loincLongName: entry.loincLongName } : {},
2531
+ ...entry.vendorSpecimenDescription !== void 0 ? { vendorSpecimenDescription: entry.vendorSpecimenDescription } : {},
2532
+ ...entry.vendorResultDescription !== void 0 ? { vendorResultDescription: entry.vendorResultDescription } : {},
2533
+ ...entry.representativeUnit !== void 0 ? { representativeUnit: entry.representativeUnit } : {},
2534
+ ...unitComparison !== void 0 ? { unitComparison } : {}
2535
+ };
2536
+ }
2537
+ function ambiguous(candidates, candidateDetails, reason) {
2538
+ return { status: "ambiguous", candidates, candidateDetails, reason };
2539
+ }
2540
+ function defineLivdCatalog(entries, publication) {
2541
+ const index = /* @__PURE__ */ new Map();
2542
+ for (const entry of entries) {
2543
+ const bucket = index.get(entry.vendorCode);
2544
+ if (bucket) bucket.push(entry);
2545
+ else index.set(entry.vendorCode, [entry]);
2546
+ }
2547
+ const declared = declaredPublication(publication);
2548
+ const catalog = {
2549
+ size: index.size,
2550
+ ...declared !== void 0 ? { publication: deepFreeze(declared) } : {},
2551
+ warnings: deepFreeze(definitionWarnings(declared)),
2552
+ lookup(vendorCode, reportedUnits) {
2553
+ const bucket = index.get(vendorCode);
2554
+ if (!bucket || bucket.length === 0) return { status: "unmapped" };
2555
+ const distinct = [...new Set(bucket.map((e) => e.loinc))];
2556
+ if (distinct.length === 1) {
2557
+ const [first] = bucket;
2558
+ if (first === void 0) return { status: "unmapped" };
2559
+ return deepFreeze(mappedFrom(first));
2560
+ }
2561
+ if (!bucket.some(carriesLivdAttributes)) {
2562
+ const plain = { status: "ambiguous", candidates: distinct };
2563
+ return deepFreeze(plain);
2564
+ }
2565
+ const details = bucket.map(candidateOf);
2566
+ const units = usableUnits(reportedUnits);
2567
+ if (units === void 0) {
2568
+ return deepFreeze(ambiguous(distinct, details, "no-reported-units"));
2569
+ }
2570
+ const matches = bucket.filter((e) => isUnitQualified(e) && e.representativeUnit === units);
2571
+ const matchedLoincs = [...new Set(matches.map((e) => e.loinc))];
2572
+ const [chosen] = matches;
2573
+ if (matchedLoincs.length === 1 && chosen !== void 0) {
2574
+ return deepFreeze(mappedFrom(chosen, unitComparisonOf(units)));
2575
+ }
2576
+ return deepFreeze(
2577
+ ambiguous(
2578
+ distinct,
2579
+ details,
2580
+ matchedLoincs.length === 0 ? "no-candidate-matched-units" : "multiple-candidates-matched-units"
2581
+ )
2582
+ );
2583
+ }
2584
+ };
2585
+ return Object.freeze(catalog);
2586
+ }
2377
2587
 
2378
2588
  // src/terminology/apply.ts
2379
- function mapTestId(uid, catalog) {
2380
- if (uid.loincCandidate !== void 0) {
2381
- return { status: "inline-loinc", loinc: uid.loincCandidate, source: "wire" };
2589
+ function mapTestId(uid, catalog, reportedUnits) {
2590
+ const code = uid.localCode;
2591
+ if (code === void 0) {
2592
+ return uid.unvalidatedWireValue !== void 0 ? { status: "no-vendor-code" } : { status: "no-code" };
2382
2593
  }
2383
- const code = primaryCode(uid);
2384
- if (code === void 0) return { status: "no-code" };
2385
- const hit = catalog.lookup(code);
2594
+ const hit = catalog.lookup(code, reportedUnits);
2386
2595
  switch (hit.status) {
2387
2596
  case "mapped":
2597
+ if (hit.loinc.length === 0) return { status: "unmapped" };
2388
2598
  return {
2389
2599
  status: "mapped",
2390
2600
  loinc: hit.loinc,
2391
2601
  ...hit.loincLongName !== void 0 ? { loincLongName: hit.loincLongName } : {},
2602
+ ...hit.vendorSpecimenDescription !== void 0 ? { vendorSpecimenDescription: hit.vendorSpecimenDescription } : {},
2603
+ ...hit.vendorResultDescription !== void 0 ? { vendorResultDescription: hit.vendorResultDescription } : {},
2604
+ ...hit.representativeUnit !== void 0 ? { representativeUnit: hit.representativeUnit } : {},
2605
+ ...hit.unitComparison !== void 0 ? { unitComparison: hit.unitComparison } : {},
2392
2606
  source: "livd",
2393
2607
  derived: true
2394
2608
  };
2395
2609
  case "ambiguous":
2396
- return { status: "ambiguous", candidates: hit.candidates };
2610
+ return {
2611
+ status: "ambiguous",
2612
+ candidates: hit.candidates,
2613
+ ...hit.candidateDetails !== void 0 ? { candidateDetails: hit.candidateDetails } : {},
2614
+ ...hit.reason !== void 0 ? { reason: hit.reason } : {}
2615
+ };
2397
2616
  case "unmapped":
2398
2617
  return { status: "unmapped" };
2399
2618
  }
2400
2619
  }
2620
+ function unitsOf(record) {
2621
+ return record.type === "R" ? record.units : void 0;
2622
+ }
2623
+ function disagrees(uid, mapping) {
2624
+ if (mapping.status !== "mapped") return false;
2625
+ const wire = uid.unvalidatedWireValue;
2626
+ return wire !== void 0 && wire !== mapping.loinc;
2627
+ }
2628
+ function declaredLoincVersion(catalog) {
2629
+ const version = catalog.publication?.loincVersion;
2630
+ return version === void 0 || version.trim() === "" ? void 0 : version;
2631
+ }
2401
2632
  function testIdOf(record) {
2402
2633
  return record.universalTestId;
2403
2634
  }
2404
2635
  function lookupLivdForRecord(record, catalog) {
2405
2636
  const uid = testIdOf(record);
2406
2637
  const recognized = uid ?? recognizeUniversalTestId([]);
2407
- const mapping = mapTestId(recognized, catalog);
2408
- const reportedCode = primaryCode(recognized);
2638
+ const mapping = mapTestId(recognized, catalog, unitsOf(record));
2639
+ const reportedCode = recognized.localCode;
2640
+ const wireValue = recognized.unvalidatedWireValue;
2641
+ const catalogLoincVersion = declaredLoincVersion(catalog);
2409
2642
  return {
2410
2643
  recordIndex: record.recordIndex,
2411
2644
  recordType: record.type,
2412
2645
  ...reportedCode !== void 0 ? { reportedCode } : {},
2646
+ ...wireValue !== void 0 ? { unvalidatedWireValue: wireValue } : {},
2647
+ wireValueDisagreesWithCatalog: disagrees(recognized, mapping),
2413
2648
  provenance: recognized.provenance,
2414
- mapping
2649
+ mapping,
2650
+ ...catalogLoincVersion !== void 0 ? { catalogLoincVersion } : {}
2415
2651
  };
2416
2652
  }
2417
2653
  function applyLivd(msg, catalog) {
@@ -2908,8 +3144,10 @@ function concatBytes3(chunks) {
2908
3144
  }
2909
3145
 
2910
3146
  // src/index.ts
2911
- var VERSION = "0.0.22";
3147
+ var VERSION = "0.1.0";
2912
3148
 
3149
+ exports.ABNORMAL_FLAG_CODES = ABNORMAL_FLAG_CODES;
3150
+ exports.ABNORMAL_FLAG_VOCABULARY = ABNORMAL_FLAG_VOCABULARY;
2913
3151
  exports.ALL_ASTM_WARNING_CODES = ALL_ASTM_WARNING_CODES;
2914
3152
  exports.AMBIGUOUS_CODES = AMBIGUOUS_CODES;
2915
3153
  exports.ASTM_ACK = ACK;
@@ -2926,8 +3164,10 @@ exports.AstmStrictError = AstmStrictError;
2926
3164
  exports.CANONICAL_DELIMITERS = CANONICAL_DELIMITERS;
2927
3165
  exports.FATAL_CODES = FATAL_CODES;
2928
3166
  exports.FRAME_WARNING_CODES = FRAME_WARNING_CODES;
3167
+ exports.LIVD_CATALOG_IDENTITY_UNDECLARED = LIVD_CATALOG_IDENTITY_UNDECLARED;
2929
3168
  exports.LIVD_WARNING_CODES = LIVD_WARNING_CODES;
2930
3169
  exports.LTP_WARNING_CODES = LTP_WARNING_CODES;
3170
+ exports.RESULT_STATUS_VOCABULARY = RESULT_STATUS_VOCABULARY;
2931
3171
  exports.SAFETY_CRITICAL_CODES = SAFETY_CRITICAL_CODES;
2932
3172
  exports.TOLERABLE_CODES = TOLERABLE_CODES;
2933
3173
  exports.VERSION = VERSION;
@@ -2958,6 +3198,7 @@ exports.defineLivdCatalog = defineLivdCatalog;
2958
3198
  exports.delimiterRoleCollision = delimiterRoleCollision;
2959
3199
  exports.delimiterSwallowedByEscape = delimiterSwallowedByEscape;
2960
3200
  exports.delimitersRedeclared = delimitersRedeclared;
3201
+ exports.describeVocabulary = describeVocabulary;
2961
3202
  exports.detectFraming = detectFraming;
2962
3203
  exports.encodeComponent = encodeComponent;
2963
3204
  exports.fieldScalar = fieldScalar;
@@ -2975,6 +3216,7 @@ exports.isNonStandard = isNonStandard;
2975
3216
  exports.isSafetyCriticalCode = isSafetyCriticalCode;
2976
3217
  exports.listAstmProfiles = listAstmProfiles;
2977
3218
  exports.livdAmbiguousMapping = livdAmbiguousMapping;
3219
+ exports.livdCatalogMissingLoincVersion = livdCatalogMissingLoincVersion;
2978
3220
  exports.livdUnmappedCode = livdUnmappedCode;
2979
3221
  exports.lookupLivdForRecord = lookupLivdForRecord;
2980
3222
  exports.ltpAmbiguousTransport = ltpAmbiguousTransport;
@@ -3008,6 +3250,9 @@ exports.serializeFramedAstm = serializeFramedAstm;
3008
3250
  exports.setDefaultAstmProfile = setDefaultAstmProfile;
3009
3251
  exports.splitEscapeAware = splitEscapeAware;
3010
3252
  exports.toChecksumHex = toChecksumHex;
3253
+ exports.toDate = toDate;
3254
+ exports.toISO = toISO;
3255
+ exports.toObject = toObject;
3011
3256
  exports.tokenizeHeader = tokenizeHeader;
3012
3257
  exports.tokenizeRecord = tokenizeRecord;
3013
3258
  exports.undefinedAbnormalFlag = undefinedAbnormalFlag;