fhirsmith 0.7.4 → 0.7.6
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 +40 -0
- package/README.md +8 -0
- package/library/html.js +4 -0
- package/package.json +1 -1
- package/packages/package-crawler.js +104 -49
- package/packages/packages.js +14 -0
- package/publisher/publisher.js +124 -33
- package/registry/registry.js +97 -89
- package/root-bare-template.html +93 -0
- package/security.md +32 -0
- package/server.js +94 -47
- package/stats.js +6 -4
- package/translations/Messages.properties +2 -0
- package/tx/README.md +6 -6
- package/tx/cs/cs-api.js +3 -0
- package/tx/cs/cs-api.md +285 -0
- package/tx/cs/cs-country.js +804 -801
- package/tx/importers/readme.md +3 -1
- package/tx/library.js +33 -6
- package/tx/provider.js +2 -1
- package/tx/tx-html.js +36 -9
- package/tx/tx.fhir.org.yml +3 -0
- package/tx/tx.js +34 -11
- package/tx/vs/vs-database.js +42 -5
- package/tx/vs/vs-vsac.js +48 -0
- package/tx/workers/validate.js +11 -6
- package/tx/workers/worker.js +2 -5
- package/utilities/dashboard.html +274 -0
package/tx/importers/readme.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# TX-Import: Medical Terminology Import Tool
|
|
2
2
|
|
|
3
|
-
A comprehensive CLI tool for importing various medical terminology standards into SQLite databases and other formats
|
|
3
|
+
A comprehensive CLI tool for importing various medical terminology standards into SQLite databases and other formats
|
|
4
|
+
for use by FHIRsmith. The tool supports LOINC, SNOMED CT, UNII, NDC, and provides extensible architecture for
|
|
5
|
+
additional terminologies.
|
|
4
6
|
|
|
5
7
|
## Table of Contents
|
|
6
8
|
|
package/tx/library.js
CHANGED
|
@@ -65,7 +65,8 @@ class Library {
|
|
|
65
65
|
*/
|
|
66
66
|
conceptMapProviders;
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
packageSources = [];
|
|
69
|
+
externalSources = [];
|
|
69
70
|
|
|
70
71
|
baseUrl = null;
|
|
71
72
|
cacheFolder = null;
|
|
@@ -101,6 +102,7 @@ class Library {
|
|
|
101
102
|
this.conceptMapProviders = [];
|
|
102
103
|
this.oclProviderSets = new Map();
|
|
103
104
|
this.oclConfig = {};
|
|
105
|
+
this.ignored = new Set();
|
|
104
106
|
|
|
105
107
|
// Create package manager for FHIR packages
|
|
106
108
|
const packageServers = ['https://packages2.fhir.org/packages'];
|
|
@@ -161,6 +163,7 @@ class Library {
|
|
|
161
163
|
const config = yaml.parse(yamlContent);
|
|
162
164
|
this.baseUrl = config.base.url;
|
|
163
165
|
this.oclConfig = config.ocl && typeof config.ocl === 'object' ? config.ocl : {};
|
|
166
|
+
this.ignored = new Set(Array.isArray(config.ignored) ? config.ignored : []);
|
|
164
167
|
|
|
165
168
|
this.log.info('Fetching Data from '+this.baseUrl);
|
|
166
169
|
|
|
@@ -288,7 +291,7 @@ class Library {
|
|
|
288
291
|
case 'ocl':
|
|
289
292
|
await this.loadOcl(details, isDefault, mode);
|
|
290
293
|
break;
|
|
291
|
-
|
|
294
|
+
|
|
292
295
|
default:
|
|
293
296
|
throw new Error(`Unknown source type: ${type}`);
|
|
294
297
|
}
|
|
@@ -358,6 +361,9 @@ class Library {
|
|
|
358
361
|
const codeSystemProvider = new OCLCodeSystemProvider(config);
|
|
359
362
|
const valueSetProvider = new OCLValueSetProvider(config);
|
|
360
363
|
const conceptMapProvider = new OCLConceptMapProvider(config);
|
|
364
|
+
this.externalSources.push(codeSystemProvider);
|
|
365
|
+
this.externalSources.push(valueSetProvider);
|
|
366
|
+
this.externalSources.push(conceptMapProvider);
|
|
361
367
|
providerSet = {
|
|
362
368
|
config,
|
|
363
369
|
codeSystemProvider,
|
|
@@ -468,6 +474,7 @@ class Library {
|
|
|
468
474
|
let vsac = new VSACValueSetProvider(this.vsacCfg, this.stats);
|
|
469
475
|
vsac.initialize();
|
|
470
476
|
this.valueSetProviders.push(vsac);
|
|
477
|
+
this.externalSources.push(vsac);
|
|
471
478
|
//const mem = process.memoryUsage();
|
|
472
479
|
let time = Math.floor(Date.now() - this.lastTime).toString().padStart(5)+" ";
|
|
473
480
|
let system = "vsac".padEnd(50);
|
|
@@ -567,6 +574,17 @@ class Library {
|
|
|
567
574
|
this.registerProvider(omopFN, omop, isDefault);
|
|
568
575
|
}
|
|
569
576
|
|
|
577
|
+
/**
|
|
578
|
+
* Returns true if the given url/version should be excluded from npm/url package loading.
|
|
579
|
+
* Matches against the ignored list using either plain url or url#version.
|
|
580
|
+
*/
|
|
581
|
+
#isIgnored(url, version) {
|
|
582
|
+
if (this.ignored.size === 0) return false;
|
|
583
|
+
if (this.ignored.has(url)) return true;
|
|
584
|
+
if (version && this.ignored.has(`${url}#${version}`)) return true;
|
|
585
|
+
return false;
|
|
586
|
+
}
|
|
587
|
+
|
|
570
588
|
async loadNpm(packageManager, details, isDefault, mode, csOnly) {
|
|
571
589
|
// Parse packageId and version from details (e.g., "hl7.terminology.r4#6.0.2")
|
|
572
590
|
let packageId = details;
|
|
@@ -584,13 +602,17 @@ class Library {
|
|
|
584
602
|
const contentLoader = new PackageContentLoader(fullPackagePath);
|
|
585
603
|
await contentLoader.initialize();
|
|
586
604
|
|
|
587
|
-
this.
|
|
605
|
+
this.packageSources.push(contentLoader.id()+"#"+contentLoader.version());
|
|
588
606
|
|
|
589
607
|
let cp = new ListCodeSystemProvider();
|
|
590
608
|
const resources = await contentLoader.getResourcesByType("CodeSystem");
|
|
591
609
|
let csc = 0;
|
|
592
610
|
for (const resource of resources) {
|
|
593
611
|
const cs = new CodeSystem(await contentLoader.loadFile(resource, contentLoader.fhirVersion()));
|
|
612
|
+
if (this.#isIgnored(cs.url, cs.version)) {
|
|
613
|
+
this.log.info(`Ignoring CodeSystem ${cs.url}${cs.version ? '#' + cs.version : ''} (excluded by config)`);
|
|
614
|
+
continue;
|
|
615
|
+
}
|
|
594
616
|
cs.sourcePackage = contentLoader.pid();
|
|
595
617
|
cp.codeSystems.push(cs);
|
|
596
618
|
csc++;
|
|
@@ -618,13 +640,17 @@ class Library {
|
|
|
618
640
|
const contentLoader = new PackageContentLoader(fullPackagePath);
|
|
619
641
|
await contentLoader.initialize();
|
|
620
642
|
|
|
621
|
-
this.
|
|
643
|
+
this.packageSources.push(contentLoader.id()+"#"+contentLoader.version());
|
|
622
644
|
|
|
623
645
|
let cp = new ListCodeSystemProvider();
|
|
624
646
|
const resources = await contentLoader.getResourcesByType("CodeSystem");
|
|
625
647
|
let csc = 0;
|
|
626
648
|
for (const resource of resources) {
|
|
627
649
|
const cs = new CodeSystem(await contentLoader.loadFile(resource, contentLoader.fhirVersion()));
|
|
650
|
+
if (this.#isIgnored(cs.url, cs.version)) {
|
|
651
|
+
this.log.info(`Ignoring CodeSystem ${cs.url}${cs.version ? '#' + cs.version : ''} (excluded by config)`);
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
628
654
|
cs.sourcePackage = contentLoader.pid();
|
|
629
655
|
cp.codeSystems.set(cs.url, cs);
|
|
630
656
|
cp.codeSystems.set(cs.vurl, cs);
|
|
@@ -826,7 +852,8 @@ class Library {
|
|
|
826
852
|
provider.lastTime = this.lastTime;
|
|
827
853
|
provider.lastMemory = this.lastMemory;
|
|
828
854
|
provider.totalDownloaded = this.totalDownloaded;
|
|
829
|
-
provider.
|
|
855
|
+
provider.packageSources = this.packageSources;
|
|
856
|
+
provider.externalSources = this.externalSources;
|
|
830
857
|
|
|
831
858
|
|
|
832
859
|
// Now add the existing value set providers after the FHIR core packages
|
|
@@ -905,4 +932,4 @@ class Library {
|
|
|
905
932
|
|
|
906
933
|
}
|
|
907
934
|
|
|
908
|
-
module.exports = { Library };
|
|
935
|
+
module.exports = { Library };
|
package/tx/provider.js
CHANGED
package/tx/tx-html.js
CHANGED
|
@@ -234,24 +234,44 @@ class TxHtmlRenderer {
|
|
|
234
234
|
html += await this.buildSearchForm(req);
|
|
235
235
|
|
|
236
236
|
// ===== Packages and Factories Section =====
|
|
237
|
-
html += '<hr/><h3>Content
|
|
237
|
+
html += '<hr/><h3>Source Content</h3>';
|
|
238
238
|
|
|
239
|
-
// List
|
|
240
|
-
html += '<h6>
|
|
241
|
-
if (provider.
|
|
242
|
-
const sorted = [...provider.
|
|
239
|
+
// List Packages
|
|
240
|
+
html += '<h6>FHIR Packages</h6>';
|
|
241
|
+
if (provider.packageSources && provider.packageSources.length > 0) {
|
|
242
|
+
const sorted = [...provider.packageSources].sort();
|
|
243
243
|
html += '<ul>';
|
|
244
244
|
for (const source of sorted) {
|
|
245
245
|
html += `<li>${escape(source)}</li>`;
|
|
246
246
|
}
|
|
247
247
|
html += '</ul>';
|
|
248
248
|
} else {
|
|
249
|
-
html += '<p><em>No
|
|
249
|
+
html += '<p><em>No FHIR Packages Loaded</em></p>';
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
//
|
|
253
|
-
|
|
254
|
-
|
|
252
|
+
// List Packages
|
|
253
|
+
html += '<h6>External Sources</h6>';
|
|
254
|
+
if (provider.externalSources && provider.externalSources.length > 0) {
|
|
255
|
+
const sorted = [...provider.externalSources].sort();
|
|
256
|
+
html += '<ul>';
|
|
257
|
+
for (const source of sorted) {
|
|
258
|
+
let n = source.name();
|
|
259
|
+
if (!n) {
|
|
260
|
+
n = source.sourcePackage();
|
|
261
|
+
}
|
|
262
|
+
let ii = source.infoName();
|
|
263
|
+
if (ii) {
|
|
264
|
+
html += `<li>${escape(n)} (<a href="info/${source.id()}">${ii}</a>)</li>`;
|
|
265
|
+
} else {
|
|
266
|
+
html += `<li>${escape(n)}</li>`;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
html += '</ul>';
|
|
270
|
+
} else {
|
|
271
|
+
html += '<p><em>No External Sources Configured</em></p>';
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
html += '<h6 class="mt-4">Special CodeSystems</h6>';
|
|
255
275
|
html += '<table class="grid">';
|
|
256
276
|
html += '<thead><tr><th>Name</th><th>URI</th><th>Version</th><th>Use Count</th></tr></thead>';
|
|
257
277
|
html += '<tbody>';
|
|
@@ -1290,6 +1310,13 @@ class TxHtmlRenderer {
|
|
|
1290
1310
|
});
|
|
1291
1311
|
}
|
|
1292
1312
|
|
|
1313
|
+
async buildInfoPage(source, req) {
|
|
1314
|
+
let html = '';
|
|
1315
|
+
const infoContent = await source.info(req);
|
|
1316
|
+
html += infoContent;
|
|
1317
|
+
return html;
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1293
1320
|
buildSourceOptions(provider) {
|
|
1294
1321
|
let result = '<option value=""></option>';
|
|
1295
1322
|
result += `<option value="internal">internal</option>`;
|
package/tx/tx.fhir.org.yml
CHANGED
package/tx/tx.js
CHANGED
|
@@ -9,7 +9,7 @@ const express = require('express');
|
|
|
9
9
|
const path = require('path');
|
|
10
10
|
const Logger = require('../library/logger');
|
|
11
11
|
const { Library } = require('./library');
|
|
12
|
-
const { OperationContext, ResourceCache, ExpansionCache } = require('./operation-context');
|
|
12
|
+
const { OperationContext, ResourceCache, ExpansionCache, debugLog} = require('./operation-context');
|
|
13
13
|
const { LanguageDefinitions } = require('../library/languages');
|
|
14
14
|
const { I18nSupport } = require('../library/i18nsupport');
|
|
15
15
|
const { CodeSystemXML } = require('./xml/codesystem-xml');
|
|
@@ -965,6 +965,29 @@ class TXModule {
|
|
|
965
965
|
this.countRequest('home', Date.now() - start);
|
|
966
966
|
}
|
|
967
967
|
});
|
|
968
|
+
|
|
969
|
+
// External source info pages
|
|
970
|
+
router.get('/info/:id', async (req, res) => {
|
|
971
|
+
const start = Date.now();
|
|
972
|
+
try {
|
|
973
|
+
const source = req.txEndpoint.provider.externalSources.find(s => s.id() === req.params.id);
|
|
974
|
+
if (!source) {
|
|
975
|
+
res.status(404).send('Not found');
|
|
976
|
+
return;
|
|
977
|
+
}
|
|
978
|
+
let txhtml = new TxHtmlRenderer(new Renderer(req.txOpContext, req.txEndpoint.provider), this.liquid, this.languages, this.i18n, req.txEndpoint.path);
|
|
979
|
+
const content = await txhtml.buildInfoPage(source, req);
|
|
980
|
+
const html = await txhtml.renderPage(source.name(), content, req.txEndpoint, start);
|
|
981
|
+
res.setHeader('Content-Type', 'text/html');
|
|
982
|
+
res.send(html);
|
|
983
|
+
} catch (error) {
|
|
984
|
+
debugLog(error);
|
|
985
|
+
this.log.error(`Error rendering info page for ${req.params.id}: ${error.message}`);
|
|
986
|
+
res.status(500).send('Internal server error');
|
|
987
|
+
} finally {
|
|
988
|
+
this.countRequest('info', Date.now() - start);
|
|
989
|
+
}
|
|
990
|
+
});
|
|
968
991
|
}
|
|
969
992
|
|
|
970
993
|
/**
|
|
@@ -1153,16 +1176,16 @@ class TXModule {
|
|
|
1153
1176
|
ec = 0;
|
|
1154
1177
|
|
|
1155
1178
|
checkProperJson() { // jsonStr) {
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1179
|
+
// const errors = [];
|
|
1180
|
+
// if (jsonStr.includes("[]")) errors.push("Found [] in json");
|
|
1181
|
+
// if (jsonStr.includes('""')) errors.push('Found "" in json');
|
|
1182
|
+
//
|
|
1183
|
+
// if (errors.length > 0) {
|
|
1184
|
+
// this.ec++;
|
|
1185
|
+
// const filename = `/Users/grahamegrieve/temp/tx-err-log/err${this.ec}.json`;
|
|
1186
|
+
// writeFileSync(filename, jsonStr);
|
|
1187
|
+
// throw new Error(errors.join('; '));
|
|
1188
|
+
// }
|
|
1166
1189
|
}
|
|
1167
1190
|
|
|
1168
1191
|
transformResourceForVersion(data, fhirVersion) {
|
package/tx/vs/vs-database.js
CHANGED
|
@@ -22,6 +22,27 @@ class ValueSetDatabase {
|
|
|
22
22
|
this._writeDb = null; // Write connection (opened only when needed)
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Apply any pending schema migrations
|
|
27
|
+
* @param {sqlite3.Database} db
|
|
28
|
+
* @returns {Promise<void>}
|
|
29
|
+
* @private
|
|
30
|
+
*/
|
|
31
|
+
_migrateIfNeeded(db) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
db.all("PRAGMA table_info(valuesets)", [], (err, cols) => {
|
|
34
|
+
if (err) { reject(err); return; }
|
|
35
|
+
const hasCol = cols.some(c => c.name === 'date_first_seen');
|
|
36
|
+
if (hasCol) { resolve(); return; }
|
|
37
|
+
db.run(
|
|
38
|
+
"ALTER TABLE valuesets ADD COLUMN date_first_seen INTEGER DEFAULT 0",
|
|
39
|
+
[],
|
|
40
|
+
(err) => err ? reject(err) : resolve()
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
25
46
|
/**
|
|
26
47
|
* Get a read-only database connection (opens lazily if needed)
|
|
27
48
|
* @returns {Promise<sqlite3.Database>}
|
|
@@ -62,7 +83,7 @@ class ValueSetDatabase {
|
|
|
62
83
|
this._writeDb = null;
|
|
63
84
|
reject(new Error(`Failed to open database for writing: ${err.message}`));
|
|
64
85
|
} else {
|
|
65
|
-
resolve(this._writeDb);
|
|
86
|
+
this._migrateIfNeeded(this._writeDb).then(() => resolve(this._writeDb)).catch(reject);
|
|
66
87
|
}
|
|
67
88
|
});
|
|
68
89
|
});
|
|
@@ -144,7 +165,8 @@ class ValueSetDatabase {
|
|
|
144
165
|
status TEXT,
|
|
145
166
|
title TEXT,
|
|
146
167
|
content TEXT NOT NULL,
|
|
147
|
-
last_seen INTEGER DEFAULT (strftime('%s', 'now'))
|
|
168
|
+
last_seen INTEGER DEFAULT (strftime('%s', 'now')),
|
|
169
|
+
date_first_seen INTEGER DEFAULT (strftime('%s', 'now'))
|
|
148
170
|
)
|
|
149
171
|
`);
|
|
150
172
|
|
|
@@ -190,6 +212,7 @@ class ValueSetDatabase {
|
|
|
190
212
|
db.run('CREATE INDEX idx_valuesets_title ON valuesets(title)');
|
|
191
213
|
db.run('CREATE INDEX idx_valuesets_publisher ON valuesets(publisher)');
|
|
192
214
|
db.run('CREATE INDEX idx_valuesets_last_seen ON valuesets(last_seen)');
|
|
215
|
+
db.run('CREATE INDEX idx_valuesets_date_first_seen ON valuesets(date_first_seen)');
|
|
193
216
|
db.run('CREATE INDEX idx_identifiers_system ON valueset_identifiers(system)');
|
|
194
217
|
db.run('CREATE INDEX idx_identifiers_value ON valueset_identifiers(value)');
|
|
195
218
|
db.run('CREATE INDEX idx_jurisdictions_system ON valueset_jurisdictions(system)');
|
|
@@ -246,10 +269,24 @@ class ValueSetDatabase {
|
|
|
246
269
|
const expansionId = valueSet.expansion?.identifier || null;
|
|
247
270
|
|
|
248
271
|
db.run(`
|
|
249
|
-
INSERT
|
|
272
|
+
INSERT INTO valuesets (
|
|
250
273
|
id, url, version, date, description, effectivePeriod_start, effectivePeriod_end,
|
|
251
|
-
expansion_identifier, name, publisher, status, title, content, last_seen
|
|
252
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'))
|
|
274
|
+
expansion_identifier, name, publisher, status, title, content, last_seen, date_first_seen
|
|
275
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, strftime('%s', 'now'), strftime('%s', 'now'))
|
|
276
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
277
|
+
url=excluded.url,
|
|
278
|
+
version=excluded.version,
|
|
279
|
+
date=excluded.date,
|
|
280
|
+
description=excluded.description,
|
|
281
|
+
effectivePeriod_start=excluded.effectivePeriod_start,
|
|
282
|
+
effectivePeriod_end=excluded.effectivePeriod_end,
|
|
283
|
+
expansion_identifier=excluded.expansion_identifier,
|
|
284
|
+
name=excluded.name,
|
|
285
|
+
publisher=excluded.publisher,
|
|
286
|
+
status=excluded.status,
|
|
287
|
+
title=excluded.title,
|
|
288
|
+
content=excluded.content,
|
|
289
|
+
last_seen=strftime('%s', 'now')
|
|
253
290
|
`, [
|
|
254
291
|
valueSet.id,
|
|
255
292
|
valueSet.url,
|
package/tx/vs/vs-vsac.js
CHANGED
|
@@ -70,6 +70,8 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
|
|
|
70
70
|
if (!(await this.database.exists())) {
|
|
71
71
|
await this.database.create();
|
|
72
72
|
} else {
|
|
73
|
+
// Ensure schema is up to date (e.g. date_first_seen column added after initial deploy)
|
|
74
|
+
await this.database._migrateIfNeeded(await this.database._getWriteConnection());
|
|
73
75
|
// Load existing data
|
|
74
76
|
await this._reloadMap();
|
|
75
77
|
}
|
|
@@ -511,6 +513,52 @@ class VSACValueSetProvider extends AbstractValueSetProvider {
|
|
|
511
513
|
this.stats.task('VSAC Sync', logMsg);
|
|
512
514
|
|
|
513
515
|
}
|
|
516
|
+
|
|
517
|
+
name() {
|
|
518
|
+
return "VSAC";
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
infoName() {
|
|
522
|
+
return "history";
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async info() {
|
|
526
|
+
const db = await this.database._getReadConnection();
|
|
527
|
+
const rows = await new Promise((resolve, reject) => {
|
|
528
|
+
db.all(
|
|
529
|
+
`SELECT url, version, date_first_seen
|
|
530
|
+
FROM valuesets
|
|
531
|
+
WHERE date_first_seen > 0
|
|
532
|
+
ORDER BY date_first_seen DESC
|
|
533
|
+
LIMIT 100`,
|
|
534
|
+
[],
|
|
535
|
+
(err, rows) => err ? reject(err) : resolve(rows)
|
|
536
|
+
);
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
const escape = require('escape-html');
|
|
540
|
+
let html = '<h3>Recently Value Sets Added to VSAC</h3>';
|
|
541
|
+
html += '<p>The last ' + rows.length + ' value sets found from VSAC, most recent first.</p>';
|
|
542
|
+
html += '<table class="grid">';
|
|
543
|
+
html += '<thead><tr><th>URL</th><th>Version</th><th>Date Observed</th></tr></thead>';
|
|
544
|
+
html += '<tbody>';
|
|
545
|
+
for (const row of rows) {
|
|
546
|
+
const date = row.date_first_seen
|
|
547
|
+
? new Date(row.date_first_seen * 1000).toISOString().replace('T', ' ').substring(0, 19) + ' UTC'
|
|
548
|
+
: 'unknown';
|
|
549
|
+
html += '<tr>';
|
|
550
|
+
html += `<td>${escape(row.url || '')}</td>`;
|
|
551
|
+
html += `<td>${escape(row.version || '')}</td>`;
|
|
552
|
+
html += `<td>${escape(date)}</td>`;
|
|
553
|
+
html += '</tr>';
|
|
554
|
+
}
|
|
555
|
+
html += '</tbody></table>';
|
|
556
|
+
return html;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
id() {
|
|
560
|
+
return "vsac";
|
|
561
|
+
}
|
|
514
562
|
}
|
|
515
563
|
|
|
516
564
|
// Usage examples:
|
package/tx/workers/validate.js
CHANGED
|
@@ -1389,17 +1389,22 @@ class ValueSetChecker {
|
|
|
1389
1389
|
let hd = list.hasDisplay(this.params.workingLanguages(), null, c.display, false, DisplayCheckingStyle.CASE_INSENSITIVE)
|
|
1390
1390
|
if (!hd.found) {
|
|
1391
1391
|
let baseMsg;
|
|
1392
|
-
|
|
1392
|
+
let severity = this.dispWarning();
|
|
1393
|
+
if (list.userDefined) {
|
|
1394
|
+
baseMsg = 'Display_Name_Not_Fixed_use_Supplement';
|
|
1395
|
+
severity = 'information';
|
|
1396
|
+
} else if (hd.difference === DisplayDifference.Normalized) {
|
|
1393
1397
|
baseMsg = 'Display_Name_WS_for__should_be_one_of__instead_of';
|
|
1394
1398
|
} else {
|
|
1395
1399
|
baseMsg = 'Display_Name_for__should_be_one_of__instead_of';
|
|
1396
1400
|
}
|
|
1397
|
-
let mid = baseMsg;
|
|
1398
1401
|
let dc = list.displayCount(this.params.workingLanguages(), null, true);
|
|
1399
|
-
let
|
|
1400
|
-
if (
|
|
1401
|
-
|
|
1402
|
-
|
|
1402
|
+
let mid = baseMsg;
|
|
1403
|
+
if (severity !== 'information') {
|
|
1404
|
+
if (dc === 0) {
|
|
1405
|
+
severity = 'warning';
|
|
1406
|
+
dc = list.displayCount(this.params.workingLanguages(), null, false);
|
|
1407
|
+
}
|
|
1403
1408
|
}
|
|
1404
1409
|
|
|
1405
1410
|
let m, ds;
|
package/tx/workers/worker.js
CHANGED
|
@@ -276,12 +276,9 @@ class TerminologyWorker {
|
|
|
276
276
|
if (this.hasSupplement(cs, supplements)) {
|
|
277
277
|
continue;
|
|
278
278
|
}
|
|
279
|
-
// Handle exact URL match (no version specified in supplements)
|
|
279
|
+
// Handle exact URL match (no version specified in supplements field)
|
|
280
280
|
if (supplementsUrl === url) {
|
|
281
|
-
|
|
282
|
-
if (!version) {
|
|
283
|
-
supplements.push(cs);
|
|
284
|
-
}
|
|
281
|
+
supplements.push(cs);
|
|
285
282
|
continue;
|
|
286
283
|
}
|
|
287
284
|
|