fhirsmith 0.7.6 → 0.8.2

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 (57) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +5 -1
  3. package/library/languages.js +10 -0
  4. package/package.json +1 -1
  5. package/packages/package-crawler.js +2 -2
  6. package/publisher/publisher.js +1 -1
  7. package/registry/registry.js +2 -2
  8. package/root-bare-template.html +1 -2
  9. package/security.md +3 -0
  10. package/server.js +100 -70
  11. package/stats.js +37 -6
  12. package/tx/cs/cs-api.js +8 -4
  13. package/tx/cs/cs-loinc.js +14 -2
  14. package/tx/cs/cs-omop.js +5 -3
  15. package/tx/cs/cs-rxnorm.js +18 -16
  16. package/tx/cs/cs-snomed.js +279 -6
  17. package/tx/data/cpt-fragment.db +0 -0
  18. package/tx/data/cs-de.json +186 -0
  19. package/tx/data/cs-extensions.json +92 -0
  20. package/tx/data/cs-simple.json +130 -0
  21. package/tx/data/cs-supplement.json +78 -0
  22. package/tx/data/lang.dat +49180 -0
  23. package/tx/data/languages.csv +191 -0
  24. package/tx/data/loinc-subset.txt +75 -0
  25. package/tx/data/omop-fragment.db +0 -0
  26. package/tx/data/readme.md +43 -0
  27. package/tx/data/regions.csv +273 -0
  28. package/tx/data/rxnorm-subset.txt +22 -0
  29. package/tx/data/snomed-subset.txt +47 -0
  30. package/tx/data/ucum-essence.xml +2059 -0
  31. package/tx/html/dash-metrics.liquid +147 -0
  32. package/tx/importers/import-rxnorm.module.js +4 -30
  33. package/tx/library/canonical-resource.js +8 -0
  34. package/tx/library/conceptmap.js +29 -1
  35. package/tx/library/designations.js +4 -8
  36. package/tx/library/extensions.js +4 -3
  37. package/tx/library/renderer.js +9 -9
  38. package/tx/ocl/cm-ocl.cjs +185 -65
  39. package/tx/ocl/cs-ocl.cjs +69 -50
  40. package/tx/ocl/jobs/background-queue.cjs +0 -8
  41. package/tx/ocl/mappers/concept-mapper.cjs +13 -3
  42. package/tx/ocl/shared/patches.cjs +1 -0
  43. package/tx/ocl/vs-ocl.cjs +137 -157
  44. package/tx/operation-context.js +3 -3
  45. package/tx/params.js +2 -2
  46. package/tx/provider.js +6 -3
  47. package/tx/sct/structures.js +6 -1
  48. package/tx/tx.fhir.org.yml +1 -1
  49. package/tx/vs/vs-database.js +107 -23
  50. package/tx/vs/vs-vsac.js +66 -19
  51. package/tx/workers/expand.js +10 -10
  52. package/tx/workers/related.js +2 -2
  53. package/tx/workers/search.js +2 -1
  54. package/tx/workers/translate.js +222 -33
  55. package/tx/workers/validate.js +13 -13
  56. package/tx/xversion/xv-parameters.js +54 -1
  57. package/xig/xig.js +171 -9
