fhirsmith 0.8.5 → 0.9.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 +51 -0
- package/README.md +52 -22
- package/extension-tracker/extension-tracker-template.html +3 -1
- package/library/html-server.js +7 -0
- package/library/logger.js +234 -194
- package/library/regex-utilities.js +13 -0
- package/package.json +4 -2
- package/packages/packages-template.html +3 -1
- package/publisher/publisher-template.html +1 -0
- package/publisher/publisher.js +28 -7
- package/registry/registry-template.html +3 -1
- package/root-bare-template.html +9759 -37
- package/root-template.html +3 -2
- package/server.js +48 -12
- package/translations/Messages.properties +2 -1
- package/translations/rendering-phrases.properties +3 -1
- package/tx/cs/cs-api.js +4 -0
- package/tx/cs/cs-country.js +2 -1
- package/tx/cs/cs-cs.js +9 -4
- package/tx/cs/cs-loinc.js +2 -1
- package/tx/cs/cs-snomed.js +5 -1
- package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
- package/tx/html/tx-template.html +3 -2
- package/tx/importers/atc-to-fhir.js +27 -27
- package/tx/library/codesystem.js +4 -0
- package/tx/library/renderer.js +20 -4
- 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/operation-context.js +74 -19
- package/tx/tx-html.js +5 -5
- package/tx/tx.fhir.org.yml +4 -4
- package/tx/tx.js +1 -0
- package/tx/vs/vs-database.js +150 -100
- package/tx/vs/vs-vsac.js +90 -31
- package/tx/workers/expand.js +154 -113
- package/tx/workers/metadata.js +6 -3
- package/tx/workers/read.js +6 -3
- package/tx/workers/related.js +228 -87
- package/xig/xig-template.html +3 -1
- package/library/logger-telnet.js +0 -205
package/root-template.html
CHANGED
|
@@ -90,8 +90,9 @@
|
|
|
90
90
|
<div class="inner-wrapper">
|
|
91
91
|
<p>
|
|
92
92
|
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
93
|
-
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+  
|
|
94
|
-
|
|
93
|
+
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
94
|
+
([%ms%] ms)
|
|
95
|
+
[%sponsorMessage%]
|
|
95
96
|
</p>
|
|
96
97
|
</div> <!-- /inner-wrapper -->
|
|
97
98
|
</div> <!-- /container -->
|
package/server.js
CHANGED
|
@@ -28,7 +28,7 @@ try {
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
const Logger = require('./library/logger');
|
|
31
|
-
const serverLog = Logger.getInstance().child({ module: 'server' });
|
|
31
|
+
const serverLog = Logger.getInstance(config.logging || {}).child({ module: 'server' });
|
|
32
32
|
const packageJson = require('./package.json');
|
|
33
33
|
|
|
34
34
|
// Startup banner
|
|
@@ -64,6 +64,7 @@ const FolderModule = require("./folder/folder");
|
|
|
64
64
|
const ExtensionTrackerModule = require("./extension-tracker/extension-tracker");
|
|
65
65
|
|
|
66
66
|
htmlServer.useLog(serverLog);
|
|
67
|
+
htmlServer.setSponsorMessage(config.sponsorMessage ? config.sponsorMessage : '');
|
|
67
68
|
|
|
68
69
|
const app = express();
|
|
69
70
|
|
|
@@ -386,22 +387,44 @@ async function buildRootPageContent() {
|
|
|
386
387
|
|
|
387
388
|
// Memory usage
|
|
388
389
|
const memUsage = process.memoryUsage();
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
const
|
|
390
|
+
const heapStats = v8.getHeapStatistics();
|
|
391
|
+
|
|
392
|
+
// V8 heap: used vs limit
|
|
393
|
+
const v8UsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(0);
|
|
394
|
+
const v8LimitMB = (heapStats.heap_size_limit / 1024 / 1024).toFixed(0);
|
|
395
|
+
const v8PCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit;
|
|
396
|
+
|
|
397
|
+
// Process RSS vs cgroup limit (or system total)
|
|
398
|
+
const rssMB = (memUsage.rss / 1024 / 1024).toFixed(0);
|
|
399
|
+
let memLimit;
|
|
400
|
+
try {
|
|
401
|
+
const raw = fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
402
|
+
memLimit = raw === 'max' ? os.totalmem() : parseInt(raw);
|
|
403
|
+
} catch {
|
|
404
|
+
memLimit = os.totalmem();
|
|
405
|
+
}
|
|
406
|
+
const memLimitMB = (memLimit / 1024 / 1024).toFixed(0);
|
|
407
|
+
const processPCT = (memUsage.rss * 100) / memLimit;
|
|
408
|
+
|
|
409
|
+
// System memory
|
|
393
410
|
const totalMemMB = (os.totalmem() / 1024 / 1024).toFixed(0);
|
|
411
|
+
const usedMemMB = ((os.totalmem() - os.freemem()) / 1024 / 1024).toFixed(0);
|
|
412
|
+
const sysMemPCT = ((os.totalmem() - os.freemem()) * 100) / os.totalmem();
|
|
413
|
+
|
|
414
|
+
// Average requests per minute
|
|
415
|
+
const uptimeMinutesTotal = uptimeMs / 60000;
|
|
416
|
+
const avgReqPerMin = uptimeMinutesTotal > 0 ? (stats.requestCount / uptimeMinutesTotal).toFixed(1) : '0.0';
|
|
394
417
|
|
|
395
418
|
content += '<table class="grid">';
|
|
396
419
|
content += '<tr>';
|
|
397
420
|
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
398
421
|
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
399
|
-
content += `<td><strong>
|
|
422
|
+
content += `<td><strong>Avg Requests/min:</strong> ${avgReqPerMin}</td>`;
|
|
400
423
|
content += '</tr>';
|
|
401
424
|
content += '<tr>';
|
|
402
|
-
content += `<td><strong>
|
|
403
|
-
content += `<td><strong>
|
|
404
|
-
content += `<td><strong>
|
|
425
|
+
content += `<td style="background-color:${pctColor(v8PCT)}"><strong>V8 Memory:</strong> ${v8UsedMB} MB of ${v8LimitMB} MB (${v8PCT.toFixed(0)}%)</td>`;
|
|
426
|
+
content += `<td style="background-color:${pctColor(processPCT)}"><strong>Process Memory:</strong> ${rssMB} MB of ${memLimitMB} MB (${processPCT.toFixed(0)}%)</td>`;
|
|
427
|
+
content += `<td style="background-color:${pctColor(sysMemPCT)}"><strong>System Memory:</strong> ${usedMemMB} MB of ${totalMemMB} MB (${sysMemPCT.toFixed(0)}%)</td>`;
|
|
405
428
|
content += '</tr>';
|
|
406
429
|
content += getLogStats();
|
|
407
430
|
content += '</table>';
|
|
@@ -543,10 +566,24 @@ app.get('/dashboard', async (req, res) => {
|
|
|
543
566
|
// Memory usage
|
|
544
567
|
const memUsage = process.memoryUsage();
|
|
545
568
|
const heapStats = v8.getHeapStatistics();
|
|
546
|
-
const
|
|
569
|
+
const v8PCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit; // % of V8 heap limit used
|
|
570
|
+
|
|
571
|
+
// Process RSS as % of cgroup memory limit (or system total as fallback)
|
|
572
|
+
let memLimit;
|
|
573
|
+
try {
|
|
574
|
+
const raw = fs.readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
575
|
+
memLimit = raw === 'max' ? os.totalmem() : parseInt(raw);
|
|
576
|
+
} catch {
|
|
577
|
+
memLimit = os.totalmem();
|
|
578
|
+
}
|
|
579
|
+
const processPCT = (memUsage.rss * 100) / memLimit;
|
|
580
|
+
|
|
547
581
|
const totalMemBytes = os.totalmem();
|
|
548
582
|
const freeMemBytes = os.freemem();
|
|
549
583
|
const sysMemPCT = ((totalMemBytes - freeMemBytes) * 100) / totalMemBytes; // % of system memory used
|
|
584
|
+
|
|
585
|
+
const memMaxPCT = Math.max(v8PCT, processPCT, sysMemPCT);
|
|
586
|
+
|
|
550
587
|
const fstats = fs.statfsSync(folders.logsDir());
|
|
551
588
|
const diskPCT = 100 - ((fstats.bavail * 100) / fstats.blocks); // % of disk used
|
|
552
589
|
|
|
@@ -555,8 +592,7 @@ app.get('/dashboard', async (req, res) => {
|
|
|
555
592
|
content += '<tr>';
|
|
556
593
|
content += `<td><strong>Uptime:</strong> ${escape(uptimeStr)}</td>`;
|
|
557
594
|
content += `<td><strong>Request Count:</strong> ${stats.requestCount} (static: ${stats.staticRequestCount})</td>`;
|
|
558
|
-
content += `<td style="background-color:${pctColor(
|
|
559
|
-
content += `<td style="background-color:${pctColor(sysMemPCT)}"><strong>System Memory:</strong> ${sysMemPCT.toFixed(0)}%</td>`;
|
|
595
|
+
content += `<td style="background-color:${pctColor(memMaxPCT)}"><strong>Memory:</strong> ${v8PCT.toFixed(0)}% V8, ${processPCT.toFixed(0)}% Process, ${sysMemPCT.toFixed(0)}% System</td>`;
|
|
560
596
|
content += `<td style="background-color:${pctColor(diskPCT)}"><strong>Disk:</strong> ${diskPCT.toFixed(0)}%</td>`;
|
|
561
597
|
content += '</tr>';
|
|
562
598
|
content += '</table>';
|
|
@@ -1274,7 +1274,7 @@ VALUESET_SHAREABLE_MISSING = Published value sets SHOULD conform to the Shareabl
|
|
|
1274
1274
|
VALUESET_SHAREABLE_MISSING_HL7 = Value sets published by HL7 SHALL conform to the ShareableValueSet profile, which says that the element ValueSet.{0} is mandatory, but it is not present
|
|
1275
1275
|
VALUESET_SUPPLEMENT_MISSING_one = Required supplement not found: {1}
|
|
1276
1276
|
VALUESET_SUPPLEMENT_MISSING_other = Required supplements not found: {1}
|
|
1277
|
-
VALUESET_TOO_COSTLY = The value set ''{0}'' expansion has too many codes to
|
|
1277
|
+
VALUESET_TOO_COSTLY = The value set ''{0}'' expansion has too many codes to produce ({1})
|
|
1278
1278
|
VALUESET_TOO_COSTLY_COUNT = The value set ''{0}'' expansion has {2} codes, which is too many to display ({1})
|
|
1279
1279
|
VALUESET_TOO_COSTLY_TIME = The value set ''{0}'' {2} took too long to process (>{1}sec)
|
|
1280
1280
|
VALUESET_UNC_SYSTEM_WARNING = Unknown System ''{0}'' specified, so Concepts and Filters can''t be checked (Details: {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}
|
|
@@ -1214,4 +1214,6 @@ CONSENT_HT_RESOURCE_TYPE = Resource Type
|
|
|
1214
1214
|
CONSENT_HT_DOC_TYPE = Document Type
|
|
1215
1215
|
CONSENT_HT_CODE = Code
|
|
1216
1216
|
WEB_SOURCE = Web Source
|
|
1217
|
-
GENERAL_COPY = Click to Copy
|
|
1217
|
+
GENERAL_COPY = Click to Copy
|
|
1218
|
+
CODESYSTEM_LVL = Level
|
|
1219
|
+
FEATURE = Feature
|
package/tx/cs/cs-api.js
CHANGED
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
|
|
@@ -1309,7 +1310,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1309
1310
|
else if (op === 'regex') {
|
|
1310
1311
|
// Regular expression match
|
|
1311
1312
|
try {
|
|
1312
|
-
const regex =
|
|
1313
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1313
1314
|
const allCodes = this.codeSystem.getAllCodes();
|
|
1314
1315
|
for (const code of allCodes) {
|
|
1315
1316
|
if (regex.test(code)) {
|
|
@@ -1320,7 +1321,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1320
1321
|
}
|
|
1321
1322
|
}
|
|
1322
1323
|
} catch (error) {
|
|
1323
|
-
throw new
|
|
1324
|
+
throw new Issue('error', 'exception', null, 'INVALID_REGEX', this.opContext.i18n.translate('INVALID_REGEX', this.opContext.langs, [value, error.message]), 'vs-invalid', 422);
|
|
1324
1325
|
}
|
|
1325
1326
|
}
|
|
1326
1327
|
|
|
@@ -1428,7 +1429,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1428
1429
|
}
|
|
1429
1430
|
else if (op === 'regex') {
|
|
1430
1431
|
try {
|
|
1431
|
-
const regex =
|
|
1432
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1432
1433
|
return properties.some(p => regex.test(this._getPropertyValue(p)));
|
|
1433
1434
|
} catch (error) {
|
|
1434
1435
|
return false;
|
|
@@ -1521,7 +1522,11 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1521
1522
|
}
|
|
1522
1523
|
|
|
1523
1524
|
versionNeeded() {
|
|
1524
|
-
return this.codeSystem.jsonObj.versionNeeded;
|
|
1525
|
+
return this.codeSystem.jsonObj.versionNeeded ? true : false;
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
hasMultiHierarchy() {
|
|
1529
|
+
return this.codeSystem.hasMultiHierarchy;
|
|
1525
1530
|
}
|
|
1526
1531
|
|
|
1527
1532
|
}
|
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
|
@@ -1348,6 +1348,10 @@ class SnomedProvider extends BaseCSServices {
|
|
|
1348
1348
|
return result;
|
|
1349
1349
|
}
|
|
1350
1350
|
|
|
1351
|
+
hasMultiHierarchy() {
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1351
1355
|
}
|
|
1352
1356
|
|
|
1353
1357
|
/**
|
|
@@ -1618,7 +1622,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1618
1622
|
internalSource : this,
|
|
1619
1623
|
relationship: relationship,
|
|
1620
1624
|
id : id,
|
|
1621
|
-
url: `${this.system}?fhir_cm=${id}`,
|
|
1625
|
+
url: `${this.system()}?fhir_cm=${id}`,
|
|
1622
1626
|
version: this.version(),
|
|
1623
1627
|
name: `SNOMED CT ${name} Concept Map`,
|
|
1624
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
|
+
}
|
package/tx/html/tx-template.html
CHANGED
|
@@ -117,9 +117,10 @@
|
|
|
117
117
|
<div class="container">
|
|
118
118
|
<div class="inner-wrapper">
|
|
119
119
|
<p>
|
|
120
|
-
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> © HL7.org 2011+. |
|
|
120
|
+
<a href="http://www.hl7.org/fhir" style="color: gold" title="Fast Healthcare Interoperability Resources - Home Page"><img border="0" src="/icon-fhir-16.png" style="vertical-align: text-bottom"/> <b>FHIR</b></a> v[%fhir-version%] © HL7.org 2011+. |
|
|
121
121
|
<a href="https://github.com/HealthIntersections/FHIRsmith/blob/main/README.md" style="color: gold"><img border="0" src="/FHIRsmith16.png" style="vertical-align: text-bottom"/> FHIRsmith</a> [%ver%] © HealthIntersections.com.au 2023+ |
|
|
122
|
-
|
|
122
|
+
([%ms%] ms)
|
|
123
|
+
[%sponsorMessage%]
|
|
123
124
|
</p>
|
|
124
125
|
</div>
|
|
125
126
|
</div>
|
|
@@ -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
|
+
}
|
package/tx/library/codesystem.js
CHANGED
|
@@ -74,6 +74,8 @@ class CodeSystem extends CanonicalResource {
|
|
|
74
74
|
*/
|
|
75
75
|
childToParentsMap = new Map();
|
|
76
76
|
|
|
77
|
+
hasMultiHierarchy = false;
|
|
78
|
+
|
|
77
79
|
/**
|
|
78
80
|
* Static factory method for convenience
|
|
79
81
|
* @param {string} jsonString - JSON string representation of CodeSystem
|
|
@@ -326,6 +328,8 @@ class CodeSystem extends CanonicalResource {
|
|
|
326
328
|
|
|
327
329
|
// Third pass: handle nested concept structures
|
|
328
330
|
this._buildNestedHierarchy(this.jsonObj.concept);
|
|
331
|
+
|
|
332
|
+
this.hasMultiHierarchy = Array.from(this.childToParentsMap.values()).some(parents => parents.length > 1);
|
|
329
333
|
}
|
|
330
334
|
|
|
331
335
|
/**
|
package/tx/library/renderer.js
CHANGED
|
@@ -133,7 +133,7 @@ class Renderer {
|
|
|
133
133
|
return result;
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
async renderMetadataTable(res, tbl) {
|
|
136
|
+
async renderMetadataTable(res, tbl, sourcePackage) {
|
|
137
137
|
this.renderMetadataVersion(res, tbl);
|
|
138
138
|
await this.renderMetadataProfiles(res, tbl);
|
|
139
139
|
this.renderMetadataTags(res, tbl);
|
|
@@ -154,6 +154,11 @@ class Renderer {
|
|
|
154
154
|
this.renderProperty(tbl, 'EXT_FMM_LEVEL', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/structuredefinition-fmm'));
|
|
155
155
|
this.renderProperty(tbl, 'PAT_PERIOD', res.effectivePeriod);
|
|
156
156
|
this.renderPropertyLink(tbl, 'WEB_SOURCE', Extensions.readString(res, 'http://hl7.org/fhir/StructureDefinition/web-source'));
|
|
157
|
+
this.renderProperty(tbl, 'GENERAL_SOURCE', sourcePackage);
|
|
158
|
+
for (let ext of Extensions.list(res, 'http://hl7.org/fhir/uv/application-feature/StructureDefinition/feature')) {
|
|
159
|
+
this.renderProperty(tbl, 'FEATURE', this.featureSummary(ext));
|
|
160
|
+
}
|
|
161
|
+
|
|
157
162
|
|
|
158
163
|
// capability statement things
|
|
159
164
|
this.renderProperty(tbl, 'Kind', res.kind);
|
|
@@ -165,7 +170,6 @@ class Renderer {
|
|
|
165
170
|
}
|
|
166
171
|
}
|
|
167
172
|
this.renderProperty(tbl, 'GENERAL_URL', res.implementation?.url);
|
|
168
|
-
this.renderProperty(tbl, 'Kind', res.kind);
|
|
169
173
|
this.renderProperty(tbl, 'EX_SCEN_FVER', res.fhirVersion);
|
|
170
174
|
|
|
171
175
|
if (res.content === 'supplement' && res.supplements) {
|
|
@@ -364,7 +368,7 @@ class Renderer {
|
|
|
364
368
|
return div_.toString();
|
|
365
369
|
}
|
|
366
370
|
|
|
367
|
-
async renderCodeSystem(cs) {
|
|
371
|
+
async renderCodeSystem(cs, sourcePackage) {
|
|
368
372
|
if (cs.json) {
|
|
369
373
|
cs = cs.json;
|
|
370
374
|
}
|
|
@@ -373,7 +377,7 @@ class Renderer {
|
|
|
373
377
|
|
|
374
378
|
// Metadata table
|
|
375
379
|
div_.h3().tx("Properties");
|
|
376
|
-
await this.renderMetadataTable(cs, div_.table("grid"));
|
|
380
|
+
await this.renderMetadataTable(cs, div_.table("grid"), sourcePackage);
|
|
377
381
|
|
|
378
382
|
// Code system properties
|
|
379
383
|
const hasProps = this.generateProperties(div_, cs);
|
|
@@ -2227,6 +2231,18 @@ class Renderer {
|
|
|
2227
2231
|
}
|
|
2228
2232
|
}
|
|
2229
2233
|
|
|
2234
|
+
featureSummary(ext) {
|
|
2235
|
+
let defn = Extensions.readString(ext, 'definition');
|
|
2236
|
+
let value = Extensions.readString(ext, 'value');
|
|
2237
|
+
switch (defn) {
|
|
2238
|
+
case 'http://hl7.org/fhir/uv/tx-tests/FeatureDefinition/test-version':
|
|
2239
|
+
return 'Tx Test version = ' + value;
|
|
2240
|
+
case 'http://hl7.org/fhir/uv/tx-ecosystem/FeatureDefinition/CodeSystemAsParameter':
|
|
2241
|
+
return 'CodeSystems as parameters = ' + value;
|
|
2242
|
+
default:
|
|
2243
|
+
return defn+' = ' + value;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2230
2246
|
}
|
|
2231
2247
|
|
|
2232
2248
|
module.exports = { Renderer };
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
BaseUnit, DefinedUnit, Prefix, Value, Term, Symbol, Factor, Canonical, CanonicalUnit,
|
|
11
11
|
Registry
|
|
12
12
|
} = require('./ucum-types.js');
|
|
13
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
13
14
|
|
|
14
15
|
// Lexer for tokenizing UCUM expressions (port of Lexer.java)
|
|
15
16
|
class Lexer {
|
|
@@ -763,7 +764,7 @@ class Search {
|
|
|
763
764
|
|
|
764
765
|
if (isRegex) {
|
|
765
766
|
try {
|
|
766
|
-
const regex =
|
|
767
|
+
const regex = regexUtilities.compile(text);
|
|
767
768
|
return regex.test(value);
|
|
768
769
|
} catch (e) {
|
|
769
770
|
this.log.error(e);
|