@cosyte/terminology 0.1.5 → 0.1.6
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/CHANGELOG.md +10 -0
- package/README.md +3 -0
- package/dist/index.cjs +58 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +54 -36
- package/dist/index.d.ts +54 -36
- package/dist/index.mjs +58 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.6
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 43eea2f: An internal tidy with no change to any result, warning, or diagnostic. The value set diagnostic factories now take the path every caller already gives them, a few expressions became statements so a coverage note can name the one arm no input reaches, and those arms now carry that note.
|
|
8
|
+
- 43eea2f: A CSV source whose `columns.properties` list has a hole now fails with the typed `TERM_CODESYSTEM_MALFORMED` fatal for a missing property column, the same as an explicit `undefined` entry, instead of a `TypeError` from inside the loader.
|
|
9
|
+
- 0cb5b88: Doc comments, which your editor shows on hover and the generated API reference prints, now match the code. `resolveSystem` lists the forms it accepts in the order it tries them, and no longer says it accepts a `v2-0203` mnemonic on its own (it does not; pass the table's OID or URI). `SYSTEM_IDENTITIES` and `resolveSystem` no longer say every accepted mnemonic comes from HL7 Table 0396: aliases such as `LOINC` and `CPT` are accepted too but are not Table 0396 codes. `NdcStatus` says that `loadRxNormGraph` reads no NDC history, so every NDC it resolves is `active`. `RxNormGraph.concepts` says a concept is typed by its first defining atom. The complex-map outcome types and `MAP_CATEGORIES.AMBIGUOUS` no longer say an ambiguous or context-dependent category stops a group on its own: the group's rules decide. Links that printed as a source path, such as `../crosswalk/gems.invertGem`, now link the symbol. No code changes.
|
|
10
|
+
- e16570e: An RRF or fixed-width source whose property list has an entry that is not an object (a hole, `undefined`, or `null`) now fails with the typed `TERM_CODESYSTEM_MALFORMED` fatal, as a CSV source with a hole in its property columns does, instead of a `TypeError` from inside the loader.
|
|
11
|
+
- 43eea2f: `validateCodeInValueSet` no longer admits a code through a `compose` component that selects nothing. A component with no `system`, `concept`, `filter`, or referenced value set (the loader accepts one with an empty `valueSet` list) matched every code, so an include admitted any code and an exclude removed every code, while `expand` gave the same component no members. Membership now agrees with `expand`: such a component matches no code, and a `valueSet` list that yields no reference is an intersection with nothing.
|
|
12
|
+
|
|
3
13
|
## 0.1.5
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -379,6 +379,9 @@ Questions, bug reports, and an answer the engine got wrong go in
|
|
|
379
379
|
system, map, or value set that reproduces it. Use synthetic data only: a code in patient context can
|
|
380
380
|
be PHI. [SUPPORT.md](https://github.com/cosyte/.github/blob/main/SUPPORT.md) says where to ask what.
|
|
381
381
|
|
|
382
|
+
Need a terminology integration built, or hosted and run for you? See our
|
|
383
|
+
[integration services](https://cosyte.com/integration-services).
|
|
384
|
+
|
|
382
385
|
## Security
|
|
383
386
|
|
|
384
387
|
Report a vulnerability privately, never in a public issue, and with no real patient data in it:
|
package/dist/index.cjs
CHANGED
|
@@ -212,7 +212,7 @@ var FATAL_CODES = {
|
|
|
212
212
|
/**
|
|
213
213
|
* An **inversion** of a directional crosswalk was requested: a forbidden operation. The GEMs and
|
|
214
214
|
* the SNOMED→ICD-10-CM map are authoritative in **one direction only**; a forward map is not the
|
|
215
|
-
* inverse of the backward map. Thrown by {@link
|
|
215
|
+
* inverse of the backward map. Thrown by {@link invertGem} so the
|
|
216
216
|
* never-invert refusal is a first-class, discoverable contract rather than a silent absence.
|
|
217
217
|
*/
|
|
218
218
|
TERM_MAP_NOT_INVERTIBLE: "TERM_MAP_NOT_INVERTIBLE"
|
|
@@ -764,10 +764,10 @@ function parseCsvSource(source) {
|
|
|
764
764
|
const codeIdx = requireHeader(header, source.columns.code, "code");
|
|
765
765
|
const displayIdx = requireHeader(header, source.columns.display, "display");
|
|
766
766
|
const statusIdx = source.columns.status !== void 0 ? requireHeader(header, source.columns.status, "status") : void 0;
|
|
767
|
-
const propIdx =
|
|
768
|
-
|
|
769
|
-
index: requireHeader(header, n, "property")
|
|
770
|
-
}
|
|
767
|
+
const propIdx = [];
|
|
768
|
+
for (const n of source.columns.properties ?? []) {
|
|
769
|
+
propIdx.push({ name: n, index: requireHeader(header, n, "property") });
|
|
770
|
+
}
|
|
771
771
|
const needCols = Math.max(codeIdx, displayIdx, statusIdx ?? 0, ...propIdx.map((p) => p.index)) + 1;
|
|
772
772
|
const concepts = /* @__PURE__ */ new Map();
|
|
773
773
|
for (let r = 1; r < rows.length; r++) {
|
|
@@ -856,9 +856,10 @@ function collectConcepts(rawConcepts, into, warnings, parentCode) {
|
|
|
856
856
|
if (code !== void 0 && code !== "" && !into.has(code)) {
|
|
857
857
|
const props = [];
|
|
858
858
|
for (const p of getArray(raw, "property") ?? []) {
|
|
859
|
+
if (!isJsonObject(p)) continue;
|
|
859
860
|
const pcode = getString(p, "code");
|
|
860
861
|
if (pcode === void 0) continue;
|
|
861
|
-
const value =
|
|
862
|
+
const value = readPropertyValue(p);
|
|
862
863
|
if (value !== void 0) props.push(Object.freeze({ code: pcode, value }));
|
|
863
864
|
}
|
|
864
865
|
if (parentCode !== void 0 && !props.some((p) => p.code === "parent" && p.value === parentCode)) {
|
|
@@ -921,9 +922,21 @@ var ICD10CM_ORDER_FILE_FIELDS = Object.freeze({
|
|
|
921
922
|
Object.freeze({ name: "shortDescription", slice: Object.freeze({ start: 16, end: 76 }) })
|
|
922
923
|
])
|
|
923
924
|
});
|
|
925
|
+
function requirePropertyFields(fields) {
|
|
926
|
+
for (const p of fields.properties ?? []) {
|
|
927
|
+
const entry = p;
|
|
928
|
+
if (typeof entry !== "object" || entry === null) {
|
|
929
|
+
throw new TerminologyError(
|
|
930
|
+
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
931
|
+
"fixed-width source: a configured property field is not an object"
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
}
|
|
924
936
|
function parseFixedWidth(source) {
|
|
925
937
|
const statusMap = source.statusMap ?? defaultStatusMapper;
|
|
926
938
|
const fields = source.fields;
|
|
939
|
+
requirePropertyFields(fields);
|
|
927
940
|
const concepts = /* @__PURE__ */ new Map();
|
|
928
941
|
const warnings = [];
|
|
929
942
|
const lines = source.content.split(/\r?\n/);
|
|
@@ -985,7 +998,16 @@ function parseRrfLine(line) {
|
|
|
985
998
|
function maxColumn(source) {
|
|
986
999
|
const { code, display, status, properties } = source.columns;
|
|
987
1000
|
let max = Math.max(code, display, status ?? 0);
|
|
988
|
-
for (const p of properties ?? [])
|
|
1001
|
+
for (const p of properties ?? []) {
|
|
1002
|
+
const entry = p;
|
|
1003
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1004
|
+
throw new TerminologyError(
|
|
1005
|
+
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
1006
|
+
"RRF source: a configured property column is not an object"
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
1009
|
+
max = Math.max(max, p.column);
|
|
1010
|
+
}
|
|
989
1011
|
return max;
|
|
990
1012
|
}
|
|
991
1013
|
function cell(cells, index) {
|
|
@@ -1392,8 +1414,7 @@ function makeCoding(system, code, display, version) {
|
|
|
1392
1414
|
return coding(init);
|
|
1393
1415
|
}
|
|
1394
1416
|
function cannotExpand(detail, path) {
|
|
1395
|
-
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
|
|
1396
|
-
if (path !== void 0) d.path = path;
|
|
1417
|
+
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail, path };
|
|
1397
1418
|
return Object.freeze(d);
|
|
1398
1419
|
}
|
|
1399
1420
|
function truncated(detail, path) {
|
|
@@ -1407,9 +1428,9 @@ function truncated(detail, path) {
|
|
|
1407
1428
|
function enumeratedUndefined(detail, path) {
|
|
1408
1429
|
const d = {
|
|
1409
1430
|
code: "TERM_VALUESET_ENUMERATED_CODE_UNDEFINED",
|
|
1410
|
-
detail
|
|
1431
|
+
detail,
|
|
1432
|
+
path
|
|
1411
1433
|
};
|
|
1412
|
-
if (path !== void 0) d.path = path;
|
|
1413
1434
|
return Object.freeze(d);
|
|
1414
1435
|
}
|
|
1415
1436
|
function expansionIncompleteDetail(unclosed) {
|
|
@@ -1443,7 +1464,8 @@ function screenActive(members, component, ctx) {
|
|
|
1443
1464
|
}
|
|
1444
1465
|
function underPath(d, prefix) {
|
|
1445
1466
|
const out = { code: d.code, detail: d.detail };
|
|
1446
|
-
|
|
1467
|
+
if (d.path === void 0) out.path = prefix;
|
|
1468
|
+
else out.path = `${prefix}/${d.path}`;
|
|
1447
1469
|
return Object.freeze(out);
|
|
1448
1470
|
}
|
|
1449
1471
|
function intersectInto(into, other) {
|
|
@@ -1639,8 +1661,7 @@ function expand(vs, ctx = {}) {
|
|
|
1639
1661
|
|
|
1640
1662
|
// src/valueset/validate.ts
|
|
1641
1663
|
function cannotExpand2(detail, path) {
|
|
1642
|
-
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
|
|
1643
|
-
if (path !== void 0) d.path = path;
|
|
1664
|
+
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail, path };
|
|
1644
1665
|
return Object.freeze(d);
|
|
1645
1666
|
}
|
|
1646
1667
|
function expansionIncompleteDetail2(unclosed) {
|
|
@@ -1658,7 +1679,8 @@ function usableRelease2(component, system, ctx) {
|
|
|
1658
1679
|
}
|
|
1659
1680
|
function underPath2(d, prefix) {
|
|
1660
1681
|
const out = { code: d.code, detail: d.detail };
|
|
1661
|
-
|
|
1682
|
+
if (d.path === void 0) out.path = prefix;
|
|
1683
|
+
else out.path = `${prefix}/${d.path}`;
|
|
1662
1684
|
return Object.freeze(out);
|
|
1663
1685
|
}
|
|
1664
1686
|
function matchComponent(target, component, ctx, visited, path, activeOnly = false) {
|
|
@@ -1718,10 +1740,13 @@ function matchComponent(target, component, ctx, visited, path, activeOnly = fals
|
|
|
1718
1740
|
}
|
|
1719
1741
|
}
|
|
1720
1742
|
if (system !== void 0 && target.system === void 0 && baseComplete) baseMatched = false;
|
|
1743
|
+
if (baseMatched === null && !hasVs) return { matched: false, complete: true, diagnostics };
|
|
1721
1744
|
let refDefiniteFalse = false;
|
|
1722
1745
|
let refUndetermined = false;
|
|
1723
1746
|
if (hasVs) {
|
|
1747
|
+
let refsRead = 0;
|
|
1724
1748
|
for (const [i, url] of valueSet2.entries()) {
|
|
1749
|
+
refsRead++;
|
|
1725
1750
|
const refPath = `${path}.valueSet[${String(i)}]`;
|
|
1726
1751
|
if (visited.has(url)) {
|
|
1727
1752
|
diagnostics.push(cannotExpand2("cyclic value set reference", refPath));
|
|
@@ -1742,6 +1767,7 @@ function matchComponent(target, component, ctx, visited, path, activeOnly = fals
|
|
|
1742
1767
|
refDefiniteFalse = true;
|
|
1743
1768
|
}
|
|
1744
1769
|
}
|
|
1770
|
+
if (refsRead === 0) refDefiniteFalse = true;
|
|
1745
1771
|
}
|
|
1746
1772
|
const baseDefiniteFalse = baseMatched === false && baseComplete;
|
|
1747
1773
|
const baseDefiniteTrue = baseMatched === null || baseMatched === true && baseComplete;
|
|
@@ -3993,6 +4019,9 @@ function atBoundary(s, pos) {
|
|
|
3993
4019
|
const c = s[pos];
|
|
3994
4020
|
return c === "." || c === "/" || c === ")";
|
|
3995
4021
|
}
|
|
4022
|
+
function prefixRank(m) {
|
|
4023
|
+
return m.prefix ? 1 : 0;
|
|
4024
|
+
}
|
|
3996
4025
|
function simpleMatches(essence, s, pos) {
|
|
3997
4026
|
const out = [];
|
|
3998
4027
|
for (const atom of essence.atoms) {
|
|
@@ -4007,7 +4036,7 @@ function simpleMatches(essence, s, pos) {
|
|
|
4007
4036
|
}
|
|
4008
4037
|
}
|
|
4009
4038
|
}
|
|
4010
|
-
out.sort((a, b) => b.len - a.len || (a
|
|
4039
|
+
out.sort((a, b) => b.len - a.len || prefixRank(a) - prefixRank(b));
|
|
4011
4040
|
return out;
|
|
4012
4041
|
}
|
|
4013
4042
|
var MAX_PAREN_DEPTH = 100;
|
|
@@ -4412,7 +4441,7 @@ var MAP_CATEGORIES = {
|
|
|
4412
4441
|
NO_MAP: "447638001",
|
|
4413
4442
|
/** `447639009`: "Map of source concept is context dependent" (needs runtime patient context). */
|
|
4414
4443
|
CONTEXT_DEPENDENT: "447639009",
|
|
4415
|
-
/** `447640006`: "Source concept is ambiguous" (
|
|
4444
|
+
/** `447640006`: "Source concept is ambiguous" (carried through on the result; the rule decides). */
|
|
4416
4445
|
AMBIGUOUS: "447640006"
|
|
4417
4446
|
};
|
|
4418
4447
|
var NO_MAP_CATEGORIES = /* @__PURE__ */ new Set([MAP_CATEGORIES.NO_MAP]);
|
|
@@ -4545,8 +4574,15 @@ function evaluateConjunct(clause, context) {
|
|
|
4545
4574
|
const bound = Number(ageMatch[2]);
|
|
4546
4575
|
if (context.ageYears === void 0 || !Number.isFinite(bound)) return "unknown";
|
|
4547
4576
|
const age = context.ageYears;
|
|
4548
|
-
|
|
4549
|
-
if (
|
|
4577
|
+
let ok;
|
|
4578
|
+
if (op === "<=") ok = age <= bound;
|
|
4579
|
+
else if (op === ">=") ok = age >= bound;
|
|
4580
|
+
else if (op === "<") ok = age < bound;
|
|
4581
|
+
else if (op === ">") ok = age > bound;
|
|
4582
|
+
else {
|
|
4583
|
+
if (op === "=") ok = age === bound;
|
|
4584
|
+
else return "unknown";
|
|
4585
|
+
}
|
|
4550
4586
|
return ok ? "match" : "no-match";
|
|
4551
4587
|
}
|
|
4552
4588
|
return "unknown";
|
|
@@ -4965,7 +5001,8 @@ function jaccard(a, b) {
|
|
|
4965
5001
|
let inter = 0;
|
|
4966
5002
|
for (const t of a) if (b.has(t)) inter++;
|
|
4967
5003
|
const union = a.size + b.size - inter;
|
|
4968
|
-
|
|
5004
|
+
if (union === 0) return 0;
|
|
5005
|
+
return inter / union;
|
|
4969
5006
|
}
|
|
4970
5007
|
function approximateMatch(graph, query, options = {}) {
|
|
4971
5008
|
const minScore = options.minScore ?? 0.34;
|
|
@@ -4982,7 +5019,7 @@ function approximateMatch(graph, query, options = {}) {
|
|
|
4982
5019
|
}
|
|
4983
5020
|
|
|
4984
5021
|
// src/index.ts
|
|
4985
|
-
var VERSION = "0.1.
|
|
5022
|
+
var VERSION = "0.1.6";
|
|
4986
5023
|
|
|
4987
5024
|
exports.COMPLEX_MAP_SOURCE_SYSTEM = COMPLEX_MAP_SOURCE_SYSTEM;
|
|
4988
5025
|
exports.DIAGNOSTIC_CODES = DIAGNOSTIC_CODES;
|