@@ -0,0 +1,147 @@
1
+ <div style="display: flex; gap: 20px; margin-bottom: 20px;">
2
+ <div style="flex: 1;">
3
+ <h6>Memory Usage (Heap Used)</h6>
4
+ <canvas id="memoryChart" height="200"></canvas>
5
+ </div>
6
+ <div style="flex: 1;">
7
+ <h6>Turnaround Time</h6>
8
+ <canvas id="tatChart" height="200"></canvas>
9
+ </div>
10
+ <div style="flex: 1;">
11
+ <h6>Requests per Minute</h6>
12
+ <canvas id="requestChart" height="200"></canvas>
13
+ </div>
14
+ </div>
15
+
16
+ <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
17
+ <script>
18
+ (function() {
19
+ const historyData = {{ historyJson }};
20
+ const startTime = {{ startTime }};
21
+
22
+ function formatTime(timestamp) {
23
+ const d = new Date(timestamp);
24
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
25
+ }
26
+
27
+ function formatMB(bytes) {
28
+ return (bytes / 1024 / 1024).toFixed(1);
29
+ }
30
+
31
+ function formatDuration(ms) {
32
+ const hours = ms / 3600000;
33
+ if (hours < 1) {
34
+ return Math.round(ms / 60000) + 'm ago';
35
+ }
36
+ return hours.toFixed(1) + 'h ago';
37
+ }
38
+
39
+ const chartOptions = {
40
+ responsive: false,
41
+ maintainAspectRatio: false,
42
+ scales: {
43
+ x: {
44
+ ticks: {
45
+ maxTicksAuto: 6
46
+ }
47
+ },
48
+ y: {
49
+ beginAtZero: true
50
+ }
51
+ },
52
+ plugins: {
53
+ legend: {
54
+ display: false
55
+ }
56
+ }
57
+ };
58
+
59
+ // Memory chart
60
+ new Chart(document.getElementById('memoryChart'), {
61
+ type: 'line',
62
+ data: {
63
+ labels: historyData.map(d => formatTime(d.time)),
64
+ datasets: [{
65
+ data: historyData.map(d => parseFloat(formatMB(d.mem))),
66
+ borderColor: '#007bff',
67
+ backgroundColor: 'rgba(0, 123, 255, 0.1)',
68
+ fill: true,
69
+ tension: 0.3,
70
+ pointRadius: 2
71
+ }]
72
+ },
73
+ options: {
74
+ ...chartOptions,
75
+ scales: {
76
+ ...chartOptions.scales,
77
+ y: {
78
+ ...chartOptions.scales.y,
79
+ title: {
80
+ display: true,
81
+ text: 'MB (𝚫 from start)'
82
+ }
83
+ }
84
+ }
85
+ }
86
+ });
87
+
88
+
89
+ // Tat chart
90
+ new Chart(document.getElementById('tatChart'), {
91
+ type: 'line',
92
+ data: {
93
+ labels: historyData.map(d => formatTime(d.time)),
94
+ datasets: [{
95
+ data: historyData.map(d => d.tat),
96
+ borderColor: '#7b7b7b',
97
+ backgroundColor: 'rgba(123, 123, 123, 0.1)',
98
+ fill: true,
99
+ tension: 0.3,
100
+ pointRadius: 2
101
+ }]
102
+ },
103
+ options: {
104
+ ...chartOptions,
105
+ scales: {
106
+ ...chartOptions.scales,
107
+ y: {
108
+ ...chartOptions.scales.y,
109
+ title: {
110
+ display: true,
111
+ text: 'ms'
112
+ }
113
+ }
114
+ }
115
+ }
116
+ });
117
+
118
+ // Requests chart
119
+ new Chart(document.getElementById('requestChart'), {
120
+ type: 'line',
121
+ data: {
122
+ labels: historyData.map(d => formatTime(d.time)),
123
+ datasets: [{
124
+ data: historyData.map(d => d.rpm.toFixed(2)),
125
+ borderColor: '#28a745',
126
+ backgroundColor: 'rgba(40, 167, 69, 0.1)',
127
+ fill: true,
128
+ tension: 0.3,
129
+ pointRadius: 2
130
+ }]
131
+ },
132
+ options: {
133
+ ...chartOptions,
134
+ scales: {
135
+ ...chartOptions.scales,
136
+ y: {
137
+ ...chartOptions.scales.y,
138
+ title: {
139
+ display: true,
140
+ text: 'req/min'
141
+ }
142
+ }
143
+ }
144
+ }
145
+ });
146
+ })();
147
+ </script>
@@ -428,7 +428,8 @@ class RxNormModule extends BaseTerminologyModule {
428
428
  'CREATE INDEX IF NOT EXISTS idx_rxnrel_rel ON RXNREL(REL)',
429
429
  'CREATE INDEX IF NOT EXISTS idx_rxnrel_rela ON RXNREL(RELA)',
430
430
  'CREATE INDEX IF NOT EXISTS X_RXNSTY_2 ON RXNSTY(TUI)',
431
- 'CREATE INDEX IF NOT EXISTS idx_rxnstems_stem_cui ON RXNSTEMS(stem, CUI)'
431
+ 'CREATE INDEX IF NOT EXISTS idx_rxnstems_stem_cui ON RXNSTEMS(stem, CUI)',
432
+ 'CREATE INDEX IF NOT EXISTS idx_rxnstems_cui_stem ON RXNSTEMS(CUI, stem)'
432
433
  ];
