fhirsmith 0.3.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 +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
|
@@ -0,0 +1,904 @@
|
|
|
1
|
+
const { TerminologyError } = require('../operation-context');
|
|
2
|
+
const { CodeSystem } = require('../library/codesystem');
|
|
3
|
+
const ValueSet = require('../library/valueset');
|
|
4
|
+
const {VersionUtilities} = require("../../library/version-utilities");
|
|
5
|
+
const {getValuePrimitive} = require("../../library/utilities");
|
|
6
|
+
const {Issue} = require("../library/operation-outcome");
|
|
7
|
+
const {Languages} = require("../../library/languages");
|
|
8
|
+
const {ConceptMap} = require("../library/conceptmap");
|
|
9
|
+
const {Renderer} = require("../library/renderer");
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Custom error for terminology setup issues
|
|
13
|
+
*/
|
|
14
|
+
class TerminologySetupError extends Error {
|
|
15
|
+
constructor(message) {
|
|
16
|
+
super(message);
|
|
17
|
+
this.name = 'TerminologySetupError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Abstract base class for terminology operations
|
|
23
|
+
*/
|
|
24
|
+
class TerminologyWorker {
|
|
25
|
+
usedSources = [];
|
|
26
|
+
additionalResources = []; // Resources provided via tx-resource parameter or cache
|
|
27
|
+
foundParameters = [];
|
|
28
|
+
renderer;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* @param {OperationContext} opContext - Operation context
|
|
32
|
+
* @param {Logger} log - Provider for code systems and resources
|
|
33
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
34
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
35
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
36
|
+
*/
|
|
37
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
38
|
+
this.opContext = opContext;
|
|
39
|
+
this.log = log;
|
|
40
|
+
this.provider = provider;
|
|
41
|
+
this.languages = languages;
|
|
42
|
+
this.i18n = i18n;
|
|
43
|
+
this.noCacheThisOne = false;
|
|
44
|
+
this.params = null; // Will be set by subclasses
|
|
45
|
+
this.renderer = new Renderer(i18n, languages, provider);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Abstract method to get operation name
|
|
50
|
+
* @returns {string} Operation name
|
|
51
|
+
*/
|
|
52
|
+
opName() {
|
|
53
|
+
return '??';
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Abstract method to get value set handle
|
|
58
|
+
* @returns {ValueSet} Value set being processed
|
|
59
|
+
*/
|
|
60
|
+
vsHandle() {
|
|
61
|
+
throw new Error('vsHandle() must be implemented by subclass');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Check if operation should be terminated due to time/cost limits
|
|
66
|
+
* @param {string} place - Location identifier for debugging
|
|
67
|
+
*/
|
|
68
|
+
deadCheck(place = 'unknown') {
|
|
69
|
+
this.opContext.deadCheck(place);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Add cost diagnostics to an error
|
|
74
|
+
* @param {TooCostlyError} e - The error to enhance
|
|
75
|
+
* @returns {TooCostlyError} Enhanced error
|
|
76
|
+
*/
|
|
77
|
+
costDiags(e) {
|
|
78
|
+
e.diagnostics = this.opContext.diagnostics();
|
|
79
|
+
return e;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Find a resource in additional resources by URL and version
|
|
84
|
+
* @param {string} url - Resource URL
|
|
85
|
+
* @param {string} version - Resource version (optional)
|
|
86
|
+
* @param {string} resourceType - Expected resource type
|
|
87
|
+
* @param {boolean} error - Whether to throw error if type mismatch
|
|
88
|
+
* @returns {CodeSystem|ValueSet|null} Found resource or null
|
|
89
|
+
*/
|
|
90
|
+
findInAdditionalResources(url, version = '', resourceType, error = true) {
|
|
91
|
+
if (!this.additionalResources || this.additionalResources.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const matches = [];
|
|
96
|
+
|
|
97
|
+
for (const resource of this.additionalResources) {
|
|
98
|
+
this.deadCheck('findInAdditionalResources');
|
|
99
|
+
|
|
100
|
+
if (url && ((resource.url === url) || (resource.vurl === url)) &&
|
|
101
|
+
(!version || VersionUtilities.versionMatchesByAlgorithm(version, resource.version, resource.versionAlgorithm()))) {
|
|
102
|
+
|
|
103
|
+
if (resource.resourceType !== resourceType) {
|
|
104
|
+
if (error) {
|
|
105
|
+
throw new Error(`Attempt to reference ${url} as a ${resourceType} when it's a ${resource.resourceType}`);
|
|
106
|
+
} else {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
matches.push(resource);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (matches.length === 0) {
|
|
115
|
+
return null;
|
|
116
|
+
} else {
|
|
117
|
+
// Find the latest version
|
|
118
|
+
let latest = 0;
|
|
119
|
+
for (let i = 1; i < matches.length; i++) {
|
|
120
|
+
if (VersionUtilities.isSemVer(matches[latest].version) && VersionUtilities.isSemVer(matches[i].version) && VersionUtilities.isThisOrLater(matches[latest].version, matches[i].version)) {
|
|
121
|
+
latest = i;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return matches[latest];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Find and load a code system provider
|
|
130
|
+
* @param {string} url - Code system URL
|
|
131
|
+
* @param {string} version - Code system version (optional)
|
|
132
|
+
* @param {TxParameters} params - Operation parameters
|
|
133
|
+
* @param {Array<string>} kinds - Allowed content modes
|
|
134
|
+
* @param {OperationOutcome} op - Op for errors
|
|
135
|
+
* * @param {boolean} nullOk - Whether null result is acceptable
|
|
136
|
+
* @returns {CodeSystemProvider|null} Code system provider or null
|
|
137
|
+
*/
|
|
138
|
+
async findCodeSystem(url, version = '', params, kinds = ['complete'], op, nullOk = false, checkVer = false, noVParams = false) {
|
|
139
|
+
if (!url) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!noVParams) {
|
|
144
|
+
version = this.determineVersionBase(url, version, params);
|
|
145
|
+
}
|
|
146
|
+
let codeSystemResource = null;
|
|
147
|
+
let provider = null;
|
|
148
|
+
const supplements = this.loadSupplements(url, version);
|
|
149
|
+
|
|
150
|
+
// First check additional resources
|
|
151
|
+
codeSystemResource = this.findInAdditionalResources(url, version, 'CodeSystem', !nullOk);
|
|
152
|
+
|
|
153
|
+
if (codeSystemResource) {
|
|
154
|
+
if (codeSystemResource.content === 'complete') {
|
|
155
|
+
// Create provider from complete code system
|
|
156
|
+
provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If no provider from additional resources, try main provider
|
|
161
|
+
if (!provider) {
|
|
162
|
+
provider = await this.provider.getCodeSystemProvider(this.opContext, url, version, supplements);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// If still no provider but we have a code system with allowed content mode
|
|
166
|
+
if (!provider && codeSystemResource && kinds.includes(codeSystemResource.content)) {
|
|
167
|
+
provider = await this.provider.createCodeSystemProvider(this.opContext, codeSystemResource, supplements);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (!provider && !nullOk) {
|
|
171
|
+
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
|
+
} else {
|
|
174
|
+
const versions = await this.listVersions(url);
|
|
175
|
+
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
|
+
} 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
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (provider) {
|
|
183
|
+
if (checkVer) {
|
|
184
|
+
this.checkVersion(url, provider.version(), params, provider.versionAlgorithm(), op);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return provider;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* List available versions for a code system
|
|
193
|
+
* @param {string} url - Code system URL
|
|
194
|
+
* @returns {Array<string>} Available versions
|
|
195
|
+
*/
|
|
196
|
+
async listVersions(url) {
|
|
197
|
+
const versions = new Set();
|
|
198
|
+
|
|
199
|
+
// Check additional resources
|
|
200
|
+
if (this.additionalResources) {
|
|
201
|
+
for (const resource of this.additionalResources) {
|
|
202
|
+
this.deadCheck('listVersions-additional');
|
|
203
|
+
if (resource.url === url && resource.version) {
|
|
204
|
+
versions.add(resource.version);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Check main provider
|
|
210
|
+
const providerVersions = await this.provider.listCodeSystemVersions(url);
|
|
211
|
+
for (const version of providerVersions) {
|
|
212
|
+
this.deadCheck('listVersions-provider');
|
|
213
|
+
versions.add(version);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return Array.from(versions).sort();
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async listDisplaysFromCodeSystem(displays, cs, c) {
|
|
220
|
+
// list all known language displays
|
|
221
|
+
await cs.designations(c, displays);
|
|
222
|
+
displays.source = cs;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
listDisplaysFromConcept(displays, c) {
|
|
226
|
+
// list all known provided displays
|
|
227
|
+
// todo: supplements
|
|
228
|
+
for (let ccd of c.designations || []) {
|
|
229
|
+
displays.addDesignationFromConcept(ccd);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
listDisplaysFromIncludeConcept(displays, c, vs) {
|
|
234
|
+
if (c.display) {
|
|
235
|
+
displays.baseLang = this.languages.parse(vs.language);
|
|
236
|
+
displays.addDesignation(true, "active", '', '', c.display.trim());
|
|
237
|
+
}
|
|
238
|
+
for (let cd of c.designations || []) {
|
|
239
|
+
// see https://chat.fhir.org/#narrow/stream/179202-terminology/topic/ValueSet.20designations.20and.20languages
|
|
240
|
+
displays.addDesignationFromConcept(cd);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Load supplements for a code system
|
|
246
|
+
* @param {string} url - Code system URL
|
|
247
|
+
* @param {string} version - Code system version
|
|
248
|
+
* @returns {Array<CodeSystem>} Supplement code systems
|
|
249
|
+
*/
|
|
250
|
+
loadSupplements(url, version = '') {
|
|
251
|
+
const supplements = [];
|
|
252
|
+
|
|
253
|
+
if (!this.additionalResources) {
|
|
254
|
+
return supplements;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
for (const resource of this.additionalResources) {
|
|
258
|
+
this.deadCheck('loadSupplements');
|
|
259
|
+
if (resource.resourceType === 'CodeSystem' && resource instanceof CodeSystem) {
|
|
260
|
+
const cs = resource;
|
|
261
|
+
// Check if this code system supplements the target URL
|
|
262
|
+
const supplementsUrl = cs.jsonObj.supplements;
|
|
263
|
+
|
|
264
|
+
if (!supplementsUrl) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Handle exact URL match (no version specified in supplements)
|
|
269
|
+
if (supplementsUrl === url) {
|
|
270
|
+
// If we're looking for a specific version, only include if no version in supplements URL
|
|
271
|
+
if (!version) {
|
|
272
|
+
supplements.push(cs);
|
|
273
|
+
}
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Handle versioned URL (format: url|version)
|
|
278
|
+
if (supplementsUrl.startsWith(`${url}|`)) {
|
|
279
|
+
if (!version) {
|
|
280
|
+
// No version specified in search, include all supplements for this URL
|
|
281
|
+
supplements.push(cs);
|
|
282
|
+
} else {
|
|
283
|
+
// Version specified, check if it matches the tail of supplements URL
|
|
284
|
+
const supplementsVersion = supplementsUrl.substring(`${url}|`.length);
|
|
285
|
+
if (supplementsVersion === version || VersionUtilities.versionMatches(supplementsVersion, version)) {
|
|
286
|
+
supplements.push(cs);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return supplements;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Check supplements for a code system provider
|
|
298
|
+
* @param {CodeSystemProvider} cs - Code system provider
|
|
299
|
+
* @param {Object} src - Source element (for extensions)
|
|
300
|
+
*/
|
|
301
|
+
checkSupplements(cs, src, requiredSupplements) {
|
|
302
|
+
// Check for required supplements in extensions
|
|
303
|
+
if (src && src.getExtensions) {
|
|
304
|
+
const supplementExtensions = src.getExtensions('http://hl7.org/fhir/StructureDefinition/valueset-supplement');
|
|
305
|
+
for (const ext of supplementExtensions) {
|
|
306
|
+
const supplementUrl = ext.valueString || ext.valueUri;
|
|
307
|
+
if (supplementUrl && !cs.hasSupplement(this.opContext, supplementUrl)) {
|
|
308
|
+
throw new TerminologyError(`ValueSet depends on supplement '${supplementUrl}' on ${cs.systemUri} that is not known`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
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);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Find a ValueSet by URL and optional version
|
|
323
|
+
* @param {string} url - ValueSet URL (may include |version)
|
|
324
|
+
* @param {string} version - ValueSet version (optional, overrides URL version)
|
|
325
|
+
* @returns {ValueSet|null} Found ValueSet or null
|
|
326
|
+
*/
|
|
327
|
+
async findValueSet(url, version = '') {
|
|
328
|
+
if (!url) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Parse URL|version format
|
|
333
|
+
let effectiveUrl = url;
|
|
334
|
+
let effectiveVersion = version;
|
|
335
|
+
|
|
336
|
+
if (!effectiveVersion && url.includes('|')) {
|
|
337
|
+
const parts = url.split('|');
|
|
338
|
+
effectiveUrl = parts[0];
|
|
339
|
+
effectiveVersion = parts[1];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// First check additional resources
|
|
343
|
+
const fromAdditional = this.findInAdditionalResources(effectiveUrl, effectiveVersion, 'ValueSet', false);
|
|
344
|
+
if (fromAdditional) {
|
|
345
|
+
return fromAdditional;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Then try the provider
|
|
349
|
+
if (this.provider && this.provider.findValueSet) {
|
|
350
|
+
const vs = await this.provider.findValueSet(this.opContext, effectiveUrl, effectiveVersion);
|
|
351
|
+
if (vs) {
|
|
352
|
+
return vs;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Apply version pinning rules from parameters
|
|
361
|
+
* @param {string} url - ValueSet URL
|
|
362
|
+
* @returns {string} Potentially versioned URL
|
|
363
|
+
*/
|
|
364
|
+
pinValueSet(url) {
|
|
365
|
+
if (!url || !this.params) {
|
|
366
|
+
return url;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
let baseUrl = url.includes("|") ? url.substring(0, url.indexOf("|")) : url;
|
|
370
|
+
let version = url.includes("|") ? url.substring(url.indexOf("|") + 1) : null;
|
|
371
|
+
version = this.determineVersionBase(url, version, this.params);
|
|
372
|
+
return version ? baseUrl+"|"+version : url;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Build a canonical URL from system and version
|
|
377
|
+
* @param {string} system - System URL
|
|
378
|
+
* @param {string} version - Version (optional)
|
|
379
|
+
* @returns {string} Canonical URL (system|version or just system)
|
|
380
|
+
*/
|
|
381
|
+
canonical(system, version = '') {
|
|
382
|
+
if (!system) return '';
|
|
383
|
+
if (!version) return system;
|
|
384
|
+
return `${system}|${version}`;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Parse a canonical URL into system and version parts
|
|
389
|
+
* @param {string} canonical - Canonical URL (may include |version)
|
|
390
|
+
* @returns {{system: string, version: string}}
|
|
391
|
+
*/
|
|
392
|
+
parseCanonical(canonical) {
|
|
393
|
+
if (!canonical) {
|
|
394
|
+
return { system: '', version: '' };
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const pipeIndex = canonical.indexOf('|');
|
|
398
|
+
if (pipeIndex < 0) {
|
|
399
|
+
return { system: canonical, version: '' };
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return {
|
|
403
|
+
system: canonical.substring(0, pipeIndex),
|
|
404
|
+
version: canonical.substring(pipeIndex + 1)
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Process a ValueSet, recording context and extracting embedded expansion parameters
|
|
410
|
+
* @param {Object} vs - ValueSet resource (raw JSON)
|
|
411
|
+
* @param {Object} params - Parameters resource to add extracted params to
|
|
412
|
+
*/
|
|
413
|
+
seeValueSet(vs, params) {
|
|
414
|
+
// Build canonical URL from url and version
|
|
415
|
+
const vurl = vs.url ? (vs.url + (vs.version ? '|' + vs.version : '')) : null;
|
|
416
|
+
if (vurl) {
|
|
417
|
+
this.opContext.seeContext(vurl);
|
|
418
|
+
}
|
|
419
|
+
// Check for expansion parameter extensions on compose
|
|
420
|
+
if (vs.jsonObj.compose && vs.jsonObj.compose.extension) {
|
|
421
|
+
for (const ext of vs.jsonObj.compose.extension) {
|
|
422
|
+
if (ext.url === 'http://hl7.org/fhir/StructureDefinition/valueset-expansion-parameter' ||
|
|
423
|
+
ext.url === 'http://hl7.org/fhir/tools/StructureDefinition/valueset-expansion-parameter') {
|
|
424
|
+
// Get name and value from nested extensions
|
|
425
|
+
const nameExt = ext.extension?.find(e => e.url === 'name');
|
|
426
|
+
const valueExt = ext.extension?.find(e => e.url === 'value');
|
|
427
|
+
|
|
428
|
+
if (nameExt && valueExt) {
|
|
429
|
+
const name = nameExt.valueString || nameExt.valueCode;
|
|
430
|
+
if (name) {
|
|
431
|
+
this.params.seeParameter(name, valueExt, false);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (!params.FHTTPLanguages && vs.jsonObj.language) {
|
|
438
|
+
params.HTTPLanguages = Languages.fromAcceptLanguage(vs.jsonObj.language, this.languages, !this.isValidating());
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
isValidating() {
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ========== Parameter Handling ==========
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Build a Parameters resource from the request
|
|
450
|
+
* Handles GET query params, POST form body, and POST Parameters resource
|
|
451
|
+
* @param {express.Request} req
|
|
452
|
+
* @returns {Object} Parameters resource
|
|
453
|
+
*/
|
|
454
|
+
buildParameters(req) {
|
|
455
|
+
// If POST with Parameters resource, use directly
|
|
456
|
+
if (req.method === 'POST' && req.body && req.body.resourceType === 'Parameters') {
|
|
457
|
+
return req.body;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Convert query params or form body to Parameters
|
|
461
|
+
const source = req.method === 'POST' ? {...req.query, ...req.body} : req.query;
|
|
462
|
+
const params = {
|
|
463
|
+
resourceType: 'Parameters',
|
|
464
|
+
parameter: []
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
for (const [name, value] of Object.entries(source)) {
|
|
468
|
+
if (value === undefined || value === null) continue;
|
|
469
|
+
|
|
470
|
+
if (Array.isArray(value)) {
|
|
471
|
+
// Repeating parameter
|
|
472
|
+
for (const v of value) {
|
|
473
|
+
params.parameter.push({name, valueString: String(v)});
|
|
474
|
+
}
|
|
475
|
+
} else if (typeof value === 'object') {
|
|
476
|
+
// Could be a resource or complex type - check resourceType
|
|
477
|
+
if (value.resourceType) {
|
|
478
|
+
params.parameter.push({name, resource: value});
|
|
479
|
+
} else {
|
|
480
|
+
// Assume it's a complex type like Coding or CodeableConcept
|
|
481
|
+
params.parameter.push(this.buildComplexParameter(name, value));
|
|
482
|
+
}
|
|
483
|
+
} else {
|
|
484
|
+
params.parameter.push({name, valueString: String(value)});
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return params;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Build a parameter for complex types
|
|
493
|
+
*/
|
|
494
|
+
buildComplexParameter(name, value) {
|
|
495
|
+
// Detect type based on structure
|
|
496
|
+
if (value.system !== undefined || value.code !== undefined || value.display !== undefined) {
|
|
497
|
+
return {name, valueCoding: value};
|
|
498
|
+
}
|
|
499
|
+
if (value.coding !== undefined || value.text !== undefined) {
|
|
500
|
+
return {name, valueCodeableConcept: value};
|
|
501
|
+
}
|
|
502
|
+
// Fallback - stringify
|
|
503
|
+
return {name, valueString: JSON.stringify(value)};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
addHttpParams(req, params) {
|
|
508
|
+
if (req.headers && req.headers['accept-language']) {
|
|
509
|
+
params.parameter.push({name: '__Accept-Language', valueCode: req.headers['accept-language']});
|
|
510
|
+
}
|
|
511
|
+
if (req.headers && req.headers['content-language']) {
|
|
512
|
+
params.parameter.push({name: '__Content-Language', valueCode: req.headers['content-language']});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// ========== Additional Resources Handling ==========
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Set up additional resources from tx-resource parameters and cache
|
|
520
|
+
* @param {Object} params - Parameters resource
|
|
521
|
+
*/
|
|
522
|
+
setupAdditionalResources(params) {
|
|
523
|
+
if (!params || !params.parameter) return;
|
|
524
|
+
|
|
525
|
+
// Collect tx-resource parameters (resources provided inline)
|
|
526
|
+
const txResources = [];
|
|
527
|
+
for (const param of params.parameter) {
|
|
528
|
+
this.deadCheck('setupAdditionalResources');
|
|
529
|
+
if (param.name === 'tx-resource' && param.resource) {
|
|
530
|
+
let res = this.wrapRawResource(param.resource);
|
|
531
|
+
if (res) {
|
|
532
|
+
txResources.push(res);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Check for cache-id
|
|
538
|
+
const cacheIdParam = this.findParameter(params, 'cache-id');
|
|
539
|
+
const cacheId = cacheIdParam ? this.getParameterValue(cacheIdParam) : null;
|
|
540
|
+
|
|
541
|
+
if (cacheId && this.opContext.resourceCache) {
|
|
542
|
+
// Merge tx-resources with cached resources
|
|
543
|
+
if (txResources.length > 0) {
|
|
544
|
+
this.opContext.resourceCache.add(cacheId, txResources);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
// Set additional resources to all resources for this cache-id
|
|
548
|
+
this.additionalResources = this.opContext.resourceCache.get(cacheId);
|
|
549
|
+
} else {
|
|
550
|
+
// No cache-id, just use the tx-resources directly
|
|
551
|
+
this.additionalResources = txResources;
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Wrap a raw resource in its appropriate class wrapper
|
|
557
|
+
* @param {Object} resource - Raw resource object
|
|
558
|
+
* @returns {CodeSystem|ValueSet|null} Wrapped resource or null
|
|
559
|
+
*/
|
|
560
|
+
wrapRawResource(resource) {
|
|
561
|
+
if (resource.resourceType === 'CodeSystem') {
|
|
562
|
+
return new CodeSystem(resource, this.provider.getFhirVersion());
|
|
563
|
+
}
|
|
564
|
+
if (resource.resourceType === 'ValueSet') {
|
|
565
|
+
return new ValueSet(resource, this.provider.getFhirVersion());
|
|
566
|
+
}
|
|
567
|
+
if (resource.resourceType === 'ConceptMap') {
|
|
568
|
+
return new ConceptMap(resource, this.provider.getFhirVersion());
|
|
569
|
+
}
|
|
570
|
+
return null;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// ========== Parameters Handling ==========
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Convert query parameters to a Parameters resource
|
|
577
|
+
* @param {Object} query - Query parameters
|
|
578
|
+
* @returns {Object} Parameters resource
|
|
579
|
+
*/
|
|
580
|
+
queryToParameters(query) {
|
|
581
|
+
const params = {
|
|
582
|
+
resourceType: 'Parameters',
|
|
583
|
+
parameter: []
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
if (!query) return params;
|
|
587
|
+
|
|
588
|
+
for (const [name, value] of Object.entries(query)) {
|
|
589
|
+
if (Array.isArray(value)) {
|
|
590
|
+
// Repeating parameter
|
|
591
|
+
for (const v of value) {
|
|
592
|
+
params.parameter.push({ name, valueString: v });
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
params.parameter.push({ name, valueString: value });
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
return params;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Convert form body to a Parameters resource, merging with query params
|
|
604
|
+
* @param {Object} body - Form body
|
|
605
|
+
* @param {Object} query - Query parameters
|
|
606
|
+
* @returns {Object} Parameters resource
|
|
607
|
+
*/
|
|
608
|
+
formToParameters(body, query) {
|
|
609
|
+
const params = {
|
|
610
|
+
resourceType: 'Parameters',
|
|
611
|
+
parameter: []
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// Add query params first
|
|
615
|
+
if (query) {
|
|
616
|
+
for (const [name, value] of Object.entries(query)) {
|
|
617
|
+
if (Array.isArray(value)) {
|
|
618
|
+
for (const v of value) {
|
|
619
|
+
params.parameter.push({ name, valueString: v });
|
|
620
|
+
}
|
|
621
|
+
} else {
|
|
622
|
+
params.parameter.push({ name, valueString: value });
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Add/override with body params
|
|
628
|
+
if (body) {
|
|
629
|
+
for (const [name, value] of Object.entries(body)) {
|
|
630
|
+
if (Array.isArray(value)) {
|
|
631
|
+
for (const v of value) {
|
|
632
|
+
params.parameter.push({ name, valueString: v });
|
|
633
|
+
}
|
|
634
|
+
} else {
|
|
635
|
+
params.parameter.push({ name, valueString: value });
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
return params;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Find a parameter in a Parameters resource
|
|
645
|
+
* @param {Object} params - Parameters resource
|
|
646
|
+
* @param {string} name - Parameter name
|
|
647
|
+
* @returns {Object|null} Parameter object or null
|
|
648
|
+
*/
|
|
649
|
+
findParameter(params, name) {
|
|
650
|
+
if (!params || !params.parameter) return null;
|
|
651
|
+
return params.parameter.find(p => p.name === name) || null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* Get the value from a parameter (handles various value types)
|
|
656
|
+
* @param {Object} param - Parameter object
|
|
657
|
+
* @returns {*} Parameter value
|
|
658
|
+
*/
|
|
659
|
+
getParameterValue(param) {
|
|
660
|
+
if (!param) return null;
|
|
661
|
+
|
|
662
|
+
// Check for resource
|
|
663
|
+
if (param.resource) return param.resource;
|
|
664
|
+
|
|
665
|
+
// Check for various value types
|
|
666
|
+
const valueTypes = [
|
|
667
|
+
'valueString', 'valueCode', 'valueUri', 'valueCanonical', 'valueUrl',
|
|
668
|
+
'valueBoolean', 'valueInteger', 'valueDecimal',
|
|
669
|
+
'valueDateTime', 'valueDate', 'valueTime',
|
|
670
|
+
'valueCoding', 'valueCodeableConcept',
|
|
671
|
+
'valueIdentifier', 'valueQuantity'
|
|
672
|
+
];
|
|
673
|
+
|
|
674
|
+
for (const vt of valueTypes) {
|
|
675
|
+
if (param[vt] !== undefined) {
|
|
676
|
+
return param[vt];
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
return null;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/**
|
|
684
|
+
* Get a string parameter value
|
|
685
|
+
* @param {Object} params - Parameters resource
|
|
686
|
+
* @param {string} name - Parameter name
|
|
687
|
+
* @returns {string|null} Parameter value or null
|
|
688
|
+
*/
|
|
689
|
+
getStringParam(params, name) {
|
|
690
|
+
const p = this.findParameter(params, name);
|
|
691
|
+
if (!p) return null;
|
|
692
|
+
return getValuePrimitive(p);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/**
|
|
696
|
+
* Get a resource parameter value
|
|
697
|
+
* @param {Object} params - Parameters resource
|
|
698
|
+
* @param {string} name - Parameter name
|
|
699
|
+
* @returns {Object|null} Resource or null
|
|
700
|
+
*/
|
|
701
|
+
getResourceParam(params, name) {
|
|
702
|
+
const p = this.findParameter(params, name);
|
|
703
|
+
return p?.resource || null;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/**
|
|
707
|
+
* Get a Coding parameter value
|
|
708
|
+
* @param {Object} params - Parameters resource
|
|
709
|
+
* @param {string} name - Parameter name
|
|
710
|
+
* @returns {Object|null} Coding or null
|
|
711
|
+
*/
|
|
712
|
+
getCodingParam(params, name) {
|
|
713
|
+
const p = this.findParameter(params, name);
|
|
714
|
+
return p?.valueCoding || null;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
/**
|
|
718
|
+
* Get a CodeableConcept parameter value
|
|
719
|
+
* @param {Object} params - Parameters resource
|
|
720
|
+
* @param {string} name - Parameter name
|
|
721
|
+
* @returns {Object|null} CodeableConcept or null
|
|
722
|
+
*/
|
|
723
|
+
getCodeableConceptParam(params, name) {
|
|
724
|
+
const p = this.findParameter(params, name);
|
|
725
|
+
return p?.valueCodeableConcept || null;
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Render a coded value as string for debugging/logging
|
|
730
|
+
* @param {string|Object} system - System URI or coding object
|
|
731
|
+
* @param {string} version - Version (optional)
|
|
732
|
+
* @param {string} code - Code (optional)
|
|
733
|
+
* @param {string} display - Display (optional)
|
|
734
|
+
* @returns {string} Rendered string
|
|
735
|
+
*/
|
|
736
|
+
displayCoded(system, version = '', code = '', display = '') {
|
|
737
|
+
if (typeof system === 'object') {
|
|
738
|
+
// Handle coding or codeable concept objects
|
|
739
|
+
if (system.system !== undefined) {
|
|
740
|
+
// Coding object
|
|
741
|
+
return this.renderer.displayCoded(system.system, system.version, system.code, system.display);
|
|
742
|
+
} else if (system.codings) {
|
|
743
|
+
// Codeable concept object
|
|
744
|
+
const rendered = system.codings.map(c => this.displayCoded(c)).join(', ');
|
|
745
|
+
return `[${rendered}]`;
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
let result = system;
|
|
750
|
+
if (version) {
|
|
751
|
+
result += `|${version}`;
|
|
752
|
+
}
|
|
753
|
+
if (code) {
|
|
754
|
+
result += `#${code}`;
|
|
755
|
+
}
|
|
756
|
+
if (display) {
|
|
757
|
+
result += ` ("${display}")`;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return result;
|
|
761
|
+
}
|
|
762
|
+
determineVersionBase(url, version, params) {
|
|
763
|
+
if (params === null) {
|
|
764
|
+
return version;
|
|
765
|
+
}
|
|
766
|
+
let result = version;
|
|
767
|
+
let list = params.rulesForSystem(url);
|
|
768
|
+
let b = false;
|
|
769
|
+
for (let t of list) {
|
|
770
|
+
if (t.mode === 'override') {
|
|
771
|
+
if (!b) {
|
|
772
|
+
result = t.version;
|
|
773
|
+
this.foundParameters.push(t.asParam());
|
|
774
|
+
b = true;
|
|
775
|
+
} else if (result !== t.version) {
|
|
776
|
+
throw new Issue("error", "exception", null, 'SYSTEM_VERSION_MULTIPLE_OVERRIDE', this.FI18n.translate('SYSTEM_VERSION_MULTIPLE_OVERRIDE', params.FHTTPLanguages, [url, result, t.version]), 'version-error');
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
if (!result) {
|
|
781
|
+
b = false;
|
|
782
|
+
for (let t of list) {
|
|
783
|
+
if (t.mode === 'default') {
|
|
784
|
+
if (!b) {
|
|
785
|
+
result = t.version;
|
|
786
|
+
this.foundParameters.push(t.asParam());
|
|
787
|
+
b = true;
|
|
788
|
+
} else if (version !== t.version) {
|
|
789
|
+
throw new Issue("error", "exception", null, 'SYSTEM_VERSION_MULTIPLE_DEFAULT', this.FI18n.translate('SYSTEM_VERSION_MULTIPLE_DEFAULT', params.FHTTPLanguages, [url, result, t.version]), 'version-error');
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
for (let t of list) {
|
|
795
|
+
if (t.mode === 'check') {
|
|
796
|
+
if (!result) {
|
|
797
|
+
result = t.version;
|
|
798
|
+
this.foundParameters.push(t.asParam());
|
|
799
|
+
}
|
|
800
|
+
// if we decide to allow check to guide the selection.
|
|
801
|
+
// waiting for discussion
|
|
802
|
+
//else if (TFHIRVersions.isSubset(result, t.version)) {
|
|
803
|
+
// result = t.version;
|
|
804
|
+
//}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return result;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
checkVersion(url, version, params, versionAlgorithm, op) {
|
|
811
|
+
if (params) {
|
|
812
|
+
let list = params.rulesForSystem(url);
|
|
813
|
+
for (let t of list) {
|
|
814
|
+
if (t.mode === 'check') {
|
|
815
|
+
if (!VersionUtilities.versionMatchesByAlgorithm(t.version, version, versionAlgorithm)) {
|
|
816
|
+
let issue = new Issue("error", "exception", null, 'VALUESET_VERSION_CHECK', this.i18n.translate('VALUESET_VERSION_CHECK', params.FHTTPLanguages, [url, version, t.version]), 'version-error', 400);
|
|
817
|
+
if (op) {
|
|
818
|
+
op.addIssue(issue);
|
|
819
|
+
} else {
|
|
820
|
+
throw issue;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
makeVurl(resource) {
|
|
829
|
+
let result = resource.vurl;
|
|
830
|
+
if (!result && resource.url) {
|
|
831
|
+
if (resource.version) {
|
|
832
|
+
result = resource.url+"|"+resource.version;
|
|
833
|
+
} else {
|
|
834
|
+
result = resource.url;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
return result;
|
|
838
|
+
}
|
|
839
|
+
|
|
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
|
+
seeSourceVS(vs, url) {
|
|
866
|
+
let s = url;
|
|
867
|
+
if (vs) {
|
|
868
|
+
if (vs.jsonObj) vs = vs.jsonObj;
|
|
869
|
+
s = vs.name || vs.title || vs.id || vs.url;
|
|
870
|
+
}
|
|
871
|
+
if (!this.usedSources.find(u => u == s)) {
|
|
872
|
+
this.usedSources.push(s);
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
seeSourceProvider(cs, url) {
|
|
877
|
+
let s = url;
|
|
878
|
+
if (cs) {
|
|
879
|
+
if (cs instanceof CodeSystem) {
|
|
880
|
+
cs = cs.jsonObj;
|
|
881
|
+
s = cs.name || cs.title || cs.id || cs.url;
|
|
882
|
+
} else {
|
|
883
|
+
s = cs.name() || cs.system();
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
if (!this.usedSources.find(u => u == s)) {
|
|
887
|
+
this.usedSources.push(s);
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
presentVersionList(items) {
|
|
892
|
+
if (items.length === 0) return '';
|
|
893
|
+
if (items.length === 1) return items[0];
|
|
894
|
+
if (items.length === 2) return `${items[0]} or ${items[1]}`;
|
|
895
|
+
|
|
896
|
+
const lastItem = items.pop();
|
|
897
|
+
return `${items.join(', ')} and ${lastItem}`;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
module.exports = {
|
|
902
|
+
TerminologyWorker,
|
|
903
|
+
TerminologySetupError
|
|
904
|
+
};
|