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,492 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Translate Worker - Handles ConceptMap $translate operation
|
|
3
|
+
//
|
|
4
|
+
// GET /ConceptMap/$translate?{params}
|
|
5
|
+
// POST /ConceptMap/$translate
|
|
6
|
+
// GET /ConceptMap/{id}/$translate?{params}
|
|
7
|
+
// POST /ConceptMap/{id}/$translate
|
|
8
|
+
//
|
|
9
|
+
|
|
10
|
+
const { TerminologyWorker } = require('./worker');
|
|
11
|
+
const { TxParameters } = require('../params');
|
|
12
|
+
const { Parameters } = require('../library/parameters');
|
|
13
|
+
const { Issue, OperationOutcome } = require('../library/operation-outcome');
|
|
14
|
+
const {ConceptMap} = require("../library/conceptmap");
|
|
15
|
+
|
|
16
|
+
class TranslateWorker extends TerminologyWorker {
|
|
17
|
+
/**
|
|
18
|
+
* @param {OperationContext} opContext - Operation context
|
|
19
|
+
* @param {Logger} log - Logger instance
|
|
20
|
+
* @param {Provider} provider - Provider for concept maps and resources
|
|
21
|
+
* @param {LanguageDefinitions} languages - Language definitions
|
|
22
|
+
* @param {I18nSupport} i18n - Internationalization support
|
|
23
|
+
*/
|
|
24
|
+
constructor(opContext, log, provider, languages, i18n) {
|
|
25
|
+
super(opContext, log, provider, languages, i18n);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Get operation name
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
opName() {
|
|
33
|
+
return 'translate';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handle a type-level $translate request
|
|
38
|
+
* GET/POST /ConceptMap/$translate
|
|
39
|
+
* @param {express.Request} req - Express request
|
|
40
|
+
* @param {express.Response} res - Express response
|
|
41
|
+
*/
|
|
42
|
+
async handle(req, res) {
|
|
43
|
+
try {
|
|
44
|
+
await this.handleTypeLevelTranslate(req, res);
|
|
45
|
+
} catch (error) {
|
|
46
|
+
this.log.error(error);
|
|
47
|
+
if (error instanceof Issue) {
|
|
48
|
+
const 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 $translate request
|
|
60
|
+
* GET/POST /ConceptMap/{id}/$translate
|
|
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.handleInstanceLevelTranslate(req, res);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
this.log.error(error);
|
|
69
|
+
if (error instanceof Issue) {
|
|
70
|
+
const 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 translate: /ConceptMap/$translate
|
|
82
|
+
* ConceptMap identified by url+version params or from source/target
|
|
83
|
+
*/
|
|
84
|
+
async handleTypeLevelTranslate(req, res) {
|
|
85
|
+
this.deadCheck('translate-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.buildParameters(req));
|
|
94
|
+
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
95
|
+
txp.readParams(params.jsonObj);
|
|
96
|
+
|
|
97
|
+
// Extract required parameters per FHIR spec
|
|
98
|
+
// url - canonical URL of the concept map (optional for type-level if source/target specified)
|
|
99
|
+
// conceptMapVersion - version of the concept map
|
|
100
|
+
// sourceCode / sourceCoding / sourceCodeableConcept - the code to translate
|
|
101
|
+
// system - system of the code (if sourceCode used)
|
|
102
|
+
// version - version of the code system
|
|
103
|
+
// sourceScope - source value set scope
|
|
104
|
+
// targetScope - target value set scope
|
|
105
|
+
// targetSystem - target code system to translate to
|
|
106
|
+
// dependency - additional dependencies for translation
|
|
107
|
+
|
|
108
|
+
let coding = null;
|
|
109
|
+
let conceptMaps = [];
|
|
110
|
+
let targetScope = null;
|
|
111
|
+
let sourceScope = null;
|
|
112
|
+
let targetSystem = null;
|
|
113
|
+
|
|
114
|
+
// Get the source coding
|
|
115
|
+
if (params.has('sourceCoding')) {
|
|
116
|
+
coding = params.get('sourceCoding');
|
|
117
|
+
} else if (params.has('sourceCodeableConcept')) {
|
|
118
|
+
const cc = params.get('sourceCodeableConcept');
|
|
119
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
120
|
+
coding = cc.coding[0]; // Use first coding
|
|
121
|
+
} else {
|
|
122
|
+
throw new Issue('error', 'invalid', null, null,
|
|
123
|
+
'sourceCodeableConcept must contain at least one coding', null, 400);
|
|
124
|
+
}
|
|
125
|
+
} else if (params.has('sourceCode')) {
|
|
126
|
+
if (!params.has('sourceSystem')) {
|
|
127
|
+
throw new Issue('error', 'invalid', null, null,
|
|
128
|
+
'sourceSystem parameter is required when using sourceCode', null, 400);
|
|
129
|
+
}
|
|
130
|
+
coding = {
|
|
131
|
+
system: params.get('sourceSystem'),
|
|
132
|
+
version: params.get('sourceVersion'),
|
|
133
|
+
code: params.get('sourceCode')
|
|
134
|
+
};
|
|
135
|
+
} else {
|
|
136
|
+
throw new Issue('error', 'invalid', null, null,
|
|
137
|
+
'Must provide sourceCode (with system), sourceCoding, or sourceCodeableConcept', null, 400);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Get the concept map
|
|
141
|
+
if (params.has('url')) {
|
|
142
|
+
const url = params.get('url');
|
|
143
|
+
const cmVersion = params.get('conceptMapVersion');
|
|
144
|
+
let conceptMap = await this.provider.findConceptMap(this.opContext, url, cmVersion);
|
|
145
|
+
if (!conceptMap) {
|
|
146
|
+
const msg = cmVersion
|
|
147
|
+
? `ConceptMap not found: ${url} version ${cmVersion}`
|
|
148
|
+
: `ConceptMap not found: ${url}`;
|
|
149
|
+
throw new Issue('error', 'not-found', null, null, msg, null, 404);
|
|
150
|
+
} else {
|
|
151
|
+
conceptMaps.push(conceptMap);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Get scope parameters
|
|
156
|
+
if (params.has('sourceScope')) {
|
|
157
|
+
sourceScope = params.get('sourceScope');
|
|
158
|
+
}
|
|
159
|
+
if (params.has('targetScope')) {
|
|
160
|
+
targetScope = params.get('targetScope');
|
|
161
|
+
}
|
|
162
|
+
if (params.has('targetSystem')) {
|
|
163
|
+
targetSystem = params.get('targetSystem');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// If no explicit concept map, we need to find one based on source/target
|
|
167
|
+
if (conceptMaps.length == 0) {
|
|
168
|
+
await this.findConceptMapsInAdditionalResources(conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
|
|
169
|
+
await this.provider.findConceptMapForTranslation(this.opContext, conceptMaps, coding.system, sourceScope, targetScope, targetSystem);
|
|
170
|
+
if (conceptMaps.length == 0) {
|
|
171
|
+
throw new Issue('error', 'not-found', null, null, 'No suitable ConceptMaps found for the specified source and target', null, 404);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Perform the translation
|
|
176
|
+
const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, txp);
|
|
177
|
+
return res.status(200).json(result);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Handle instance-level translate: /ConceptMap/{id}/$translate
|
|
182
|
+
* ConceptMap identified by resource ID
|
|
183
|
+
*/
|
|
184
|
+
async handleInstanceLevelTranslate(req, res) {
|
|
185
|
+
this.deadCheck('translate-instance-level');
|
|
186
|
+
|
|
187
|
+
const { id } = req.params;
|
|
188
|
+
|
|
189
|
+
// Find the ConceptMap by ID
|
|
190
|
+
const conceptMap = await this.provider.getConceptMapById(this.opContext, id);
|
|
191
|
+
|
|
192
|
+
if (!conceptMap) {
|
|
193
|
+
throw new Issue('error', 'not-found', null, null,
|
|
194
|
+
`ConceptMap/${id} not found`, null, 404);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Handle tx-resource and cache-id parameters from Parameters resource
|
|
198
|
+
if (req.body && req.body.resourceType === 'Parameters') {
|
|
199
|
+
this.setupAdditionalResources(req.body);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Parse parameters from request
|
|
203
|
+
const params = new Parameters(this.buildParameters(req));
|
|
204
|
+
const txp = new TxParameters(this.opContext.i18n.languageDefinitions, this.opContext.i18n);
|
|
205
|
+
txp.readParams(params.jsonObj);
|
|
206
|
+
|
|
207
|
+
// Get the source coding
|
|
208
|
+
let coding = null;
|
|
209
|
+
|
|
210
|
+
if (params.has('sourceCoding')) {
|
|
211
|
+
coding = params.get('sourceCoding');
|
|
212
|
+
} else if (params.has('sourceCodeableConcept')) {
|
|
213
|
+
const cc = params.get('sourceCodeableConcept');
|
|
214
|
+
if (cc.coding && cc.coding.length > 0) {
|
|
215
|
+
coding = cc.coding[0];
|
|
216
|
+
} else {
|
|
217
|
+
throw new Issue('error', 'invalid', null, null,
|
|
218
|
+
'sourceCodeableConcept must contain at least one coding', null, 400);
|
|
219
|
+
}
|
|
220
|
+
} else if (params.has('sourceCode')) {
|
|
221
|
+
if (!params.has('system')) {
|
|
222
|
+
throw new Issue('error', 'invalid', null, null,
|
|
223
|
+
'system parameter is required when using sourceCode', null, 400);
|
|
224
|
+
}
|
|
225
|
+
coding = {
|
|
226
|
+
system: params.get('system'),
|
|
227
|
+
version: params.get('version'),
|
|
228
|
+
code: params.get('sourceCode')
|
|
229
|
+
};
|
|
230
|
+
} else {
|
|
231
|
+
throw new Issue('error', 'invalid', null, null,
|
|
232
|
+
'Must provide sourceCode (with system), sourceCoding, or sourceCodeableConcept', null, 400);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Get optional scope/target parameters
|
|
236
|
+
const targetScope = params.has('targetScope') ? params.get('targetScope') : null;
|
|
237
|
+
const targetSystem = params.has('targetSystem') ? params.get('targetSystem') : null;
|
|
238
|
+
|
|
239
|
+
let conceptMaps = [];
|
|
240
|
+
conceptMaps.push(conceptMap);
|
|
241
|
+
|
|
242
|
+
// Perform the translation
|
|
243
|
+
const result = await this.doTranslate(conceptMaps, coding, targetScope, targetSystem, params);
|
|
244
|
+
return res.status(200).json(result);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
|
|
248
|
+
checkCode(op, langList, path, code, system, version, display) {
|
|
249
|
+
let result = false;
|
|
250
|
+
const cp = this.findCodeSystem(system, version, null, ['complete', 'fragment'], true, true, false, null);
|
|
251
|
+
if (cp != null) {
|
|
252
|
+
const lct = cp.locate(this.opContext, code);
|
|
253
|
+
if (op.error('InstanceValidator', 'invalid', path, lct != null, 'Unknown Code (' + system + '#' + code + ')')) {
|
|
254
|
+
result = op.warning('InstanceValidator', 'invalid', path,
|
|
255
|
+
(!display) || (display === cp.display(this.opContext, lct, null)),
|
|
256
|
+
'Display for ' + system + ' code "' + code + '" should be "' + cp.display(this.opContext, lct, null) + '"');
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return result;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
translateUsingGroups(cm, coding, targetScope, targetSystem, params, output) {
|
|
263
|
+
let result = false;
|
|
264
|
+
const matches = cm.listTranslations(coding, targetScope, targetSystem);
|
|
265
|
+
if (matches.length > 0) {
|
|
266
|
+
for (let match of matches) {
|
|
267
|
+
const g = match.group;
|
|
268
|
+
const em = match.match;
|
|
269
|
+
for (const map of em.target || []) {
|
|
270
|
+
if (['null', 'equivalent', 'equal', 'wider', 'subsumes', 'narrower', 'specializes', 'inexact'].includes(map.relationship)) {
|
|
271
|
+
result = true;
|
|
272
|
+
|
|
273
|
+
const outcome = {
|
|
274
|
+
system: g.target,
|
|
275
|
+
code: map.code
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const matchParts = [];
|
|
279
|
+
matchParts.push({
|
|
280
|
+
name: 'concept',
|
|
281
|
+
valueCoding: outcome
|
|
282
|
+
});
|
|
283
|
+
matchParts.push({
|
|
284
|
+
name: 'relationship',
|
|
285
|
+
valueCode: map.relationship
|
|
286
|
+
});
|
|
287
|
+
if (map.comments) {
|
|
288
|
+
matchParts.push({
|
|
289
|
+
name: 'message',
|
|
290
|
+
valueString: map.comments
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
for (const prod of map.products || []) {
|
|
294
|
+
const productParts = [];
|
|
295
|
+
productParts.push({
|
|
296
|
+
name: 'element',
|
|
297
|
+
valueString: prod.property
|
|
298
|
+
});
|
|
299
|
+
productParts.push({
|
|
300
|
+
name: 'concept',
|
|
301
|
+
valueCoding: {
|
|
302
|
+
system: prod.system,
|
|
303
|
+
code: prod.value
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
matchParts.push({
|
|
307
|
+
name: 'product',
|
|
308
|
+
part: productParts
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
output.push({
|
|
312
|
+
name: 'match',
|
|
313
|
+
part: matchParts
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async translateUsingCodeSystem(cm, coding, target, params, output) {
|
|
323
|
+
let result = false;
|
|
324
|
+
const factory = cm.jsonObj.internalSource;
|
|
325
|
+
let prov = await factory.build(this.opContext, []);
|
|
326
|
+
|
|
327
|
+
output.push({
|
|
328
|
+
name: 'used-system',
|
|
329
|
+
valueUri: prov.system() + '|' + prov.version()
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
let translations = await prov.getTranslations(coding, target);
|
|
333
|
+
|
|
334
|
+
if (translations.length > 0) {
|
|
335
|
+
result = true;
|
|
336
|
+
|
|
337
|
+
for (const t of translations) {
|
|
338
|
+
if (t.map) {
|
|
339
|
+
output.push({
|
|
340
|
+
name: 'used-conceptmap',
|
|
341
|
+
valueUri: t.map
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
const outcome = {
|
|
346
|
+
system: t.uri,
|
|
347
|
+
code: t.code,
|
|
348
|
+
version: t.version,
|
|
349
|
+
display: t.display
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
const matchParts = [];
|
|
353
|
+
matchParts.push({
|
|
354
|
+
name: 'concept',
|
|
355
|
+
valueCoding: outcome
|
|
356
|
+
});
|
|
357
|
+
matchParts.push({
|
|
358
|
+
name: 'relationship',
|
|
359
|
+
valueCode: t.relationship
|
|
360
|
+
});
|
|
361
|
+
if (t.message) {
|
|
362
|
+
matchParts.push({
|
|
363
|
+
name: 'message',
|
|
364
|
+
valueString: t.message
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
output.push({
|
|
368
|
+
name: 'match',
|
|
369
|
+
part: matchParts
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return result;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Perform the actual translate operation
|
|
378
|
+
* @param {Object} conceptMap - ConceptMap resource
|
|
379
|
+
* @param {Object} coding - Source coding to translate
|
|
380
|
+
* @param {string} targetScope - Target value set scope (optional)
|
|
381
|
+
* @param {string} targetSystem - Target code system (optional)
|
|
382
|
+
* @param {Parameters} params - Full parameters object
|
|
383
|
+
* @returns {Object} Parameters resource with translate result
|
|
384
|
+
*/
|
|
385
|
+
async doTranslate(conceptMaps, coding, targetScope, targetSystem, params) {
|
|
386
|
+
this.deadCheck('doTranslate');
|
|
387
|
+
|
|
388
|
+
const result = [];
|
|
389
|
+
|
|
390
|
+
try {
|
|
391
|
+
let added = false;
|
|
392
|
+
for (const cm of conceptMaps) {
|
|
393
|
+
if (cm.jsonObj.internalSource) {
|
|
394
|
+
added = await this.translateUsingCodeSystem(cm, coding, targetSystem, params, result) || added;
|
|
395
|
+
} else {
|
|
396
|
+
added = this.translateUsingGroups(cm, coding, targetScope, targetSystem, params, result) || added;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
result.push({
|
|
400
|
+
name: 'result',
|
|
401
|
+
valueBoolean: added
|
|
402
|
+
});
|
|
403
|
+
if (!added) {
|
|
404
|
+
result.push({
|
|
405
|
+
name: 'message',
|
|
406
|
+
valueString: 'No translations found'
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
} catch (e) {
|
|
410
|
+
result.push({
|
|
411
|
+
name: 'result',
|
|
412
|
+
valueBoolean: false
|
|
413
|
+
});
|
|
414
|
+
result.push({
|
|
415
|
+
name: 'message',
|
|
416
|
+
valueString: e.message
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return {
|
|
421
|
+
resourceType: 'Parameters',
|
|
422
|
+
parameter: result
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// eslint-disable-next-line no-unused-vars
|
|
427
|
+
isOkTarget(cm, vs) {
|
|
428
|
+
// if cm.target != null then
|
|
429
|
+
// result := cm.target.url = vs.url
|
|
430
|
+
// else
|
|
431
|
+
return false;
|
|
432
|
+
// todo: or it might be ok to use this value set if it's a subset of the specified one?
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// isOkSourceWithValueSet(cm, vs, coding) {
|
|
436
|
+
// let result = { found: false, group: null, match: null };
|
|
437
|
+
//
|
|
438
|
+
// if (true /* (vs == null) || ((cm.source != null) && (cm.source.url === vs.url)) */) {
|
|
439
|
+
// for (const g of cm.groups || []) {
|
|
440
|
+
// for (const em of g.elements || []) {
|
|
441
|
+
// if ((g.source === coding.system) && (em.code === coding.code)) {
|
|
442
|
+
// result = {
|
|
443
|
+
// found: true,
|
|
444
|
+
// group: g,
|
|
445
|
+
// match: em
|
|
446
|
+
// };
|
|
447
|
+
// }
|
|
448
|
+
// }
|
|
449
|
+
// }
|
|
450
|
+
// }
|
|
451
|
+
// return result;
|
|
452
|
+
// }
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
findConceptMap(cm) {
|
|
456
|
+
let msg = '';
|
|
457
|
+
if (cm != null) {
|
|
458
|
+
return { found: true, message: msg };
|
|
459
|
+
} else {
|
|
460
|
+
return { found: false, message: msg };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Build an OperationOutcome
|
|
465
|
+
* @param {string} severity - error, warning, information
|
|
466
|
+
* @param {string} code - Issue code
|
|
467
|
+
* @param {string} message - Diagnostic message
|
|
468
|
+
* @returns {Object} OperationOutcome resource
|
|
469
|
+
*/
|
|
470
|
+
operationOutcome(severity, code, message) {
|
|
471
|
+
return {
|
|
472
|
+
resourceType: 'OperationOutcome',
|
|
473
|
+
issue: [{
|
|
474
|
+
severity,
|
|
475
|
+
code,
|
|
476
|
+
diagnostics: message
|
|
477
|
+
}]
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async findConceptMapsInAdditionalResources(conceptMaps, system, sourceScope, targetScope, targetSystem) {
|
|
482
|
+
for (let res of this.additionalResources || []) {
|
|
483
|
+
if (res instanceof ConceptMap) {
|
|
484
|
+
if (res.providesTranslation(system, sourceScope, targetScope, targetSystem)) {
|
|
485
|
+
conceptMaps.push(res);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
module.exports = TranslateWorker;
|