433
434
 
434
435
  return new Promise((resolve, reject) => {
@@ -779,7 +780,7 @@ class RxNormImporter {
779
780
  if (this.options.verbose) console.log('Generating word stems...');
780
781
 
781
782
  // Simple English stemmer implementation
782
- const stemmer = new SimpleStemmer();
783
+ var natural = require('natural');
783
784
  const stems = new Map();
784
785
 
785
786
  // Get all RXNORM concepts
@@ -791,7 +792,7 @@ class RxNormImporter {
791
792
  rows.forEach(row => {
792
793
  const words = this.extractWords(row.STR);
793
794
  words.forEach(word => {
794
- const stem = stemmer.stem(word.toLowerCase());
795
+ const stem = natural.PorterStemmer.stem(word.toLowerCase());
795
796
  if (stem.length > 0 && stem.length <= 20) {
796
797
  if (!stems.has(stem)) {
797
798
  stems.set(stem, new Set());
@@ -864,33 +865,6 @@ class RxNormImporter {
864
865
  }
865
866
  }
866
867
 
867
- // Simple English stemmer (basic implementation)
868
- class SimpleStemmer {
869
- constructor() {
870
- // Common English suffixes to remove
871
- this.suffixes = [
872
- 'ing', 'ly', 'ed', 'ies', 'ied', 'ies', 'ies', 'y', 's',
873
- 'tion', 'sion', 'ness', 'ment', 'able', 'ible', 'ant', 'ent'
874
- ].sort((a, b) => b.length - a.length); // Longest first
875
- }
876
-
877
- stem(word) {
878
- if (word.length <= 3) return word;
879
-
880
- // Try to remove suffixes
881
- for (const suffix of this.suffixes) {
882
- if (word.endsWith(suffix) && word.length > suffix.length + 2) {
883
- const stem = word.substring(0, word.length - suffix.length);
884
- // Basic validation - stem should still be reasonable length
885
- if (stem.length >= 3) {
886
- return stem;
887
- }
888
- }
889
- }
890
-
891
- return word;
892
- }
893
- }
894
868
 
895
869
  module.exports = {
896
870
  RxNormModule,
@@ -64,6 +64,14 @@ class CanonicalResource {
64
64
  return this.version ? this.url+'|' + this.version : this.url;
65
65
  }
66
66
 
67
+ get vurlOrMsg() {
68
+ if (this.url) {
69
+ return this.version ? this.url+'|' + this.version : this.url;
70
+ } else {
71
+ return '(unidentified)';
72
+ }
73
+ }
74
+
67
75
  get fhirType() {
68
76
  return this.jsonObj.resourceType;
69
77
  }
@@ -132,7 +132,9 @@ class ConceptMap extends CanonicalResource {
132
132
 
133
133
  let all = this.canonicalMatches(targetScope, this.targetScope);
134
134
  for (const g of this.jsonObj.group || []) {
135
- if (all || (this.canonicalMatches(vurl, g.source) && this.canonicalMatches(targetSystem, g.target) )) {
135
+ const sourceOk = this.canonicalMatches(vurl, g.source);
136
+ const targetOk = !targetSystem || this.canonicalMatches(targetSystem, g.target);
137
+ if (all || (sourceOk && targetOk)) {
136
138
  for (const em of g.element || []) {
137
139
  if (em.code === coding.code) {
138
140
  let match = {
@@ -146,6 +148,32 @@ class ConceptMap extends CanonicalResource {
146
148
  }
147
149
  return result;
148
150
  }
151
+
152
+ listTranslationsReverse(coding, targetScope, sourceSystem) {
153
+ let result = [];
154
+ let vurl = VersionUtilities.vurl(coding.system, coding.version);
155
+
156
+ let all = this.canonicalMatches(targetScope, this.targetScope);
157
+ for (const g of this.jsonObj.group || []) {
158
+ const targetOk = this.canonicalMatches(vurl, g.target);
159
+ const sourceOk = !sourceSystem || this.canonicalMatches(sourceSystem, g.source);
160
+ if (all || (sourceOk && targetOk)) {
161
+ for (const em of g.element || []) {
162
+ for (const tm of em.target || []) {
163
+ if (tm.code === coding.code) {
164
+ let match = {
165
+ group: g,
166
+ match: em,
167
+ target: tm
168
+ };
169
+ result.push(match);
170
+ }
171
+ }
172
+ }
173
+ }
174
+ }
175
+ return result;
176
+ }
149
177
  /**
150
178
  * Gets the source scope (R5) or source system (R3/R4)
151
179
  * @returns {string|undefined} Source scope/system
@@ -1,6 +1,6 @@
1
1
  const { LanguagePartType, Languages, Language, LanguageDefinitions} = require('../../library/languages');
2
2
  const {validateParameter, validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
3
-
3
+ const natural = require('natural');
4
4
  /**
5
5
  * Display checking modes for concept designations
6
6
  */
@@ -26,6 +26,7 @@ class SearchFilterText {
26
26
 
27
27
  this.filter = filter ? filter.toLowerCase() : null;
28
28
  this.stems = [];
29
+ this.stemmer = natural.PorterStemmer;
29
30
  if (filter) {
30
31
  this._process();
31
32
  }
@@ -59,7 +60,7 @@ class SearchFilterText {
59
60
  i++;
60
61
  }
61
62
  const word = value.substring(j, i).toLowerCase();
62
- const stemmed = this._stem(word);
63
+ const stemmed = this.stemmer.stem(word);
63
64
 
64
65
  if (this._find(stemmed)) {
65
66
  if (returnRating) {
@@ -142,7 +143,7 @@ class SearchFilterText {
142
143
  i++;
143
144
  }
144
145
  const word = this.filter.substring(j, i);
145
- this.stems.push(this._stem(word));
146
+ this.stems.push(this.stemmer.stem(word.toLowerCase()));
146
147
  } else {
147
148
  i++;
148
149
  }
@@ -155,11 +156,6 @@ class SearchFilterText {
155
156
  return /[0-9a-zA-Z]/.test(char);
156
157
  }
157
158
 
158
- _stem(word) {
159
- // Simple stemming - in practice you'd want a proper stemmer
160
- return word.toLowerCase();
161
- }
162
-
163
159
  _find(stem) {
164
160
  // Binary search
165
161
  let left = 0;
@@ -29,7 +29,7 @@ const Extensions = {
29
29
  }
30
30
  },
31
31
 
32
- checkNoModifiers(element, place, name) {
32
+ checkNoModifiers(element, place, name, resource) {
33
33
  if (!element) {
34
34
  return;
35
35
  }
@@ -41,11 +41,12 @@ const Extensions = {
41
41
  for (const extension of element.modifierExtension) {
42
42
  urls.add(extension.url);
43
43
  }
44
+ const resId = resource ? resource : "";
44
45
  const urlList = [...urls].join('\', \'');
45
46
  if (urls.size > 1) {
46
- throw new Issue("error", "business-rule", null, null, 'Cannot process resource at "' + name + '" due to the presence of modifier extensions '+urlList);
47
+ throw new Issue("error", "business-rule", null, null, 'Cannot process resource '+resId+' at "' + name + '" due to the presence of modifier extensions '+urlList);
47
48
  } else {
48
- throw new Issue("error", "business-rule", null, null, 'Cannot process resource at "' + name + '" due to the presence of the modifier extension '+urlList);
49
+ throw new Issue("error", "business-rule", null, null, 'Cannot process resource '+resId+' at "' + name + '" due to the presence of the modifier extension '+urlList);
49
50
  }
50
51
  }
51
52
  return true;
@@ -1800,12 +1800,12 @@ class Renderer {
1800
1800
  if (elem.noMap) {
1801
1801
  const nomapComment = Extensions.readString(elem, 'http://hl7.org/fhir/StructureDefinition/conceptmap-nomap-comment');
1802
1802
  if (!hasComment) {
1803
- tr.td().colspan("2").style("background-color: #efefef").tx("(not mapped)");
1803
+ tr.td().setAttribute('colspan',"2").style("background-color: #efefef").tx("(not mapped)");
1804
1804
  } else if (nomapComment) {
1805
- tr.td().colspan("2").style("background-color: #efefef").tx("(not mapped)");
1805
+ tr.td().setAttribute('colspan',"2").style("background-color: #efefef").tx("(not mapped)");
1806
1806
  tr.td().style("background-color: #efefef").tx(nomapComment);
1807
1807
  } else {
1808
- tr.td().colspan("3").style("background-color: #efefef").tx("(not mapped)");
1808
+ tr.td().setAttribute('colspan',"3").style("background-color: #efefef").tx("(not mapped)");
1809
1809
  }
1810
1810
  } else {
1811
1811
  let first = true;
@@ -1858,16 +1858,16 @@ class Renderer {
1858
1858
  let tr = tbl.tr();
1859
1859
  const sourceColCount = 1 + Object.keys(sources).length - 1; // code + dependsOn attributes
1860
1860
  const targetColCount = 1 + Object.keys(targets).length - 1; // code + product attributes
1861
- tr.td().colspan(String(sourceColCount + 1)).b().tx(this.translate('CONC_MAP_SRC_DET'));
1861
+ tr.td().setAttribute('colspan', String(sourceColCount + 1)).b().tx(this.translate('CONC_MAP_SRC_DET'));
1862
1862
  if (hasRelationships) {
1863
1863
  tr.td().b().tx(this.translate('CONC_MAP_REL'));
1864
1864
  }
1865
- tr.td().colspan(String(targetColCount + 1)).b().tx(this.translate('CONC_MAP_TRGT_DET'));
1865
+ tr.td().setAttribute('colspan', String(targetColCount + 1)).b().tx(this.translate('CONC_MAP_TRGT_DET'));
1866
1866
  if (hasComment) {
1867
1867
  tr.td().b().tx(this.translate('GENERAL_COMMENT'));
1868
1868
  }
1869
1869
  if (hasProperties) {
1870
- tr.td().colspan(String(Object.keys(props).length)).b().tx(this.translate('GENERAL_PROPS'));
1870
+ tr.td().setAttribute('colspan', String(Object.keys(props).length)).b().tx(this.translate('GENERAL_PROPS'));
1871
1871
  }
1872
1872
 
1873
1873
  // Second header row: actual column headers
@@ -1944,10 +1944,10 @@ class Renderer {
1944
1944
 
1945
1945
  const nomapComment = Extensions.readString(elem, 'http://hl7.org/fhir/StructureDefinition/conceptmap-nomap-comment');
1946
1946
  if (nomapComment) {
1947
- tr.td().colspan("3").style("background-color: #efefef").tx("(not mapped)");
1947
+ tr.td().setAttribute('colspan',"3").style("background-color: #efefef").tx("(not mapped)");
1948
1948
  tr.td().style("background-color: #efefef").tx(nomapComment);
1949
1949
  } else {
1950
- tr.td().colspan("4").style("background-color: #efefef").tx("(not mapped)");
1950
+ tr.td().setAttribute('colspan',"4").style("background-color: #efefef").tx("(not mapped)");
1951
1951
  }
1952
1952
  } else {
1953
1953
  let first = true;
@@ -2072,7 +2072,7 @@ class Renderer {
2072
2072
  async renderCSDetailsLink(tr, url, span2) {
2073
2073
  const td = tr.td();
2074
2074
  if (span2) {
2075
- td.colspan("2");
2075
+ td.setAttribute('colspan',"2");
2076
2076
  }
2077
2077
  td.b().tx(this.translate('CONC_MAP_CODES'));
2078
2078
  td.tx(" " + this.translate('CONC_MAP_FRM') + " ");