fhirsmith 0.4.2 → 0.5.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.
Files changed (92) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/README.md +1 -1
  3. package/library/cron-utilities.js +136 -0
  4. package/library/html-server.js +13 -29
  5. package/library/html.js +3 -8
  6. package/library/languages.js +160 -37
  7. package/library/package-manager.js +48 -1
  8. package/library/utilities.js +100 -19
  9. package/package.json +2 -2
  10. package/packages/package-crawler.js +6 -1
  11. package/packages/packages.js +38 -54
  12. package/publisher/publisher.js +19 -27
  13. package/registry/api.js +11 -10
  14. package/registry/crawler.js +31 -29
  15. package/registry/model.js +5 -26
  16. package/registry/registry.js +32 -41
  17. package/server.js +89 -12
  18. package/shl/shl.js +0 -18
  19. package/static/assets/js/statuspage.js +1 -9
  20. package/stats.js +39 -1
  21. package/token/token.js +14 -9
  22. package/translations/Messages.properties +2 -1
  23. package/tx/README.md +17 -6
  24. package/tx/cs/cs-api.js +19 -1
  25. package/tx/cs/cs-base.js +77 -0
  26. package/tx/cs/cs-country.js +46 -0
  27. package/tx/cs/cs-cpt.js +9 -5
  28. package/tx/cs/cs-cs.js +27 -13
  29. package/tx/cs/cs-lang.js +60 -22
  30. package/tx/cs/cs-loinc.js +69 -98
  31. package/tx/cs/cs-mimetypes.js +4 -0
  32. package/tx/cs/cs-ndc.js +6 -0
  33. package/tx/cs/cs-omop.js +16 -15
  34. package/tx/cs/cs-rxnorm.js +23 -1
  35. package/tx/cs/cs-snomed.js +283 -40
  36. package/tx/cs/cs-ucum.js +90 -70
  37. package/tx/importers/import-sct.module.js +371 -35
  38. package/tx/importers/readme.md +117 -7
  39. package/tx/library/bundle.js +5 -0
  40. package/tx/library/capabilitystatement.js +3 -142
  41. package/tx/library/codesystem.js +19 -173
  42. package/tx/library/conceptmap.js +4 -218
  43. package/tx/library/designations.js +14 -1
  44. package/tx/library/extensions.js +7 -0
  45. package/tx/library/namingsystem.js +3 -89
  46. package/tx/library/operation-outcome.js +8 -3
  47. package/tx/library/parameters.js +3 -2
  48. package/tx/library/renderer.js +10 -6
  49. package/tx/library/terminologycapabilities.js +3 -243
  50. package/tx/library/valueset.js +3 -235
  51. package/tx/library.js +100 -13
  52. package/tx/operation-context.js +23 -4
  53. package/tx/params.js +35 -38
  54. package/tx/provider.js +6 -5
  55. package/tx/sct/expressions.js +12 -3
  56. package/tx/tx-html.js +80 -89
  57. package/tx/tx.fhir.org.yml +6 -5
  58. package/tx/tx.js +163 -13
  59. package/tx/vs/vs-database.js +56 -39
  60. package/tx/vs/vs-package.js +21 -2
  61. package/tx/vs/vs-vsac.js +175 -39
  62. package/tx/workers/batch-validate.js +2 -0
  63. package/tx/workers/batch.js +2 -0
  64. package/tx/workers/expand.js +132 -112
  65. package/tx/workers/lookup.js +33 -14
  66. package/tx/workers/metadata.js +2 -2
  67. package/tx/workers/read.js +3 -2
  68. package/tx/workers/related.js +574 -0
  69. package/tx/workers/search.js +46 -9
  70. package/tx/workers/subsumes.js +13 -3
  71. package/tx/workers/translate.js +7 -3
  72. package/tx/workers/validate.js +258 -285
  73. package/tx/workers/worker.js +43 -39
  74. package/tx/xml/bundle-xml.js +237 -0
  75. package/tx/xml/xml-base.js +215 -64
  76. package/tx/xversion/xv-bundle.js +71 -0
  77. package/tx/xversion/xv-capabiliityStatement.js +137 -0
  78. package/tx/xversion/xv-codesystem.js +169 -0
  79. package/tx/xversion/xv-conceptmap.js +224 -0
  80. package/tx/xversion/xv-namingsystem.js +88 -0
  81. package/tx/xversion/xv-operationoutcome.js +27 -0
  82. package/tx/xversion/xv-parameters.js +87 -0
  83. package/tx/xversion/xv-resource.js +45 -0
  84. package/tx/xversion/xv-terminologyCapabilities.js +214 -0
  85. package/tx/xversion/xv-valueset.js +234 -0
  86. package/utilities/dev-proxy-server.js +126 -0
  87. package/utilities/explode-results.js +58 -0
  88. package/utilities/split-by-system.js +198 -0
  89. package/utilities/vsac-cs-fetcher.js +0 -0
  90. package/{windows-install.js → utilities/windows-install.js} +2 -0
  91. package/vcl/vcl.js +0 -18
  92. package/xig/xig.js +241 -230
