fhirsmith 0.7.6 → 0.8.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/CHANGELOG.md +26 -0
- package/library/languages.js +10 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +2 -2
- package/publisher/publisher.js +1 -1
- package/registry/registry.js +2 -2
- package/root-bare-template.html +1 -2
- package/server.js +100 -70
- package/stats.js +37 -6
- package/tx/cs/cs-loinc.js +14 -2
- package/tx/cs/cs-rxnorm.js +14 -10
- package/tx/cs/cs-snomed.js +166 -5
- package/tx/html/dash-metrics.liquid +147 -0
- package/tx/importers/import-rxnorm.module.js +4 -30
- package/tx/library/canonical-resource.js +8 -0
- package/tx/library/conceptmap.js +3 -1
- package/tx/library/designations.js +4 -8
- package/tx/library/renderer.js +9 -9
- package/tx/ocl/cm-ocl.cjs +185 -65
- package/tx/ocl/cs-ocl.cjs +69 -50
- package/tx/ocl/jobs/background-queue.cjs +0 -8
- package/tx/ocl/mappers/concept-mapper.cjs +13 -3
- package/tx/ocl/shared/patches.cjs +1 -0
- package/tx/ocl/vs-ocl.cjs +137 -157
- package/tx/operation-context.js +3 -3
- package/tx/provider.js +2 -2
- package/tx/sct/structures.js +5 -0
- package/tx/tx.fhir.org.yml +1 -1
- package/tx/vs/vs-database.js +107 -23
- package/tx/vs/vs-vsac.js +66 -19
- package/tx/workers/search.js +2 -1
- package/tx/workers/translate.js +39 -14
- package/tx/workers/validate.js +3 -3
- package/xig/xig.js +171 -9
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -371,6 +371,29 @@ class SnomedServices {
|
|
|
371
371
|
return result;
|
|
372
372
|
}
|
|
373
373
|
|
|
374
|
+
filterInactive(state) {
|
|
375
|
+
const result = new SnomedFilterContext();
|
|
376
|
+
result.inactive = state;
|
|
377
|
+
return result;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
filterModuleId(id) {
|
|
381
|
+
const result = new SnomedFilterContext();
|
|
382
|
+
let concept = this.concepts.findConcept(id);
|
|
383
|
+
result.moduleId = concept.index;
|
|
384
|
+
return result;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
filterByProperty(prop, value) {
|
|
388
|
+
const result = new SnomedFilterContext();
|
|
389
|
+
let p = this.concepts.findConcept(prop);
|
|
390
|
+
let v = this.concepts.findConcept(value);
|
|
391
|
+
result.propProp = p.index;
|
|
392
|
+
result.propValue = v.index;
|
|
393
|
+
return result;
|
|
394
|
+
|
|
395
|
+
}
|
|
396
|
+
|
|
374
397
|
searchFilter(searchText, includeInactive = false, exactMatch = false) {
|
|
375
398
|
const result = new SnomedFilterContext();
|
|
376
399
|
|
|
@@ -765,6 +788,12 @@ class SnomedProvider extends BaseCSServices {
|
|
|
765
788
|
this._addCodeProperty(params, 'property', 'child', code, null, description);
|
|
766
789
|
}
|
|
767
790
|
|
|
791
|
+
const moduleId = this.sct.concepts.getModuleId(ctxt.getReference());
|
|
792
|
+
if (moduleId) {
|
|
793
|
+
const code = this.sct.getConceptId(moduleId);
|
|
794
|
+
this._addCodeProperty(params, 'property', 'module', code, null, null);
|
|
795
|
+
}
|
|
796
|
+
|
|
768
797
|
const relationships = this.sct.getConceptRelationships(ctxt.getReference());
|
|
769
798
|
let set = new Set();
|
|
770
799
|
for (let relationshipRef of relationships) {
|
|
@@ -812,10 +841,25 @@ class SnomedProvider extends BaseCSServices {
|
|
|
812
841
|
return this.sct.conceptExists(value);
|
|
813
842
|
}
|
|
814
843
|
}
|
|
844
|
+
if (prop === 'inactive') {
|
|
845
|
+
return op === '=' && ['true', 'false'].includes(value);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
if (prop === 'moduleId') {
|
|
849
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
850
|
+
return id !== 0n && op === '=';
|
|
851
|
+
}
|
|
852
|
+
|
|
815
853
|
if (prop == 'expressions' && op == '=' && ['true', 'false'].includes(value)) {
|
|
816
854
|
return true;
|
|
817
855
|
}
|
|
818
856
|
|
|
857
|
+
const cid = this.sct.stringToIdOrZero(prop);
|
|
858
|
+
if (cid != 0) {
|
|
859
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
860
|
+
return id !== 0n && op === '=';
|
|
861
|
+
}
|
|
862
|
+
|
|
819
863
|
return false;
|
|
820
864
|
}
|
|
821
865
|
|
|
@@ -851,7 +895,38 @@ class SnomedProvider extends BaseCSServices {
|
|
|
851
895
|
return null;
|
|
852
896
|
}
|
|
853
897
|
default:
|
|
854
|
-
throw new Error(`Unsupported filter operation: ${op}`);
|
|
898
|
+
throw new Error(`Unsupported filter operation: concept ${op} ${value}`);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
if (prop === 'inactive') {
|
|
903
|
+
if (value !== 'true' && value !== 'false') {
|
|
904
|
+
throw new Error(`Invalid filter value: ${value}`);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
switch (op) {
|
|
908
|
+
case '=': {
|
|
909
|
+
filterContext.filters.push(this.sct.filterInactive(value === 'true'));
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
default:
|
|
913
|
+
throw new Error(`Unsupported filter operation: inactive ${op} ${value}`);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
if (prop === 'moduleId') {
|
|
918
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
919
|
+
if (id === 0n) {
|
|
920
|
+
throw new Error(`Invalid concept ID: ${value}`);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
switch (op) {
|
|
924
|
+
case '=': {
|
|
925
|
+
filterContext.filters.push(this.sct.filterModuleId(id));
|
|
926
|
+
return null;
|
|
927
|
+
}
|
|
928
|
+
default:
|
|
929
|
+
throw new Error(`Unsupported filter operation: moduleId ${op} ${value}`);
|
|
855
930
|
}
|
|
856
931
|
}
|
|
857
932
|
|
|
@@ -862,6 +937,24 @@ class SnomedProvider extends BaseCSServices {
|
|
|
862
937
|
return null;
|
|
863
938
|
}
|
|
864
939
|
|
|
940
|
+
const cid = this.sct.stringToIdOrZero(prop);
|
|
941
|
+
if (cid != 0) {
|
|
942
|
+
|
|
943
|
+
const id = this.sct.stringToIdOrZero(value);
|
|
944
|
+
if (id === 0n) {
|
|
945
|
+
throw new Error(`Invalid concept ID: ${value}`);
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
switch (op) {
|
|
949
|
+
case '=': {
|
|
950
|
+
filterContext.filters.push(this.sct.filterByProperty(cid, id));
|
|
951
|
+
return null;
|
|
952
|
+
}
|
|
953
|
+
default:
|
|
954
|
+
throw new Error(`Unsupported filter operation: ${prop} ${op} ${value}`);
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
865
958
|
throw new Error(`Unsupported filter property: ${prop}`);
|
|
866
959
|
}
|
|
867
960
|
|
|
@@ -893,7 +986,7 @@ class SnomedProvider extends BaseCSServices {
|
|
|
893
986
|
|
|
894
987
|
async filterMore(filterContext, set) {
|
|
895
988
|
set.cursor = set.cursor || 0;
|
|
896
|
-
|
|
989
|
+
this.#ensurePopulated(set);
|
|
897
990
|
const size = await this.filterSize(filterContext, set);
|
|
898
991
|
return set.cursor < size;
|
|
899
992
|
}
|
|
@@ -927,12 +1020,27 @@ class SnomedProvider extends BaseCSServices {
|
|
|
927
1020
|
}
|
|
928
1021
|
|
|
929
1022
|
const ctxt = conceptResult.context;
|
|
930
|
-
|
|
931
|
-
|
|
932
1023
|
const reference = ctxt.getReference();
|
|
933
1024
|
let found = false;
|
|
934
1025
|
|
|
935
|
-
if (set.
|
|
1026
|
+
if (set.inactive !== undefined) {
|
|
1027
|
+
let concept = this.sct.concepts.getConcept(reference);
|
|
1028
|
+
let active = (concept.flags & 0x0F) === 0;
|
|
1029
|
+
found = active !== set.inactive
|
|
1030
|
+
} else if (set.moduleId) {
|
|
1031
|
+
let concept = this.sct.concepts.getConcept(reference);
|
|
1032
|
+
let moduleId = this.sct.concepts.getModuleId(concept.index);
|
|
1033
|
+
found = moduleId === set.moduleId;
|
|
1034
|
+
} else if (set.propProp || set.propValue) {
|
|
1035
|
+
found = false;
|
|
1036
|
+
const relationships = this.sct.getConceptRelationships(reference);
|
|
1037
|
+
for (let relationshipRef of relationships) {
|
|
1038
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1039
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1040
|
+
found = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
} else if (set.matches && set.matches.length > 0) {
|
|
936
1044
|
found = set.matches.some(m => m.index === reference);
|
|
937
1045
|
} else if (set.members && set.members.length > 0) {
|
|
938
1046
|
found = set.members.some(m => m.ref === reference);
|
|
@@ -958,6 +1066,23 @@ class SnomedProvider extends BaseCSServices {
|
|
|
958
1066
|
}
|
|
959
1067
|
|
|
960
1068
|
const reference = concept.getReference();
|
|
1069
|
+
if (set.inactive !== undefined) {
|
|
1070
|
+
return this.sct.isActive(reference) !== set.inactive;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
if (set.moduleId) {
|
|
1074
|
+
return this.sct.concepts.getModuleId(reference) === set.moduleId;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
if (set.propProp || set.propValue) {
|
|
1078
|
+
const relationships = this.sct.getConceptRelationships(reference);
|
|
1079
|
+
for (let relationshipRef of relationships) {
|
|
1080
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1081
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
961
1086
|
|
|
962
1087
|
if (set.matches && set.matches.length > 0) {
|
|
963
1088
|
return set.matches.some(m => m.index === reference);
|
|
@@ -970,6 +1095,42 @@ class SnomedProvider extends BaseCSServices {
|
|
|
970
1095
|
return false;
|
|
971
1096
|
}
|
|
972
1097
|
|
|
1098
|
+
#ensurePopulated(set) {
|
|
1099
|
+
if (set.populationDone) {
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (set.inactive !== undefined && set.descendants.length === 0) {
|
|
1103
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1104
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1105
|
+
let active = (concept.flags & 0x0F) === 0;
|
|
1106
|
+
if (active !== set.inactive) {
|
|
1107
|
+
set.descendants.push(concept.index);
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
if (set.moduleId) {
|
|
1112
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1113
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1114
|
+
let moduleId = this.sct.concepts.getModuleId(concept.index);
|
|
1115
|
+
if (moduleId === set.moduleId) {
|
|
1116
|
+
set.descendants.push(concept.index);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
if (set.propProp || set.propValue) {
|
|
1121
|
+
for (let i = 0; i < this.sct.concepts.count(); i++) {
|
|
1122
|
+
let concept = this.sct.concepts.getConceptByCount(i);
|
|
1123
|
+
const relationships = this.sct.getConceptRelationships(concept.getReference());
|
|
1124
|
+
for (let relationshipRef of relationships) {
|
|
1125
|
+
const relationship = this.sct.relationships.getRelationship(relationshipRef);
|
|
1126
|
+
if (set.propProp === relationship.relType && set.propValue === relationship.target) {
|
|
1127
|
+
set.descendants.push(concept.index);
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
set.populationDone = true;
|
|
1133
|
+
}
|
|
973
1134
|
|
|
974
1135
|
// Search filter
|
|
975
1136
|
async searchFilter(filterContext, filter, sort) {
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
|
|
2
|
+
<div style="flex: 1;">
|
|
3
|
+
<h6>Memory Usage (Heap Used)</h6>
|
|
4
|
+
<canvas id="memoryChart" height="200"></canvas>
|
|
5
|
+
</div>
|
|
6
|
+
<div style="flex: 1;">
|
|
7
|
+
<h6>Turnaround Time</h6>
|
|
8
|
+
<canvas id="tatChart" height="200"></canvas>
|
|
9
|
+
</div>
|
|
10
|
+
<div style="flex: 1;">
|
|
11
|
+
<h6>Requests per Minute</h6>
|
|
12
|
+
<canvas id="requestChart" height="200"></canvas>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
17
|
+
<script>
|
|
18
|
+
(function() {
|
|
19
|
+
const historyData = {{ historyJson }};
|
|
20
|
+
const startTime = {{ startTime }};
|
|
21
|
+
|
|
22
|
+
function formatTime(timestamp) {
|
|
23
|
+
const d = new Date(timestamp);
|
|
24
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatMB(bytes) {
|
|
28
|
+
return (bytes / 1024 / 1024).toFixed(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function formatDuration(ms) {
|
|
32
|
+
const hours = ms / 3600000;
|
|
33
|
+
if (hours < 1) {
|
|
34
|
+
return Math.round(ms / 60000) + 'm ago';
|
|
35
|
+
}
|
|
36
|
+
return hours.toFixed(1) + 'h ago';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const chartOptions = {
|
|
40
|
+
responsive: false,
|
|
41
|
+
maintainAspectRatio: false,
|
|
42
|
+
scales: {
|
|
43
|
+
x: {
|
|
44
|
+
ticks: {
|
|
45
|
+
maxTicksAuto: 6
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
y: {
|
|
49
|
+
beginAtZero: true
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
plugins: {
|
|
53
|
+
legend: {
|
|
54
|
+
display: false
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Memory chart
|
|
60
|
+
new Chart(document.getElementById('memoryChart'), {
|
|
61
|
+
type: 'line',
|
|
62
|
+
data: {
|
|
63
|
+
labels: historyData.map(d => formatTime(d.time)),
|
|
64
|
+
datasets: [{
|
|
65
|
+
data: historyData.map(d => parseFloat(formatMB(d.mem))),
|
|
66
|
+
borderColor: '#007bff',
|
|
67
|
+
backgroundColor: 'rgba(0, 123, 255, 0.1)',
|
|
68
|
+
fill: true,
|
|
69
|
+
tension: 0.3,
|
|
70
|
+
pointRadius: 2
|
|
71
|
+
}]
|
|
72
|
+
},
|
|
73
|
+
options: {
|
|
74
|
+
...chartOptions,
|
|
75
|
+
scales: {
|
|
76
|
+
...chartOptions.scales,
|
|
77
|
+
y: {
|
|
78
|
+
...chartOptions.scales.y,
|
|
79
|
+
title: {
|
|
80
|
+
display: true,
|
|
81
|
+
text: 'MB (𝚫 from start)'
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Tat chart
|
|
90
|
+
new Chart(document.getElementById('tatChart'), {
|
|
91
|
+
type: 'line',
|
|
92
|
+
data: {
|
|
93
|
+
labels: historyData.map(d => formatTime(d.time)),
|
|
94
|
+
datasets: [{
|
|
95
|
+
data: historyData.map(d => d.tat),
|
|
96
|
+
borderColor: '#7b7b7b',
|
|
97
|
+
backgroundColor: 'rgba(123, 123, 123, 0.1)',
|
|
98
|
+
fill: true,
|
|
99
|
+
tension: 0.3,
|
|
100
|
+
pointRadius: 2
|
|
101
|
+
}]
|
|
102
|
+
},
|
|
103
|
+
options: {
|
|
104
|
+
...chartOptions,
|
|
105
|
+
scales: {
|
|
106
|
+
...chartOptions.scales,
|
|
107
|
+
y: {
|
|
108
|
+
...chartOptions.scales.y,
|
|
109
|
+
title: {
|
|
110
|
+
display: true,
|
|
111
|
+
text: 'ms'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Requests chart
|
|
119
|
+
new Chart(document.getElementById('requestChart'), {
|
|
120
|
+
type: 'line',
|
|
121
|
+
data: {
|
|
122
|
+
labels: historyData.map(d => formatTime(d.time)),
|
|
123
|
+
datasets: [{
|
|
124
|
+
data: historyData.map(d => d.rpm.toFixed(2)),
|
|
125
|
+
borderColor: '#28a745',
|
|
126
|
+
backgroundColor: 'rgba(40, 167, 69, 0.1)',
|
|
127
|
+
fill: true,
|
|
128
|
+
tension: 0.3,
|
|
129
|
+
pointRadius: 2
|
|
130
|
+
}]
|
|
131
|
+
},
|
|
132
|
+
options: {
|
|
133
|
+
...chartOptions,
|
|
134
|
+
scales: {
|
|
135
|
+
...chartOptions.scales,
|
|
136
|
+
y: {
|
|
137
|
+
...chartOptions.scales.y,
|
|
138
|
+
title: {
|
|
139
|
+
display: true,
|
|
140
|
+
text: 'req/min'
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
})();
|
|
147
|
+
</script>
|
|
@@ -428,7 +428,8 @@ class RxNormModule extends BaseTerminologyModule {
|
|
|
428
428
|
'CREATE INDEX IF NOT EXISTS idx_rxnrel_rel ON RXNREL(REL)',
|
|
429
429
|
'CREATE INDEX IF NOT EXISTS idx_rxnrel_rela ON RXNREL(RELA)',
|
|
430
430
|
'CREATE INDEX IF NOT EXISTS X_RXNSTY_2 ON RXNSTY(TUI)',
|
|
431
|
-
'CREATE INDEX IF NOT EXISTS idx_rxnstems_stem_cui ON RXNSTEMS(stem, CUI)'
|
|
431
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnstems_stem_cui ON RXNSTEMS(stem, CUI)',
|
|
432
|
+
'CREATE INDEX IF NOT EXISTS idx_rxnstems_cui_stem ON RXNSTEMS(CUI, stem)'
|
|
432
433
|
];
|
|
433
434
|
|
|
434
435
|
return new Promise((resolve, reject) => {
|
|
@@ -779,7 +780,7 @@ class RxNormImporter {
|
|
|
779
780
|
if (this.options.verbose) console.log('Generating word stems...');
|
|
780
781
|
|
|
781
782
|
// Simple English stemmer implementation
|
|
782
|
-
|
|
783
|
+
var natural = require('natural');
|
|
783
784
|
const stems = new Map();
|
|
784
785
|
|
|
785
786
|
// Get all RXNORM concepts
|
|
@@ -791,7 +792,7 @@ class RxNormImporter {
|
|
|
791
792
|
rows.forEach(row => {
|
|
792
793
|
const words = this.extractWords(row.STR);
|
|
793
794
|
words.forEach(word => {
|
|
794
|
-
const stem =
|
|
795
|
+
const stem = natural.PorterStemmer.stem(word.toLowerCase());
|
|
795
796
|
if (stem.length > 0 && stem.length <= 20) {
|
|
796
797
|
if (!stems.has(stem)) {
|
|
797
798
|
stems.set(stem, new Set());
|
|
@@ -864,33 +865,6 @@ class RxNormImporter {
|
|
|
864
865
|
}
|
|
865
866
|
}
|
|
866
867
|
|
|
867
|
-
// Simple English stemmer (basic implementation)
|
|
868
|
-
class SimpleStemmer {
|
|
869
|
-
constructor() {
|
|
870
|
-
// Common English suffixes to remove
|
|
871
|
-
this.suffixes = [
|
|
872
|
-
'ing', 'ly', 'ed', 'ies', 'ied', 'ies', 'ies', 'y', 's',
|
|
873
|
-
'tion', 'sion', 'ness', 'ment', 'able', 'ible', 'ant', 'ent'
|
|
874
|
-
].sort((a, b) => b.length - a.length); // Longest first
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
stem(word) {
|
|
878
|
-
if (word.length <= 3) return word;
|
|
879
|
-
|
|
880
|
-
// Try to remove suffixes
|
|
881
|
-
for (const suffix of this.suffixes) {
|
|
882
|
-
if (word.endsWith(suffix) && word.length > suffix.length + 2) {
|
|
883
|
-
const stem = word.substring(0, word.length - suffix.length);
|
|
884
|
-
// Basic validation - stem should still be reasonable length
|
|
885
|
-
if (stem.length >= 3) {
|
|
886
|
-
return stem;
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
return word;
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
868
|
|
|
895
869
|
module.exports = {
|
|
896
870
|
RxNormModule,
|
|
@@ -64,6 +64,14 @@ class CanonicalResource {
|
|
|
64
64
|
return this.version ? this.url+'|' + this.version : this.url;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
get vurlOrMsg() {
|
|
68
|
+
if (this.url) {
|
|
69
|
+
return this.version ? this.url+'|' + this.version : this.url;
|
|
70
|
+
} else {
|
|
71
|
+
return '(unidentified)';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
67
75
|
get fhirType() {
|
|
68
76
|
return this.jsonObj.resourceType;
|
|
69
77
|
}
|
package/tx/library/conceptmap.js
CHANGED
|
@@ -132,7 +132,9 @@ class ConceptMap extends CanonicalResource {
|
|
|
132
132
|
|
|
133
133
|
let all = this.canonicalMatches(targetScope, this.targetScope);
|
|
134
134
|
for (const g of this.jsonObj.group || []) {
|
|
135
|
-
|
|
135
|
+
const sourceOk = this.canonicalMatches(vurl, g.source);
|
|
136
|
+
const targetOk = !targetSystem || this.canonicalMatches(targetSystem, g.target);
|
|
137
|
+
if (all || (sourceOk && targetOk)) {
|
|
136
138
|
for (const em of g.element || []) {
|
|
137
139
|
if (em.code === coding.code) {
|
|
138
140
|
let match = {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { LanguagePartType, Languages, Language, LanguageDefinitions} = require('../../library/languages');
|
|
2
2
|
const {validateParameter, validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
|
|
3
|
-
|
|
3
|
+
const natural = require('natural');
|
|
4
4
|
/**
|
|
5
5
|
* Display checking modes for concept designations
|
|
6
6
|
*/
|
|
@@ -26,6 +26,7 @@ class SearchFilterText {
|
|
|
26
26
|
|
|
27
27
|
this.filter = filter ? filter.toLowerCase() : null;
|
|
28
28
|
this.stems = [];
|
|
29
|
+
this.stemmer = natural.PorterStemmer;
|
|
29
30
|
if (filter) {
|
|
30
31
|
this._process();
|
|
31
32
|
}
|
|
@@ -59,7 +60,7 @@ class SearchFilterText {
|
|
|
59
60
|
i++;
|
|
60
61
|
}
|
|
61
62
|
const word = value.substring(j, i).toLowerCase();
|
|
62
|
-
const stemmed = this.
|
|
63
|
+
const stemmed = this.stemmer.stem(word);
|
|
63
64
|
|
|
64
65
|
if (this._find(stemmed)) {
|
|
65
66
|
if (returnRating) {
|
|
@@ -142,7 +143,7 @@ class SearchFilterText {
|
|
|
142
143
|
i++;
|
|
143
144
|
}
|
|
144
145
|
const word = this.filter.substring(j, i);
|
|
145
|
-
this.stems.push(this.
|
|
146
|
+
this.stems.push(this.stemmer.stem(word.toLowerCase()));
|
|
146
147
|
} else {
|
|
147
148
|
i++;
|
|
148
149
|
}
|
|
@@ -155,11 +156,6 @@ class SearchFilterText {
|
|
|
155
156
|
return /[0-9a-zA-Z]/.test(char);
|
|
156
157
|
}
|
|
157
158
|
|
|
158
|
-
_stem(word) {
|
|
159
|
-
// Simple stemming - in practice you'd want a proper stemmer
|
|
160
|
-
return word.toLowerCase();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
159
|
_find(stem) {
|
|
164
160
|
// Binary search
|
|
165
161
|
let left = 0;
|
package/tx/library/renderer.js
CHANGED
|
@@ -1800,12 +1800,12 @@ class Renderer {
|
|
|
1800
1800
|
if (elem.noMap) {
|
|
1801
1801
|
const nomapComment = Extensions.readString(elem, 'http://hl7.org/fhir/StructureDefinition/conceptmap-nomap-comment');
|
|
1802
1802
|
if (!hasComment) {
|
|
1803
|
-
tr.td().colspan
|
|
1803
|
+
tr.td().setAttribute('colspan',"2").style("background-color: #efefef").tx("(not mapped)");
|
|
1804
1804
|
} else if (nomapComment) {
|
|
1805
|
-
tr.td().colspan
|
|
1805
|
+
tr.td().setAttribute('colspan',"2").style("background-color: #efefef").tx("(not mapped)");
|
|
1806
1806
|
tr.td().style("background-color: #efefef").tx(nomapComment);
|
|
1807
1807
|
} else {
|
|
1808
|
-
tr.td().colspan
|
|
1808
|
+
tr.td().setAttribute('colspan',"3").style("background-color: #efefef").tx("(not mapped)");
|
|
1809
1809
|
}
|
|
1810
1810
|
} else {
|
|
1811
1811
|
let first = true;
|
|
@@ -1858,16 +1858,16 @@ class Renderer {
|
|
|
1858
1858
|
let tr = tbl.tr();
|
|
1859
1859
|
const sourceColCount = 1 + Object.keys(sources).length - 1; // code + dependsOn attributes
|
|
1860
1860
|
const targetColCount = 1 + Object.keys(targets).length - 1; // code + product attributes
|
|
1861
|
-
tr.td().colspan
|
|
1861
|
+
tr.td().setAttribute('colspan', String(sourceColCount + 1)).b().tx(this.translate('CONC_MAP_SRC_DET'));
|
|
1862
1862
|
if (hasRelationships) {
|
|
1863
1863
|
tr.td().b().tx(this.translate('CONC_MAP_REL'));
|
|
1864
1864
|
}
|
|
1865
|
-
tr.td().colspan
|
|
1865
|
+
tr.td().setAttribute('colspan', String(targetColCount + 1)).b().tx(this.translate('CONC_MAP_TRGT_DET'));
|
|
1866
1866
|
if (hasComment) {
|
|
1867
1867
|
tr.td().b().tx(this.translate('GENERAL_COMMENT'));
|
|
1868
1868
|
}
|
|
1869
1869
|
if (hasProperties) {
|
|
1870
|
-
tr.td().colspan
|
|
1870
|
+
tr.td().setAttribute('colspan', String(Object.keys(props).length)).b().tx(this.translate('GENERAL_PROPS'));
|
|
1871
1871
|
}
|
|
1872
1872
|
|
|
1873
1873
|
// Second header row: actual column headers
|
|
@@ -1944,10 +1944,10 @@ class Renderer {
|
|
|
1944
1944
|
|
|
1945
1945
|
const nomapComment = Extensions.readString(elem, 'http://hl7.org/fhir/StructureDefinition/conceptmap-nomap-comment');
|
|
1946
1946
|
if (nomapComment) {
|
|
1947
|
-
tr.td().colspan
|
|
1947
|
+
tr.td().setAttribute('colspan',"3").style("background-color: #efefef").tx("(not mapped)");
|
|
1948
1948
|
tr.td().style("background-color: #efefef").tx(nomapComment);
|
|
1949
1949
|
} else {
|
|
1950
|
-
tr.td().colspan
|
|
1950
|
+
tr.td().setAttribute('colspan',"4").style("background-color: #efefef").tx("(not mapped)");
|
|
1951
1951
|
}
|
|
1952
1952
|
} else {
|
|
1953
1953
|
let first = true;
|
|
@@ -2072,7 +2072,7 @@ class Renderer {
|
|
|
2072
2072
|
async renderCSDetailsLink(tr, url, span2) {
|
|
2073
2073
|
const td = tr.td();
|
|
2074
2074
|
if (span2) {
|
|
2075
|
-
td.colspan
|
|
2075
|
+
td.setAttribute('colspan',"2");
|
|
2076
2076
|
}
|
|
2077
2077
|
td.b().tx(this.translate('CONC_MAP_CODES'));
|
|
2078
2078
|
td.tx(" " + this.translate('CONC_MAP_FRM') + " ");
|