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.
Files changed (41) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/README.md +52 -22
  3. package/extension-tracker/extension-tracker-template.html +3 -1
  4. package/library/html-server.js +7 -0
  5. package/library/logger.js +234 -194
  6. package/library/regex-utilities.js +13 -0
  7. package/package.json +4 -2
  8. package/packages/packages-template.html +3 -1
  9. package/publisher/publisher-template.html +1 -0
  10. package/publisher/publisher.js +28 -7
  11. package/registry/registry-template.html +3 -1
  12. package/root-bare-template.html +9759 -37
  13. package/root-template.html +3 -2
  14. package/server.js +48 -12
  15. package/translations/Messages.properties +2 -1
  16. package/translations/rendering-phrases.properties +3 -1
  17. package/tx/cs/cs-api.js +4 -0
  18. package/tx/cs/cs-country.js +2 -1
  19. package/tx/cs/cs-cs.js +9 -4
  20. package/tx/cs/cs-loinc.js +2 -1
  21. package/tx/cs/cs-snomed.js +5 -1
  22. package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
  23. package/tx/html/tx-template.html +3 -2
  24. package/tx/importers/atc-to-fhir.js +27 -27
  25. package/tx/library/codesystem.js +4 -0
  26. package/tx/library/renderer.js +20 -4
  27. package/tx/library/ucum-parsers.js +2 -1
  28. package/tx/ocl/cs-ocl.cjs +48 -15
  29. package/tx/ocl/vs-ocl.cjs +57 -34
  30. package/tx/operation-context.js +74 -19
  31. package/tx/tx-html.js +5 -5
  32. package/tx/tx.fhir.org.yml +4 -4
  33. package/tx/tx.js +1 -0
  34. package/tx/vs/vs-database.js +150 -100
  35. package/tx/vs/vs-vsac.js +90 -31
  36. package/tx/workers/expand.js +154 -113
  37. package/tx/workers/metadata.js +6 -3
  38. package/tx/workers/read.js +6 -3
  39. package/tx/workers/related.js +228 -87
  40. package/xig/xig-template.html +3 -1
  41. package/library/logger-telnet.js +0 -205
@@ -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> &copy; HL7.org 2011+. &nbsp;|&nbsp;
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%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp; ([%ms%] ms)
94
- &nbsp;
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%] &copy; HealthIntersections.com.au 2023+ &nbsp;|
94
+ &nbsp; ([%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 heapUsedMB = (memUsage.heapUsed / 1024 / 1024).toFixed(2);
390
- const heapAvailableMB = ((memUsage.heapTotal - memUsage.heapUsed) / 1024 / 1024).toFixed(2);
391
- const rssMB = (memUsage.rss / 1024 / 1024).toFixed(2);
392
- const freeMemMB = (os.freemem() / 1024 / 1024).toFixed(0);
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>Free Memory:</strong> ${freeMemMB} MB of ${totalMemMB} MB</td>`;
422
+ content += `<td><strong>Avg Requests/min:</strong> ${avgReqPerMin}</td>`;
400
423
  content += '</tr>';
401
424
  content += '<tr>';
402
- content += `<td><strong>Heap Used:</strong> ${heapUsedMB} MB</td>`;
403
- content += `<td><strong>Heap Available:</strong> ${heapAvailableMB} MB</td>`;
404
- content += `<td><strong>Process Memory:</strong> ${rssMB} MB</td>`;
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 nodeMemPCT = (memUsage.heapUsed * 100) / heapStats.heap_size_limit; // % of Node.js memory limit used
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(nodeMemPCT)}"><strong>Node Memory:</strong> ${nodeMemPCT.toFixed(0)}%</td>`;
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 display ({1})
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
@@ -719,7 +719,11 @@ class CodeSystemProvider {
719
719
  return false;
720
720
  }
721
721
 
722
+ hasMultiHierarchy() {
723
+ return false;
724
+ }
722
725
  /**
726
+ *
723
727
  * @returns {string} valueset for the code system
724
728
  */
725
729
  valueSet() {
@@ -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 = new RegExp('^' + value + '$');
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 = new RegExp('^' + value + '$');
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 Error(`Invalid regex pattern: ${value}`);
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 = new RegExp('^' + value + '$');
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 = new RegExp(pattern);
942
+ const regex = regexUtilities.compile(pattern);
942
943
  const matchingKeys = [];
943
944
 
944
945
  this.db.all(sql, (err, rows) => {
@@ -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
+ }
@@ -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> &copy; HL7.org 2011+. &nbsp;|&nbsp;
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%] &copy; HL7.org 2011+. &nbsp;|&nbsp;
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%] &copy; HealthIntersections.com.au 2023+ &nbsp;|&nbsp;
122
- FHIR Version [%fhir-version%] &nbsp;|&nbsp; ([%ms%] ms)
122
+ &nbsp; ([%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.group';
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
+ }
@@ -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
  /**
@@ -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 = new RegExp(text);
767
+ const regex = regexUtilities.compile(text);
767
768
  return regex.test(value);
768
769
  } catch (e) {
769
770
  this.log.error(e);