fhirsmith 0.8.6 → 0.9.1
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 -0
- package/library/regex-utilities.js +13 -0
- package/package.json +3 -2
- package/publisher/publisher.js +28 -7
- package/root-bare-template.html +60 -55
- package/translations/Messages.properties +3 -2
- package/tx/cs/cs-country.js +2 -1
- package/tx/cs/cs-cs.js +36 -8
- package/tx/cs/cs-loinc.js +2 -1
- package/tx/cs/cs-snomed.js +1 -1
- package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
- package/tx/importers/atc-to-fhir.js +27 -27
- package/tx/library/extensions.js +7 -1
- package/tx/library/renderer.js +11 -2
- package/tx/library/ucum-parsers.js +2 -1
- package/tx/ocl/cs-ocl.cjs +48 -15
- package/tx/ocl/vs-ocl.cjs +57 -34
- package/tx/tx.fhir.org.yml +4 -4
- package/tx/vs/vs-database.js +150 -100
- package/tx/vs/vs-vsac.js +92 -32
- package/tx/workers/expand.js +5 -1
- package/tx/workers/metadata.js +3 -1
- package/tx/workers/validate.js +1 -1
- package/tx/xversion/xv-valueset.js +28 -8
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,53 @@ All notable changes to the Health Intersections Node Server will be documented i
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [v0.9.1] - 2026-04-10
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- TX: Add support for child-of filters
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- TX: increase vsac timeout
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Tidy up dashboard
|
|
21
|
+
- TX: fix bug listing versions when validating
|
|
22
|
+
- Fix support for child-of in R4/R3
|
|
23
|
+
|
|
24
|
+
### Tx Conformance Statement
|
|
25
|
+
|
|
26
|
+
FHIRsmith passed all 1578 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1, runner v6.9.5)
|
|
27
|
+
|
|
28
|
+
## [v0.9.0] - 2026-04-09
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
|
|
32
|
+
- TX: VSAC upgrade to pick up more changes
|
|
33
|
+
- TX: add definition of $related operation to CapabilityStatement
|
|
34
|
+
|
|
35
|
+
### Changed
|
|
36
|
+
|
|
37
|
+
- TX: Deal with regex Denial of Service Issue
|
|
38
|
+
- TX: improve fragment handling in extensions per TI decision
|
|
39
|
+
- TX: Reduce snomed loaded versions - have already moved to affiliate managed servers
|
|
40
|
+
- TX: fix bug handling excluded concepts using a filter
|
|
41
|
+
- improve dashboard template
|
|
42
|
+
|
|
43
|
+
### Fixed
|
|
44
|
+
|
|
45
|
+
- Update dependencies for security fixes
|
|
46
|
+
- TX: fix error in SNOMED translate for implicit concept maps
|
|
47
|
+
- TX: Fix OCL cache invalidation and case-insensitive concept lookups
|
|
48
|
+
- Publisher: fix handling of web templates folder
|
|
49
|
+
- Publisher: fix webtemplates table headings
|
|
50
|
+
|
|
51
|
+
### Tx Conformance Statement
|
|
52
|
+
|
|
53
|
+
FHIRsmith passed all 1578 HL7 terminology service tests (modes tx.fhir.org+omop+general+snomed, tests v1.9.1, runner v6.9.5)
|
|
54
|
+
|
|
8
55
|
## [v0.8.6] - 2026-04-06
|
|
9
56
|
|
|
10
57
|
### Added
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const { RE2 } = require('re2-wasm');
|
|
2
|
+
|
|
3
|
+
class RegExUtilities {
|
|
4
|
+
|
|
5
|
+
compile(pattern, flags) {
|
|
6
|
+
// RE2 requires the unicode flag; add it if not already present
|
|
7
|
+
const re2Flags = flags && flags.includes('u') ? flags : (flags || '') + 'u';
|
|
8
|
+
return new RE2(pattern, re2Flags);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = new RegExUtilities();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fhirsmith",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"txVersion": "1.9.1",
|
|
5
5
|
"description": "A Node.js server that provides a collection of tools to serve the FHIR ecosystem",
|
|
6
6
|
"main": "server.js",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"fs-extra": "^11.3.3",
|
|
52
52
|
"ini": "^6.0.0",
|
|
53
53
|
"inquirer": "^8.2.5",
|
|
54
|
-
"liquidjs": "^10.
|
|
54
|
+
"liquidjs": "^10.25.5",
|
|
55
55
|
"lusca": "^1.7.0",
|
|
56
56
|
"natural": "^6.12.0",
|
|
57
57
|
"node-cron": "^3.0.3",
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"passport-github2": "^0.1.12",
|
|
62
62
|
"passport-google-oauth20": "^2.0.0",
|
|
63
63
|
"properties-file": "^3.6.4",
|
|
64
|
+
"re2-wasm": "^1.0.2",
|
|
64
65
|
"rimraf": "^5.0.10",
|
|
65
66
|
"sqlite3": "^5.1.7",
|
|
66
67
|
"tar": "^7.5.7",
|
package/publisher/publisher.js
CHANGED
|
@@ -714,17 +714,38 @@ class PublisherModule {
|
|
|
714
714
|
|
|
715
715
|
// Step 1: Clone supporting repositories into the task directory
|
|
716
716
|
const registryDir = path.join(taskDir, 'ig-registry');
|
|
717
|
-
const historyDir = path.join(taskDir, 'fhir-ig-history-template');
|
|
718
|
-
const templatesDir = path.join(taskDir, 'fhir-web-templates');
|
|
719
717
|
|
|
720
718
|
await this.runCommand('git', ['clone', 'git@github.com:FHIR/ig-registry.git', registryDir],
|
|
721
719
|
{}, task.id, 'Cloning ig-registry');
|
|
722
720
|
|
|
723
|
-
|
|
724
|
-
|
|
721
|
+
// Use website-configured history templates path if provided, otherwise clone the default repo
|
|
722
|
+
let historyDir;
|
|
723
|
+
if (website.history_templates) {
|
|
724
|
+
historyDir = website.history_templates;
|
|
725
|
+
await this.logTaskMessage(task.id, 'info', 'Using configured history templates: ' + historyDir);
|
|
726
|
+
if (!fs.existsSync(historyDir)) {
|
|
727
|
+
throw new Error('Configured history_templates path does not exist: ' + historyDir);
|
|
728
|
+
}
|
|
729
|
+
} else {
|
|
730
|
+
historyDir = path.join(taskDir, 'fhir-ig-history-template');
|
|
731
|
+
await this.runCommand('git', ['clone', 'https://github.com/HL7/fhir-ig-history-template.git', historyDir],
|
|
732
|
+
{}, task.id, 'Cloning fhir-ig-history-template');
|
|
733
|
+
}
|
|
725
734
|
|
|
726
|
-
|
|
727
|
-
|
|
735
|
+
// Use website-configured web templates path if provided, otherwise clone the default repo.
|
|
736
|
+
// This allows pointing to a subdirectory of the templates repo for different target websites.
|
|
737
|
+
let templatesDir;
|
|
738
|
+
if (website.web_templates) {
|
|
739
|
+
templatesDir = website.web_templates;
|
|
740
|
+
await this.logTaskMessage(task.id, 'info', 'Using configured web templates: ' + templatesDir);
|
|
741
|
+
if (!fs.existsSync(templatesDir)) {
|
|
742
|
+
throw new Error('Configured web_templates path does not exist: ' + templatesDir);
|
|
743
|
+
}
|
|
744
|
+
} else {
|
|
745
|
+
templatesDir = path.join(taskDir, 'fhir-web-templates');
|
|
746
|
+
await this.runCommand('git', ['clone', 'https://github.com/HL7/fhir-web-templates.git', templatesDir],
|
|
747
|
+
{}, task.id, 'Cloning fhir-web-templates');
|
|
748
|
+
}
|
|
728
749
|
|
|
729
750
|
// Step 2: Reuse the publisher.jar from the draft build
|
|
730
751
|
const publisherJar = path.join(taskDir, 'publisher.jar');
|
|
@@ -1853,7 +1874,7 @@ class PublisherModule {
|
|
|
1853
1874
|
} else {
|
|
1854
1875
|
content += '<div class="table-responsive">';
|
|
1855
1876
|
content += '<table class="table table-striped">';
|
|
1856
|
-
content += '<thead><tr><th>Name</th><th>Local Folder</th><th>Git Root</th><th>Update Script</th><th>Active</th><th>Created</th><th>Actions</th></tr></thead>';
|
|
1877
|
+
content += '<thead><tr><th>Name</th><th>Local Folder</th><th>Git Root</th><th>History Templates</th><th>Web Templates</th><th>Update Script</th><th>Active</th><th>Created</th><th>Actions</th></tr></thead>';
|
|
1857
1878
|
content += '<tbody>';
|
|
1858
1879
|
|
|
1859
1880
|
websites.forEach(website => {
|
package/root-bare-template.html
CHANGED
|
@@ -9,68 +9,73 @@
|
|
|
9
9
|
<meta content="http://hl7.org/fhir" name="author"/>
|
|
10
10
|
<meta charset="utf-8" http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
12
|
+
<style>
|
|
13
|
+
html {
|
|
14
|
+
font-family: sans-serif;
|
|
15
|
+
-webkit-text-size-adjust: 100%;
|
|
16
|
+
-ms-text-size-adjust: 100%;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
body {
|
|
20
|
+
margin: 0;
|
|
21
|
+
font-size: 12px;
|
|
22
|
+
color: #333;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
h2 {
|
|
26
|
+
font-size: 20px;
|
|
27
|
+
margin: 0.67em 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
h6 {
|
|
31
|
+
font-size: 12px;
|
|
32
|
+
margin: 0.67em 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
strong {
|
|
36
|
+
font-weight: bold;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
p {
|
|
40
|
+
margin: 1em 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
canvas {
|
|
44
|
+
display: block;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
table {
|
|
48
|
+
border-collapse: collapse;
|
|
49
|
+
border-spacing: 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.row {
|
|
53
|
+
display: flex;
|
|
54
|
+
flex-wrap: wrap;
|
|
55
|
+
margin-right: -15px;
|
|
56
|
+
margin-left: -15px;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
.col-12 {
|
|
60
|
+
flex: 0 0 100%;
|
|
61
|
+
max-width: 100%;
|
|
62
|
+
padding-right: 15px;
|
|
63
|
+
padding-left: 15px;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.mb-4 {
|
|
67
|
+
margin-bottom: 1.5rem;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
45
70
|
</head>
|
|
46
71
|
|
|
47
|
-
<body>
|
|
72
|
+
<body style="margin-left: 10px">
|
|
48
73
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
<div id="segment-content" class="segment"> <!-- segment-content -->
|
|
52
|
-
<div class="container"> <!-- container -->
|
|
53
|
-
<div class="row">
|
|
54
|
-
<div class="inner-wrapper">
|
|
55
|
-
<div class="col-9">
|
|
56
|
-
|
|
57
|
-
<h2>[%title%] </h2>
|
|
74
|
+
<h2>[%title%] </h2>
|
|
58
75
|
|
|
59
76
|
[%content%]
|
|
60
77
|
|
|
61
78
|
|
|
62
|
-
</div>
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
</div> <!-- /inner-wrapper -->
|
|
66
|
-
</div> <!-- /row -->
|
|
67
|
-
</div> <!-- /container -->
|
|
68
|
-
</div> <!-- /segment-content -->
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
79
|
<!-- JS and analytics only. -->
|
|
75
80
|
<!-- Bootstrap core JavaScript
|
|
76
81
|
================================================== -->
|
|
@@ -1298,7 +1298,7 @@ VIEWDEFINITION_SHOULD_HAVE_NAME = No name provided. A name is required in many c
|
|
|
1298
1298
|
VIEWDEFINITION_TYPE_MISMATCH = The path expression ''{0}'' does not return a value of the type ''{1}'' - found ''{2}''{3}
|
|
1299
1299
|
VIEWDEFINITION_UNABLE_TO_TYPE = Unable to determine a type (found ''{0}''){1}
|
|
1300
1300
|
VIEWDEFINITION_UNKNOWN_RESOURCE = The name ''{0}'' is not a valid resource{1}
|
|
1301
|
-
VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported yet
|
|
1301
|
+
VS_EXP_FILTER_UNK = ValueSet ''{0}'' Filter by property ''{1}'' and op ''{2}'' is not supported (yet?)
|
|
1302
1302
|
VS_EXP_IMPORT_CS = Cannot include value set ''{0}'' because it''s actually a code system
|
|
1303
1303
|
VS_EXP_IMPORT_CS_PINNED = Cannot include value set ''{0}'' version ''{1}'' because it''s actually a code system
|
|
1304
1304
|
VS_EXP_IMPORT_CS_PINNED_X = Cannot exclude value set ''{0}'' version ''{1}'' because it''s actually a code system
|
|
@@ -1400,7 +1400,7 @@ TEXT_LINK_NO_DATA = No data element was found in the textLink extension
|
|
|
1400
1400
|
TEXT_LINK_SELECTOR_INVALID = The textLink selector ''{0}'' is invalid: {1}
|
|
1401
1401
|
SD_CONTEXT_SHOULD_ELEMENT_NOT_FOUND = The element {0} is not valid
|
|
1402
1402
|
SD_CONTEXT_SHOULD_ELEMENT_NOT_FOUND_VER = The element {0} is not valid in version {1}
|
|
1403
|
-
FILTER_NOT_UNDERSTOOD = The filter "{0} {1} {2}"
|
|
1403
|
+
FILTER_NOT_UNDERSTOOD = The filter "{0} {1} {2}" is not understood or supported
|
|
1404
1404
|
XHTML_CONTROL_NO_SOURCE = The xhtml node at ''{0}'' does not have a class attribute to indicate its source (boilerplate, generated, or original) and this is required by the profile {1}
|
|
1405
1405
|
XHTML_XHTML_MIXED_LANG = The xhtml has some language sections ({0}), and also has content that is not in a language section
|
|
1406
1406
|
XHTML_CONTROL_NO_LANGS = The xhtml has some language sections ({0}), but language sections are prohibited in this context by the profile {1}
|
|
@@ -1511,3 +1511,4 @@ CONFORMANCE_STATEMENT_WORD = The html source contains the word ''{0}'' but it is
|
|
|
1511
1511
|
VALUESET_CODE_CONCEPT_HINT = {3}. Note that the display in the ValueSet does not have to match; this check exists to help check that it''s not accidentally the wrong code
|
|
1512
1512
|
VALUESET_CODE_CONCEPT_HINT_VER ={3}. Note that the display in the ValueSet does not have to match; this check exists to help check that it''s not accidentally the wrong code
|
|
1513
1513
|
TERMINOLOGY_TX_SYSTEM_UNSUPPORTED = The code cannot be checked because codeSystem ''{0}'' version ''{1}'' is not supported ({2})
|
|
1514
|
+
INVALID_REGEX = The regex ''{0}'' is not valid: {1}
|
package/tx/cs/cs-country.js
CHANGED
|
@@ -2,6 +2,7 @@ const { CodeSystemProvider, FilterExecutionContext } = require('../../tx/cs/cs-a
|
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { CodeSystem } = require("../library/codesystem");
|
|
4
4
|
const {CodeSystemFactoryProvider} = require("./cs-api");
|
|
5
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
5
6
|
|
|
6
7
|
class CountryCodeConcept {
|
|
7
8
|
constructor(userDefined, code, display, french) {
|
|
@@ -199,7 +200,7 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
199
200
|
|
|
200
201
|
try {
|
|
201
202
|
// Create regex with anchors to match the Pascal implementation (^value$)
|
|
202
|
-
const regex =
|
|
203
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
203
204
|
|
|
204
205
|
for (const concept of this.codes) {
|
|
205
206
|
if (regex.test(concept.code)) {
|
package/tx/cs/cs-cs.js
CHANGED
|
@@ -6,6 +6,7 @@ const { validateOptionalParameter, getValuePrimitive, validateArrayParameter} =
|
|
|
6
6
|
const {Issue} = require("../library/operation-outcome");
|
|
7
7
|
const {Extensions} = require("../library/extensions");
|
|
8
8
|
const {BaseCSServices} = require("./cs-base");
|
|
9
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Context class for FHIR CodeSystem provider concepts
|
|
@@ -1223,7 +1224,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1223
1224
|
|
|
1224
1225
|
// Handle concept/code hierarchy filters
|
|
1225
1226
|
if ((prop === 'concept' || prop === 'code')) {
|
|
1226
|
-
results = await this._handleConceptFilter(filterContext, op, value);
|
|
1227
|
+
results = await this._handleConceptFilter(filterContext, prop, op, value);
|
|
1227
1228
|
}
|
|
1228
1229
|
|
|
1229
1230
|
// Handle child existence filter
|
|
@@ -1247,7 +1248,8 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1247
1248
|
}
|
|
1248
1249
|
|
|
1249
1250
|
if (!results) {
|
|
1250
|
-
throw new
|
|
1251
|
+
throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
|
|
1252
|
+
this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
|
|
1251
1253
|
}
|
|
1252
1254
|
// Add to filter context
|
|
1253
1255
|
if (!filterContext.filters) {
|
|
@@ -1266,15 +1268,17 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1266
1268
|
* @returns {Promise<FhirCodeSystemProviderFilterContext>} Filter results
|
|
1267
1269
|
* @private
|
|
1268
1270
|
*/
|
|
1269
|
-
async _handleConceptFilter(filterContext, op, value) {
|
|
1271
|
+
async _handleConceptFilter(filterContext, prop, op, value) {
|
|
1270
1272
|
const results = new FhirCodeSystemProviderFilterContext();
|
|
1271
1273
|
|
|
1272
1274
|
if (op === 'is-a' || op === 'descendent-of') {
|
|
1273
1275
|
// Find all descendants of the specified code
|
|
1274
1276
|
const includeRoot = (op === 'is-a');
|
|
1275
1277
|
await this._addDescendants(results, value, includeRoot);
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
+
} else if (op === 'child-of') {
|
|
1279
|
+
// Find all descendants of the specified code
|
|
1280
|
+
await this._addChildren(results, value);
|
|
1281
|
+
} else if (op === 'is-not-a') {
|
|
1278
1282
|
// Find all concepts that are NOT descendants of the specified code
|
|
1279
1283
|
const excludeDescendants = this.codeSystem.getDescendants(value);
|
|
1280
1284
|
const excludeSet = new Set([value, ...excludeDescendants]);
|
|
@@ -1309,7 +1313,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1309
1313
|
else if (op === 'regex') {
|
|
1310
1314
|
// Regular expression match
|
|
1311
1315
|
try {
|
|
1312
|
-
const regex =
|
|
1316
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1313
1317
|
const allCodes = this.codeSystem.getAllCodes();
|
|
1314
1318
|
for (const code of allCodes) {
|
|
1315
1319
|
if (regex.test(code)) {
|
|
@@ -1320,8 +1324,11 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1320
1324
|
}
|
|
1321
1325
|
}
|
|
1322
1326
|
} catch (error) {
|
|
1323
|
-
throw new
|
|
1327
|
+
throw new Issue('error', 'exception', null, 'INVALID_REGEX', this.opContext.i18n.translate('INVALID_REGEX', this.opContext.langs, [value, error.message]), 'vs-invalid', 422);
|
|
1324
1328
|
}
|
|
1329
|
+
} else {
|
|
1330
|
+
throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
|
|
1331
|
+
this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
|
|
1325
1332
|
}
|
|
1326
1333
|
|
|
1327
1334
|
return results;
|
|
@@ -1352,6 +1359,27 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1352
1359
|
}
|
|
1353
1360
|
}
|
|
1354
1361
|
|
|
1362
|
+
/**
|
|
1363
|
+
* Add immediate children of a code to the results
|
|
1364
|
+
* @param {FhirCodeSystemProviderFilterContext} results - Results to add to
|
|
1365
|
+
* @param {string} ancestorCode - The parent code
|
|
1366
|
+
* @private
|
|
1367
|
+
*/
|
|
1368
|
+
async _addChildren(results, parentCode) {
|
|
1369
|
+
const concept = this.codeSystem.getConceptByCode(parentCode);
|
|
1370
|
+
if (concept) {
|
|
1371
|
+
const descendants = this.codeSystem.getChildren(parentCode);
|
|
1372
|
+
for (const code of descendants) {
|
|
1373
|
+
if (code !== parentCode) { // should not be
|
|
1374
|
+
const concept = this.codeSystem.getConceptByCode(code);
|
|
1375
|
+
if (concept) {
|
|
1376
|
+
results.add(concept, 0);
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1355
1383
|
/**
|
|
1356
1384
|
* Handle child exists filter
|
|
1357
1385
|
* @param {FilterExecutionContext} filterContext - Filter context
|
|
@@ -1428,7 +1456,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1428
1456
|
}
|
|
1429
1457
|
else if (op === 'regex') {
|
|
1430
1458
|
try {
|
|
1431
|
-
const regex =
|
|
1459
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1432
1460
|
return properties.some(p => regex.test(this._getPropertyValue(p)));
|
|
1433
1461
|
} catch (error) {
|
|
1434
1462
|
return false;
|
package/tx/cs/cs-loinc.js
CHANGED
|
@@ -6,6 +6,7 @@ const { CodeSystemFactoryProvider} = require('./cs-api');
|
|
|
6
6
|
const { validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
|
|
7
7
|
const {BaseCSServices} = require("./cs-base");
|
|
8
8
|
const {sqlEscapeString} = require("../../xig/xig");
|
|
9
|
+
const regexUtilities = require('../../library/regex-utilities');
|
|
9
10
|
|
|
10
11
|
// Context kinds matching Pascal enum
|
|
11
12
|
const LoincProviderContextKind = {
|
|
@@ -938,7 +939,7 @@ class LoincServices extends BaseCSServices {
|
|
|
938
939
|
// Helper method for regex matching
|
|
939
940
|
async #findRegexMatches(sql, pattern, valueColumn, keyColumn = 'Key') {
|
|
940
941
|
return new Promise((resolve, reject) => {
|
|
941
|
-
const regex =
|
|
942
|
+
const regex = regexUtilities.compile(pattern);
|
|
942
943
|
const matchingKeys = [];
|
|
943
944
|
|
|
944
945
|
this.db.all(sql, (err, rows) => {
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -1622,7 +1622,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1622
1622
|
internalSource : this,
|
|
1623
1623
|
relationship: relationship,
|
|
1624
1624
|
id : id,
|
|
1625
|
-
url: `${this.system}?fhir_cm=${id}`,
|
|
1625
|
+
url: `${this.system()}?fhir_cm=${id}`,
|
|
1626
1626
|
version: this.version(),
|
|
1627
1627
|
name: `SNOMED CT ${name} Concept Map`,
|
|
1628
1628
|
description: `The concept map implicitly defined by the ${name} Association Reference Set`,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"resourceType": "OperationDefinition",
|
|
3
|
+
"id": "ValueSet-related",
|
|
4
|
+
"url": "http://hl7.org/fhir/OperationDefinition/ValueSet-related",
|
|
5
|
+
"version": "5.0.0",
|
|
6
|
+
"name": "ValueSetRelated",
|
|
7
|
+
"title": "Value Set Related Determination",
|
|
8
|
+
"status": "active",
|
|
9
|
+
"kind": "operation",
|
|
10
|
+
"experimental": false,
|
|
11
|
+
"date": "2023-03-26T15:21:02+11:00",
|
|
12
|
+
"publisher": "FHIRsmith",
|
|
13
|
+
"description": "Determine the relationship between two value sets. Different versions of code systems are considered compatible unless versionNeeded = true for the code system",
|
|
14
|
+
"jurisdiction": [
|
|
15
|
+
{
|
|
16
|
+
"coding": [
|
|
17
|
+
{
|
|
18
|
+
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
|
19
|
+
"code": "001",
|
|
20
|
+
"display": "World"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"affectsState": false,
|
|
26
|
+
"code": "related",
|
|
27
|
+
"comment": "An $expand will be performed internally if needed.",
|
|
28
|
+
"resource": [
|
|
29
|
+
"ValueSet"
|
|
30
|
+
],
|
|
31
|
+
"system": false,
|
|
32
|
+
"type": true,
|
|
33
|
+
"instance": false,
|
|
34
|
+
"parameter": [
|
|
35
|
+
{
|
|
36
|
+
"name": "thisUrl",
|
|
37
|
+
"use": "in",
|
|
38
|
+
"scope": [
|
|
39
|
+
"type"
|
|
40
|
+
],
|
|
41
|
+
"min": 0,
|
|
42
|
+
"max": "1",
|
|
43
|
+
"documentation": "Value set Canonical URL for the first value set of the pair (the base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
44
|
+
"type": "uri"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "otherUrl",
|
|
48
|
+
"use": "in",
|
|
49
|
+
"scope": [
|
|
50
|
+
"type"
|
|
51
|
+
],
|
|
52
|
+
"min": 0,
|
|
53
|
+
"max": "1",
|
|
54
|
+
"documentation": "Value set Canonical URL for the second value set of the pair (the one being compared to base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
55
|
+
"type": "uri"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "thisValueSet",
|
|
59
|
+
"use": "in",
|
|
60
|
+
"scope": [
|
|
61
|
+
"type"
|
|
62
|
+
],
|
|
63
|
+
"min": 0,
|
|
64
|
+
"max": "1",
|
|
65
|
+
"documentation": "The first value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
66
|
+
"type": "ValueSet"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "otherValueSet",
|
|
70
|
+
"use": "in",
|
|
71
|
+
"scope": [
|
|
72
|
+
"type"
|
|
73
|
+
],
|
|
74
|
+
"min": 0,
|
|
75
|
+
"max": "1",
|
|
76
|
+
"documentation": "The other value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
77
|
+
"type": "ValueSet"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "thisVersion",
|
|
81
|
+
"use": "in",
|
|
82
|
+
"scope": [
|
|
83
|
+
"type"
|
|
84
|
+
],
|
|
85
|
+
"min": 0,
|
|
86
|
+
"max": "1",
|
|
87
|
+
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "otherVersion",
|
|
92
|
+
"use": "in",
|
|
93
|
+
"scope": [
|
|
94
|
+
"type"
|
|
95
|
+
],
|
|
96
|
+
"min": 0,
|
|
97
|
+
"max": "1",
|
|
98
|
+
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
99
|
+
"type": "string"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "useSupplement",
|
|
103
|
+
"use": "in",
|
|
104
|
+
"min": 0,
|
|
105
|
+
"max": "*",
|
|
106
|
+
"documentation": "The supplement must be used when validating the code. Use of this parameter should result in $validate-code behaving the same way as if the supplements were included in the value set definition using the [http://hl7.org/fhir/StructureDefinition/valueset-supplement](http://hl7.org/fhir/extensions/StructureDefinition-valueset-supplement.html)",
|
|
107
|
+
"type": "canonical"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "diagnostics",
|
|
111
|
+
"use": "in",
|
|
112
|
+
"min": 0,
|
|
113
|
+
"max": "1",
|
|
114
|
+
"documentation": "Whether to return information about the reasoning process"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "result",
|
|
118
|
+
"use": "out",
|
|
119
|
+
"min": 1,
|
|
120
|
+
"max": "1",
|
|
121
|
+
"documentation": "The relationship between the ValueSets. One of: same, superset, subset, overlapping, dsjoint, empty, and indeterminate",
|
|
122
|
+
"type": "boolean"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "message",
|
|
126
|
+
"use": "out",
|
|
127
|
+
"min": 0,
|
|
128
|
+
"max": "1",
|
|
129
|
+
"documentation": "Explanation of the code, with reason if appropriate",
|
|
130
|
+
"type": "string"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
@@ -6,7 +6,7 @@ const ATC_FILE = process.argv[2] || '2025_ATC.xml';
|
|
|
6
6
|
const DDD_FILE = process.argv[3] || '2025_ATC_ddd.xml';
|
|
7
7
|
const OUTPUT_FILE = process.argv[4] || 'atc-codesystem.json';
|
|
8
8
|
|
|
9
|
-
const PROPERTY_GROUP_EXT_URL = 'http://hl7.org/fhir/property
|
|
9
|
+
const PROPERTY_GROUP_EXT_URL = 'http://hl7.org/fhir/StructureDefinition/Codesystem-property-group';
|
|
10
10
|
|
|
11
11
|
// Parse XML files
|
|
12
12
|
function parseXML(filePath) {
|
|
@@ -277,32 +277,7 @@ try {
|
|
|
277
277
|
console.log(`Writing to ${OUTPUT_FILE}...`);
|
|
278
278
|
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(codeSystem, null, 2));
|
|
279
279
|
console.log('Done!');
|
|
280
|
-
|
|
281
|
-
// Print some stats
|
|
282
|
-
function countConcepts(concepts) {
|
|
283
|
-
let count = 0;
|
|
284
|
-
for (const c of concepts) {
|
|
285
|
-
count++;
|
|
286
|
-
if (c.concept) {
|
|
287
|
-
count += countConcepts(c.concept);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return count;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function countWithDDD(concepts) {
|
|
294
|
-
let count = 0;
|
|
295
|
-
for (const c of concepts) {
|
|
296
|
-
if (c.property?.some(p => p.code === 'dddValue')) {
|
|
297
|
-
count++;
|
|
298
|
-
}
|
|
299
|
-
if (c.concept) {
|
|
300
|
-
count += countWithDDD(c.concept);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return count;
|
|
304
|
-
}
|
|
305
|
-
|
|
280
|
+
|
|
306
281
|
const totalConcepts = countConcepts(codeSystem.concept);
|
|
307
282
|
const withDDD = countWithDDD(codeSystem.concept);
|
|
308
283
|
console.log(`\nStatistics:`);
|
|
@@ -314,3 +289,28 @@ try {
|
|
|
314
289
|
console.error('Error:', error.message);
|
|
315
290
|
process.exit(1);
|
|
316
291
|
}
|
|
292
|
+
|
|
293
|
+
// Print some stats
|
|
294
|
+
function countConcepts(concepts) {
|
|
295
|
+
let count = 0;
|
|
296
|
+
for (const c of concepts) {
|
|
297
|
+
count++;
|
|
298
|
+
if (c.concept) {
|
|
299
|
+
count += countConcepts(c.concept);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return count;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function countWithDDD(concepts) {
|
|
306
|
+
let count = 0;
|
|
307
|
+
for (const c of concepts) {
|
|
308
|
+
if (c.property?.some(p => p.code === 'dddValue')) {
|
|
309
|
+
count++;
|
|
310
|
+
}
|
|
311
|
+
if (c.concept) {
|
|
312
|
+
count += countWithDDD(c.concept);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return count;
|
|
316
|
+
}
|