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