fhirsmith 0.8.6 → 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 +27 -0
- package/library/regex-utilities.js +13 -0
- package/package.json +3 -2
- package/publisher/publisher.js +28 -7
- package/root-bare-template.html +9759 -37
- package/translations/Messages.properties +1 -0
- package/tx/cs/cs-country.js +2 -1
- package/tx/cs/cs-cs.js +4 -3
- package/tx/cs/cs-loinc.js +2 -1
- package/tx/cs/cs-snomed.js +1 -1
- package/tx/data/OperationDefinition-ValueSet-related.json +133 -0
- package/tx/importers/atc-to-fhir.js +27 -27
- 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/tx.fhir.org.yml +4 -4
- package/tx/vs/vs-database.js +150 -100
- package/tx/vs/vs-vsac.js +90 -31
- package/tx/workers/expand.js +5 -1
- package/tx/workers/metadata.js +3 -1
|
@@ -1511,3 +1511,4 @@ CONFORMANCE_STATEMENT_WORD = The html source contains the word ''{0}'' but it is
|
|
|
1511
1511
|
VALUESET_CODE_CONCEPT_HINT = {3}. Note that the display in the ValueSet does not have to match; this check exists to help check that it''s not accidentally the wrong code
|
|
1512
1512
|
VALUESET_CODE_CONCEPT_HINT_VER ={3}. Note that the display in the ValueSet does not have to match; this check exists to help check that it''s not accidentally the wrong code
|
|
1513
1513
|
TERMINOLOGY_TX_SYSTEM_UNSUPPORTED = The code cannot be checked because codeSystem ''{0}'' version ''{1}'' is not supported ({2})
|
|
1514
|
+
INVALID_REGEX = The regex ''{0}'' is not valid: {1}
|
package/tx/cs/cs-country.js
CHANGED
|
@@ -2,6 +2,7 @@ const { CodeSystemProvider, FilterExecutionContext } = require('../../tx/cs/cs-a
|
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { CodeSystem } = require("../library/codesystem");
|
|
4
4
|
const {CodeSystemFactoryProvider} = require("./cs-api");
|
|
5
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
5
6
|
|
|
6
7
|
class CountryCodeConcept {
|
|
7
8
|
constructor(userDefined, code, display, french) {
|
|
@@ -199,7 +200,7 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
199
200
|
|
|
200
201
|
try {
|
|
201
202
|
// Create regex with anchors to match the Pascal implementation (^value$)
|
|
202
|
-
const regex =
|
|
203
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
203
204
|
|
|
204
205
|
for (const concept of this.codes) {
|
|
205
206
|
if (regex.test(concept.code)) {
|
package/tx/cs/cs-cs.js
CHANGED
|
@@ -6,6 +6,7 @@ const { validateOptionalParameter, getValuePrimitive, validateArrayParameter} =
|
|
|
6
6
|
const {Issue} = require("../library/operation-outcome");
|
|
7
7
|
const {Extensions} = require("../library/extensions");
|
|
8
8
|
const {BaseCSServices} = require("./cs-base");
|
|
9
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Context class for FHIR CodeSystem provider concepts
|
|
@@ -1309,7 +1310,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1309
1310
|
else if (op === 'regex') {
|
|
1310
1311
|
// Regular expression match
|
|
1311
1312
|
try {
|
|
1312
|
-
const regex =
|
|
1313
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1313
1314
|
const allCodes = this.codeSystem.getAllCodes();
|
|
1314
1315
|
for (const code of allCodes) {
|
|
1315
1316
|
if (regex.test(code)) {
|
|
@@ -1320,7 +1321,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1320
1321
|
}
|
|
1321
1322
|
}
|
|
1322
1323
|
} catch (error) {
|
|
1323
|
-
throw new
|
|
1324
|
+
throw new Issue('error', 'exception', null, 'INVALID_REGEX', this.opContext.i18n.translate('INVALID_REGEX', this.opContext.langs, [value, error.message]), 'vs-invalid', 422);
|
|
1324
1325
|
}
|
|
1325
1326
|
}
|
|
1326
1327
|
|
|
@@ -1428,7 +1429,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1428
1429
|
}
|
|
1429
1430
|
else if (op === 'regex') {
|
|
1430
1431
|
try {
|
|
1431
|
-
const regex =
|
|
1432
|
+
const regex = regexUtilities.compile('^' + value + '$');
|
|
1432
1433
|
return properties.some(p => regex.test(this._getPropertyValue(p)));
|
|
1433
1434
|
} catch (error) {
|
|
1434
1435
|
return false;
|
package/tx/cs/cs-loinc.js
CHANGED
|
@@ -6,6 +6,7 @@ const { CodeSystemFactoryProvider} = require('./cs-api');
|
|
|
6
6
|
const { validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
|
|
7
7
|
const {BaseCSServices} = require("./cs-base");
|
|
8
8
|
const {sqlEscapeString} = require("../../xig/xig");
|
|
9
|
+
const regexUtilities = require('../../library/regex-utilities');
|
|
9
10
|
|
|
10
11
|
// Context kinds matching Pascal enum
|
|
11
12
|
const LoincProviderContextKind = {
|
|
@@ -938,7 +939,7 @@ class LoincServices extends BaseCSServices {
|
|
|
938
939
|
// Helper method for regex matching
|
|
939
940
|
async #findRegexMatches(sql, pattern, valueColumn, keyColumn = 'Key') {
|
|
940
941
|
return new Promise((resolve, reject) => {
|
|
941
|
-
const regex =
|
|
942
|
+
const regex = regexUtilities.compile(pattern);
|
|
942
943
|
const matchingKeys = [];
|
|
943
944
|
|
|
944
945
|
this.db.all(sql, (err, rows) => {
|
package/tx/cs/cs-snomed.js
CHANGED
|
@@ -1622,7 +1622,7 @@ class SnomedServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1622
1622
|
internalSource : this,
|
|
1623
1623
|
relationship: relationship,
|
|
1624
1624
|
id : id,
|
|
1625
|
-
url: `${this.system}?fhir_cm=${id}`,
|
|
1625
|
+
url: `${this.system()}?fhir_cm=${id}`,
|
|
1626
1626
|
version: this.version(),
|
|
1627
1627
|
name: `SNOMED CT ${name} Concept Map`,
|
|
1628
1628
|
description: `The concept map implicitly defined by the ${name} Association Reference Set`,
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
{
|
|
2
|
+
"resourceType": "OperationDefinition",
|
|
3
|
+
"id": "ValueSet-related",
|
|
4
|
+
"url": "http://hl7.org/fhir/OperationDefinition/ValueSet-related",
|
|
5
|
+
"version": "5.0.0",
|
|
6
|
+
"name": "ValueSetRelated",
|
|
7
|
+
"title": "Value Set Related Determination",
|
|
8
|
+
"status": "active",
|
|
9
|
+
"kind": "operation",
|
|
10
|
+
"experimental": false,
|
|
11
|
+
"date": "2023-03-26T15:21:02+11:00",
|
|
12
|
+
"publisher": "FHIRsmith",
|
|
13
|
+
"description": "Determine the relationship between two value sets. Different versions of code systems are considered compatible unless versionNeeded = true for the code system",
|
|
14
|
+
"jurisdiction": [
|
|
15
|
+
{
|
|
16
|
+
"coding": [
|
|
17
|
+
{
|
|
18
|
+
"system": "http://unstats.un.org/unsd/methods/m49/m49.htm",
|
|
19
|
+
"code": "001",
|
|
20
|
+
"display": "World"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
],
|
|
25
|
+
"affectsState": false,
|
|
26
|
+
"code": "related",
|
|
27
|
+
"comment": "An $expand will be performed internally if needed.",
|
|
28
|
+
"resource": [
|
|
29
|
+
"ValueSet"
|
|
30
|
+
],
|
|
31
|
+
"system": false,
|
|
32
|
+
"type": true,
|
|
33
|
+
"instance": false,
|
|
34
|
+
"parameter": [
|
|
35
|
+
{
|
|
36
|
+
"name": "thisUrl",
|
|
37
|
+
"use": "in",
|
|
38
|
+
"scope": [
|
|
39
|
+
"type"
|
|
40
|
+
],
|
|
41
|
+
"min": 0,
|
|
42
|
+
"max": "1",
|
|
43
|
+
"documentation": "Value set Canonical URL for the first value set of the pair (the base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
44
|
+
"type": "uri"
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"name": "otherUrl",
|
|
48
|
+
"use": "in",
|
|
49
|
+
"scope": [
|
|
50
|
+
"type"
|
|
51
|
+
],
|
|
52
|
+
"min": 0,
|
|
53
|
+
"max": "1",
|
|
54
|
+
"documentation": "Value set Canonical URL for the second value set of the pair (the one being compared to base). The server must know the value set (e.g. it is provided as an attached resource, it is defined explicitly in the server's value sets, or it is defined implicitly by some code system known to the server",
|
|
55
|
+
"type": "uri"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "thisValueSet",
|
|
59
|
+
"use": "in",
|
|
60
|
+
"scope": [
|
|
61
|
+
"type"
|
|
62
|
+
],
|
|
63
|
+
"min": 0,
|
|
64
|
+
"max": "1",
|
|
65
|
+
"documentation": "The first value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
66
|
+
"type": "ValueSet"
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"name": "otherValueSet",
|
|
70
|
+
"use": "in",
|
|
71
|
+
"scope": [
|
|
72
|
+
"type"
|
|
73
|
+
],
|
|
74
|
+
"min": 0,
|
|
75
|
+
"max": "1",
|
|
76
|
+
"documentation": "The other value set is provided directly as part of the request. Servers may choose not to accept value sets in this fashion. This parameter is used when the client wants the server to expand a value set that is not stored on the server",
|
|
77
|
+
"type": "ValueSet"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "thisVersion",
|
|
81
|
+
"use": "in",
|
|
82
|
+
"scope": [
|
|
83
|
+
"type"
|
|
84
|
+
],
|
|
85
|
+
"min": 0,
|
|
86
|
+
"max": "1",
|
|
87
|
+
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"name": "otherVersion",
|
|
92
|
+
"use": "in",
|
|
93
|
+
"scope": [
|
|
94
|
+
"type"
|
|
95
|
+
],
|
|
96
|
+
"min": 0,
|
|
97
|
+
"max": "1",
|
|
98
|
+
"documentation": "The identifier that is used to identify a specific version of the value set to be used when validating the code. This is an arbitrary value managed by the value set author and is not expected to be globally unique. For example, it might be a timestamp (e.g. yyyymmdd) if a managed version is not available.",
|
|
99
|
+
"type": "string"
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"name": "useSupplement",
|
|
103
|
+
"use": "in",
|
|
104
|
+
"min": 0,
|
|
105
|
+
"max": "*",
|
|
106
|
+
"documentation": "The supplement must be used when validating the code. Use of this parameter should result in $validate-code behaving the same way as if the supplements were included in the value set definition using the [http://hl7.org/fhir/StructureDefinition/valueset-supplement](http://hl7.org/fhir/extensions/StructureDefinition-valueset-supplement.html)",
|
|
107
|
+
"type": "canonical"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"name": "diagnostics",
|
|
111
|
+
"use": "in",
|
|
112
|
+
"min": 0,
|
|
113
|
+
"max": "1",
|
|
114
|
+
"documentation": "Whether to return information about the reasoning process"
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
"name": "result",
|
|
118
|
+
"use": "out",
|
|
119
|
+
"min": 1,
|
|
120
|
+
"max": "1",
|
|
121
|
+
"documentation": "The relationship between the ValueSets. One of: same, superset, subset, overlapping, dsjoint, empty, and indeterminate",
|
|
122
|
+
"type": "boolean"
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
"name": "message",
|
|
126
|
+
"use": "out",
|
|
127
|
+
"min": 0,
|
|
128
|
+
"max": "1",
|
|
129
|
+
"documentation": "Explanation of the code, with reason if appropriate",
|
|
130
|
+
"type": "string"
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
@@ -6,7 +6,7 @@ const ATC_FILE = process.argv[2] || '2025_ATC.xml';
|
|
|
6
6
|
const DDD_FILE = process.argv[3] || '2025_ATC_ddd.xml';
|
|
7
7
|
const OUTPUT_FILE = process.argv[4] || 'atc-codesystem.json';
|
|
8
8
|
|
|
9
|
-
const PROPERTY_GROUP_EXT_URL = 'http://hl7.org/fhir/property
|
|
9
|
+
const PROPERTY_GROUP_EXT_URL = 'http://hl7.org/fhir/StructureDefinition/Codesystem-property-group';
|
|
10
10
|
|
|
11
11
|
// Parse XML files
|
|
12
12
|
function parseXML(filePath) {
|
|
@@ -277,32 +277,7 @@ try {
|
|
|
277
277
|
console.log(`Writing to ${OUTPUT_FILE}...`);
|
|
278
278
|
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(codeSystem, null, 2));
|
|
279
279
|
console.log('Done!');
|
|
280
|
-
|
|
281
|
-
// Print some stats
|
|
282
|
-
function countConcepts(concepts) {
|
|
283
|
-
let count = 0;
|
|
284
|
-
for (const c of concepts) {
|
|
285
|
-
count++;
|
|
286
|
-
if (c.concept) {
|
|
287
|
-
count += countConcepts(c.concept);
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
return count;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function countWithDDD(concepts) {
|
|
294
|
-
let count = 0;
|
|
295
|
-
for (const c of concepts) {
|
|
296
|
-
if (c.property?.some(p => p.code === 'dddValue')) {
|
|
297
|
-
count++;
|
|
298
|
-
}
|
|
299
|
-
if (c.concept) {
|
|
300
|
-
count += countWithDDD(c.concept);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
return count;
|
|
304
|
-
}
|
|
305
|
-
|
|
280
|
+
|
|
306
281
|
const totalConcepts = countConcepts(codeSystem.concept);
|
|
307
282
|
const withDDD = countWithDDD(codeSystem.concept);
|
|
308
283
|
console.log(`\nStatistics:`);
|
|
@@ -314,3 +289,28 @@ try {
|
|
|
314
289
|
console.error('Error:', error.message);
|
|
315
290
|
process.exit(1);
|
|
316
291
|
}
|
|
292
|
+
|
|
293
|
+
// Print some stats
|
|
294
|
+
function countConcepts(concepts) {
|
|
295
|
+
let count = 0;
|
|
296
|
+
for (const c of concepts) {
|
|
297
|
+
count++;
|
|
298
|
+
if (c.concept) {
|
|
299
|
+
count += countConcepts(c.concept);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return count;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function countWithDDD(concepts) {
|
|
306
|
+
let count = 0;
|
|
307
|
+
for (const c of concepts) {
|
|
308
|
+
if (c.property?.some(p => p.code === 'dddValue')) {
|
|
309
|
+
count++;
|
|
310
|
+
}
|
|
311
|
+
if (c.concept) {
|
|
312
|
+
count += countWithDDD(c.concept);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return count;
|
|
316
|
+
}
|
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
BaseUnit, DefinedUnit, Prefix, Value, Term, Symbol, Factor, Canonical, CanonicalUnit,
|
|
11
11
|
Registry
|
|
12
12
|
} = require('./ucum-types.js');
|
|
13
|
+
const regexUtilities = require("../../library/regex-utilities");
|
|
13
14
|
|
|
14
15
|
// Lexer for tokenizing UCUM expressions (port of Lexer.java)
|
|
15
16
|
class Lexer {
|
|
@@ -763,7 +764,7 @@ class Search {
|
|
|
763
764
|
|
|
764
765
|
if (isRegex) {
|
|
765
766
|
try {
|
|
766
|
-
const regex =
|
|
767
|
+
const regex = regexUtilities.compile(text);
|
|
767
768
|
return regex.test(value);
|
|
768
769
|
} catch (e) {
|
|
769
770
|
this.log.error(e);
|
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/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
|