@@ -1,4 +1,4 @@
1
- const { TerminologyError } = require('../operation-context');
1
+ const { TerminologyError, isDebugging} = require('../operation-context');
2
2
  const { CodeSystem } = require('../library/codesystem');
3
3
  const ValueSet = require('../library/valueset');
4
4
  const {VersionUtilities} = require("../../library/version-utilities");
@@ -133,9 +133,10 @@ class TerminologyWorker {
133
133
  * @param {Array<string>} kinds - Allowed content modes
134
134
  * @param {OperationOutcome} op - Op for errors
135
135
  * * @param {boolean} nullOk - Whether null result is acceptable
136
+ * @param {Set<string>} statedSupplements - Supplements invoked in context
136
137
  * @returns {CodeSystemProvider|null} Code system provider or null
137
138
  */
138
- async findCodeSystem(url, version = '', params, kinds = ['complete'], op, nullOk = false, checkVer = false, noVParams = false) {
139
+ async findCodeSystem(url, version = '', params, kinds = ['complete'], op, nullOk = false, checkVer = false, noVParams = false, statedSupplements = null) {
139
140
  if (!url) {
140
141
  return null;
141
142
  }
@@ -145,7 +146,7 @@ class TerminologyWorker {
145
146
  }
146
147
  let codeSystemResource = null;
147
148
  let provider = null;
148
- const supplements = this.loadSupplements(url, version);
149
+ const supplements = this.loadSupplements(url, version, statedSupplements);
149
150
 
150
151
  // First check additional resources
151
152
  codeSystemResource = this.findInAdditionalResources(url, version, 'CodeSystem', !nullOk);
@@ -169,13 +170,13 @@ class TerminologyWorker {
169
170
 
170
171
  if (!provider && !nullOk) {
171
172
  if (!version) {
172
- throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_EXP", params.FHTTPLanguages, [url]), "not-found", 404);
173
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_EXP", params.FHTTPLanguages, [url]), "not-found", 422);
173
174
  } else {
174
175
  const versions = await this.listVersions(url);
175
176
  if (versions.length === 0) {
176
- throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", params.FHTTPLanguages, [url, version]), "not-found", 404);
177
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP_NONE", params.FHTTPLanguages, [url, version]), "not-found", 422);
177
178
  } else {
178
- throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP", params.FHTTPLanguages, [url, version, this.presentVersionList(versions)]), "not-found", 404);
179
+ throw new Issue("error", "not-found", null, "UNKNOWN_CODESYSTEM_VERSION_EXP", this.i18n.translate("UNKNOWN_CODESYSTEM_VERSION_EXP", params.FHTTPLanguages, [url, version, this.presentVersionList(versions)]), "not-found", 422);
179
180
  }
180
181
  }
181
182
  }
@@ -245,9 +246,10 @@ class TerminologyWorker {
245
246
  * Load supplements for a code system
246
247
  * @param {string} url - Code system URL
247
248
  * @param {string} version - Code system version
249
+ * @param {Set<string>} statedSupplements - Supplements invoked in context
248
250
  * @returns {Array<CodeSystem>} Supplement code systems
249
251
  */
250
- loadSupplements(url, version = '') {
252
+ loadSupplements(url, version = '', statedSupplements) {
251
253
  const supplements = [];
252
254
 
253
255
  if (!this.additionalResources) {
@@ -265,6 +267,10 @@ class TerminologyWorker {
265
267
  continue;
266
268
  }
267
269
 
270
+ // we consider either language packs or specified supplements
271
+ if (!(cs.isLangPack() || (statedSupplements && (statedSupplements.has(cs.url) || statedSupplements.has(cs.vurl))))) {
272
+ continue;
273
+ }
268
274
  // Handle exact URL match (no version specified in supplements)
269
275
  if (supplementsUrl === url) {
270
276
  // If we're looking for a specific version, only include if no version in supplements URL
@@ -298,10 +304,10 @@ class TerminologyWorker {
298
304
  * @param {CodeSystemProvider} cs - Code system provider
299
305
  * @param {Object} src - Source element (for extensions)
300
306
  */
301
- checkSupplements(cs, src, requiredSupplements) {
307
+ checkSupplements(cs, src, requiredSupplements, usedSupplements = null) {
302
308
  // Check for required supplements in extensions
303
- if (src && src.getExtensions) {
304
- const supplementExtensions = src.getExtensions('http://hl7.org/fhir/StructureDefinition/valueset-supplement');
309
+ if (src && src.extension) {
310
+ const supplementExtensions = src.extension.filter(x => x.url == 'http://hl7.org/fhir/StructureDefinition/valueset-supplement');
305
311
  for (const ext of supplementExtensions) {
306
312
  const supplementUrl = ext.valueString || ext.valueUri;
307
313
  if (supplementUrl && !cs.hasSupplement(this.opContext, supplementUrl)) {
@@ -310,10 +316,12 @@ class TerminologyWorker {
310
316
  }
311
317
  }
312
318
 
313
- // Remove required supplements that are satisfied
314
- for (let i = requiredSupplements.length - 1; i >= 0; i--) {
315
- if (cs.hasSupplement(requiredSupplements[i])) {
316
- requiredSupplements.splice(i, 1);
319
+ // Note required supplements that are satisfied
320
+ if (usedSupplements) {
321
+ for (const s of requiredSupplements) {
322
+ if (cs.hasSupplement(s)) {
323
+ usedSupplements.add(s);
324
+ }
317
325
  }
318
326
  }
319
327
  }
@@ -456,6 +464,10 @@ class TerminologyWorker {
456
464
  if (req.method === 'POST' && req.body && req.body.resourceType === 'Parameters') {
457
465
  return req.body;
458
466
  }
467
+ if (req.method === 'POST' && req.body && req.body.resourceType) {
468
+ let langs = this.languages.parse(req.headers['accept-language']);
469
+ throw new Issue('error', 'invalid', null, 'Wrong_type_for_resource_expected', this.i18n.translate('Wrong_type_for_resource_expected', langs, ["Parameters", req.body.resourceType])).handleAsOO(400);
470
+ }
459
471
 
460
472
  // Convert query params or form body to Parameters
461
473
  const source = req.method === 'POST' ? {...req.query, ...req.body} : req.query;
@@ -480,6 +492,10 @@ class TerminologyWorker {
480
492
  // Assume it's a complex type like Coding or CodeableConcept
481
493
  params.parameter.push(this.buildComplexParameter(name, value));
482
494
  }
495
+ } else if (value == 'true') {
496
+ params.parameter.push({name, valueBoolean: true});
497
+ } else if (value == 'false') {
498
+ params.parameter.push({name, valueBoolean: false});
483
499
  } else {
484
500
  params.parameter.push({name, valueString: String(value)});
485
501
  }
@@ -837,31 +853,6 @@ class TerminologyWorker {
837
853
  return result;
838
854
  }
839
855
 
840
- // Note: findParameter, getStringParam, getResourceParam, getCodingParam,
841
- // and getCodeableConceptParam are inherited from TerminologyWorker base class
842
-
843
- fixForVersion(resource) {
844
- if (this.provider.fhirVersion >= 5) {
845
- return resource;
846
- }
847
- let rt = resource.resourceType;
848
- switch (rt) {
849
- case "ValueSet": {
850
- let vs = new ValueSet(resource);
851
- if (this.provider.fhirVersion == 4) {
852
- return vs.convertFromR5(resource, "R4");
853
- } else if (this.provider.fhirVersion == 3) {
854
- return vs.convertFromR5(resource, "R3");
855
- } else {
856
- return resource;
857
- }
858
- }
859
- default:
860
- return resource;
861
- }
862
- }
863
-
864
-
865
856
  seeSourceVS(vs, url) {
866
857
  let s = url;
867
858
  if (vs) {
@@ -896,6 +887,19 @@ class TerminologyWorker {
896
887
  const lastItem = items.pop();
897
888
  return `${items.join(', ')} and ${lastItem}`;
898
889
  }
890
+
891
+ checkNoLockedDate(url, compose) {
892
+ if (compose.lockedDate) {
893
+ throw new Issue("error", "business-rule", null, null, `Cannot process ValueSet ${url} due to the presence of a lockedDate on the compose`);
894
+ }
895
+ return true;
896
+ }
897
+
898
+ debugLog(error) {
899
+ if (isDebugging()) {
900
+ console.log(error);
901
+ }
902
+ }
899
903
  }
900
904
 
901
905
  module.exports = {
@@ -0,0 +1,237 @@
1
+ //
2
+ // Bundle XML Serialization
3
+ //
4
+
5
+ const { FhirXmlBase } = require('./xml-base');
6
+
7
+ /**
8
+ * XML support for FHIR Bundle resources
9
+ */
10
+ class BundleXML extends FhirXmlBase {
11
+
12
+ /**
13
+ * Element order for Bundle (FHIR requires specific order)
14
+ */
15
+ static _elementOrder = [
16
+ 'id', 'meta', 'implicitRules', 'language',
17
+ 'identifier', 'type', 'timestamp', 'total',
18
+ 'link', 'entry', 'signature', 'issues'
19
+ ];
20
+
21
+ /**
22
+ * Element order for Bundle.entry
23
+ */
24
+ static _entryElementOrder = [
25
+ 'link', 'fullUrl', 'resource', 'search', 'request', 'response'
26
+ ];
27
+
28
+ /**
29
+ * Element order for Bundle.link
30
+ */
31
+ static _linkElementOrder = [
32
+ 'relation', 'url'
33
+ ];
34
+
35
+ /**
36
+ * Element order for Bundle.entry.search
37
+ */
38
+ static _searchElementOrder = [
39
+ 'mode', 'score'
40
+ ];
41
+
42
+ /**
43
+ * Convert Bundle JSON to XML string
44
+ * @param {Object} json - Bundle as JSON
45
+ * @param {number} fhirVersion - FHIR version (3, 4, or 5)
46
+ * @returns {string} XML string
47
+ */
48
+ static toXml(json, fhirVersion) {
49
+ let content = '';
50
+ const indent = ' ';
51
+
52
+ // Render simple elements first
53
+ for (const key of this._elementOrder) {
54
+ if (json[key] === undefined) continue;
55
+
56
+ if (key === 'link') {
57
+ // Handle link array
58
+ for (const link of json.link || []) {
59
+ content += `${indent}<link>\n`;
60
+ content += this.renderElementsInOrder(link, 2, this._linkElementOrder);
61
+ content += `${indent}</link>\n`;
62
+ }
63
+ } else if (key === 'entry') {
64
+ // Handle entry array with nested resources
65
+ for (const entry of json.entry || []) {
66
+ content += `${indent}<entry>\n`;
67
+
68
+ // fullUrl
69
+ if (entry.fullUrl) {
70
+ content += `${indent}${indent}<fullUrl value="${this.escapeXml(entry.fullUrl)}"/>\n`;
71
+ }
72
+
73
+ // resource - needs special handling to embed full resource
74
+ if (entry.resource) {
75
+ content += `${indent}${indent}<resource>\n`;
76
+ content += this.renderResource(entry.resource, 3, fhirVersion);
77
+ content += `${indent}${indent}</resource>\n`;
78
+ }
79
+
80
+ // search
81
+ if (entry.search) {
82
+ content += `${indent}${indent}<search>\n`;
83
+ if (entry.search.mode) {
84
+ content += `${indent}${indent}${indent}<mode value="${this.escapeXml(entry.search.mode)}"/>\n`;
85
+ }
86
+ if (entry.search.score !== undefined) {
87
+ content += `${indent}${indent}${indent}<score value="${entry.search.score}"/>\n`;
88
+ }
89
+ content += `${indent}${indent}</search>\n`;
90
+ }
91
+
92
+ content += `${indent}</entry>\n`;
93
+ }
94
+ } else if (key === 'total') {
95
+ content += `${indent}<total value="${json.total}"/>\n`;
96
+ } else if (key === 'type') {
97
+ content += `${indent}<type value="${this.escapeXml(json.type)}"/>\n`;
98
+ } else if (key === 'timestamp') {
99
+ content += `${indent}<timestamp value="${this.escapeXml(json.timestamp)}"/>\n`;
100
+ } else if (key === 'id') {
101
+ content += `${indent}<id value="${this.escapeXml(json.id)}"/>\n`;
102
+ } else if (key === 'meta') {
103
+ content += this.renderMeta(json.meta, 1);
104
+ } else {
105
+ // Generic element handling
106
+ content += this.renderElement(key, json[key], 1);
107
+ }
108
+ }
109
+
110
+ return this.wrapInRootElement('Bundle', content);
111
+ }
112
+
113
+ /**
114
+ * Render a nested resource as XML
115
+ */
116
+ static renderResource(resource, indentLevel, _fhirVersion) {
117
+ void _fhirVersion; // reserved for future version-specific rendering
118
+
119
+ const indent = ' '.repeat(indentLevel);
120
+ const resourceType = resource.resourceType;
121
+
122
+ // For known resource types, delegate to their specific converters
123
+ // For unknown types, render generically
124
+ let innerContent = '';
125
+
126
+ // Get element order based on resource type
127
+ const elementOrder = this.getElementOrderForResource(resourceType);
128
+
129
+ innerContent = this.renderElementsInOrder(resource, indentLevel + 1, elementOrder);
130
+
131
+ return `${indent}<${resourceType} xmlns="http://hl7.org/fhir">\n${innerContent}${indent}</${resourceType}>\n`;
132
+ }
133
+
134
+ /**
135
+ * Get element order for a resource type
136
+ */
137
+ static getElementOrderForResource(resourceType) {
138
+ // Common elements that most resources have
139
+ const commonElements = [
140
+ 'id', 'meta', 'implicitRules', 'language', 'text', 'contained',
141
+ 'extension', 'modifierExtension'
142
+ ];
143
+
144
+ switch (resourceType) {
145
+ case 'CodeSystem':
146
+ return [
147
+ ...commonElements,
148
+ 'url', 'identifier', 'version', 'versionAlgorithmString', 'versionAlgorithmCoding',
149
+ 'name', 'title', 'status', 'experimental', 'date', 'publisher',
150
+ 'contact', 'description', 'useContext', 'jurisdiction', 'purpose', 'copyright',
151
+ 'copyrightLabel', 'approvalDate', 'lastReviewDate',
152
+ 'caseSensitive', 'valueSet', 'hierarchyMeaning', 'compositional',
153
+ 'versionNeeded', 'content', 'supplements', 'count', 'filter', 'property', 'concept'
154
+ ];
155
+ case 'ValueSet':
156
+ return [
157
+ ...commonElements,
158
+ 'url', 'identifier', 'version', 'versionAlgorithmString', 'versionAlgorithmCoding',
159
+ 'name', 'title', 'status', 'experimental', 'date', 'publisher',
160
+ 'contact', 'description', 'useContext', 'jurisdiction', 'purpose', 'copyright',
161
+ 'copyrightLabel', 'approvalDate', 'lastReviewDate', 'effectivePeriod',
162
+ 'immutable', 'compose', 'expansion'
163
+ ];
164
+ case 'ConceptMap':
165
+ return [
166
+ ...commonElements,
167
+ 'url', 'identifier', 'version', 'name', 'title', 'status', 'experimental',
168
+ 'date', 'publisher', 'contact', 'description', 'useContext', 'jurisdiction',
169
+ 'purpose', 'copyright', 'sourceUri', 'sourceCanonical', 'targetUri', 'targetCanonical',
170
+ 'group'
171
+ ];
172
+ default:
173
+ // Return common elements plus all other keys from the resource
174
+ return commonElements;
175
+ }
176
+ }
177
+
178
+ /**
179
+ * Render meta element
180
+ */
181
+ static renderMeta(meta, indentLevel) {
182
+ if (!meta) return '';
183
+ const indent = ' '.repeat(indentLevel);
184
+ let content = `${indent}<meta>\n`;
185
+
186
+ if (meta.versionId) {
187
+ content += `${indent} <versionId value="${this.escapeXml(meta.versionId)}"/>\n`;
188
+ }
189
+ if (meta.lastUpdated) {
190
+ content += `${indent} <lastUpdated value="${this.escapeXml(meta.lastUpdated)}"/>\n`;
191
+ }
192
+ if (meta.source) {
193
+ content += `${indent} <source value="${this.escapeXml(meta.source)}"/>\n`;
194
+ }
195
+ for (const profile of meta.profile || []) {
196
+ content += `${indent} <profile value="${this.escapeXml(profile)}"/>\n`;
197
+ }
198
+ for (const security of meta.security || []) {
199
+ content += this.renderCoding(security, indentLevel + 1, 'security');
200
+ }
201
+ for (const tag of meta.tag || []) {
202
+ content += this.renderCoding(tag, indentLevel + 1, 'tag');
203
+ }
204
+
205
+ content += `${indent}</meta>\n`;
206
+ return content;
207
+ }
208
+
209
+ /**
210
+ * Render a Coding element
211
+ */
212
+ static renderCoding(coding, indentLevel, elementName) {
213
+ const indent = ' '.repeat(indentLevel);
214
+ let content = `${indent}<${elementName}>\n`;
215
+
216
+ if (coding.system) {
217
+ content += `${indent} <system value="${this.escapeXml(coding.system)}"/>\n`;
218
+ }
219
+ if (coding.version) {
220
+ content += `${indent} <version value="${this.escapeXml(coding.version)}"/>\n`;
221
+ }
222
+ if (coding.code) {
223
+ content += `${indent} <code value="${this.escapeXml(coding.code)}"/>\n`;
224
+ }
225
+ if (coding.display) {
226
+ content += `${indent} <display value="${this.escapeXml(coding.display)}"/>\n`;
227
+ }
228
+ if (coding.userSelected !== undefined) {
229
+ content += `${indent} <userSelected value="${coding.userSelected}"/>\n`;
230
+ }
231
+
232
+ content += `${indent}</${elementName}>\n`;
233
+ return content;
234
+ }
235
+ }
236
+
237
+ module.exports = { BundleXML };