fhirsmith 0.13.3 → 0.14.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 +47 -1
- package/README.md +48 -4
- package/config-template.json +70 -1
- package/library/cgroup-memory.js +113 -0
- package/library/folder-setup.js +8 -1
- package/nginx.md +391 -0
- package/package.json +3 -2
- package/publisher/git-remote.js +141 -0
- package/publisher/github-release.js +195 -0
- package/publisher/publisher.js +146 -14
- package/server.js +51 -14
- package/stats.js +19 -13
- package/testing/readme.md +112 -0
- package/testing/render.js +482 -0
- package/testing/search.js +378 -0
- package/testing/store.js +266 -0
- package/testing/testing-template.html +126 -0
- package/testing/testing.js +486 -0
- package/translations/Messages.properties +32 -0
- package/tx/closure/closure-store.js +396 -0
- package/tx/cs/cs-api.js +6 -1
- package/tx/cs/cs-rxnorm.js +35 -8
- package/tx/cs/cs-snomed.js +142 -27
- package/tx/html/codesystem-operations.liquid +177 -0
- package/tx/html/ecl-panel.liquid +93 -0
- package/tx/html/home-metrics.liquid +39 -76
- package/tx/html/tx-template.html +1 -0
- package/tx/library/conceptmap.js +22 -6
- package/tx/library/designations.js +12 -3
- package/tx/library/extensions.js +2 -2
- package/tx/library/operation-outcome.js +3 -1
- package/tx/library/renderer.js +42 -5
- package/tx/operation-context.js +111 -15
- package/tx/provider.js +85 -0
- package/tx/sct/expressions.js +128 -44
- package/tx/sct/text-index.js +419 -0
- package/tx/tx-html.js +426 -44
- package/tx/tx.js +117 -9
- package/tx/workers/cache-control.js +2 -2
- package/tx/workers/closure.js +483 -13
- package/tx/workers/expand.js +20 -0
- package/tx/workers/metadata.js +29 -4
- package/tx/workers/read.js +18 -3
- package/tx/workers/subsumes.js +18 -2
- package/tx/workers/translate.js +167 -20
- package/tx/workers/validate.js +61 -182
- package/tx/workers/worker.js +9 -0
- package/tx/xversion/xv-conceptmap.js +17 -8
- package/utilities/submit-test-report.js +150 -0
|
@@ -263,16 +263,23 @@ function conceptMapR5ToR3(r5Obj) {
|
|
|
263
263
|
* @returns {string} R5 relationship value
|
|
264
264
|
* @private
|
|
265
265
|
*/
|
|
266
|
+
// The two vocabularies name the relationship from opposite ends. R3/R4 equivalence
|
|
267
|
+
// describes the TARGET ('wider' = the target is wider than the source); R5 relationship
|
|
268
|
+
// describes the SOURCE ('source-is-narrower-than-target'). So wider <-> narrower-than-
|
|
269
|
+
// target, and narrower <-> broader-than-target. These tables follow the Java convertors
|
|
270
|
+
// (ConceptMap30_50 / ConceptMap40_50), which are the reference.
|
|
266
271
|
function convertEquivalenceToRelationship(equivalence) {
|
|
267
272
|
const equivalenceToRelationship = {
|
|
268
273
|
'relatedto': 'related-to',
|
|
269
274
|
'equivalent': 'equivalent',
|
|
270
275
|
'equal': 'equivalent',
|
|
271
|
-
'wider': 'source-is-
|
|
272
|
-
'subsumes': 'source-is-
|
|
273
|
-
'narrower': 'source-is-
|
|
274
|
-
'specializes': 'source-is-
|
|
275
|
-
'inexact': '
|
|
276
|
+
'wider': 'source-is-narrower-than-target',
|
|
277
|
+
'subsumes': 'source-is-narrower-than-target',
|
|
278
|
+
'narrower': 'source-is-broader-than-target',
|
|
279
|
+
'specializes': 'source-is-broader-than-target',
|
|
280
|
+
'inexact': 'related-to',
|
|
281
|
+
// Java gives no relationship for 'unmatched' (a target-less 'unmatched' is lifted to
|
|
282
|
+
// element.noMap before this is reached); R5 requires one, so not-related-to it is
|
|
276
283
|
'unmatched': 'not-related-to',
|
|
277
284
|
'disjoint': 'not-related-to'
|
|
278
285
|
};
|
|
@@ -289,9 +296,11 @@ function convertRelationshipToEquivalence(relationship) {
|
|
|
289
296
|
const relationshipToEquivalence = {
|
|
290
297
|
'related-to': 'relatedto',
|
|
291
298
|
'equivalent': 'equivalent',
|
|
292
|
-
'source-is-
|
|
293
|
-
'source-is-
|
|
294
|
-
'not-related-to
|
|
299
|
+
'source-is-narrower-than-target': 'wider',
|
|
300
|
+
'source-is-broader-than-target': 'narrower',
|
|
301
|
+
// 'unmatched' is reserved for "no map" (see conceptMapToR5); a real not-related-to
|
|
302
|
+
// target is disjoint, as in the Java convertors
|
|
303
|
+
'not-related-to': 'disjoint'
|
|
295
304
|
};
|
|
296
305
|
return relationshipToEquivalence[relationship] || 'relatedto';
|
|
297
306
|
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
//
|
|
3
|
+
// Copyright 2026, Health Intersections Pty Ltd (http://www.healthintersections.com.au)
|
|
4
|
+
//
|
|
5
|
+
// Licensed under BSD-3: https://opensource.org/license/bsd-3-clause
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Submit the tx test report (test-cases-report.json, written by the tx test run) to a
|
|
10
|
+
* FHIRsmith /testing server.
|
|
11
|
+
*
|
|
12
|
+
* node utilities/submit-test-report.js [-server url] [-file path] [-token token] [-header name]
|
|
13
|
+
*
|
|
14
|
+
* -server the /testing base; default https://testing.fhir.org/testing
|
|
15
|
+
* -file default test-cases-report.json in the FHIRsmith root
|
|
16
|
+
* -token if the server requires one; or set FHIRSMITH_TESTING_TOKEN
|
|
17
|
+
* -header the header the token goes in; default Authorization (sent as "Bearer <token>")
|
|
18
|
+
*
|
|
19
|
+
* Exits 0 when the server accepted the report, 1 otherwise.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const DEFAULT_SERVER = 'https://testing.fhir.org/testing';
|
|
26
|
+
const DEFAULT_FILE = path.join(__dirname, '..', 'test-cases-report.json');
|
|
27
|
+
|
|
28
|
+
function usage(msg) {
|
|
29
|
+
if (msg) {
|
|
30
|
+
console.error(msg);
|
|
31
|
+
}
|
|
32
|
+
console.error('Usage: node utilities/submit-test-report.js [-server url] [-file path] [-token token] [-header name]');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseArgs(argv) {
|
|
37
|
+
const opts = {
|
|
38
|
+
server: DEFAULT_SERVER,
|
|
39
|
+
file: DEFAULT_FILE,
|
|
40
|
+
token: process.env.FHIRSMITH_TESTING_TOKEN || null,
|
|
41
|
+
header: 'Authorization'
|
|
42
|
+
};
|
|
43
|
+
for (let i = 0; i < argv.length; i++) {
|
|
44
|
+
const a = argv[i].replace(/^--?/, '');
|
|
45
|
+
if (a === 'help' || a === 'h') {
|
|
46
|
+
usage();
|
|
47
|
+
}
|
|
48
|
+
if (!['server', 'file', 'token', 'header'].includes(a)) {
|
|
49
|
+
usage(`Unknown parameter: ${argv[i]}`);
|
|
50
|
+
}
|
|
51
|
+
if (i + 1 >= argv.length) {
|
|
52
|
+
usage(`Missing value for ${argv[i]}`);
|
|
53
|
+
}
|
|
54
|
+
opts[a] = argv[++i];
|
|
55
|
+
}
|
|
56
|
+
return opts;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The TestReport endpoint for a server base, whether or not it was given with /TestReport. */
|
|
60
|
+
function endpoint(server) {
|
|
61
|
+
const base = server.replace(/\/+$/, '');
|
|
62
|
+
return /\/TestReport$/.test(base) ? base : base + '/TestReport';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Warn if the report doesn't describe the version being released. */
|
|
66
|
+
function checkVersion(report) {
|
|
67
|
+
const pkg = require('../package.json');
|
|
68
|
+
const fhirsmith = (report.participant || []).find(p => p && p.uri === 'https://github.com/HealthIntersections/FHIRsmith');
|
|
69
|
+
const reported = fhirsmith && typeof fhirsmith.version === 'string' ? fhirsmith.version : null;
|
|
70
|
+
if (reported && reported !== pkg.version) {
|
|
71
|
+
console.warn(`Warning: the report is from FHIRsmith v${reported}, but package.json is v${pkg.version}`);
|
|
72
|
+
} else if (reported && /snapshot/i.test(reported)) {
|
|
73
|
+
console.warn(`Warning: the report is from a snapshot (v${reported}), not a release`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function describeOutcome(text) {
|
|
78
|
+
try {
|
|
79
|
+
const oo = JSON.parse(text);
|
|
80
|
+
if (oo.resourceType === 'OperationOutcome' && Array.isArray(oo.issue)) {
|
|
81
|
+
return oo.issue.map(i => ` ${i.severity}: ${(i.details && i.details.text) || i.diagnostics || i.code}`).join('\n');
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// not JSON - fall through to the raw text
|
|
85
|
+
}
|
|
86
|
+
return ' ' + text.substring(0, 1000);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function main() {
|
|
90
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
91
|
+
|
|
92
|
+
if (!fs.existsSync(opts.file)) {
|
|
93
|
+
console.error(`No report at ${opts.file} - run the tx tests first (npx jest tests/tx/test-cases.test.js)`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
const body = fs.readFileSync(opts.file, 'utf8');
|
|
97
|
+
let report;
|
|
98
|
+
try {
|
|
99
|
+
report = JSON.parse(body);
|
|
100
|
+
} catch (e) {
|
|
101
|
+
console.error(`${opts.file} is not valid JSON: ${e.message}`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
if (report.resourceType !== 'TestReport') {
|
|
105
|
+
console.error(`${opts.file} is not a TestReport`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
checkVersion(report);
|
|
109
|
+
|
|
110
|
+
const url = endpoint(opts.server);
|
|
111
|
+
const headers = {
|
|
112
|
+
'Content-Type': 'application/fhir+json',
|
|
113
|
+
'Accept': 'application/fhir+json',
|
|
114
|
+
'Prefer': 'return=minimal'
|
|
115
|
+
};
|
|
116
|
+
if (opts.token) {
|
|
117
|
+
headers[opts.header] = opts.header.toLowerCase() === 'authorization' ? `Bearer ${opts.token}` : opts.token;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
console.log(`Submitting ${path.basename(opts.file)} (${report.result}, ${Array.isArray(report.test) ? report.test.length : 0} tests) to ${url}`);
|
|
121
|
+
let res;
|
|
122
|
+
try {
|
|
123
|
+
res = await fetch(url, { method: 'POST', headers, body });
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(`Could not reach ${url}: ${e.cause ? e.cause.message : e.message}`);
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
const text = await res.text();
|
|
129
|
+
if (res.status !== 201) {
|
|
130
|
+
console.error(`The server refused the report: HTTP ${res.status}`);
|
|
131
|
+
if (text) {
|
|
132
|
+
console.error(describeOutcome(text));
|
|
133
|
+
}
|
|
134
|
+
if (res.status === 401 && !opts.token) {
|
|
135
|
+
console.error('The server requires a token: use -token, or set FHIRSMITH_TESTING_TOKEN');
|
|
136
|
+
}
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
const location = res.headers.get('location');
|
|
140
|
+
console.log(`Accepted: ${location || '(no Location returned)'}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (require.main === module) {
|
|
144
|
+
main().catch(e => {
|
|
145
|
+
console.error(e.stack || e.message);
|
|
146
|
+
process.exit(1);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { parseArgs, endpoint };
|