fhirsmith 0.8.5 → 0.9.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 +51 -0
- package/README.md +52 -22
- package/extension-tracker/extension-tracker-template.html +3 -1
- package/library/html-server.js +7 -0
- package/library/logger.js +234 -194
- package/library/regex-utilities.js +13 -0
- package/package.json +4 -2
- package/packages/packages-template.html +3 -1
- package/publisher/publisher-template.html +1 -0
- package/publisher/publisher.js +28 -7
- package/registry/registry-template.html +3 -1
- package/root-bare-template.html +9759 -37
- package/root-template.html +3 -2
- package/server.js +48 -12
- package/translations/Messages.properties +2 -1
- package/translations/rendering-phrases.properties +3 -1
- package/tx/cs/cs-api.js +4 -0
- package/tx/cs/cs-country.js +2 -1
- package/tx/cs/cs-cs.js +9 -4
- package/tx/cs/cs-loinc.js +2 -1
- package/tx/cs/cs-snomed.js +5 -1
- package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
- package/tx/html/tx-template.html +3 -2
- package/tx/importers/atc-to-fhir.js +27 -27
- package/tx/library/codesystem.js +4 -0
- package/tx/library/renderer.js +20 -4
- package/tx/library/ucum-parsers.js +2 -1
- package/tx/ocl/cs-ocl.cjs +48 -15
- package/tx/ocl/vs-ocl.cjs +57 -34
- package/tx/operation-context.js +74 -19
- package/tx/tx-html.js +5 -5
- package/tx/tx.fhir.org.yml +4 -4
- package/tx/tx.js +1 -0
- package/tx/vs/vs-database.js +150 -100
- package/tx/vs/vs-vsac.js +90 -31
- package/tx/workers/expand.js +154 -113
- package/tx/workers/metadata.js +6 -3
- package/tx/workers/read.js +6 -3
- package/tx/workers/related.js +228 -87
- package/xig/xig-template.html +3 -1
- package/library/logger-telnet.js +0 -205
package/tx/ocl/cs-ocl.cjs
CHANGED
|
@@ -13,6 +13,7 @@ const { OCLBackgroundJobQueue } = require('./jobs/background-queue');
|
|
|
13
13
|
const { OCLConceptFilterContext } = require('./model/concept-filter-context');
|
|
14
14
|
const { toConceptContext } = require('./mappers/concept-mapper');
|
|
15
15
|
const { patchSearchWorkerForOCLCodeFiltering } = require('./shared/patches');
|
|
16
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
16
17
|
|
|
17
18
|
patchSearchWorkerForOCLCodeFiltering();
|
|
18
19
|
|
|
@@ -735,6 +736,17 @@ class OCLSourceCodeSystemProvider extends CodeSystemProvider {
|
|
|
735
736
|
return { context: this.conceptCache.get(code), message: null };
|
|
736
737
|
}
|
|
737
738
|
|
|
739
|
+
// OCL concept IDs may differ in case from the FHIR code (e.g. "y" vs "Y").
|
|
740
|
+
// Try a case-insensitive cache lookup before hitting the network.
|
|
741
|
+
const codeLower = code.toLowerCase();
|
|
742
|
+
for (const [key, value] of this.conceptCache.entries()) {
|
|
743
|
+
if (key.toLowerCase() === codeLower) {
|
|
744
|
+
// Cache under the requested case as well so future lookups are O(1).
|
|
745
|
+
this.conceptCache.set(code, value);
|
|
746
|
+
return { context: value, message: null };
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
|
|
738
750
|
if (this.scheduleBackgroundLoad) {
|
|
739
751
|
this.scheduleBackgroundLoad('lookup-miss');
|
|
740
752
|
}
|
|
@@ -1032,23 +1044,26 @@ class OCLSourceCodeSystemProvider extends CodeSystemProvider {
|
|
|
1032
1044
|
this.scheduleBackgroundLoad('concept-miss');
|
|
1033
1045
|
}
|
|
1034
1046
|
|
|
1035
|
-
const url = this.#buildConceptUrl(code);
|
|
1036
1047
|
const pending = (async () => {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
} catch (error) {
|
|
1041
|
-
// Missing concept should be treated as not-found, not as an internal server failure.
|
|
1042
|
-
if (error && error.response && error.response.status === 404) {
|
|
1043
|
-
return null;
|
|
1044
|
-
}
|
|
1045
|
-
throw error;
|
|
1048
|
+
const concept = await this.#fetchConceptByCode(code);
|
|
1049
|
+
if (concept) {
|
|
1050
|
+
return concept;
|
|
1046
1051
|
}
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1052
|
+
// OCL concept IDs may differ in case from the FHIR code (e.g. "y" vs "Y").
|
|
1053
|
+
// Try common case alternatives before giving up.
|
|
1054
|
+
const lower = code.toLowerCase();
|
|
1055
|
+
const upper = code.toUpperCase();
|
|
1056
|
+
for (const alt of [lower, upper]) {
|
|
1057
|
+
if (alt !== code) {
|
|
1058
|
+
const altConcept = await this.#fetchConceptByCode(alt);
|
|
1059
|
+
if (altConcept) {
|
|
1060
|
+
// Cache under the originally requested code so future lookups hit directly.
|
|
1061
|
+
this.conceptCache.set(code, altConcept);
|
|
1062
|
+
return altConcept;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1050
1065
|
}
|
|
1051
|
-
return
|
|
1066
|
+
return null;
|
|
1052
1067
|
})();
|
|
1053
1068
|
|
|
1054
1069
|
this.pendingConceptRequests.set(pendingKey, pending);
|
|
@@ -1059,6 +1074,24 @@ class OCLSourceCodeSystemProvider extends CodeSystemProvider {
|
|
|
1059
1074
|
}
|
|
1060
1075
|
}
|
|
1061
1076
|
|
|
1077
|
+
async #fetchConceptByCode(code) {
|
|
1078
|
+
const url = this.#buildConceptUrl(code);
|
|
1079
|
+
let response;
|
|
1080
|
+
try {
|
|
1081
|
+
response = await this.httpClient.get(url, { params: { verbose: true } });
|
|
1082
|
+
} catch (error) {
|
|
1083
|
+
if (error && error.response && error.response.status === 404) {
|
|
1084
|
+
return null;
|
|
1085
|
+
}
|
|
1086
|
+
throw error;
|
|
1087
|
+
}
|
|
1088
|
+
const concept = this.#toConceptContext(response.data);
|
|
1089
|
+
if (concept && concept.code) {
|
|
1090
|
+
this.conceptCache.set(concept.code, concept);
|
|
1091
|
+
}
|
|
1092
|
+
return concept;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1062
1095
|
async #allConceptContexts() {
|
|
1063
1096
|
const concepts = new Map();
|
|
1064
1097
|
|
|
@@ -1135,7 +1168,7 @@ class OCLSourceCodeSystemProvider extends CodeSystemProvider {
|
|
|
1135
1168
|
|
|
1136
1169
|
#buildPropertyMatcher(prop, op, value) {
|
|
1137
1170
|
if (op === 'regex') {
|
|
1138
|
-
const regex =
|
|
1171
|
+
const regex = regexUtilities.compile(String(value), 'i');
|
|
1139
1172
|
return concept => {
|
|
1140
1173
|
const candidate = this.#valueForFilter(concept, prop);
|
|
1141
1174
|
if (candidate == null) {
|
package/tx/ocl/vs-ocl.cjs
CHANGED
|
@@ -389,20 +389,26 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
389
389
|
}
|
|
390
390
|
|
|
391
391
|
#indexValueSet(vs) {
|
|
392
|
-
const existing = this.valueSetMap.get(vs.url)
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
//
|
|
398
|
-
if (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
392
|
+
const existing = this.valueSetMap.get(vs.url) || null;
|
|
393
|
+
|
|
394
|
+
// When fresh discovery metadata replaces a cold-cached entry, carry over
|
|
395
|
+
// the enumerated compose so the expand engine doesn't fall back to
|
|
396
|
+
// "include whole CodeSystem". The background expansion will eventually
|
|
397
|
+
// refresh it with up-to-date collection contents.
|
|
398
|
+
if (existing && existing !== vs
|
|
399
|
+
&& Array.isArray(existing.jsonObj?.compose?.include)
|
|
400
|
+
&& existing.jsonObj.compose.include.some(inc => Array.isArray(inc.concept) && inc.concept.length > 0)
|
|
401
|
+
&& (!vs.jsonObj.compose || !Array.isArray(vs.jsonObj.compose.include) || vs.jsonObj.compose.include.length === 0)
|
|
402
|
+
) {
|
|
403
|
+
vs.jsonObj.compose = existing.jsonObj.compose;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.valueSetMap.set(vs.url, vs);
|
|
407
|
+
if (vs.version) {
|
|
408
|
+
this.valueSetMap.set(`${vs.url}|${vs.version}`, vs);
|
|
405
409
|
}
|
|
410
|
+
this.valueSetMap.set(vs.id, vs);
|
|
411
|
+
this._idMap.set(vs.id, vs);
|
|
406
412
|
}
|
|
407
413
|
|
|
408
414
|
#toValueSet(collection) {
|
|
@@ -568,6 +574,17 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
568
574
|
? vs.jsonObj.compose.include
|
|
569
575
|
: [];
|
|
570
576
|
|
|
577
|
+
// If the compose already has enumerated concepts (from background expansion),
|
|
578
|
+
// it is the authoritative representation of the collection contents — don't
|
|
579
|
+
// overwrite it with system-only entries that would cause the expand engine
|
|
580
|
+
// to include ALL concepts from the CodeSystem.
|
|
581
|
+
const hasEnumeratedConcepts = existingInclude.some(
|
|
582
|
+
inc => Array.isArray(inc.concept) && inc.concept.length > 0
|
|
583
|
+
);
|
|
584
|
+
if (hasEnumeratedConcepts) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
571
588
|
// Always normalize existing compose entries first because discovery metadata
|
|
572
589
|
// can carry non-canonical preferred_source values.
|
|
573
590
|
const include = this.#normalizeComposeIncludes(existingInclude);
|
|
@@ -972,9 +989,12 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
972
989
|
return;
|
|
973
990
|
}
|
|
974
991
|
|
|
975
|
-
|
|
992
|
+
let cached = this.backgroundExpansionCache.get(cacheKey);
|
|
993
|
+
let invalidated = false;
|
|
976
994
|
if (cached && !this.#isCachedExpansionValid(vs, cached)) {
|
|
977
995
|
this.backgroundExpansionCache.delete(cacheKey);
|
|
996
|
+
cached = null;
|
|
997
|
+
invalidated = true;
|
|
978
998
|
}
|
|
979
999
|
|
|
980
1000
|
// Already have a cached compose ready
|
|
@@ -982,23 +1002,22 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
982
1002
|
return;
|
|
983
1003
|
}
|
|
984
1004
|
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
const
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
return;
|
|
1005
|
+
// Skip freshness check when cache was just invalidated (VS metadata changed
|
|
1006
|
+
// on the server) — the cold cache file is stale even if recently written.
|
|
1007
|
+
if (!invalidated) {
|
|
1008
|
+
const cacheFilePath = getCacheFilePath(CACHE_VS_DIR, vs.url, vs.version || null, paramsKey);
|
|
1009
|
+
const cacheAgeFromFileMs = getColdCacheAgeMs(cacheFilePath);
|
|
1010
|
+
const persistedCache = this.backgroundExpansionCache.get(cacheKey);
|
|
1011
|
+
const cacheAgeFromMetadataMs = Number.isFinite(persistedCache?.createdAt)
|
|
1012
|
+
? Math.max(0, Date.now() - persistedCache.createdAt)
|
|
1013
|
+
: null;
|
|
1014
|
+
|
|
1015
|
+
// Treat cache as fresh when either file mtime or persisted timestamp is recent.
|
|
1016
|
+
const freshnessCandidates = [cacheAgeFromFileMs, cacheAgeFromMetadataMs].filter(age => age != null);
|
|
1017
|
+
const freshestCacheAgeMs = freshnessCandidates.length > 0 ? Math.min(...freshnessCandidates) : null;
|
|
1018
|
+
if (freshestCacheAgeMs != null && freshestCacheAgeMs <= COLD_CACHE_FRESHNESS_MS) {
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1002
1021
|
}
|
|
1003
1022
|
|
|
1004
1023
|
const jobKey = `vs:${cacheKey}`;
|
|
@@ -1121,7 +1140,11 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
1121
1140
|
if (!systemConcepts.has(entry.system)) {
|
|
1122
1141
|
systemConcepts.set(entry.system, []);
|
|
1123
1142
|
}
|
|
1124
|
-
|
|
1143
|
+
const concept = { code: entry.code };
|
|
1144
|
+
if (Array.isArray(entry.designation) && entry.designation.length > 0) {
|
|
1145
|
+
concept.designation = entry.designation;
|
|
1146
|
+
}
|
|
1147
|
+
systemConcepts.get(entry.system).push(concept);
|
|
1125
1148
|
totalCount++;
|
|
1126
1149
|
}
|
|
1127
1150
|
if (progressState) {
|
|
@@ -1138,9 +1161,9 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
|
|
|
1138
1161
|
}
|
|
1139
1162
|
|
|
1140
1163
|
return {
|
|
1141
|
-
include: Array.from(systemConcepts.entries()).map(([system,
|
|
1164
|
+
include: Array.from(systemConcepts.entries()).map(([system, concepts]) => ({
|
|
1142
1165
|
system,
|
|
1143
|
-
concept:
|
|
1166
|
+
concept: concepts
|
|
1144
1167
|
}))
|
|
1145
1168
|
};
|
|
1146
1169
|
}
|
package/tx/operation-context.js
CHANGED
|
@@ -15,7 +15,7 @@ function isDebugging() {
|
|
|
15
15
|
}
|
|
16
16
|
// Also check for debug flags in case inspector not yet attached
|
|
17
17
|
return process.execArgv.some(arg =>
|
|
18
|
-
|
|
18
|
+
arg.includes('--inspect') || arg.includes('--debug')
|
|
19
19
|
);
|
|
20
20
|
}
|
|
21
21
|
|
|
@@ -233,16 +233,16 @@ class ExpansionCache {
|
|
|
233
233
|
// Resources are now CodeSystem/ValueSet wrappers, not raw JSON
|
|
234
234
|
if (additionalResources && additionalResources.length > 0) {
|
|
235
235
|
const resourceHashes = additionalResources
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
236
|
+
.map(r => {
|
|
237
|
+
// Get the JSON object from wrapper or use directly
|
|
238
|
+
const json = r.jsonObj || r;
|
|
239
|
+
// Create a content hash for this resource
|
|
240
|
+
return crypto.createHash('sha256')
|
|
241
|
+
.update(JSON.stringify(json))
|
|
242
|
+
.digest('hex')
|
|
243
|
+
.substring(0, 16); // Use first 16 chars for brevity
|
|
244
|
+
})
|
|
245
|
+
.sort();
|
|
246
246
|
keyParts.push(`additional:${resourceHashes.join(',')}`);
|
|
247
247
|
}
|
|
248
248
|
|
|
@@ -314,7 +314,7 @@ class ExpansionCache {
|
|
|
314
314
|
|
|
315
315
|
// Get entries sorted by lastUsed (oldest first)
|
|
316
316
|
const entries = Array.from(this.cache.entries())
|
|
317
|
-
|
|
317
|
+
.sort((a, b) => a[1].lastUsed - b[1].lastUsed);
|
|
318
318
|
|
|
319
319
|
const toEvict = Math.min(count, entries.length);
|
|
320
320
|
for (let i = 0; i < toEvict; i++) {
|
|
@@ -413,7 +413,29 @@ class ExpansionCache {
|
|
|
413
413
|
}
|
|
414
414
|
|
|
415
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Read the cgroup memory limit once at startup.
|
|
418
|
+
* Returns the byte limit, or 0 if unavailable (disables the check).
|
|
419
|
+
*/
|
|
420
|
+
function readMemoryLimit() {
|
|
421
|
+
try {
|
|
422
|
+
const raw = require('fs').readFileSync('/sys/fs/cgroup/memory.max', 'utf8').trim();
|
|
423
|
+
if (raw === 'max') return 0; // no cgroup limit
|
|
424
|
+
return parseInt(raw);
|
|
425
|
+
} catch {
|
|
426
|
+
return 0; // not on Linux / no cgroup
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const MEMORY_LIMIT = readMemoryLimit();
|
|
431
|
+
const MEMORY_FRACTION = 0.98;
|
|
432
|
+
const MEMORY_THRESHOLD = MEMORY_LIMIT > 0 ? MEMORY_LIMIT * MEMORY_FRACTION : 0; // 90% of cgroup limit
|
|
433
|
+
const CHECK_FREQUENCY = 100;
|
|
434
|
+
|
|
416
435
|
class OperationContext {
|
|
436
|
+
// Shared counter across all instances — only check RSS every CHECK_FREQUENCY calls
|
|
437
|
+
static _checkCounter = 0;
|
|
438
|
+
|
|
417
439
|
constructor(langs, i18n = null, id = null, timeLimit = 30, resourceCache = null, expansionCache = null) {
|
|
418
440
|
this.i18n = i18n;
|
|
419
441
|
this.langs = this._ensureLanguages(langs);
|
|
@@ -445,8 +467,8 @@ class OperationContext {
|
|
|
445
467
|
*/
|
|
446
468
|
copy() {
|
|
447
469
|
const newContext = new OperationContext(
|
|
448
|
-
|
|
449
|
-
|
|
470
|
+
this.langs, this.i18n, this.id, this.timeLimit / 1000,
|
|
471
|
+
this.resourceCache, this.expansionCache
|
|
450
472
|
);
|
|
451
473
|
newContext.contexts = [...this.contexts];
|
|
452
474
|
newContext.startTime = this.startTime;
|
|
@@ -458,31 +480,64 @@ class OperationContext {
|
|
|
458
480
|
}
|
|
459
481
|
|
|
460
482
|
/**
|
|
461
|
-
* Check if operation has exceeded time limit
|
|
483
|
+
* Check if operation has exceeded time limit, or is pushing is over the memory limit
|
|
462
484
|
* Skipped when running under debugger
|
|
485
|
+
*
|
|
486
|
+
* note: if the server pushes over the memory limit for the process, the process is terminated.
|
|
487
|
+
* the memory check here is intended to prevent process termination on the grounds that some
|
|
488
|
+
* big operation is pushing the limit. It might not be the big operation that is terminated first,
|
|
489
|
+
* but eventually it'll get terminated.
|
|
490
|
+
*
|
|
491
|
+
* this is called a *lot* so it's important to be efficient. Only check every CHECK_FREQUENCY
|
|
492
|
+
* times means that there could be a small overrun, but it's called often enough that the
|
|
493
|
+
* overrun won't be that signiifcant
|
|
494
|
+
*
|
|
463
495
|
* @param {string} place - Location identifier for debugging
|
|
464
496
|
* @returns {boolean} true if operation should be terminated
|
|
465
497
|
*/
|
|
466
498
|
deadCheck(place = 'unknown') {
|
|
467
|
-
// Skip time limit checks when debugging
|
|
468
499
|
if (this.debugging) {
|
|
469
500
|
return false;
|
|
470
501
|
}
|
|
471
502
|
|
|
472
|
-
|
|
503
|
+
OperationContext._checkCounter++;
|
|
504
|
+
if (OperationContext._checkCounter < CHECK_FREQUENCY) {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
OperationContext._checkCounter = 0;
|
|
473
508
|
|
|
509
|
+
// Time check
|
|
510
|
+
const elapsed = performance.now() - this.startTime;
|
|
474
511
|
if (elapsed > this.timeLimit) {
|
|
475
512
|
const timeInSeconds = Math.round(this.timeLimit / 1000);
|
|
476
513
|
this.log(`Operation took too long @ ${place} (${this.constructor.name})`);
|
|
477
|
-
|
|
478
|
-
|
|
514
|
+
const error = new Issue("error", "too-costly", null,
|
|
515
|
+
`Operation exceeded time limit of ${timeInSeconds} seconds at ${place}`);
|
|
479
516
|
error.diagnostics = this.diagnostics();
|
|
480
517
|
throw error;
|
|
481
518
|
}
|
|
482
519
|
|
|
520
|
+
// Memory check (piggyback on same sample)
|
|
521
|
+
if (MEMORY_THRESHOLD > 0) {
|
|
522
|
+
const rss = process.memoryUsage.rss();
|
|
523
|
+
if (rss > MEMORY_THRESHOLD) {
|
|
524
|
+
const usedGB = (rss / 1024 / 1024 / 1024).toFixed(1);
|
|
525
|
+
const limitGB = (MEMORY_LIMIT / 1024 / 1024 / 1024).toFixed(1);
|
|
526
|
+
this.log(`Memory Limit: ${usedGB} GB of ${limitGB} GB limit @ ${place}`);
|
|
527
|
+
const error = new Issue("error", "too-costly", null,
|
|
528
|
+
`Operation aborted: server memory usage (${usedGB} GB) exceeds safe threshold (${MEMORY_FRACTION * 100}% of ${limitGB} GB limit) at ${place}`);
|
|
529
|
+
error.diagnostics = this.diagnostics();
|
|
530
|
+
throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
483
534
|
return false;
|
|
484
535
|
}
|
|
485
536
|
|
|
537
|
+
unSeeAll() {
|
|
538
|
+
this.contexts = [];
|
|
539
|
+
}
|
|
540
|
+
|
|
486
541
|
/**
|
|
487
542
|
* Track a context URL and detect circular references
|
|
488
543
|
* @param {string} vurl - Value set URL to track
|
package/tx/tx-html.js
CHANGED
|
@@ -319,7 +319,7 @@ class TxHtmlRenderer {
|
|
|
319
319
|
case 'Parameters':
|
|
320
320
|
return await this.renderParameters(json);
|
|
321
321
|
case 'CodeSystem':
|
|
322
|
-
return await this.renderCodeSystem(json, inBundle, _fmt, op);
|
|
322
|
+
return await this.renderCodeSystem(json, inBundle, _fmt, op, req.sourcePackage);
|
|
323
323
|
case 'ValueSet': {
|
|
324
324
|
let exp = undefined;
|
|
325
325
|
if (!inBundle && !op && (!_fmt || _fmt == 'html')) {
|
|
@@ -330,10 +330,10 @@ class TxHtmlRenderer {
|
|
|
330
330
|
exp = error;
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
|
-
return await this.renderValueSet(json, inBundle, _fmt, op, exp);
|
|
333
|
+
return await this.renderValueSet(json, inBundle, _fmt, op, exp, req.sourcePackage);
|
|
334
334
|
}
|
|
335
335
|
case 'ConceptMap':
|
|
336
|
-
return await this.renderConceptMap(json, inBundle, _fmt, op);
|
|
336
|
+
return await this.renderConceptMap(json, inBundle, _fmt, op, req.sourcePackage);
|
|
337
337
|
case 'CapabilityStatement':
|
|
338
338
|
return await this.renderCapabilityStatement(json, inBundle);
|
|
339
339
|
case 'TerminologyCapabilities':
|
|
@@ -632,7 +632,7 @@ class TxHtmlRenderer {
|
|
|
632
632
|
/**
|
|
633
633
|
* Render CodeSystem resource
|
|
634
634
|
*/
|
|
635
|
-
async renderCodeSystem(json, inBundle, _fmt) {
|
|
635
|
+
async renderCodeSystem(json, inBundle, _fmt, op, sourcePackage) {
|
|
636
636
|
if (inBundle) {
|
|
637
637
|
return await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json));
|
|
638
638
|
} else {
|
|
@@ -645,7 +645,7 @@ class TxHtmlRenderer {
|
|
|
645
645
|
html += `</ul>`;
|
|
646
646
|
|
|
647
647
|
if (!_fmt || _fmt == 'html') {
|
|
648
|
-
html += await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json));
|
|
648
|
+
html += await this.renderResourceWithNarrative(json, await this.renderer.renderCodeSystem(json, sourcePackage));
|
|
649
649
|
} else if (_fmt == "html/json") {
|
|
650
650
|
html += await this.renderResourceJson(json);
|
|
651
651
|
} else if (_fmt == "html/xml") {
|
package/tx/tx.fhir.org.yml
CHANGED
|
@@ -18,11 +18,11 @@ sources:
|
|
|
18
18
|
- unii:unii_20240622.db
|
|
19
19
|
- snomed:sct_intl_20240201.cache
|
|
20
20
|
- snomed!:sct_intl_20250201.cache
|
|
21
|
-
- snomed:sct_se_20231130.cache
|
|
22
|
-
- snomed:sct_au_20230731.cache
|
|
23
|
-
- snomed:sct_be_20231115.cache
|
|
21
|
+
# - snomed:sct_se_20231130.cache
|
|
22
|
+
# - snomed:sct_au_20230731.cache
|
|
23
|
+
# - snomed:sct_be_20231115.cache
|
|
24
24
|
- snomed:sct_ch_20230607.cache
|
|
25
|
-
- snomed:sct_dk_20250930.cache
|
|
25
|
+
# - snomed:sct_dk_20250930.cache
|
|
26
26
|
- snomed:sct_ips_20241216.cache
|
|
27
27
|
- snomed:sct_nl_20240930.cache
|
|
28
28
|
- snomed:sct_uk_20230412.cache
|
package/tx/tx.js
CHANGED
|
@@ -173,6 +173,7 @@ class TXModule {
|
|
|
173
173
|
this.metadataHandler = new MetadataHandler({
|
|
174
174
|
baseUrl: config.baseUrl,
|
|
175
175
|
serverVersion: packageJson.version,
|
|
176
|
+
txVersion: packageJson.txVersion,
|
|
176
177
|
softwareName: config.softwareName || 'FHIRsmith',
|
|
177
178
|
name: config.name || 'FHIRTerminologyServer',
|
|
178
179
|
title: config.title || 'FHIR Terminology Server',
|