@cosyte/terminology 0.0.5 → 0.0.7

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
@@ -197,6 +197,9 @@ var TerminologyError = class _TerminologyError extends Error {
197
197
  /**
198
198
  * @param code - The stable {@link FatalCode} describing the fault.
199
199
  * @param message - A **value-free** structural description (path + fault; never an input value).
200
+ * Every piece of it must be engine-owned — a literal, a frozen-table entry, or a locus this
201
+ * package built. This string reaches `err.stack`, so interpolating a caller-supplied or
202
+ * document-derived value into it is how a diagnostic becomes a leak.
200
203
  */
201
204
  constructor(code, message) {
202
205
  super(message);
@@ -650,13 +653,16 @@ function parseCsvRows(content) {
650
653
  function parseCsv(content) {
651
654
  return parseCsvRows(content).rows;
652
655
  }
653
- function requireHeader(header, name) {
656
+ var MISSING_COLUMN_MESSAGE = Object.freeze({
657
+ code: "CSV source: the configured 'code' column is not present in the header row",
658
+ display: "CSV source: the configured 'display' column is not present in the header row",
659
+ status: "CSV source: the configured 'status' column is not present in the header row",
660
+ property: "CSV source: a configured property column is not present in the header row"
661
+ });
662
+ function requireHeader(header, name, role) {
654
663
  const idx = header.indexOf(name);
655
664
  if (idx < 0) {
656
- throw new TerminologyError(
657
- FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
658
- `CSV source: required column '${name}' is not present in the header row`
659
- );
665
+ throw new TerminologyError(FATAL_CODES.TERM_CODESYSTEM_MALFORMED, MISSING_COLUMN_MESSAGE[role]);
660
666
  }
661
667
  return idx;
662
668
  }
@@ -680,12 +686,12 @@ function parseCsvSource(source) {
680
686
  })
681
687
  );
682
688
  }
683
- const codeIdx = requireHeader(header, source.columns.code);
684
- const displayIdx = requireHeader(header, source.columns.display);
685
- const statusIdx = source.columns.status !== void 0 ? requireHeader(header, source.columns.status) : void 0;
689
+ const codeIdx = requireHeader(header, source.columns.code, "code");
690
+ const displayIdx = requireHeader(header, source.columns.display, "display");
691
+ const statusIdx = source.columns.status !== void 0 ? requireHeader(header, source.columns.status, "status") : void 0;
686
692
  const propIdx = (source.columns.properties ?? []).map((n) => ({
687
693
  name: n,
688
- index: requireHeader(header, n)
694
+ index: requireHeader(header, n, "property")
689
695
  }));
690
696
  const needCols = Math.max(codeIdx, displayIdx, statusIdx ?? 0, ...propIdx.map((p) => p.index)) + 1;
691
697
  const concepts = /* @__PURE__ */ new Map();
@@ -1289,48 +1295,54 @@ function makeCoding(system, code, display, version) {
1289
1295
  if (display !== void 0) init.display = display;
1290
1296
  return coding(init);
1291
1297
  }
1292
- function cannotExpand(detail, system) {
1298
+ function cannotExpand(detail, path) {
1293
1299
  const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
1294
- if (system !== void 0) d.system = system;
1300
+ if (path !== void 0) d.path = path;
1295
1301
  return Object.freeze(d);
1296
1302
  }
1297
- function truncated(detail, system) {
1303
+ function truncated(detail, path) {
1298
1304
  const d = { code: "TERM_VALUESET_EXPANSION_TRUNCATED", detail };
1299
- if (system !== void 0) d.system = system;
1305
+ d.path = path;
1300
1306
  return Object.freeze(d);
1301
1307
  }
1308
+ function underPath(d, prefix) {
1309
+ const out = { code: d.code, detail: d.detail };
1310
+ out.path = d.path === void 0 ? prefix : `${prefix}/${d.path}`;
1311
+ return Object.freeze(out);
1312
+ }
1302
1313
  function intersectInto(into, other) {
1303
1314
  for (const k of into.keys()) if (!other.has(k)) into.delete(k);
1304
1315
  return into;
1305
1316
  }
1306
- function expandReferencedValueSets(urls, ctx, visited) {
1317
+ function expandReferencedValueSets(urls, ctx, visited, path) {
1307
1318
  const diagnostics = [];
1308
1319
  let complete = true;
1309
1320
  let acc = null;
1310
- for (const url of urls) {
1321
+ for (const [i, url] of urls.entries()) {
1322
+ const refPath = `${path}.valueSet[${String(i)}]`;
1311
1323
  if (visited.has(url)) {
1312
- diagnostics.push(cannotExpand("cyclic value set reference", url));
1324
+ diagnostics.push(cannotExpand("cyclic value set reference", refPath));
1313
1325
  complete = false;
1314
1326
  acc = /* @__PURE__ */ new Map();
1315
1327
  continue;
1316
1328
  }
1317
1329
  const vs = ctx.valueSets?.get(url);
1318
1330
  if (vs === void 0) {
1319
- diagnostics.push(cannotExpand("referenced value set not supplied", url));
1331
+ diagnostics.push(cannotExpand("referenced value set not supplied", refPath));
1320
1332
  complete = false;
1321
1333
  acc = /* @__PURE__ */ new Map();
1322
1334
  continue;
1323
1335
  }
1324
1336
  const exp = expandInternal(vs, ctx, /* @__PURE__ */ new Set([...visited, url]));
1325
1337
  if (!exp.complete) complete = false;
1326
- for (const d of exp.diagnostics) diagnostics.push(d);
1338
+ for (const d of exp.diagnostics) diagnostics.push(underPath(d, refPath));
1327
1339
  const members = /* @__PURE__ */ new Map();
1328
1340
  for (const c of exp.contains) members.set(codingKey(c.system, c.code), c);
1329
1341
  acc = acc === null ? members : intersectInto(acc, members);
1330
1342
  }
1331
1343
  return { members: acc ?? /* @__PURE__ */ new Map(), complete, diagnostics };
1332
1344
  }
1333
- function expandComponent(component, ctx, visited) {
1345
+ function expandComponent(component, ctx, visited, path) {
1334
1346
  const diagnostics = [];
1335
1347
  let complete = true;
1336
1348
  const { system, version, concept, filter, valueSet: valueSet2 } = component;
@@ -1345,18 +1357,18 @@ function expandComponent(component, ctx, visited) {
1345
1357
  }
1346
1358
  } else if (hasFilter || system !== void 0 && !hasVs) {
1347
1359
  if (system === void 0) {
1348
- diagnostics.push(cannotExpand("intensional filter without a code system 'system'"));
1360
+ diagnostics.push(cannotExpand("intensional filter without a code system 'system'", path));
1349
1361
  return { members: /* @__PURE__ */ new Map(), complete: false, diagnostics };
1350
1362
  }
1351
1363
  const cs = ctx.codeSystems?.get(system);
1352
1364
  if (cs === void 0) {
1353
- diagnostics.push(cannotExpand("code system not supplied for intensional include", system));
1365
+ diagnostics.push(cannotExpand("code system not supplied for intensional include", path));
1354
1366
  return { members: /* @__PURE__ */ new Map(), complete: false, diagnostics };
1355
1367
  }
1356
1368
  if (hasFilter) {
1357
1369
  const unsupported = unsupportedOps(filter);
1358
1370
  if (unsupported.length > 0) {
1359
- diagnostics.push(cannotExpand("unsupported filter operator", system));
1371
+ diagnostics.push(cannotExpand("unsupported filter operator", path));
1360
1372
  return { members: /* @__PURE__ */ new Map(), complete: false, diagnostics };
1361
1373
  }
1362
1374
  const sub = buildSubsumption(cs);
@@ -1380,7 +1392,7 @@ function expandComponent(component, ctx, visited) {
1380
1392
  }
1381
1393
  }
1382
1394
  if (hasVs) {
1383
- const refs = expandReferencedValueSets(valueSet2, ctx, visited);
1395
+ const refs = expandReferencedValueSets(valueSet2, ctx, visited, path);
1384
1396
  for (const d of refs.diagnostics) diagnostics.push(d);
1385
1397
  if (!refs.complete) complete = false;
1386
1398
  base = base === null ? refs.members : intersectInto(base, refs.members);
@@ -1405,7 +1417,7 @@ function expandInternal(vs, ctx, visited) {
1405
1417
  if (vs.expansion.truncated) {
1406
1418
  complete = false;
1407
1419
  diagnostics.push(
1408
- truncated("pre-computed expansion is incomplete (truncated or too-costly)", vs.url)
1420
+ truncated("pre-computed expansion is incomplete (truncated or too-costly)", "expansion")
1409
1421
  );
1410
1422
  }
1411
1423
  const out = {
@@ -1420,14 +1432,14 @@ function expandInternal(vs, ctx, visited) {
1420
1432
  const included = /* @__PURE__ */ new Map();
1421
1433
  const diagnostics = [];
1422
1434
  let complete = true;
1423
- for (const inc of vs.compose.include) {
1424
- const r = expandComponent(inc, ctx, visited);
1435
+ for (const [i, inc] of vs.compose.include.entries()) {
1436
+ const r = expandComponent(inc, ctx, visited, `compose.include[${String(i)}]`);
1425
1437
  if (!r.complete) complete = false;
1426
1438
  for (const d of r.diagnostics) diagnostics.push(d);
1427
1439
  for (const [k, c] of r.members) if (!included.has(k)) included.set(k, c);
1428
1440
  }
1429
- for (const exc of vs.compose.exclude) {
1430
- const r = expandComponent(exc, ctx, visited);
1441
+ for (const [i, exc] of vs.compose.exclude.entries()) {
1442
+ const r = expandComponent(exc, ctx, visited, `compose.exclude[${String(i)}]`);
1431
1443
  for (const d of r.diagnostics) diagnostics.push(d);
1432
1444
  for (const k of r.members.keys()) included.delete(k);
1433
1445
  if (!r.complete) {
@@ -1454,12 +1466,17 @@ function expand(vs, ctx = {}) {
1454
1466
  }
1455
1467
 
1456
1468
  // src/valueset/validate.ts
1457
- function cannotExpand2(detail, system) {
1469
+ function cannotExpand2(detail, path) {
1458
1470
  const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
1459
- if (system !== void 0) d.system = system;
1471
+ if (path !== void 0) d.path = path;
1460
1472
  return Object.freeze(d);
1461
1473
  }
1462
- function matchComponent(target, component, ctx, visited) {
1474
+ function underPath2(d, prefix) {
1475
+ const out = { code: d.code, detail: d.detail };
1476
+ out.path = d.path === void 0 ? prefix : `${prefix}/${d.path}`;
1477
+ return Object.freeze(out);
1478
+ }
1479
+ function matchComponent(target, component, ctx, visited, path) {
1463
1480
  const diagnostics = [];
1464
1481
  const { system, concept, filter, valueSet: valueSet2 } = component;
1465
1482
  const hasConcept = concept !== void 0;
@@ -1473,18 +1490,18 @@ function matchComponent(target, component, ctx, visited) {
1473
1490
  baseMatched = concept.some((c) => c.code === target.code);
1474
1491
  } else if (hasFilter || system !== void 0 && !hasVs) {
1475
1492
  if (system === void 0) {
1476
- diagnostics.push(cannotExpand2("intensional filter without a code system 'system'"));
1493
+ diagnostics.push(cannotExpand2("intensional filter without a code system 'system'", path));
1477
1494
  baseMatched = false;
1478
1495
  baseComplete = false;
1479
1496
  } else {
1480
1497
  const cs = ctx.codeSystems?.get(system);
1481
1498
  if (cs === void 0) {
1482
- diagnostics.push(cannotExpand2("code system not supplied for intensional include", system));
1499
+ diagnostics.push(cannotExpand2("code system not supplied for intensional include", path));
1483
1500
  baseMatched = false;
1484
1501
  baseComplete = false;
1485
1502
  } else if (hasFilter) {
1486
1503
  if (unsupportedOps(filter).length > 0) {
1487
- diagnostics.push(cannotExpand2("unsupported filter operator", system));
1504
+ diagnostics.push(cannotExpand2("unsupported filter operator", path));
1488
1505
  baseMatched = false;
1489
1506
  baseComplete = false;
1490
1507
  } else {
@@ -1499,21 +1516,22 @@ function matchComponent(target, component, ctx, visited) {
1499
1516
  let refDefiniteFalse = false;
1500
1517
  let refUndetermined = false;
1501
1518
  if (hasVs) {
1502
- for (const url of valueSet2) {
1519
+ for (const [i, url] of valueSet2.entries()) {
1520
+ const refPath = `${path}.valueSet[${String(i)}]`;
1503
1521
  if (visited.has(url)) {
1504
- diagnostics.push(cannotExpand2("cyclic value set reference", url));
1522
+ diagnostics.push(cannotExpand2("cyclic value set reference", refPath));
1505
1523
  refUndetermined = true;
1506
1524
  continue;
1507
1525
  }
1508
1526
  const vs = ctx.valueSets?.get(url);
1509
1527
  if (vs === void 0) {
1510
- diagnostics.push(cannotExpand2("referenced value set not supplied", url));
1528
+ diagnostics.push(cannotExpand2("referenced value set not supplied", refPath));
1511
1529
  refUndetermined = true;
1512
1530
  continue;
1513
1531
  }
1514
1532
  const m = validateInternal(target, vs, ctx, /* @__PURE__ */ new Set([...visited, url]));
1515
1533
  if (m.undetermined) {
1516
- for (const d of m.diagnostics) diagnostics.push(d);
1534
+ for (const d of m.diagnostics) diagnostics.push(underPath2(d, refPath));
1517
1535
  refUndetermined = true;
1518
1536
  } else if (!m.result) {
1519
1537
  refDefiniteFalse = true;
@@ -1550,7 +1568,7 @@ function validateInternal(target, vs, ctx, visited) {
1550
1568
  if (found) return decided(true, target);
1551
1569
  if (vs.expansion.truncated) {
1552
1570
  return undetermined(target, [
1553
- cannotExpand2("pre-computed expansion is incomplete (truncated or too-costly)", vs.url)
1571
+ cannotExpand2("pre-computed expansion is incomplete (truncated or too-costly)", "expansion")
1554
1572
  ]);
1555
1573
  }
1556
1574
  return decided(false, target);
@@ -1559,16 +1577,16 @@ function validateInternal(target, vs, ctx, visited) {
1559
1577
  const diagnostics = [];
1560
1578
  let includedDefinite = false;
1561
1579
  let anyIncludeUndetermined = false;
1562
- for (const inc of vs.compose.include) {
1563
- const m = matchComponent(target, inc, ctx, visited);
1580
+ for (const [i, inc] of vs.compose.include.entries()) {
1581
+ const m = matchComponent(target, inc, ctx, visited, `compose.include[${String(i)}]`);
1564
1582
  for (const d of m.diagnostics) diagnostics.push(d);
1565
1583
  if (m.complete && m.matched) includedDefinite = true;
1566
1584
  if (!m.complete) anyIncludeUndetermined = true;
1567
1585
  }
1568
1586
  let excludedDefinite = false;
1569
1587
  let anyExcludeUndetermined = false;
1570
- for (const exc of vs.compose.exclude) {
1571
- const m = matchComponent(target, exc, ctx, visited);
1588
+ for (const [i, exc] of vs.compose.exclude.entries()) {
1589
+ const m = matchComponent(target, exc, ctx, visited, `compose.exclude[${String(i)}]`);
1572
1590
  for (const d of m.diagnostics) diagnostics.push(d);
1573
1591
  if (m.complete && m.matched) excludedDefinite = true;
1574
1592
  if (!m.complete) anyExcludeUndetermined = true;
@@ -3713,7 +3731,29 @@ function parseEssence(xml) {
3713
3731
  atoms.sort((a, b) => b.code.length - a.code.length);
3714
3732
  const atomByCode = new Map(atoms.map((a) => [a.code, a]));
3715
3733
  const prefixByCode = new Map(prefixes.map((p) => [p.code, p]));
3716
- return { prefixes, atoms, atomByCode, prefixByCode };
3734
+ return freezeEssence({ prefixes, atoms, atomByCode, prefixByCode });
3735
+ }
3736
+ function refuseTableWrite() {
3737
+ throw new TypeError("the UCUM unit table is immutable");
3738
+ }
3739
+ function sealLookup(map) {
3740
+ return Object.defineProperties(map, {
3741
+ set: { value: refuseTableWrite },
3742
+ delete: { value: refuseTableWrite },
3743
+ clear: { value: refuseTableWrite }
3744
+ });
3745
+ }
3746
+ function freezeEssence(essence) {
3747
+ for (const atom of essence.atoms) {
3748
+ if (atom.value) Object.freeze(atom.value);
3749
+ Object.freeze(atom);
3750
+ }
3751
+ for (const prefix of essence.prefixes) Object.freeze(prefix);
3752
+ Object.freeze(essence.atoms);
3753
+ Object.freeze(essence.prefixes);
3754
+ sealLookup(essence.atomByCode);
3755
+ sealLookup(essence.prefixByCode);
3756
+ return Object.freeze(essence);
3717
3757
  }
3718
3758
  var cached;
3719
3759
  function loadUcumEssence() {
@@ -3891,32 +3931,37 @@ function power(r, p) {
3891
3931
  }
3892
3932
  return { kind: "linear", factor: Math.pow(r.factor, p), dims };
3893
3933
  }
3894
- var DIMENSIONLESS = { kind: "linear", factor: 1, dims: {} };
3895
- var atomMemo = /* @__PURE__ */ new Map();
3896
- var inProgress = /* @__PURE__ */ new Set();
3934
+ var DIMENSIONLESS = Object.freeze({
3935
+ kind: "linear",
3936
+ factor: 1,
3937
+ dims: Object.freeze({})
3938
+ });
3939
+ var atomMemo = /* @__PURE__ */ new WeakMap();
3940
+ var inProgress = /* @__PURE__ */ new WeakSet();
3897
3941
  function reduceAtomLinear(atom) {
3898
3942
  if (atom.base) return { kind: "linear", factor: 1, dims: { [atom.dim ?? atom.code]: 1 } };
3899
3943
  if (atom.arbitrary) return { kind: "linear", factor: 1, dims: { [`arb:${atom.code}`]: 1 } };
3900
- const cached2 = atomMemo.get(atom.code);
3944
+ const cached2 = atomMemo.get(atom);
3901
3945
  if (cached2) return cached2;
3902
- if (inProgress.has(atom.code)) {
3903
- throw new Error(`cyclic UCUM atom definition for '${atom.code}'`);
3946
+ if (inProgress.has(atom)) {
3947
+ throw new Error("cyclic UCUM atom definition in the unit table");
3904
3948
  }
3905
3949
  if (!atom.value) {
3906
- atomMemo.set(atom.code, DIMENSIONLESS);
3907
- return DIMENSIONLESS;
3950
+ throw new Error("UCUM atom has no linear definition");
3908
3951
  }
3909
- inProgress.add(atom.code);
3910
- const parsed = parseUcum(atom.value.unit);
3911
- if (!parsed.ok) {
3912
- inProgress.delete(atom.code);
3913
- throw new Error(`unparseable UCUM essence definition for '${atom.code}'`);
3952
+ inProgress.add(atom);
3953
+ try {
3954
+ const parsed = parseUcum(atom.value.unit);
3955
+ if (!parsed.ok) {
3956
+ throw new Error("unparseable UCUM essence definition in the unit table");
3957
+ }
3958
+ const reduced = linearReduceTerm(parsed.node);
3959
+ const result = multiply({ kind: "linear", factor: atom.value.factor, dims: {} }, reduced);
3960
+ atomMemo.set(atom, result);
3961
+ return result;
3962
+ } finally {
3963
+ inProgress.delete(atom);
3914
3964
  }
3915
- const reduced = linearReduceTerm(parsed.node);
3916
- const result = multiply({ factor: atom.value.factor, dims: {} }, reduced);
3917
- inProgress.delete(atom.code);
3918
- atomMemo.set(atom.code, result);
3919
- return result;
3920
3965
  }
3921
3966
  function reduceSimpleLinear(node) {
3922
3967
  const base = reduceAtomLinear(node.atom);
@@ -3936,7 +3981,7 @@ function linearReduceComponent(comp) {
3936
3981
  }
3937
3982
  }
3938
3983
  function linearReduceTerm(node) {
3939
- let acc = DIMENSIONLESS;
3984
+ let acc = { kind: "linear", factor: 1, dims: {} };
3940
3985
  for (const { sign, node: comp } of node.factors) {
3941
3986
  acc = multiply(acc, power(linearReduceComponent(comp), sign));
3942
3987
  }
@@ -4118,10 +4163,15 @@ function applyGem(map, source) {
4118
4163
  if (combinations !== void 0) matched.combinations = combinations;
4119
4164
  return Object.freeze(matched);
4120
4165
  }
4166
+ var NOT_INVERTIBLE_MESSAGE = /* @__PURE__ */ new Map([
4167
+ ["9-to-10", "GEM map (direction 9-to-10) cannot be inverted \u2014 load the 10-to-9 GEM file instead"],
4168
+ ["10-to-9", "GEM map (direction 10-to-9) cannot be inverted \u2014 load the 9-to-10 GEM file instead"]
4169
+ ]);
4170
+ var NOT_INVERTIBLE_UNKNOWN_DIRECTION = "GEM map cannot be inverted \u2014 load the reverse GEM file instead";
4121
4171
  function invertGem(map) {
4122
4172
  throw new TerminologyError(
4123
4173
  FATAL_CODES.TERM_MAP_NOT_INVERTIBLE,
4124
- `GEM map (direction ${map.direction}) cannot be inverted \u2014 load the reverse GEM file instead`
4174
+ NOT_INVERTIBLE_MESSAGE.get(map.direction) ?? NOT_INVERTIBLE_UNKNOWN_DIRECTION
4125
4175
  );
4126
4176
  }
4127
4177
 
@@ -4701,7 +4751,7 @@ function approximateMatch(graph, query, options = {}) {
4701
4751
  }
4702
4752
 
4703
4753
  // src/index.ts
4704
- var VERSION = "0.0.5";
4754
+ var VERSION = "0.0.7";
4705
4755
 
4706
4756
  export { COMPLEX_MAP_SOURCE_SYSTEM, DIAGNOSTIC_CODES, FATAL_CODES, HL7_V2_OID_ROOT, HL7_V2_URL_PREFIX, ICD10CM_ORDER_FILE_FIELDS, ICD10CM_SYSTEM, ICD9CM_SYSTEM, MAP_CATEGORIES, NO_MAP_CATEGORIES, RELA, RELA_INVERSE, RXNORM_SYSTEM, SNOMEDCT_SYSTEM, SYSTEM_IDENTITIES, TERM_TYPES, TerminologyError, VERSION, applyComplexMap, applyGem, approximateMatch, asTermType, brandsFor, buildSubsumption, codeableConcept, coding, consistsOf, defaultStatusMapper, doseFormsOf, expand, genericFor, getArray, getBoolean, getConcept, getNumber, getString, ingredientsOf, invertGem, isA, isJsonObject, isSynonymTermType, isUnknownSystem, loadCodeSystem, loadComplexMap, loadConceptMap, loadGems, loadRxNormGraph, loadUcumEssence, loadValueSet, lookup, makeStatus, matchesFilter, parseCsv, parseCsvSource, parseFhirCodeSystem, parseFixedWidth, parseRrf, parseRrfLine, parseUcum, reduce, relatedByRela, resolveNdc, resolveSystem, sliceField, translate, ucumEqual, unsupportedOps, validateCode, validateCodeInValueSet, validateUcum };
4707
4757
  //# sourceMappingURL=index.mjs.map