fhirsmith 0.4.2 → 0.5.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 +12 -0
- package/README.md +1 -1
- package/library/cron-utilities.js +136 -0
- package/library/html-server.js +13 -29
- package/library/html.js +3 -8
- package/library/languages.js +160 -37
- package/library/package-manager.js +48 -1
- package/library/utilities.js +100 -19
- package/package.json +2 -2
- package/packages/package-crawler.js +6 -1
- package/packages/packages.js +38 -54
- package/publisher/publisher.js +19 -27
- package/registry/api.js +11 -10
- package/registry/crawler.js +31 -29
- package/registry/model.js +5 -26
- package/registry/registry.js +32 -41
- package/server.js +53 -5
- package/shl/shl.js +0 -18
- package/static/assets/js/statuspage.js +1 -9
- package/stats.js +39 -1
- package/token/token.js +14 -9
- package/translations/Messages.properties +2 -1
- package/tx/README.md +17 -6
- package/tx/cs/cs-api.js +19 -1
- package/tx/cs/cs-base.js +77 -0
- package/tx/cs/cs-country.js +46 -0
- package/tx/cs/cs-cpt.js +9 -5
- package/tx/cs/cs-cs.js +27 -13
- package/tx/cs/cs-lang.js +60 -22
- package/tx/cs/cs-loinc.js +69 -98
- package/tx/cs/cs-mimetypes.js +4 -0
- package/tx/cs/cs-ndc.js +6 -0
- package/tx/cs/cs-omop.js +16 -15
- package/tx/cs/cs-rxnorm.js +23 -1
- package/tx/cs/cs-snomed.js +283 -40
- package/tx/cs/cs-ucum.js +90 -70
- package/tx/importers/import-sct.module.js +371 -35
- package/tx/importers/readme.md +117 -7
- package/tx/library/bundle.js +5 -0
- package/tx/library/capabilitystatement.js +3 -142
- package/tx/library/codesystem.js +19 -173
- package/tx/library/conceptmap.js +4 -218
- package/tx/library/designations.js +14 -1
- package/tx/library/extensions.js +7 -0
- package/tx/library/namingsystem.js +3 -89
- package/tx/library/operation-outcome.js +8 -3
- package/tx/library/parameters.js +3 -2
- package/tx/library/renderer.js +10 -6
- package/tx/library/terminologycapabilities.js +3 -243
- package/tx/library/valueset.js +3 -235
- package/tx/library.js +100 -13
- package/tx/operation-context.js +23 -4
- package/tx/params.js +35 -38
- package/tx/provider.js +6 -5
- package/tx/sct/expressions.js +12 -3
- package/tx/tx-html.js +80 -89
- package/tx/tx.fhir.org.yml +6 -5
- package/tx/tx.js +163 -13
- package/tx/vs/vs-database.js +56 -39
- package/tx/vs/vs-package.js +21 -2
- package/tx/vs/vs-vsac.js +175 -39
- package/tx/workers/batch-validate.js +2 -0
- package/tx/workers/batch.js +2 -0
- package/tx/workers/expand.js +132 -112
- package/tx/workers/lookup.js +33 -14
- package/tx/workers/metadata.js +2 -2
- package/tx/workers/read.js +3 -2
- package/tx/workers/related.js +574 -0
- package/tx/workers/search.js +46 -9
- package/tx/workers/subsumes.js +13 -3
- package/tx/workers/translate.js +7 -3
- package/tx/workers/validate.js +258 -285
- package/tx/workers/worker.js +43 -39
- package/tx/xml/bundle-xml.js +237 -0
- package/tx/xml/xml-base.js +215 -64
- package/tx/xversion/xv-bundle.js +71 -0
- package/tx/xversion/xv-capabiliityStatement.js +137 -0
- package/tx/xversion/xv-codesystem.js +169 -0
- package/tx/xversion/xv-conceptmap.js +224 -0
- package/tx/xversion/xv-namingsystem.js +88 -0
- package/tx/xversion/xv-operationoutcome.js +27 -0
- package/tx/xversion/xv-parameters.js +87 -0
- package/tx/xversion/xv-resource.js +45 -0
- package/tx/xversion/xv-terminologyCapabilities.js +214 -0
- package/tx/xversion/xv-valueset.js +234 -0
- package/utilities/dev-proxy-server.js +126 -0
- package/utilities/explode-results.js +58 -0
- package/utilities/split-by-system.js +198 -0
- package/utilities/vsac-cs-fetcher.js +0 -0
- package/{windows-install.js → utilities/windows-install.js} +2 -0
- package/vcl/vcl.js +0 -18
- package/xig/xig.js +108 -99
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createReadStream, writeFileSync } from 'fs';
|
|
2
|
+
import { createInterface } from 'readline';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
4
|
+
|
|
5
|
+
const inputFile = '/Users/grahamegrieve/temp/tx-comp/comparison.ndjson';
|
|
6
|
+
const outDir = '/Users/grahamegrieve/temp/tx-comp/';
|
|
7
|
+
|
|
8
|
+
// Map well-known system URLs to short names
|
|
9
|
+
const systemNames = {
|
|
10
|
+
'http://snomed.info/sct': 'snomed',
|
|
11
|
+
'http://loinc.org': 'loinc',
|
|
12
|
+
'http://unitsofmeasure.org': 'ucum',
|
|
13
|
+
'http://hl7.org/fhir/sid/icd-10': 'icd10',
|
|
14
|
+
'http://hl7.org/fhir/sid/icd-10-cm': 'icd10cm',
|
|
15
|
+
'http://hl7.org/fhir/sid/icd-9-cm': 'icd9cm',
|
|
16
|
+
'http://www.nlm.nih.gov/research/umls/rxnorm': 'rxnorm',
|
|
17
|
+
'http://hl7.org/fhir/sid/ndc': 'ndc',
|
|
18
|
+
'http://www.ama-assn.org/go/cpt': 'cpt',
|
|
19
|
+
'urn:ietf:bcp:13': 'mimetypes',
|
|
20
|
+
'urn:ietf:bcp:47': 'bcp47',
|
|
21
|
+
'urn:iso:std:iso:3166': 'iso3166',
|
|
22
|
+
'urn:iso:std:iso:4217': 'iso4217',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
let unknownCounter = 0;
|
|
26
|
+
const unknownMap = new Map(); // url -> assigned name
|
|
27
|
+
|
|
28
|
+
function nameForSystem(url) {
|
|
29
|
+
if (!url) return null;
|
|
30
|
+
// exact match
|
|
31
|
+
if (systemNames[url]) return systemNames[url];
|
|
32
|
+
// check if it starts with a known prefix (for versioned URLs like snomed with editions)
|
|
33
|
+
for (const [key, name] of Object.entries(systemNames)) {
|
|
34
|
+
if (url.startsWith(key)) return name;
|
|
35
|
+
}
|
|
36
|
+
// try to derive a name from the URL
|
|
37
|
+
if (url.startsWith('http://hl7.org/fhir/')) {
|
|
38
|
+
// e.g. http://hl7.org/fhir/administrative-gender -> administrative-gender
|
|
39
|
+
const parts = url.replace('http://hl7.org/fhir/', '').split('/');
|
|
40
|
+
const last = parts[parts.length - 1];
|
|
41
|
+
if (last && last.length > 0 && last.length < 60) return 'fhir-' + last;
|
|
42
|
+
}
|
|
43
|
+
if (url.startsWith('http://terminology.hl7.org/')) {
|
|
44
|
+
const parts = url.replace('http://terminology.hl7.org/', '').split('/');
|
|
45
|
+
const last = parts[parts.length - 1];
|
|
46
|
+
if (last && last.length > 0 && last.length < 60) return 'tho-' + last;
|
|
47
|
+
}
|
|
48
|
+
if (url.startsWith('http://hl7.org/fhir/v2/')) {
|
|
49
|
+
return 'v2-' + url.replace('http://hl7.org/fhir/v2/', '').replace(/\//g, '-');
|
|
50
|
+
}
|
|
51
|
+
if (url.startsWith('http://hl7.org/fhir/v3/') || url.startsWith('http://terminology.hl7.org/CodeSystem/v3-')) {
|
|
52
|
+
const tail = url.includes('v3/') ? url.split('v3/').pop() : url.split('v3-').pop();
|
|
53
|
+
return 'v3-' + tail.replace(/\//g, '-');
|
|
54
|
+
}
|
|
55
|
+
// fall back to numbered
|
|
56
|
+
if (unknownMap.has(url)) return unknownMap.get(url);
|
|
57
|
+
unknownCounter++;
|
|
58
|
+
const name = 'n' + String(unknownCounter).padStart(3, '0');
|
|
59
|
+
unknownMap.set(url, name);
|
|
60
|
+
return name;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function extractSystems(line) {
|
|
64
|
+
let obj;
|
|
65
|
+
try { obj = JSON.parse(line); } catch { return null; }
|
|
66
|
+
|
|
67
|
+
const systems = new Set();
|
|
68
|
+
const reqBody = obj.requestBody;
|
|
69
|
+
if (!reqBody) return { systems: new Set(), obj };
|
|
70
|
+
|
|
71
|
+
let req;
|
|
72
|
+
try { req = JSON.parse(reqBody); } catch { return { systems: new Set(), obj }; }
|
|
73
|
+
|
|
74
|
+
// Walk the parameters looking for system values, codings, codeableConcepts
|
|
75
|
+
if (req.parameter) {
|
|
76
|
+
for (const p of req.parameter) {
|
|
77
|
+
if (p.name === 'system' && p.valueUri) {
|
|
78
|
+
systems.add(p.valueUri);
|
|
79
|
+
}
|
|
80
|
+
if (p.name === 'coding' && p.valueCoding?.system) {
|
|
81
|
+
systems.add(p.valueCoding.system);
|
|
82
|
+
}
|
|
83
|
+
if (p.name === 'codeableConcept' && p.valueCodeableConcept?.coding) {
|
|
84
|
+
for (const c of p.valueCodeableConcept.coding) {
|
|
85
|
+
if (c.system) systems.add(c.system);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (p.name === 'url' && p.valueUri) {
|
|
89
|
+
// This is a ValueSet URL, not a system - skip
|
|
90
|
+
}
|
|
91
|
+
// For batch-validate, look inside nested resources
|
|
92
|
+
if (p.name === 'validation' && p.resource?.parameter) {
|
|
93
|
+
for (const inner of p.resource.parameter) {
|
|
94
|
+
if (inner.name === 'system' && inner.valueUri) systems.add(inner.valueUri);
|
|
95
|
+
if (inner.name === 'coding' && inner.valueCoding?.system) systems.add(inner.valueCoding.system);
|
|
96
|
+
if (inner.name === 'codeableConcept' && inner.valueCodeableConcept?.coding) {
|
|
97
|
+
for (const c of inner.valueCodeableConcept.coding) {
|
|
98
|
+
if (c.system) systems.add(c.system);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Also check if it's a Bundle (batch)
|
|
107
|
+
if (req.entry) {
|
|
108
|
+
for (const entry of req.entry) {
|
|
109
|
+
const res = entry.resource;
|
|
110
|
+
if (res?.parameter) {
|
|
111
|
+
for (const p of res.parameter) {
|
|
112
|
+
if (p.name === 'system' && p.valueUri) systems.add(p.valueUri);
|
|
113
|
+
if (p.name === 'coding' && p.valueCoding?.system) systems.add(p.valueCoding.system);
|
|
114
|
+
if (p.name === 'codeableConcept' && p.valueCodeableConcept?.coding) {
|
|
115
|
+
for (const c of p.valueCodeableConcept.coding) {
|
|
116
|
+
if (c.system) systems.add(c.system);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return { systems, obj };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
async function run() {
|
|
128
|
+
const seenHashes = new Set();
|
|
129
|
+
const fileLines = new Map(); // filename -> lines[]
|
|
130
|
+
let totalLines = 0;
|
|
131
|
+
let dupes = 0;
|
|
132
|
+
let noSystem = 0;
|
|
133
|
+
|
|
134
|
+
const rl = createInterface({
|
|
135
|
+
input: createReadStream(inputFile),
|
|
136
|
+
crlfDelay: Infinity
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
for await (const line of rl) {
|
|
140
|
+
if (!line.trim()) continue;
|
|
141
|
+
totalLines++;
|
|
142
|
+
|
|
143
|
+
// Hash the requestBody for dedup (not the whole line, since prod/dev responses differ)
|
|
144
|
+
let parsed;
|
|
145
|
+
try { parsed = JSON.parse(line); } catch { continue; }
|
|
146
|
+
|
|
147
|
+
const hashInput = (parsed.method || '') + '|' + (parsed.url || '') + '|' + (parsed.requestBody || '');
|
|
148
|
+
const hash = createHash('md5').update(hashInput).digest('hex');
|
|
149
|
+
|
|
150
|
+
if (seenHashes.has(hash)) {
|
|
151
|
+
dupes++;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
seenHashes.add(hash);
|
|
155
|
+
|
|
156
|
+
const result = extractSystems(line);
|
|
157
|
+
if (!result) continue;
|
|
158
|
+
|
|
159
|
+
const { systems } = result;
|
|
160
|
+
|
|
161
|
+
let filename;
|
|
162
|
+
if (systems.size === 0) {
|
|
163
|
+
noSystem++;
|
|
164
|
+
filename = 'system-unknown.ndjson';
|
|
165
|
+
} else if (systems.size === 1) {
|
|
166
|
+
const sysUrl = [...systems][0];
|
|
167
|
+
const name = nameForSystem(sysUrl);
|
|
168
|
+
filename = `system-${name}.ndjson`;
|
|
169
|
+
} else {
|
|
170
|
+
filename = 'system-multiple.ndjson';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!fileLines.has(filename)) fileLines.set(filename, []);
|
|
174
|
+
fileLines.get(filename).push(line);
|
|
175
|
+
|
|
176
|
+
if (totalLines % 50000 === 0) {
|
|
177
|
+
console.log(` processed ${totalLines} lines...`);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Write all files
|
|
182
|
+
for (const [filename, lines] of fileLines) {
|
|
183
|
+
const path = outDir + filename;
|
|
184
|
+
writeFileSync(path, lines.join('\n') + '\n');
|
|
185
|
+
console.log(` ${filename}: ${lines.length} entries`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
console.log(`\nDone. ${totalLines} total lines, ${dupes} duplicates removed, ${noSystem} with no system found.`);
|
|
189
|
+
|
|
190
|
+
// Write the unknown system mapping
|
|
191
|
+
if (unknownMap.size > 0) {
|
|
192
|
+
const mapping = Object.fromEntries(unknownMap);
|
|
193
|
+
writeFileSync(outDir + 'system-mapping.json', JSON.stringify(mapping, null, 2));
|
|
194
|
+
console.log(`\nUnknown system mapping written to system-mapping.json (${unknownMap.size} systems)`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
run().catch(e => { console.error(e); process.exit(1); });
|
|
File without changes
|
package/vcl/vcl.js
CHANGED
|
@@ -112,24 +112,6 @@ class VCLModule {
|
|
|
112
112
|
};
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Enhanced HTML escaping
|
|
116
|
-
escapeHtml(str) {
|
|
117
|
-
if (!str || typeof str !== 'string') return '';
|
|
118
|
-
|
|
119
|
-
const escapeMap = {
|
|
120
|
-
'&': '&',
|
|
121
|
-
'<': '<',
|
|
122
|
-
'>': '>',
|
|
123
|
-
'"': '"',
|
|
124
|
-
"'": ''',
|
|
125
|
-
'/': '/',
|
|
126
|
-
'`': '`',
|
|
127
|
-
'=': '='
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
return str.replace(/[&<>"'`=/]/g, (match) => escapeMap[match]);
|
|
131
|
-
}
|
|
132
|
-
|
|
133
115
|
// VCL expression validation
|
|
134
116
|
validateVCLInput(vcl) {
|
|
135
117
|
if (!vcl || typeof vcl !== 'string') {
|