@cosyte/terminology 0.1.5 → 0.1.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/CHANGELOG.md +44 -0
- package/README.md +3 -0
- package/dist/index.cjs +430 -106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -38
- package/dist/index.d.ts +56 -38
- package/dist/index.mjs +430 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +11 -4
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"
|
|
@@ -446,6 +446,10 @@ function loadUnmapped(raw, path) {
|
|
|
446
446
|
assignString(out, raw, "url");
|
|
447
447
|
return Object.freeze(out);
|
|
448
448
|
}
|
|
449
|
+
var loadedGroups = /* @__PURE__ */ new WeakSet();
|
|
450
|
+
function isLoadedGroup(group) {
|
|
451
|
+
return loadedGroups.has(group);
|
|
452
|
+
}
|
|
449
453
|
function loadGroup(raw, path) {
|
|
450
454
|
if (!isJsonObject(raw)) malformed(path, "group is not an object");
|
|
451
455
|
const rawElements = getArray(raw, "element") ?? [];
|
|
@@ -460,7 +464,9 @@ function loadGroup(raw, path) {
|
|
|
460
464
|
if (isJsonObject(raw) && raw["unmapped"] !== void 0) {
|
|
461
465
|
out.unmapped = loadUnmapped(raw["unmapped"], `${path}.unmapped`);
|
|
462
466
|
}
|
|
463
|
-
|
|
467
|
+
const group = Object.freeze(out);
|
|
468
|
+
loadedGroups.add(group);
|
|
469
|
+
return group;
|
|
464
470
|
}
|
|
465
471
|
function loadConceptMap(json) {
|
|
466
472
|
if (!isJsonObject(json)) malformed("", "resource is not a JSON object");
|
|
@@ -504,6 +510,24 @@ function groupApplies(group, system) {
|
|
|
504
510
|
if (system === void 0) return true;
|
|
505
511
|
return group.source === void 0 || group.source === system;
|
|
506
512
|
}
|
|
513
|
+
var elementIndex = /* @__PURE__ */ new WeakMap();
|
|
514
|
+
var NO_ELEMENTS = Object.freeze([]);
|
|
515
|
+
function candidates(group, code) {
|
|
516
|
+
if (typeof code !== "string" || !isLoadedGroup(group)) return group.element;
|
|
517
|
+
let index = elementIndex.get(group);
|
|
518
|
+
if (index === void 0) {
|
|
519
|
+
const built = /* @__PURE__ */ new Map();
|
|
520
|
+
for (const element of group.element) {
|
|
521
|
+
if (element.code === void 0) continue;
|
|
522
|
+
const list = built.get(element.code);
|
|
523
|
+
if (list === void 0) built.set(element.code, [element]);
|
|
524
|
+
else list.push(element);
|
|
525
|
+
}
|
|
526
|
+
index = built;
|
|
527
|
+
elementIndex.set(group, index);
|
|
528
|
+
}
|
|
529
|
+
return index.get(code) ?? NO_ELEMENTS;
|
|
530
|
+
}
|
|
507
531
|
function provenanceOf(map, group, sourceSystem) {
|
|
508
532
|
const out = {};
|
|
509
533
|
if (map.url !== void 0) out.conceptMapUrl = map.url;
|
|
@@ -537,7 +561,7 @@ function translate(sourceCoding, map) {
|
|
|
537
561
|
for (const group of map.group) {
|
|
538
562
|
if (!groupApplies(group, system)) continue;
|
|
539
563
|
if (fallbackGroup === void 0 && group.unmapped !== void 0) fallbackGroup = group;
|
|
540
|
-
for (const element of group.
|
|
564
|
+
for (const element of candidates(group, source.code)) {
|
|
541
565
|
if (element.code !== source.code) continue;
|
|
542
566
|
sourceCodePresent = true;
|
|
543
567
|
for (const t of element.target) {
|
|
@@ -640,6 +664,12 @@ function frozenMap(source, what) {
|
|
|
640
664
|
return Object.freeze(view);
|
|
641
665
|
}
|
|
642
666
|
|
|
667
|
+
// src/common/own-string.ts
|
|
668
|
+
var VIEW_MIN_LENGTH = 13;
|
|
669
|
+
function ownString(s) {
|
|
670
|
+
return s.length < VIEW_MIN_LENGTH ? s : ` ${s}`.slice(1);
|
|
671
|
+
}
|
|
672
|
+
|
|
643
673
|
// src/codesystem/status.ts
|
|
644
674
|
var defaultStatusMapper = (raw) => {
|
|
645
675
|
switch (raw.trim().toUpperCase()) {
|
|
@@ -674,7 +704,79 @@ function makeStatus(activity, raw, billable) {
|
|
|
674
704
|
}
|
|
675
705
|
|
|
676
706
|
// src/codesystem/csv.ts
|
|
677
|
-
|
|
707
|
+
var QUOTE = 34;
|
|
708
|
+
var COMMA = 44;
|
|
709
|
+
var LINE_FEED = 10;
|
|
710
|
+
var CARRIAGE_RETURN = 13;
|
|
711
|
+
function scanCsv(content, sink) {
|
|
712
|
+
if (typeof content !== "string") return readCsvByCharAt(content, sink);
|
|
713
|
+
const n = content.length;
|
|
714
|
+
const cells = [];
|
|
715
|
+
let column = 0;
|
|
716
|
+
const keeps = (col) => sink.wanted === void 0 || sink.wanted[col] === true;
|
|
717
|
+
let keep = keeps(0);
|
|
718
|
+
let field = "";
|
|
719
|
+
let runStart = 0;
|
|
720
|
+
let inQuotes = false;
|
|
721
|
+
let fieldStart = true;
|
|
722
|
+
let sawAny = false;
|
|
723
|
+
const endField = (end) => {
|
|
724
|
+
if (keep) cells[column] = field + content.slice(runStart, end);
|
|
725
|
+
field = "";
|
|
726
|
+
};
|
|
727
|
+
const endRow = () => {
|
|
728
|
+
sink.row(cells, column + 1);
|
|
729
|
+
column = 0;
|
|
730
|
+
keep = keeps(0);
|
|
731
|
+
};
|
|
732
|
+
for (let i = 0; i < n; i++) {
|
|
733
|
+
const ch = content.charCodeAt(i);
|
|
734
|
+
if (inQuotes) {
|
|
735
|
+
if (ch === QUOTE) {
|
|
736
|
+
if (content.charCodeAt(i + 1) === QUOTE) {
|
|
737
|
+
if (keep) field += content.slice(runStart, i + 1);
|
|
738
|
+
i++;
|
|
739
|
+
} else {
|
|
740
|
+
if (keep) field += content.slice(runStart, i);
|
|
741
|
+
inQuotes = false;
|
|
742
|
+
}
|
|
743
|
+
runStart = i + 1;
|
|
744
|
+
}
|
|
745
|
+
continue;
|
|
746
|
+
}
|
|
747
|
+
if (ch === QUOTE && fieldStart) {
|
|
748
|
+
inQuotes = true;
|
|
749
|
+
fieldStart = false;
|
|
750
|
+
sawAny = true;
|
|
751
|
+
runStart = i + 1;
|
|
752
|
+
} else if (ch === COMMA) {
|
|
753
|
+
endField(i);
|
|
754
|
+
column++;
|
|
755
|
+
keep = keeps(column);
|
|
756
|
+
runStart = i + 1;
|
|
757
|
+
fieldStart = true;
|
|
758
|
+
sawAny = true;
|
|
759
|
+
} else if (ch === LINE_FEED) {
|
|
760
|
+
endField(i);
|
|
761
|
+
endRow();
|
|
762
|
+
runStart = i + 1;
|
|
763
|
+
fieldStart = true;
|
|
764
|
+
sawAny = false;
|
|
765
|
+
} else if (ch === CARRIAGE_RETURN) {
|
|
766
|
+
if (keep) field += content.slice(runStart, i);
|
|
767
|
+
runStart = i + 1;
|
|
768
|
+
} else {
|
|
769
|
+
fieldStart = false;
|
|
770
|
+
sawAny = true;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
if (sawAny) {
|
|
774
|
+
endField(n);
|
|
775
|
+
endRow();
|
|
776
|
+
}
|
|
777
|
+
return inQuotes;
|
|
778
|
+
}
|
|
779
|
+
function readCsvByCharAt(content, sink) {
|
|
678
780
|
const rows = [];
|
|
679
781
|
let row = [];
|
|
680
782
|
let field = "";
|
|
@@ -713,20 +815,30 @@ function parseCsvRows(content) {
|
|
|
713
815
|
field = "";
|
|
714
816
|
fieldStart = true;
|
|
715
817
|
sawAny = false;
|
|
716
|
-
} else if (ch
|
|
818
|
+
} else if (ch !== "\r") {
|
|
717
819
|
field += ch;
|
|
718
820
|
fieldStart = false;
|
|
719
821
|
sawAny = true;
|
|
720
822
|
}
|
|
721
823
|
}
|
|
722
|
-
if (sawAny
|
|
824
|
+
if (sawAny) {
|
|
723
825
|
row.push(field);
|
|
724
826
|
rows.push(row);
|
|
725
827
|
}
|
|
726
|
-
|
|
828
|
+
for (const r of rows) sink.row(r, r.length);
|
|
829
|
+
return inQuotes;
|
|
727
830
|
}
|
|
728
831
|
function parseCsv(content) {
|
|
729
|
-
|
|
832
|
+
const rows = [];
|
|
833
|
+
scanCsv(content, {
|
|
834
|
+
wanted: void 0,
|
|
835
|
+
row: (cells, count) => {
|
|
836
|
+
const row = [];
|
|
837
|
+
for (let c = 0; c < count; c++) row.push(ownString(cells[c]));
|
|
838
|
+
rows.push(row);
|
|
839
|
+
}
|
|
840
|
+
});
|
|
841
|
+
return rows;
|
|
730
842
|
}
|
|
731
843
|
var MISSING_COLUMN_MESSAGE = Object.freeze({
|
|
732
844
|
code: "CSV source: the configured 'code' column is not present in the header row",
|
|
@@ -743,47 +855,44 @@ function requireHeader(header, name, role) {
|
|
|
743
855
|
}
|
|
744
856
|
function parseCsvSource(source) {
|
|
745
857
|
const statusMap = source.statusMap ?? defaultStatusMapper;
|
|
746
|
-
const
|
|
747
|
-
const header = rows[0];
|
|
748
|
-
if (header === void 0) {
|
|
749
|
-
throw new TerminologyError(
|
|
750
|
-
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
751
|
-
"CSV source: the file has no header row"
|
|
752
|
-
);
|
|
753
|
-
}
|
|
754
|
-
const warnings = [];
|
|
755
|
-
if (unterminatedQuote) {
|
|
756
|
-
warnings.push(
|
|
757
|
-
Object.freeze({
|
|
758
|
-
code: DIAGNOSTIC_CODES.TERM_CSV_MALFORMED,
|
|
759
|
-
line: rows.length,
|
|
760
|
-
detail: "an opened quoted field was not terminated before end-of-input"
|
|
761
|
-
})
|
|
762
|
-
);
|
|
763
|
-
}
|
|
764
|
-
const codeIdx = requireHeader(header, source.columns.code, "code");
|
|
765
|
-
const displayIdx = requireHeader(header, source.columns.display, "display");
|
|
766
|
-
const statusIdx = source.columns.status !== void 0 ? requireHeader(header, source.columns.status, "status") : void 0;
|
|
767
|
-
const propIdx = (source.columns.properties ?? []).map((n) => ({
|
|
768
|
-
name: n,
|
|
769
|
-
index: requireHeader(header, n, "property")
|
|
770
|
-
}));
|
|
771
|
-
const needCols = Math.max(codeIdx, displayIdx, statusIdx ?? 0, ...propIdx.map((p) => p.index)) + 1;
|
|
858
|
+
const content = source.content;
|
|
772
859
|
const concepts = /* @__PURE__ */ new Map();
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
860
|
+
const warnings = [];
|
|
861
|
+
let rowCount = 0;
|
|
862
|
+
let codeIdx = 0;
|
|
863
|
+
let displayIdx = 0;
|
|
864
|
+
let statusIdx;
|
|
865
|
+
const propIdx = [];
|
|
866
|
+
let needCols = 0;
|
|
867
|
+
const header = (cells, count) => {
|
|
868
|
+
const names = cells.slice(0, count);
|
|
869
|
+
codeIdx = requireHeader(names, source.columns.code, "code");
|
|
870
|
+
displayIdx = requireHeader(names, source.columns.display, "display");
|
|
871
|
+
statusIdx = source.columns.status !== void 0 ? requireHeader(names, source.columns.status, "status") : void 0;
|
|
872
|
+
for (const n of source.columns.properties ?? []) {
|
|
873
|
+
propIdx.push({ name: n, index: requireHeader(names, n, "property") });
|
|
874
|
+
}
|
|
875
|
+
needCols = Math.max(codeIdx, displayIdx, statusIdx ?? 0, ...propIdx.map((p) => p.index)) + 1;
|
|
876
|
+
const wanted = [];
|
|
877
|
+
wanted[codeIdx] = true;
|
|
878
|
+
wanted[displayIdx] = true;
|
|
879
|
+
if (statusIdx !== void 0) wanted[statusIdx] = true;
|
|
880
|
+
for (const p of propIdx) wanted[p.index] = true;
|
|
881
|
+
sink.wanted = wanted;
|
|
882
|
+
};
|
|
883
|
+
const dataRow = (cells, count, lineNo) => {
|
|
884
|
+
if (count < needCols) {
|
|
777
885
|
warnings.push(
|
|
778
886
|
Object.freeze({
|
|
779
887
|
code: DIAGNOSTIC_CODES.TERM_CSV_MALFORMED,
|
|
780
888
|
line: lineNo,
|
|
781
|
-
detail: `row has ${String(
|
|
889
|
+
detail: `row has ${String(count)} fields, need ${String(needCols)}`
|
|
782
890
|
})
|
|
783
891
|
);
|
|
784
|
-
|
|
892
|
+
return;
|
|
785
893
|
}
|
|
786
|
-
const
|
|
894
|
+
const cell3 = (index) => cells[index];
|
|
895
|
+
const code = cell3(codeIdx).trim();
|
|
787
896
|
if (code === "") {
|
|
788
897
|
warnings.push(
|
|
789
898
|
Object.freeze({
|
|
@@ -792,23 +901,47 @@ function parseCsvSource(source) {
|
|
|
792
901
|
detail: "row is missing its code"
|
|
793
902
|
})
|
|
794
903
|
);
|
|
795
|
-
|
|
904
|
+
return;
|
|
796
905
|
}
|
|
797
|
-
if (concepts.has(code))
|
|
798
|
-
const out = { code, properties: [] };
|
|
799
|
-
const display = (
|
|
800
|
-
if (display !== "") out.display = display;
|
|
906
|
+
if (concepts.has(code)) return;
|
|
907
|
+
const out = { code: ownString(code), properties: [] };
|
|
908
|
+
const display = cell3(displayIdx).trim();
|
|
909
|
+
if (display !== "") out.display = ownString(display);
|
|
801
910
|
if (statusIdx !== void 0) {
|
|
802
|
-
const rawStatus = (
|
|
911
|
+
const rawStatus = ownString(cell3(statusIdx).trim());
|
|
803
912
|
out.status = makeStatus(statusMap(rawStatus), rawStatus);
|
|
804
913
|
}
|
|
805
914
|
const props = [];
|
|
806
915
|
for (const p of propIdx) {
|
|
807
|
-
const value = (
|
|
808
|
-
if (value !== "") props.push(Object.freeze({ code: p.name, value }));
|
|
916
|
+
const value = cell3(p.index).trim();
|
|
917
|
+
if (value !== "") props.push(Object.freeze({ code: p.name, value: ownString(value) }));
|
|
809
918
|
}
|
|
810
919
|
out.properties = Object.freeze(props);
|
|
811
|
-
concepts.set(code, Object.freeze(out));
|
|
920
|
+
concepts.set(out.code, Object.freeze(out));
|
|
921
|
+
};
|
|
922
|
+
const sink = {
|
|
923
|
+
wanted: void 0,
|
|
924
|
+
row: (cells, count) => {
|
|
925
|
+
rowCount++;
|
|
926
|
+
if (rowCount === 1) header(cells, count);
|
|
927
|
+
else dataRow(cells, count, rowCount);
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
const unterminatedQuote = scanCsv(content, sink);
|
|
931
|
+
if (rowCount === 0) {
|
|
932
|
+
throw new TerminologyError(
|
|
933
|
+
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
934
|
+
"CSV source: the file has no header row"
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
if (unterminatedQuote) {
|
|
938
|
+
warnings.unshift(
|
|
939
|
+
Object.freeze({
|
|
940
|
+
code: DIAGNOSTIC_CODES.TERM_CSV_MALFORMED,
|
|
941
|
+
line: rowCount,
|
|
942
|
+
detail: "an opened quoted field was not terminated before end-of-input"
|
|
943
|
+
})
|
|
944
|
+
);
|
|
812
945
|
}
|
|
813
946
|
return { concepts, warnings };
|
|
814
947
|
}
|
|
@@ -856,9 +989,10 @@ function collectConcepts(rawConcepts, into, warnings, parentCode) {
|
|
|
856
989
|
if (code !== void 0 && code !== "" && !into.has(code)) {
|
|
857
990
|
const props = [];
|
|
858
991
|
for (const p of getArray(raw, "property") ?? []) {
|
|
992
|
+
if (!isJsonObject(p)) continue;
|
|
859
993
|
const pcode = getString(p, "code");
|
|
860
994
|
if (pcode === void 0) continue;
|
|
861
|
-
const value =
|
|
995
|
+
const value = readPropertyValue(p);
|
|
862
996
|
if (value !== void 0) props.push(Object.freeze({ code: pcode, value }));
|
|
863
997
|
}
|
|
864
998
|
if (parentCode !== void 0 && !props.some((p) => p.code === "parent" && p.value === parentCode)) {
|
|
@@ -921,9 +1055,21 @@ var ICD10CM_ORDER_FILE_FIELDS = Object.freeze({
|
|
|
921
1055
|
Object.freeze({ name: "shortDescription", slice: Object.freeze({ start: 16, end: 76 }) })
|
|
922
1056
|
])
|
|
923
1057
|
});
|
|
1058
|
+
function requirePropertyFields(fields) {
|
|
1059
|
+
for (const p of fields.properties ?? []) {
|
|
1060
|
+
const entry = p;
|
|
1061
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1062
|
+
throw new TerminologyError(
|
|
1063
|
+
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
1064
|
+
"fixed-width source: a configured property field is not an object"
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
}
|
|
924
1069
|
function parseFixedWidth(source) {
|
|
925
1070
|
const statusMap = source.statusMap ?? defaultStatusMapper;
|
|
926
1071
|
const fields = source.fields;
|
|
1072
|
+
requirePropertyFields(fields);
|
|
927
1073
|
const concepts = /* @__PURE__ */ new Map();
|
|
928
1074
|
const warnings = [];
|
|
929
1075
|
const lines = source.content.split(/\r?\n/);
|
|
@@ -985,7 +1131,16 @@ function parseRrfLine(line) {
|
|
|
985
1131
|
function maxColumn(source) {
|
|
986
1132
|
const { code, display, status, properties } = source.columns;
|
|
987
1133
|
let max = Math.max(code, display, status ?? 0);
|
|
988
|
-
for (const p of properties ?? [])
|
|
1134
|
+
for (const p of properties ?? []) {
|
|
1135
|
+
const entry = p;
|
|
1136
|
+
if (typeof entry !== "object" || entry === null) {
|
|
1137
|
+
throw new TerminologyError(
|
|
1138
|
+
FATAL_CODES.TERM_CODESYSTEM_MALFORMED,
|
|
1139
|
+
"RRF source: a configured property column is not an object"
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
max = Math.max(max, p.column);
|
|
1143
|
+
}
|
|
989
1144
|
return max;
|
|
990
1145
|
}
|
|
991
1146
|
function cell(cells, index) {
|
|
@@ -1046,6 +1201,10 @@ function parseRrf(source) {
|
|
|
1046
1201
|
}
|
|
1047
1202
|
|
|
1048
1203
|
// src/codesystem/load.ts
|
|
1204
|
+
var loadedConcepts = /* @__PURE__ */ new WeakSet();
|
|
1205
|
+
function isLoadedConcepts(concepts) {
|
|
1206
|
+
return loadedConcepts.has(concepts);
|
|
1207
|
+
}
|
|
1049
1208
|
function loadCodeSystem(source) {
|
|
1050
1209
|
let concepts;
|
|
1051
1210
|
let warnings;
|
|
@@ -1084,6 +1243,7 @@ function loadCodeSystem(source) {
|
|
|
1084
1243
|
}
|
|
1085
1244
|
out.count = concepts.size;
|
|
1086
1245
|
out.concepts = frozenMap(concepts, "a loaded CodeSystem's concepts map");
|
|
1246
|
+
loadedConcepts.add(out.concepts);
|
|
1087
1247
|
out.warnings = Object.freeze(warnings);
|
|
1088
1248
|
return Object.freeze(out);
|
|
1089
1249
|
}
|
|
@@ -1286,8 +1446,11 @@ function directParents(concept) {
|
|
|
1286
1446
|
return parents;
|
|
1287
1447
|
}
|
|
1288
1448
|
function buildSubsumption(cs) {
|
|
1449
|
+
return subsumptionOver(cs.concepts);
|
|
1450
|
+
}
|
|
1451
|
+
function subsumptionOver(concepts) {
|
|
1289
1452
|
const parentsOf = /* @__PURE__ */ new Map();
|
|
1290
|
-
for (const concept of
|
|
1453
|
+
for (const concept of concepts.values()) {
|
|
1291
1454
|
const parents = directParents(concept);
|
|
1292
1455
|
if (parents.length > 0) parentsOf.set(concept.code, parents);
|
|
1293
1456
|
}
|
|
@@ -1308,6 +1471,17 @@ function buildSubsumption(cs) {
|
|
|
1308
1471
|
};
|
|
1309
1472
|
return { ancestorsOf };
|
|
1310
1473
|
}
|
|
1474
|
+
var loadedSubsumption = /* @__PURE__ */ new WeakMap();
|
|
1475
|
+
function subsumptionOf(cs) {
|
|
1476
|
+
const concepts = cs.concepts;
|
|
1477
|
+
if (!isLoadedConcepts(concepts)) return subsumptionOver(concepts);
|
|
1478
|
+
let sub = loadedSubsumption.get(concepts);
|
|
1479
|
+
if (sub === void 0) {
|
|
1480
|
+
sub = subsumptionOver(concepts);
|
|
1481
|
+
loadedSubsumption.set(concepts, sub);
|
|
1482
|
+
}
|
|
1483
|
+
return sub;
|
|
1484
|
+
}
|
|
1311
1485
|
function isA(sub, code, anchor) {
|
|
1312
1486
|
return code === anchor || sub.ancestorsOf(code).has(anchor);
|
|
1313
1487
|
}
|
|
@@ -1392,8 +1566,7 @@ function makeCoding(system, code, display, version) {
|
|
|
1392
1566
|
return coding(init);
|
|
1393
1567
|
}
|
|
1394
1568
|
function cannotExpand(detail, path) {
|
|
1395
|
-
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
|
|
1396
|
-
if (path !== void 0) d.path = path;
|
|
1569
|
+
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail, path };
|
|
1397
1570
|
return Object.freeze(d);
|
|
1398
1571
|
}
|
|
1399
1572
|
function truncated(detail, path) {
|
|
@@ -1407,9 +1580,9 @@ function truncated(detail, path) {
|
|
|
1407
1580
|
function enumeratedUndefined(detail, path) {
|
|
1408
1581
|
const d = {
|
|
1409
1582
|
code: "TERM_VALUESET_ENUMERATED_CODE_UNDEFINED",
|
|
1410
|
-
detail
|
|
1583
|
+
detail,
|
|
1584
|
+
path
|
|
1411
1585
|
};
|
|
1412
|
-
if (path !== void 0) d.path = path;
|
|
1413
1586
|
return Object.freeze(d);
|
|
1414
1587
|
}
|
|
1415
1588
|
function expansionIncompleteDetail(unclosed) {
|
|
@@ -1443,7 +1616,8 @@ function screenActive(members, component, ctx) {
|
|
|
1443
1616
|
}
|
|
1444
1617
|
function underPath(d, prefix) {
|
|
1445
1618
|
const out = { code: d.code, detail: d.detail };
|
|
1446
|
-
|
|
1619
|
+
if (d.path === void 0) out.path = prefix;
|
|
1620
|
+
else out.path = `${prefix}/${d.path}`;
|
|
1447
1621
|
return Object.freeze(out);
|
|
1448
1622
|
}
|
|
1449
1623
|
function intersectInto(into, other) {
|
|
@@ -1639,8 +1813,7 @@ function expand(vs, ctx = {}) {
|
|
|
1639
1813
|
|
|
1640
1814
|
// src/valueset/validate.ts
|
|
1641
1815
|
function cannotExpand2(detail, path) {
|
|
1642
|
-
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail };
|
|
1643
|
-
if (path !== void 0) d.path = path;
|
|
1816
|
+
const d = { code: "TERM_VALUESET_CANNOT_EXPAND", detail, path };
|
|
1644
1817
|
return Object.freeze(d);
|
|
1645
1818
|
}
|
|
1646
1819
|
function expansionIncompleteDetail2(unclosed) {
|
|
@@ -1658,7 +1831,8 @@ function usableRelease2(component, system, ctx) {
|
|
|
1658
1831
|
}
|
|
1659
1832
|
function underPath2(d, prefix) {
|
|
1660
1833
|
const out = { code: d.code, detail: d.detail };
|
|
1661
|
-
|
|
1834
|
+
if (d.path === void 0) out.path = prefix;
|
|
1835
|
+
else out.path = `${prefix}/${d.path}`;
|
|
1662
1836
|
return Object.freeze(out);
|
|
1663
1837
|
}
|
|
1664
1838
|
function matchComponent(target, component, ctx, visited, path, activeOnly = false) {
|
|
@@ -1710,7 +1884,7 @@ function matchComponent(target, component, ctx, visited, path, activeOnly = fals
|
|
|
1710
1884
|
baseComplete = false;
|
|
1711
1885
|
} else {
|
|
1712
1886
|
const concpt = cs.concepts.get(target.code);
|
|
1713
|
-
baseMatched = concpt !== void 0 && matchesAllFilters(concpt, filter,
|
|
1887
|
+
baseMatched = concpt !== void 0 && matchesAllFilters(concpt, filter, subsumptionOf(cs)).matched;
|
|
1714
1888
|
}
|
|
1715
1889
|
} else {
|
|
1716
1890
|
baseMatched = cs.concepts.get(target.code) !== void 0;
|
|
@@ -1718,10 +1892,13 @@ function matchComponent(target, component, ctx, visited, path, activeOnly = fals
|
|
|
1718
1892
|
}
|
|
1719
1893
|
}
|
|
1720
1894
|
if (system !== void 0 && target.system === void 0 && baseComplete) baseMatched = false;
|
|
1895
|
+
if (baseMatched === null && !hasVs) return { matched: false, complete: true, diagnostics };
|
|
1721
1896
|
let refDefiniteFalse = false;
|
|
1722
1897
|
let refUndetermined = false;
|
|
1723
1898
|
if (hasVs) {
|
|
1899
|
+
let refsRead = 0;
|
|
1724
1900
|
for (const [i, url] of valueSet2.entries()) {
|
|
1901
|
+
refsRead++;
|
|
1725
1902
|
const refPath = `${path}.valueSet[${String(i)}]`;
|
|
1726
1903
|
if (visited.has(url)) {
|
|
1727
1904
|
diagnostics.push(cannotExpand2("cyclic value set reference", refPath));
|
|
@@ -1742,6 +1919,7 @@ function matchComponent(target, component, ctx, visited, path, activeOnly = fals
|
|
|
1742
1919
|
refDefiniteFalse = true;
|
|
1743
1920
|
}
|
|
1744
1921
|
}
|
|
1922
|
+
if (refsRead === 0) refDefiniteFalse = true;
|
|
1745
1923
|
}
|
|
1746
1924
|
const baseDefiniteFalse = baseMatched === false && baseComplete;
|
|
1747
1925
|
const baseDefiniteTrue = baseMatched === null || baseMatched === true && baseComplete;
|
|
@@ -3993,7 +4171,32 @@ function atBoundary(s, pos) {
|
|
|
3993
4171
|
const c = s[pos];
|
|
3994
4172
|
return c === "." || c === "/" || c === ")";
|
|
3995
4173
|
}
|
|
4174
|
+
function prefixRank(m) {
|
|
4175
|
+
return m.prefix ? 1 : 0;
|
|
4176
|
+
}
|
|
4177
|
+
function byPreference(a, b) {
|
|
4178
|
+
return b.len - a.len || prefixRank(a) - prefixRank(b);
|
|
4179
|
+
}
|
|
3996
4180
|
function simpleMatches(essence, s, pos) {
|
|
4181
|
+
if (typeof s !== "string") return simpleMatchesByScan(essence, s, pos);
|
|
4182
|
+
const index = indexOf(essence);
|
|
4183
|
+
const out = [];
|
|
4184
|
+
for (const atom of index.atoms.get(s.charCodeAt(pos)) ?? NONE) {
|
|
4185
|
+
if (s.startsWith(atom.code, pos)) out.push({ atom, len: atom.code.length });
|
|
4186
|
+
}
|
|
4187
|
+
for (const prefix of index.prefixes.get(s.charCodeAt(pos)) ?? NONE) {
|
|
4188
|
+
if (!s.startsWith(prefix.code, pos)) continue;
|
|
4189
|
+
const rem = pos + prefix.code.length;
|
|
4190
|
+
for (const atom of index.metricAtoms.get(s.charCodeAt(rem)) ?? NONE) {
|
|
4191
|
+
if (s.startsWith(atom.code, rem)) {
|
|
4192
|
+
out.push({ prefix, atom, len: prefix.code.length + atom.code.length });
|
|
4193
|
+
}
|
|
4194
|
+
}
|
|
4195
|
+
}
|
|
4196
|
+
out.sort(byPreference);
|
|
4197
|
+
return out;
|
|
4198
|
+
}
|
|
4199
|
+
function simpleMatchesByScan(essence, s, pos) {
|
|
3997
4200
|
const out = [];
|
|
3998
4201
|
for (const atom of essence.atoms) {
|
|
3999
4202
|
if (s.startsWith(atom.code, pos)) out.push({ atom, len: atom.code.length });
|
|
@@ -4007,9 +4210,33 @@ function simpleMatches(essence, s, pos) {
|
|
|
4007
4210
|
}
|
|
4008
4211
|
}
|
|
4009
4212
|
}
|
|
4010
|
-
out.sort(
|
|
4213
|
+
out.sort(byPreference);
|
|
4214
|
+
return out;
|
|
4215
|
+
}
|
|
4216
|
+
var NONE = Object.freeze([]);
|
|
4217
|
+
var firstCharIndexes = /* @__PURE__ */ new WeakMap();
|
|
4218
|
+
function byFirstChar(items) {
|
|
4219
|
+
const out = /* @__PURE__ */ new Map();
|
|
4220
|
+
for (const item of items) {
|
|
4221
|
+
const first = item.code.charCodeAt(0);
|
|
4222
|
+
const list = out.get(first);
|
|
4223
|
+
if (list === void 0) out.set(first, [item]);
|
|
4224
|
+
else list.push(item);
|
|
4225
|
+
}
|
|
4011
4226
|
return out;
|
|
4012
4227
|
}
|
|
4228
|
+
function indexOf(essence) {
|
|
4229
|
+
let index = firstCharIndexes.get(essence);
|
|
4230
|
+
if (index === void 0) {
|
|
4231
|
+
index = {
|
|
4232
|
+
atoms: byFirstChar(essence.atoms),
|
|
4233
|
+
metricAtoms: byFirstChar(essence.atoms.filter((a) => a.metric)),
|
|
4234
|
+
prefixes: byFirstChar(essence.prefixes)
|
|
4235
|
+
};
|
|
4236
|
+
firstCharIndexes.set(essence, index);
|
|
4237
|
+
}
|
|
4238
|
+
return index;
|
|
4239
|
+
}
|
|
4013
4240
|
var MAX_PAREN_DEPTH = 100;
|
|
4014
4241
|
function parseExponent(cur) {
|
|
4015
4242
|
const { s } = cur;
|
|
@@ -4042,8 +4269,8 @@ function parseAnnotation(cur) {
|
|
|
4042
4269
|
return { ok: false, reason: "annotation is not closed with '}'" };
|
|
4043
4270
|
}
|
|
4044
4271
|
function parseSimpleUnit(cur) {
|
|
4045
|
-
const
|
|
4046
|
-
for (const cand of
|
|
4272
|
+
const candidates2 = simpleMatches(cur.essence, cur.s, cur.pos);
|
|
4273
|
+
for (const cand of candidates2) {
|
|
4047
4274
|
const save = cur.pos;
|
|
4048
4275
|
cur.pos = save + cand.len;
|
|
4049
4276
|
const exp = parseExponent(cur);
|
|
@@ -4269,57 +4496,62 @@ function isNoMapSentinel(target) {
|
|
|
4269
4496
|
return target === NO_DX || target === NO_PCS;
|
|
4270
4497
|
}
|
|
4271
4498
|
function decodeFlags(raw) {
|
|
4272
|
-
if (!/^[0-9]{5}$/.test(raw)) return void 0;
|
|
4273
4499
|
return Object.freeze({
|
|
4274
|
-
approximate: raw
|
|
4275
|
-
noMap: raw
|
|
4276
|
-
combination: raw
|
|
4277
|
-
scenario:
|
|
4278
|
-
choiceList:
|
|
4500
|
+
approximate: raw.charCodeAt(0) === DIGIT_ONE,
|
|
4501
|
+
noMap: raw.charCodeAt(1) === DIGIT_ONE,
|
|
4502
|
+
combination: raw.charCodeAt(2) === DIGIT_ONE,
|
|
4503
|
+
scenario: raw.charCodeAt(3) - DIGIT_ZERO,
|
|
4504
|
+
choiceList: raw.charCodeAt(4) - DIGIT_ZERO,
|
|
4279
4505
|
raw
|
|
4280
4506
|
});
|
|
4281
4507
|
}
|
|
4508
|
+
var DIGIT_ZERO = 48;
|
|
4509
|
+
var DIGIT_ONE = 49;
|
|
4510
|
+
var DIGIT_NINE = 57;
|
|
4511
|
+
var SPACE = 32;
|
|
4512
|
+
var NON_ASCII_SPACE = /^\s$/;
|
|
4513
|
+
function isSpace(c) {
|
|
4514
|
+
if (c < 128) return c === SPACE || c >= 9 && c <= 13;
|
|
4515
|
+
return NON_ASCII_SPACE.test(String.fromCharCode(c));
|
|
4516
|
+
}
|
|
4282
4517
|
function loadGems(source) {
|
|
4283
4518
|
const entries = /* @__PURE__ */ new Map();
|
|
4284
4519
|
const warnings = [];
|
|
4285
4520
|
let count = 0;
|
|
4286
|
-
const
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
if (raw === void 0) continue;
|
|
4290
|
-
const trimmed = raw.trim();
|
|
4291
|
-
if (trimmed === "") continue;
|
|
4292
|
-
const fields = trimmed.split(/\s+/);
|
|
4293
|
-
if (fields.length !== 3) {
|
|
4521
|
+
const sink = {
|
|
4522
|
+
flags: /* @__PURE__ */ new Map(),
|
|
4523
|
+
malformed: (lineNo, fieldCount) => {
|
|
4294
4524
|
warnings.push(
|
|
4295
4525
|
Object.freeze({
|
|
4296
4526
|
code: "TERM_GEM_MALFORMED_ROW",
|
|
4297
|
-
line:
|
|
4298
|
-
detail: `expected 3 whitespace-delimited fields, found ${String(
|
|
4527
|
+
line: lineNo,
|
|
4528
|
+
detail: `expected 3 whitespace-delimited fields, found ${String(fieldCount)}`
|
|
4299
4529
|
})
|
|
4300
4530
|
);
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4308
|
-
|
|
4309
|
-
|
|
4310
|
-
|
|
4311
|
-
|
|
4531
|
+
},
|
|
4532
|
+
line: (lineNo, src, tgt, flags) => {
|
|
4533
|
+
if (flags === void 0) {
|
|
4534
|
+
warnings.push(
|
|
4535
|
+
Object.freeze({
|
|
4536
|
+
code: "TERM_GEM_MALFORMED_ROW",
|
|
4537
|
+
line: lineNo,
|
|
4538
|
+
detail: "flag field is not a 5-digit code"
|
|
4539
|
+
})
|
|
4540
|
+
);
|
|
4541
|
+
return;
|
|
4542
|
+
}
|
|
4543
|
+
const frozen = Object.freeze(
|
|
4544
|
+
isNoMapSentinel(tgt) ? { source: src, flags } : { source: src, flags, target: tgt }
|
|
4312
4545
|
);
|
|
4313
|
-
|
|
4546
|
+
const list = entries.get(src);
|
|
4547
|
+
if (list) list.push(frozen);
|
|
4548
|
+
else entries.set(src, [frozen]);
|
|
4549
|
+
count++;
|
|
4314
4550
|
}
|
|
4315
|
-
|
|
4316
|
-
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
if (list) list.push(frozen);
|
|
4320
|
-
else entries.set(src, [frozen]);
|
|
4321
|
-
count++;
|
|
4322
|
-
}
|
|
4551
|
+
};
|
|
4552
|
+
const content = source.content;
|
|
4553
|
+
if (typeof content === "string") readLinesInPlace(content, sink);
|
|
4554
|
+
else readLinesBySplit(content, sink);
|
|
4323
4555
|
for (const list of entries.values()) Object.freeze(list);
|
|
4324
4556
|
return Object.freeze({
|
|
4325
4557
|
direction: source.direction,
|
|
@@ -4329,6 +4561,90 @@ function loadGems(source) {
|
|
|
4329
4561
|
warnings: Object.freeze(warnings)
|
|
4330
4562
|
});
|
|
4331
4563
|
}
|
|
4564
|
+
function sharedFlags(sink, text, from, to) {
|
|
4565
|
+
const key = fiveDigitsAt(text, from, to);
|
|
4566
|
+
if (key === -1) return void 0;
|
|
4567
|
+
let flags = sink.flags.get(key);
|
|
4568
|
+
if (flags === void 0) {
|
|
4569
|
+
flags = decodeFlags(text.slice(from, to));
|
|
4570
|
+
sink.flags.set(key, flags);
|
|
4571
|
+
}
|
|
4572
|
+
return flags;
|
|
4573
|
+
}
|
|
4574
|
+
function fiveDigitsAt(content, from, to) {
|
|
4575
|
+
if (to - from !== 5) return -1;
|
|
4576
|
+
let value = 0;
|
|
4577
|
+
for (let i = from; i < to; i++) {
|
|
4578
|
+
const c = content.charCodeAt(i);
|
|
4579
|
+
if (c < DIGIT_ZERO || c > DIGIT_NINE) return -1;
|
|
4580
|
+
value = value * 10 + (c - DIGIT_ZERO);
|
|
4581
|
+
}
|
|
4582
|
+
return value;
|
|
4583
|
+
}
|
|
4584
|
+
function readLinesInPlace(content, sink) {
|
|
4585
|
+
const n = content.length;
|
|
4586
|
+
let lineNo = 0;
|
|
4587
|
+
for (let start = 0; start <= n; ) {
|
|
4588
|
+
let end = content.indexOf("\n", start);
|
|
4589
|
+
if (end === -1) end = n;
|
|
4590
|
+
lineNo++;
|
|
4591
|
+
let fieldCount = 0;
|
|
4592
|
+
let srcStart = 0;
|
|
4593
|
+
let srcEnd = 0;
|
|
4594
|
+
let tgtStart = 0;
|
|
4595
|
+
let tgtEnd = 0;
|
|
4596
|
+
let flagStart = 0;
|
|
4597
|
+
let flagEnd = 0;
|
|
4598
|
+
for (let i = start; i < end; ) {
|
|
4599
|
+
if (isSpace(content.charCodeAt(i))) {
|
|
4600
|
+
i++;
|
|
4601
|
+
continue;
|
|
4602
|
+
}
|
|
4603
|
+
const from = i;
|
|
4604
|
+
while (i < end && !isSpace(content.charCodeAt(i))) i++;
|
|
4605
|
+
fieldCount++;
|
|
4606
|
+
if (fieldCount === 1) {
|
|
4607
|
+
srcStart = from;
|
|
4608
|
+
srcEnd = i;
|
|
4609
|
+
} else if (fieldCount === 2) {
|
|
4610
|
+
tgtStart = from;
|
|
4611
|
+
tgtEnd = i;
|
|
4612
|
+
} else if (fieldCount === 3) {
|
|
4613
|
+
flagStart = from;
|
|
4614
|
+
flagEnd = i;
|
|
4615
|
+
}
|
|
4616
|
+
}
|
|
4617
|
+
start = end + 1;
|
|
4618
|
+
if (fieldCount === 0) continue;
|
|
4619
|
+
if (fieldCount !== 3) {
|
|
4620
|
+
sink.malformed(lineNo, fieldCount);
|
|
4621
|
+
continue;
|
|
4622
|
+
}
|
|
4623
|
+
const flags = sharedFlags(sink, content, flagStart, flagEnd);
|
|
4624
|
+
sink.line(
|
|
4625
|
+
lineNo,
|
|
4626
|
+
ownString(content.slice(srcStart, srcEnd)),
|
|
4627
|
+
ownString(content.slice(tgtStart, tgtEnd)),
|
|
4628
|
+
flags
|
|
4629
|
+
);
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
function readLinesBySplit(content, sink) {
|
|
4633
|
+
const lines = content.split(/\r?\n/);
|
|
4634
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4635
|
+
const raw = lines[i];
|
|
4636
|
+
if (raw === void 0) continue;
|
|
4637
|
+
const trimmed = raw.trim();
|
|
4638
|
+
if (trimmed === "") continue;
|
|
4639
|
+
const fields = trimmed.split(/\s+/);
|
|
4640
|
+
if (fields.length !== 3) {
|
|
4641
|
+
sink.malformed(i + 1, fields.length);
|
|
4642
|
+
continue;
|
|
4643
|
+
}
|
|
4644
|
+
const [src, tgt, flagStr] = fields;
|
|
4645
|
+
sink.line(i + 1, src, tgt, sharedFlags(sink, flagStr, 0, flagStr.length));
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4332
4648
|
function buildCombinations(entries) {
|
|
4333
4649
|
const combo = entries.filter((e) => e.flags.combination && e.target !== void 0);
|
|
4334
4650
|
if (combo.length === 0) return void 0;
|
|
@@ -4412,7 +4728,7 @@ var MAP_CATEGORIES = {
|
|
|
4412
4728
|
NO_MAP: "447638001",
|
|
4413
4729
|
/** `447639009`: "Map of source concept is context dependent" (needs runtime patient context). */
|
|
4414
4730
|
CONTEXT_DEPENDENT: "447639009",
|
|
4415
|
-
/** `447640006`: "Source concept is ambiguous" (
|
|
4731
|
+
/** `447640006`: "Source concept is ambiguous" (carried through on the result; the rule decides). */
|
|
4416
4732
|
AMBIGUOUS: "447640006"
|
|
4417
4733
|
};
|
|
4418
4734
|
var NO_MAP_CATEGORIES = /* @__PURE__ */ new Set([MAP_CATEGORIES.NO_MAP]);
|
|
@@ -4545,8 +4861,15 @@ function evaluateConjunct(clause, context) {
|
|
|
4545
4861
|
const bound = Number(ageMatch[2]);
|
|
4546
4862
|
if (context.ageYears === void 0 || !Number.isFinite(bound)) return "unknown";
|
|
4547
4863
|
const age = context.ageYears;
|
|
4548
|
-
|
|
4549
|
-
if (
|
|
4864
|
+
let ok;
|
|
4865
|
+
if (op === "<=") ok = age <= bound;
|
|
4866
|
+
else if (op === ">=") ok = age >= bound;
|
|
4867
|
+
else if (op === "<") ok = age < bound;
|
|
4868
|
+
else if (op === ">") ok = age > bound;
|
|
4869
|
+
else {
|
|
4870
|
+
if (op === "=") ok = age === bound;
|
|
4871
|
+
else return "unknown";
|
|
4872
|
+
}
|
|
4550
4873
|
return ok ? "match" : "no-match";
|
|
4551
4874
|
}
|
|
4552
4875
|
return "unknown";
|
|
@@ -4965,7 +5288,8 @@ function jaccard(a, b) {
|
|
|
4965
5288
|
let inter = 0;
|
|
4966
5289
|
for (const t of a) if (b.has(t)) inter++;
|
|
4967
5290
|
const union = a.size + b.size - inter;
|
|
4968
|
-
|
|
5291
|
+
if (union === 0) return 0;
|
|
5292
|
+
return inter / union;
|
|
4969
5293
|
}
|
|
4970
5294
|
function approximateMatch(graph, query, options = {}) {
|
|
4971
5295
|
const minScore = options.minScore ?? 0.34;
|
|
@@ -4982,7 +5306,7 @@ function approximateMatch(graph, query, options = {}) {
|
|
|
4982
5306
|
}
|
|
4983
5307
|
|
|
4984
5308
|
// src/index.ts
|
|
4985
|
-
var VERSION = "0.1.
|
|
5309
|
+
var VERSION = "0.1.7";
|
|
4986
5310
|
|
|
4987
5311
|
exports.COMPLEX_MAP_SOURCE_SYSTEM = COMPLEX_MAP_SOURCE_SYSTEM;
|
|
4988
5312
|
exports.DIAGNOSTIC_CODES = DIAGNOSTIC_CODES;
|