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,384 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Search Worker - Handles resource search operations
|
|
3
|
+
//
|
|
4
|
+
// GET /{type}?{params}
|
|
5
|
+
// POST /{type}/_search
|
|
6
|
+
//
|
|
7
|
+
|
|
8
|
+
const { TerminologyWorker } = require('./worker');
|
|
9
|
+
|
|
10
|
+
class SearchWorker extends TerminologyWorker {
|
|
11
|
+
/**
|
|
12
|
+
* @param {OperationContext} opContext - Operation context
|
|
13
|
+
* @param {Logger} log - Logger instance
|
|
14
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
15
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
16
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
17
|
+
*/
|
|
18
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
19
|
+
super(opContext, log, provider, languages, i18n);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Get operation name
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
opName() {
|
|
27
|
+
return 'search';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
// Allowed search parameters
|
|
32
|
+
static ALLOWED_PARAMS = [
|
|
33
|
+
'_offset', '_count', '_elements', '_sort',
|
|
34
|
+
'url', 'version', 'content-mode', 'date', 'description',
|
|
35
|
+
'supplements', 'identifier', 'jurisdiction', 'name',
|
|
36
|
+
'publisher', 'status', 'system', 'title', 'text'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
// Sortable fields
|
|
40
|
+
static SORT_FIELDS = ['id', 'url', 'version', 'date', 'name', 'vurl'];
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Handle a search request
|
|
44
|
+
* @param {express.Request} req - Express request (with txProvider attached)
|
|
45
|
+
* @param {express.Response} res - Express response
|
|
46
|
+
* @param {string} resourceType - The resource type (CodeSystem, ValueSet, ConceptMap)
|
|
47
|
+
* @param {Object} log - Logger instance
|
|
48
|
+
*/
|
|
49
|
+
async handle(req, res, resourceType) {
|
|
50
|
+
const params = req.method === 'POST' ? req.body : req.query;
|
|
51
|
+
|
|
52
|
+
this.log.debug(`Search ${resourceType} with params:`, params);
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
// Parse pagination parameters
|
|
56
|
+
const offset = Math.max(0, parseInt(params._offset) || 0);
|
|
57
|
+
const elements = params._elements ? decodeURIComponent(params._elements).split(',').map(e => e.trim()) : null;
|
|
58
|
+
const count = Math.min(elements ? 2000 : 200, Math.max(1, parseInt(params._count) || 20));
|
|
59
|
+
const sort = params._sort || "id";
|
|
60
|
+
|
|
61
|
+
// Get matching resources
|
|
62
|
+
let matches = [];
|
|
63
|
+
switch (resourceType) {
|
|
64
|
+
case 'CodeSystem':
|
|
65
|
+
matches = this.searchCodeSystems(params);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'ValueSet':
|
|
69
|
+
matches = await this.searchValueSets(params, elements);
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
case 'ConceptMap':
|
|
73
|
+
// Not implemented yet - return empty set
|
|
74
|
+
matches = [];
|
|
75
|
+
break;
|
|
76
|
+
|
|
77
|
+
default:
|
|
78
|
+
matches = [];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Sort results
|
|
82
|
+
matches = this.sortResults(matches, sort);
|
|
83
|
+
|
|
84
|
+
// Build and return the bundle
|
|
85
|
+
const bundle = this.buildSearchBundle(
|
|
86
|
+
req, resourceType, matches, offset, count, elements
|
|
87
|
+
);
|
|
88
|
+
req.logInfo = `${bundle.entry.length} matches`;
|
|
89
|
+
return res.json(bundle);
|
|
90
|
+
|
|
91
|
+
} catch (error) {
|
|
92
|
+
req.logInfo = "error "+(error.msgId || error.className);
|
|
93
|
+
this.log.error(error);
|
|
94
|
+
return res.status(500).json({
|
|
95
|
+
resourceType: 'OperationOutcome',
|
|
96
|
+
issue: [{
|
|
97
|
+
severity: 'error',
|
|
98
|
+
code: 'exception',
|
|
99
|
+
diagnostics: error.message
|
|
100
|
+
}]
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Search CodeSystems
|
|
107
|
+
*/
|
|
108
|
+
searchCodeSystems(params) {
|
|
109
|
+
const matches = [];
|
|
110
|
+
|
|
111
|
+
// Extract search parameters (excluding special params)
|
|
112
|
+
const searchParams = {};
|
|
113
|
+
for (const [key, value] of Object.entries(params)) {
|
|
114
|
+
if (!key.startsWith('_') && value && SearchWorker.ALLOWED_PARAMS.includes(key)) {
|
|
115
|
+
searchParams[key] = value.toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// If no search params, return all
|
|
120
|
+
const hasSearchParams = Object.keys(searchParams).length > 0;
|
|
121
|
+
|
|
122
|
+
for (const [key, cs] of this.provider.codeSystems) {
|
|
123
|
+
this.deadCheck('searchCodeSystems');
|
|
124
|
+
if (key == cs.vurl) {
|
|
125
|
+
const json = cs.jsonObj;
|
|
126
|
+
|
|
127
|
+
if (!hasSearchParams) {
|
|
128
|
+
matches.push(json);
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Check each search parameter for partial match
|
|
133
|
+
let isMatch = true;
|
|
134
|
+
for (const [param, searchValue] of Object.entries(searchParams)) {
|
|
135
|
+
// 'system' doesn't do anything for CodeSystem search
|
|
136
|
+
if (param === 'system') {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Map content-mode to content property
|
|
141
|
+
const jsonProp = param === 'content-mode' ? 'content' : param;
|
|
142
|
+
|
|
143
|
+
if (param === 'jurisdiction') {
|
|
144
|
+
// Special handling for jurisdiction - array of CodeableConcept
|
|
145
|
+
if (!this.matchJurisdiction(json.jurisdiction, searchValue)) {
|
|
146
|
+
isMatch = false;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
} else if (param === 'text') {
|
|
150
|
+
const propValue = json.title + json.description;
|
|
151
|
+
if (!this.matchValue(propValue, searchValue)) {
|
|
152
|
+
isMatch = false;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Standard partial text match
|
|
157
|
+
const propValue = json[jsonProp];
|
|
158
|
+
if (!this.matchValue(propValue, searchValue)) {
|
|
159
|
+
isMatch = false;
|
|
160
|
+
break;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (isMatch) {
|
|
166
|
+
matches.push(json);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return matches;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Search ValueSets by delegating to providers
|
|
176
|
+
*/
|
|
177
|
+
async searchValueSets(params, elements) {
|
|
178
|
+
const allMatches = [];
|
|
179
|
+
|
|
180
|
+
// Convert params object to array format expected by ValueSet providers
|
|
181
|
+
// Exclude control params (_offset, _count, _elements, _sort)
|
|
182
|
+
const searchParams = [];
|
|
183
|
+
for (const [key, value] of Object.entries(params)) {
|
|
184
|
+
if (!key.startsWith('_') && value && SearchWorker.ALLOWED_PARAMS.includes(key)) {
|
|
185
|
+
searchParams.push({ name: key, value: value });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const vsp of this.provider.valueSetProviders) {
|
|
190
|
+
this.deadCheck('searchValueSets-providers');
|
|
191
|
+
const results = await vsp.searchValueSets(searchParams, elements);
|
|
192
|
+
if (results && Array.isArray(results)) {
|
|
193
|
+
for (const vs of results) {
|
|
194
|
+
this.deadCheck('searchValueSets-results');
|
|
195
|
+
allMatches.push(vs.jsonObj || vs);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return allMatches;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Check if a value matches the search term (partial, case-insensitive)
|
|
205
|
+
*/
|
|
206
|
+
matchValue(propValue, searchValue) {
|
|
207
|
+
if (propValue === undefined || propValue === null) {
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const strValue = String(propValue).toLowerCase();
|
|
212
|
+
return strValue.includes(searchValue);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Check if jurisdiction matches - jurisdiction is an array of CodeableConcept
|
|
217
|
+
*/
|
|
218
|
+
matchJurisdiction(jurisdictions, searchValue) {
|
|
219
|
+
if (!jurisdictions || !Array.isArray(jurisdictions)) {
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
for (const cc of jurisdictions) {
|
|
224
|
+
// Check coding array
|
|
225
|
+
if (cc.coding && Array.isArray(cc.coding)) {
|
|
226
|
+
for (const coding of cc.coding) {
|
|
227
|
+
if (coding.code && coding.code.toLowerCase().includes(searchValue)) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
if (coding.display && coding.display.toLowerCase().includes(searchValue)) {
|
|
231
|
+
return true;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Check text
|
|
236
|
+
if (cc.text && cc.text.toLowerCase().includes(searchValue)) {
|
|
237
|
+
return true;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Sort results by the specified field
|
|
246
|
+
*/
|
|
247
|
+
sortResults(results, sortField) {
|
|
248
|
+
if (!SearchWorker.SORT_FIELDS.includes(sortField)) {
|
|
249
|
+
return results;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return results.sort((a, b) => {
|
|
253
|
+
if (sortField === 'vurl') {
|
|
254
|
+
// Sort by url then version
|
|
255
|
+
const urlCompare = (a.url || '').localeCompare(b.url || '');
|
|
256
|
+
if (urlCompare !== 0) return urlCompare;
|
|
257
|
+
return (a.version || '').localeCompare(b.version || '');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const aVal = a[sortField] || '';
|
|
261
|
+
const bVal = b[sortField] || '';
|
|
262
|
+
return String(aVal).localeCompare(String(bVal));
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Build a FHIR search Bundle with pagination
|
|
268
|
+
*/
|
|
269
|
+
buildSearchBundle(req, resourceType, allMatches, offset, count, elements) {
|
|
270
|
+
const total = allMatches.length;
|
|
271
|
+
|
|
272
|
+
// Get the slice for this page
|
|
273
|
+
const pageResults = allMatches.slice(offset, offset + count);
|
|
274
|
+
|
|
275
|
+
// Build base URL for pagination links
|
|
276
|
+
const protocol = req.protocol;
|
|
277
|
+
const host = req.get('host');
|
|
278
|
+
const basePath = req.baseUrl + req.path;
|
|
279
|
+
const baseUrl = `${protocol}://${host}${basePath}`;
|
|
280
|
+
|
|
281
|
+
// Preserve search params for pagination links (excluding _offset)
|
|
282
|
+
const searchParams = new URLSearchParams();
|
|
283
|
+
const params = req.method === 'POST' ? req.body : req.query;
|
|
284
|
+
for (const [key, value] of Object.entries(params)) {
|
|
285
|
+
if (key !== '_offset' && value) {
|
|
286
|
+
searchParams.set(key, value);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Build pagination links
|
|
291
|
+
const links = [];
|
|
292
|
+
|
|
293
|
+
// Self link
|
|
294
|
+
const selfParams = new URLSearchParams(searchParams);
|
|
295
|
+
selfParams.set('_offset', offset);
|
|
296
|
+
links.push({
|
|
297
|
+
relation: 'self',
|
|
298
|
+
url: `${baseUrl}?${selfParams.toString()}`
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// First link
|
|
302
|
+
const firstParams = new URLSearchParams(searchParams);
|
|
303
|
+
firstParams.set('_offset', 0);
|
|
304
|
+
links.push({
|
|
305
|
+
relation: 'first',
|
|
306
|
+
url: `${baseUrl}?${firstParams.toString()}`
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
// Previous link (if not on first page)
|
|
310
|
+
if (offset > 0) {
|
|
311
|
+
const prevParams = new URLSearchParams(searchParams);
|
|
312
|
+
prevParams.set('_offset', Math.max(0, offset - count));
|
|
313
|
+
links.push({
|
|
314
|
+
relation: 'previous',
|
|
315
|
+
url: `${baseUrl}?${prevParams.toString()}`
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Next link (if more results)
|
|
320
|
+
if (offset + count < total) {
|
|
321
|
+
const nextParams = new URLSearchParams(searchParams);
|
|
322
|
+
nextParams.set('_offset', offset + count);
|
|
323
|
+
links.push({
|
|
324
|
+
relation: 'next',
|
|
325
|
+
url: `${baseUrl}?${nextParams.toString()}`
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Last link
|
|
330
|
+
const lastOffset = Math.max(0, Math.floor((total - 1) / count) * count);
|
|
331
|
+
const lastParams = new URLSearchParams(searchParams);
|
|
332
|
+
lastParams.set('_offset', lastOffset);
|
|
333
|
+
links.push({
|
|
334
|
+
relation: 'last',
|
|
335
|
+
url: `${baseUrl}?${lastParams.toString()}`
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
// Build entries
|
|
339
|
+
const entries = pageResults.map(resource => {
|
|
340
|
+
// Apply _elements filter if specified
|
|
341
|
+
let filteredResource = resource;
|
|
342
|
+
if (elements) {
|
|
343
|
+
filteredResource = this.filterElements(resource, elements);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
fullUrl: `${protocol}://${host}${req.baseUrl}/${resourceType}/${resource.id}`,
|
|
348
|
+
resource: filteredResource,
|
|
349
|
+
search: {
|
|
350
|
+
mode: 'match'
|
|
351
|
+
}
|
|
352
|
+
};
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
resourceType: 'Bundle',
|
|
357
|
+
type: 'searchset',
|
|
358
|
+
total: total,
|
|
359
|
+
link: links,
|
|
360
|
+
entry: entries
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Filter resource to only include specified elements
|
|
366
|
+
*/
|
|
367
|
+
filterElements(resource, elements) {
|
|
368
|
+
// Always include resourceType and id
|
|
369
|
+
const filtered = {
|
|
370
|
+
resourceType: resource.resourceType,
|
|
371
|
+
id: resource.id
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
for (const element of elements) {
|
|
375
|
+
if (resource[element] !== undefined) {
|
|
376
|
+
filtered[element] = resource[element];
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
return filtered;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
module.exports = SearchWorker;
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Subsumes Worker - Handles CodeSystem $subsumes operation
|
|
3
|
+
//
|
|
4
|
+
// GET /CodeSystem/$subsumes?{params}
|
|
5
|
+
// POST /CodeSystem/$subsumes
|
|
6
|
+
// GET /CodeSystem/{id}/$subsumes?{params}
|
|
7
|
+
// POST /CodeSystem/{id}/$subsumes
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
const { TerminologyWorker } = require('./worker');
|
|
11
|
+
const { FhirCodeSystemProvider } = require('../cs/cs-cs');
|
|
12
|
+
const {TxParameters} = require("../params");
|
|
13
|
+
const {Parameters} = require("../library/parameters");
|
|
14
|
+
const {Issue, OperationOutcome} = require("../library/operation-outcome");
|
|
15
|
+
class SubsumesWorker extends TerminologyWorker {
|
|
16
|
+
/**
|
|
17
|
+
* @param {OperationContext} opContext - Operation context
|
|
18
|
+
* @param {Logger} log - Logger instance
|
|
19
|
+
* @param {Provider} provider - Provider for code systems and resources
|
|
20
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
21
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
22
|
+
*/
|
|
23
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
24
|
+
super(opContext, log, provider, languages, i18n);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get operation name
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
opName() {
|
|
32
|
+
return 'subsumes';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Handle a type-level $subsumes request
|
|
37
|
+
* GET/POST /CodeSystem/$subsumes
|
|
38
|
+
* @param {express.Request} req - Express request
|
|
39
|
+
* @param {express.Response} res - Express response
|
|
40
|
+
*/
|
|
41
|
+
async handle(req, res) {
|
|
42
|
+
try {
|
|
43
|
+
await this.handleTypeLevelSubsumes(req, res);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
req.logInfo = "error "+(error.msgId || error.className);
|
|
46
|
+
this.log.error(error);
|
|
47
|
+
if (error instanceof Issue) {
|
|
48
|
+
let oo = new OperationOutcome();
|
|
49
|
+
oo.addIssue(error);
|
|
50
|
+
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
51
|
+
} else {
|
|
52
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
53
|
+
'error', error.issueCode || 'exception', error.message));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Handle an instance-level $subsumes request
|
|
60
|
+
* GET/POST /CodeSystem/{id}/$subsumes
|
|
61
|
+
* @param {express.Request} req - Express request
|
|
62
|
+
* @param {express.Response} res - Express response
|
|
63
|
+
*/
|
|
64
|
+
async handleInstance(req, res) {
|
|
65
|
+
try {
|
|
66
|
+
await this.handleInstanceLevelSubsumes(req, res);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.log.error(error);
|
|
69
|
+
if (error instanceof Issue) {
|
|
70
|
+
let oo = new OperationOutcome();
|
|
71
|
+
oo.addIssue(error);
|
|
72
|
+
return res.status(error.statusCode || 500).json(oo.jsonObj);
|
|
73
|
+
} else {
|
|
74
|
+
return res.status(error.statusCode || 500).json(this.operationOutcome(
|
|
75
|
+
'error', error.issueCode || 'exception', error.message));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Handle type-level subsumes: /CodeSystem/$subsumes
|
|
82
|
+
* CodeSystem identified by system+version params or from codingA/codingB
|
|
83
|
+
*/
|
|
84
|
+
async handleTypeLevelSubsumes(req, res) {
|
|
85
|
+
this.deadCheck('subsumes-type-level');
|
|
86
|
+
|
|
87
|
+
// Handle tx-resource and cache-id parameters from Parameters resource
|
|
88
|
+
if (req.body && req.body.resourceType === 'Parameters') {
|
|
89
|
+
this.setupAdditionalResources(req.body);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Parse parameters from request
|
|
93
|
+
const params = new Parameters(this.parseParameters(req));
|
|
94
|
+
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
95
|
+
txp.readParams(params.jsonObj);
|
|
96
|
+
|
|
97
|
+
// Get the codings and code system provider
|
|
98
|
+
let codingA, codingB;
|
|
99
|
+
let csProvider;
|
|
100
|
+
|
|
101
|
+
if (params.has('codingA') && params.has('codingB')) {
|
|
102
|
+
// Using codingA and codingB (only from Parameters resource)
|
|
103
|
+
codingA = params.get('codingA');
|
|
104
|
+
codingB = params.get('codingB');
|
|
105
|
+
|
|
106
|
+
// Codings must have the same system
|
|
107
|
+
if (codingA.system !== codingB.system) {
|
|
108
|
+
throw new Issue('error', 'not-found', null, null, 'codingA and codingB must have the same system', null, 400);
|
|
109
|
+
}
|
|
110
|
+
// Get the code system provider from the coding's system
|
|
111
|
+
csProvider = await this.findCodeSystem(codingA.system, codingA.version || '', txp, ['complete'], null, false);
|
|
112
|
+
this.seeSourceProvider(csProvider, codingA.system);
|
|
113
|
+
} else if (params.has('codeA') && params.has('codeB')) {
|
|
114
|
+
// Using codeA, codeB - system is required
|
|
115
|
+
if (!params.has('system')) {
|
|
116
|
+
throw new Issue('error', 'not-found', null, null, 'system parameter is required when using codeA and codeB', null, 404);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
csProvider = await this.findCodeSystem(params.get('system'), params.get('version') || '', txp, ['complete'], null, false);
|
|
120
|
+
this.seeSourceProvider(csProvider, params.get('system'));
|
|
121
|
+
// Create codings from the codes
|
|
122
|
+
codingA = {
|
|
123
|
+
system: csProvider.system(),
|
|
124
|
+
version: csProvider.version(),
|
|
125
|
+
code: params.get('codeA')
|
|
126
|
+
};
|
|
127
|
+
codingB = {
|
|
128
|
+
system: csProvider.system(),
|
|
129
|
+
version: csProvider.version(),
|
|
130
|
+
code: params.get('codeB')
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
} else {
|
|
134
|
+
throw new Issue('error', 'invalid', null, null, 'Must provide either codingA and codingB, or codeA and codeB with system', null, 400);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Perform the subsumes check
|
|
138
|
+
const result = await this.doSubsumes(csProvider, codingA, codingB);
|
|
139
|
+
req.logInfo = this.usedSources.join("|")+txp.logInfo();
|
|
140
|
+
return res.status(200).json(result);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Handle instance-level subsumes: /CodeSystem/{id}/$subsumes
|
|
145
|
+
* CodeSystem identified by resource ID
|
|
146
|
+
*/
|
|
147
|
+
async handleInstanceLevelSubsumes(req, res) {
|
|
148
|
+
this.deadCheck('subsumes-instance-level');
|
|
149
|
+
|
|
150
|
+
const { id } = req.params;
|
|
151
|
+
|
|
152
|
+
// Find the CodeSystem by ID
|
|
153
|
+
const codeSystem = await this.provider.getCodeSystemById(this.opContext, id);
|
|
154
|
+
|
|
155
|
+
if (!codeSystem) {
|
|
156
|
+
throw new Issue('error', 'not found', null, null, `CodeSystem/${id} not found`, null, 404);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Handle tx-resource and cache-id parameters from Parameters resource
|
|
160
|
+
if (req.body && req.body.resourceType === 'Parameters') {
|
|
161
|
+
this.setupAdditionalResources(req.body);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Parse parameters from request
|
|
165
|
+
const params = new Parameters(this.parseParameters(req));
|
|
166
|
+
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
167
|
+
txp.readParams(params.jsonObj);
|
|
168
|
+
|
|
169
|
+
// Load any supplements
|
|
170
|
+
const supplements = this.loadSupplements(codeSystem.url, codeSystem.version);
|
|
171
|
+
|
|
172
|
+
// Create a FhirCodeSystemProvider for this CodeSystem
|
|
173
|
+
const csProvider = new FhirCodeSystemProvider(this.opContext, codeSystem, supplements);
|
|
174
|
+
|
|
175
|
+
// Get the codings
|
|
176
|
+
let codingA, codingB;
|
|
177
|
+
|
|
178
|
+
if (params.has('codingA') && params.has('codingB')) {
|
|
179
|
+
codingA = params.get('codingA');
|
|
180
|
+
codingB = params.get('codingB');
|
|
181
|
+
} else if (params.has('codeA') && params.has('codeB')) {
|
|
182
|
+
// Create codings from the codes using this CodeSystem
|
|
183
|
+
codingA = {
|
|
184
|
+
system: csProvider.system(),
|
|
185
|
+
version: csProvider.version(),
|
|
186
|
+
code: params.get('codeA')
|
|
187
|
+
};
|
|
188
|
+
codingB = {
|
|
189
|
+
system: csProvider.system(),
|
|
190
|
+
version: csProvider.version(),
|
|
191
|
+
code: params.get('codeB')
|
|
192
|
+
};
|
|
193
|
+
} else {
|
|
194
|
+
throw new Issue('error', 'invalid', null, null, 'Must provide either codingA and codingB, or codeA and codeB with system', null, 400);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Perform the subsumes check
|
|
198
|
+
const result = await this.doSubsumes(csProvider, codingA, codingB);
|
|
199
|
+
req.logInfo = this.usedSources.join("|")+txp.logInfo();
|
|
200
|
+
return res.json(result);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Parse parameters from request (query params, form body, or Parameters resource)
|
|
204
|
+
* Returns a FHIR Parameters resource
|
|
205
|
+
* @param {express.Request} req - Express request
|
|
206
|
+
* @returns {Object} FHIR Parameters resource
|
|
207
|
+
*/
|
|
208
|
+
parseParameters(req) {
|
|
209
|
+
// Check if body is a Parameters resource
|
|
210
|
+
if (req.body && req.body.resourceType === 'Parameters') {
|
|
211
|
+
return req.body;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Parse from query params or form body and convert to Parameters resource
|
|
215
|
+
const params = req.method === 'POST' ? req.body : req.query;
|
|
216
|
+
return this.simpleParamsToParametersResource(params);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Convert simple parameters (query string or form body) to a FHIR Parameters resource
|
|
221
|
+
* @param {Object} params - Query params or form body
|
|
222
|
+
* @returns {Object} FHIR Parameters resource
|
|
223
|
+
*/
|
|
224
|
+
simpleParamsToParametersResource(params) {
|
|
225
|
+
const result = {
|
|
226
|
+
resourceType: 'Parameters',
|
|
227
|
+
parameter: []
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
if (!params) {
|
|
231
|
+
return result;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
for (const [name, value] of Object.entries(params)) {
|
|
235
|
+
if (value === undefined || value === null) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Handle arrays (e.g., repeated query params)
|
|
240
|
+
if (Array.isArray(value)) {
|
|
241
|
+
for (const v of value) {
|
|
242
|
+
result.parameter.push({
|
|
243
|
+
name: name,
|
|
244
|
+
valueString: String(v)
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
result.parameter.push({
|
|
249
|
+
name: name,
|
|
250
|
+
valueString: String(value)
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Perform the actual subsumes check
|
|
260
|
+
* @param {CodeSystemProvider} csProvider - CodeSystem provider
|
|
261
|
+
* @param {Object} codingA - First coding
|
|
262
|
+
* @param {Object} codingB - Second coding
|
|
263
|
+
* @returns {Object} Parameters resource with subsumes result
|
|
264
|
+
*/
|
|
265
|
+
async doSubsumes(csProvider, codingA, codingB) {
|
|
266
|
+
this.deadCheck('doSubsumes');
|
|
267
|
+
|
|
268
|
+
const csSystem = csProvider.system();
|
|
269
|
+
|
|
270
|
+
// Check system uri matches for both codings
|
|
271
|
+
if (csSystem !== codingA.system) {
|
|
272
|
+
const error = new Error(`System uri / code uri mismatch - not supported at this time (${csSystem}/${codingA.system})`);
|
|
273
|
+
error.statusCode = 400;
|
|
274
|
+
error.issueCode = 'not-supported';
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
if (csSystem !== codingB.system) {
|
|
278
|
+
const error = new Error(`System uri / code uri mismatch - not supported at this time (${csSystem}/${codingB.system})`);
|
|
279
|
+
error.statusCode = 400;
|
|
280
|
+
error.issueCode = 'not-supported';
|
|
281
|
+
throw error;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Validate both codes exist
|
|
285
|
+
const locateA = await csProvider.locate(codingA.code);
|
|
286
|
+
if (!locateA || !locateA.context) {
|
|
287
|
+
const error = new Error(`Invalid code: '${codingA.code}' not found in CodeSystem '${csSystem}'`);
|
|
288
|
+
error.statusCode = 404;
|
|
289
|
+
error.issueCode = 'not-found';
|
|
290
|
+
throw error;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const locateB = await csProvider.locate(codingB.code);
|
|
294
|
+
if (!locateB || !locateB.context) {
|
|
295
|
+
const error = new Error(`Invalid code: '${codingB.code}' not found in CodeSystem '${csSystem}'`);
|
|
296
|
+
error.statusCode = 404;
|
|
297
|
+
error.issueCode = 'not-found';
|
|
298
|
+
throw error;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Determine the subsumption relationship
|
|
302
|
+
let outcome = await csProvider.subsumesTest(codingA.code, codingB.code);
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
resourceType: 'Parameters',
|
|
306
|
+
parameter: [
|
|
307
|
+
{
|
|
308
|
+
name: 'outcome',
|
|
309
|
+
valueCode: outcome
|
|
310
|
+
}
|
|
311
|
+
]
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Build an OperationOutcome
|
|
317
|
+
* @param {string} severity - error, warning, information
|
|
318
|
+
* @param {string} code - Issue code
|
|
319
|
+
* @param {string} message - Diagnostic message
|
|
320
|
+
* @returns {Object} OperationOutcome resource
|
|
321
|
+
*/
|
|
322
|
+
operationOutcome(severity, code, message) {
|
|
323
|
+
return {
|
|
324
|
+
resourceType: 'OperationOutcome',
|
|
325
|
+
issue: [{
|
|
326
|
+
severity,
|
|
327
|
+
code,
|
|
328
|
+
diagnostics: message
|
|
329
|
+
}]
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
module.exports = SubsumesWorker;
|