fhirsmith 0.7.5 → 0.8.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -0
  3. package/library/html.js +4 -0
  4. package/library/languages.js +10 -0
  5. package/package.json +1 -1
  6. package/packages/package-crawler.js +106 -51
  7. package/packages/packages.js +14 -0
  8. package/publisher/publisher.js +118 -28
  9. package/registry/registry.js +99 -91
  10. package/root-bare-template.html +92 -0
  11. package/security.md +32 -0
  12. package/server.js +99 -22
  13. package/stats.js +43 -10
  14. package/tx/README.md +6 -6
  15. package/tx/cs/cs-api.js +3 -0
  16. package/tx/cs/cs-api.md +285 -0
  17. package/tx/cs/cs-loinc.js +14 -2
  18. package/tx/cs/cs-rxnorm.js +14 -10
  19. package/tx/cs/cs-snomed.js +166 -5
  20. package/tx/html/dash-metrics.liquid +147 -0
  21. package/tx/importers/import-rxnorm.module.js +4 -30
  22. package/tx/importers/readme.md +3 -1
  23. package/tx/library/canonical-resource.js +8 -0
  24. package/tx/library/conceptmap.js +3 -1
  25. package/tx/library/designations.js +4 -8
  26. package/tx/library/renderer.js +9 -9
  27. package/tx/library.js +10 -4
  28. package/tx/ocl/cm-ocl.cjs +185 -65
  29. package/tx/ocl/cs-ocl.cjs +69 -50
  30. package/tx/ocl/jobs/background-queue.cjs +0 -8
  31. package/tx/ocl/mappers/concept-mapper.cjs +13 -3
  32. package/tx/ocl/shared/patches.cjs +1 -0
  33. package/tx/ocl/vs-ocl.cjs +137 -157
  34. package/tx/operation-context.js +3 -3
  35. package/tx/provider.js +4 -3
  36. package/tx/sct/structures.js +5 -0
  37. package/tx/tx-html.js +36 -9
  38. package/tx/tx.fhir.org.yml +1 -1
  39. package/tx/tx.js +34 -11
  40. package/tx/vs/vs-database.js +127 -6
  41. package/tx/vs/vs-vsac.js +98 -3
  42. package/tx/workers/search.js +2 -1
  43. package/tx/workers/translate.js +39 -14
  44. package/tx/workers/validate.js +3 -3
  45. package/utilities/dashboard.html +274 -0
  46. package/xig/xig.js +171 -9
package/tx/ocl/vs-ocl.cjs CHANGED
@@ -14,6 +14,7 @@ const { ensureCacheDirectories, getColdCacheAgeMs, formatCacheAgeMinutes } = req
14
14
  const { computeValueSetExpansionFingerprint } = require('./fingerprint/fingerprint');
15
15
  const { ensureTxParametersHashIncludesFilter, patchValueSetExpandWholeSystemForOcl } = require('./shared/patches');
16
16
 
17
+
17
18
  ensureTxParametersHashIncludesFilter(TxParameters);
18
19
  //patchValueSetExpandWholeSystemForOcl();
19
20
 
@@ -31,6 +32,7 @@ function normalizeCanonicalSystem(system) {
31
32
  }
32
33
 
33
34
  class OCLValueSetProvider extends AbstractValueSetProvider {
35
+
34
36
  constructor(config = {}) {
35
37
  super();
36
38
  const options = typeof config === 'string' ? { baseUrl: config } : (config || {});
@@ -51,6 +53,9 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
51
53
  this.pendingSourceCanonicalRequests = new Map();
52
54
  this.collectionByCanonicalCache = new Map();
53
55
  this.pendingCollectionByCanonicalRequests = new Map();
56
+ this._organizationIdsCache = null;
57
+ this._organizationIdsFetchPromise = null;
58
+ this._negativeUrlCache = new Set();
54
59
  this._composePromises = new Map();
55
60
  this.backgroundExpansionCache = new Map();
56
61
  this.backgroundExpansionProgress = new Map();
@@ -77,7 +82,13 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
77
82
  const data = await fs.readFile(filePath, 'utf-8');
78
83
  const cached = JSON.parse(data);
79
84
 
80
- if (!cached || !cached.canonicalUrl || !cached.expansion) {
85
+ if (!cached || !cached.canonicalUrl) {
86
+ continue;
87
+ }
88
+
89
+ // Support both old (expansion-based) and new (compose-based) cache formats
90
+ const compose = cached.compose || this.#composeFromExpansion(cached.expansion);
91
+ if (!compose || !Array.isArray(compose.include) || compose.include.length === 0) {
81
92
  continue;
82
93
  }
83
94
 
@@ -88,37 +99,19 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
88
99
  );
89
100
  const createdAt = cached.timestamp ? new Date(cached.timestamp).getTime() : null;
90
101
  this.backgroundExpansionCache.set(cacheKey, {
91
- expansion: cached.expansion,
102
+ compose,
92
103
  metadataSignature: cached.metadataSignature || null,
93
104
  dependencyChecksums: cached.dependencyChecksums || {},
94
105
  createdAt: Number.isFinite(createdAt) ? createdAt : null
95
106
  });
96
- // Instancia ValueSet para garantir jsonObj
97
- // Reconstrói compose.include se não existir
98
- let compose = cached.expansion?.compose;
99
- if (!compose || !Array.isArray(compose.include)) {
100
- // Reconstrói a partir dos sistemas/códigos em expansion.contains
101
- const systemConcepts = new Map();
102
- if (Array.isArray(cached.expansion?.contains)) {
103
- for (const entry of cached.expansion.contains) {
104
- if (!entry.system || !entry.code) continue;
105
- if (!systemConcepts.has(entry.system)) {
106
- systemConcepts.set(entry.system, []);
107
- }
108
- systemConcepts.get(entry.system).push({ code: entry.code });
109
- }
110
- }
111
- compose = { include: Array.from(systemConcepts.entries()).map(([system, concepts]) => ({ system, concept: concepts })) };
112
- }
113
107
  const valueSetObj = new ValueSet({
114
108
  resourceType: 'ValueSet',
115
109
  url: cached.canonicalUrl,
116
110
  version: cached.version || null,
117
- expansion: cached.expansion,
118
111
  compose,
119
- id: cached.canonicalUrl // ou outro identificador se necessário
112
+ id: cached.canonicalUrl
120
113
  }, 'R5');
121
- this.#applyCachedExpansion(valueSetObj, paramsKey);
114
+ this.#applyCachedCompose(valueSetObj, paramsKey);
122
115
  // Indexa o ValueSet restaurado para torná-lo disponível via fetchValueSet
123
116
  this.valueSetMap.set(valueSetObj.url, valueSetObj);
124
117
  if (valueSetObj.version) {
@@ -128,7 +121,6 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
128
121
  this._idMap.set(valueSetObj.id, valueSetObj);
129
122
  this.valueSetFingerprints.set(cacheKey, cached.fingerprint || null);
130
123
  loadedCount++;
131
- console.log(`[OCL-ValueSet] Loaded ValueSet from cold cache into memory: ${cached.canonicalUrl}`);
132
124
  } catch (error) {
133
125
  console.error(`[OCL-ValueSet] Failed to load cold cache file ${file}:`, error.message);
134
126
  }
@@ -144,10 +136,10 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
144
136
  }
145
137
  }
146
138
 
147
- async #saveColdCacheForValueSet(vs, expansion, metadataSignature, dependencyChecksums, paramsKey = 'default') {
139
+ async #saveColdCacheForValueSet(vs, compose, metadataSignature, dependencyChecksums, paramsKey = 'default') {
148
140
  const canonicalUrl = vs?.url;
149
141
  const version = vs?.version || null;
150
- if (!canonicalUrl || !expansion) {
142
+ if (!canonicalUrl || !compose) {
151
143
  return null;
152
144
  }
153
145
 
@@ -156,22 +148,25 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
156
148
  try {
157
149
  await ensureCacheDirectories(CACHE_VS_DIR);
158
150
 
159
- const fingerprint = computeValueSetExpansionFingerprint(expansion);
151
+ const conceptCount = Array.isArray(compose.include)
152
+ ? compose.include.reduce((sum, inc) => sum + (Array.isArray(inc.concept) ? inc.concept.length : 0), 0)
153
+ : 0;
154
+ const fingerprint = computeValueSetExpansionFingerprint(compose);
160
155
  const cacheData = {
161
156
  canonicalUrl,
162
157
  version,
163
158
  paramsKey,
164
159
  fingerprint,
165
160
  timestamp: new Date().toISOString(),
166
- conceptCount: expansion.contains?.length || 0,
167
- expansion,
161
+ conceptCount,
162
+ compose,
168
163
  metadataSignature,
169
164
  dependencyChecksums
170
165
  };
171
166
 
172
167
  await fs.writeFile(cacheFilePath, JSON.stringify(cacheData, null, 2), 'utf-8');
173
- console.log(`[OCL-ValueSet] Saved ValueSet expansion to cold cache: ${canonicalUrl} (${expansion.contains?.length || 0} concepts, fingerprint=${fingerprint?.substring(0, 8)})`);
174
-
168
+ console.log(`[OCL-ValueSet] Saved ValueSet compose to cold cache: ${canonicalUrl} (${conceptCount} concepts, fingerprint=${fingerprint?.substring(0, 8)})`);
169
+
175
170
  return fingerprint;
176
171
  } catch (error) {
177
172
  console.error(`[OCL-ValueSet] Failed to save cold cache for ValueSet ${canonicalUrl}:`, error.message);
@@ -249,12 +244,15 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
249
244
  async fetchValueSet(url, version) {
250
245
  this._validateFetchParams(url, version);
251
246
 
247
+ // Fast path: skip URLs already known to not be in OCL
248
+ if (this._negativeUrlCache.has(url)) {
249
+ return null;
250
+ }
251
+
252
252
  let key = `${url}|${version}`;
253
253
  if (this.valueSetMap.has(key)) {
254
254
  const vs = this.valueSetMap.get(key);
255
- // await this.#ensureComposeIncludes(vs);
256
- this.#clearInlineExpansion(vs);
257
- console.log(`[OCL-ValueSet] fetchValueSet cache hit for ${url} (version: ${version || 'none'})`);
255
+ await this.#ensureComposeIncludes(vs);
258
256
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset', userRequested: true });
259
257
  return vs;
260
258
  }
@@ -265,8 +263,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
265
263
  key = `${url}|${majorMinor}`;
266
264
  if (this.valueSetMap.has(key)) {
267
265
  const vs = this.valueSetMap.get(key);
268
- // await this.#ensureComposeIncludes(vs);
269
- this.#clearInlineExpansion(vs);
266
+ await this.#ensureComposeIncludes(vs);
270
267
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-mm' });
271
268
  return vs;
272
269
  }
@@ -275,16 +272,14 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
275
272
 
276
273
  if (this.valueSetMap.has(url)) {
277
274
  const vs = this.valueSetMap.get(url);
278
- // await this.#ensureComposeIncludes(vs);
279
- this.#clearInlineExpansion(vs);
275
+ await this.#ensureComposeIncludes(vs);
280
276
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-url' });
281
277
  return vs;
282
278
  }
283
279
 
284
280
  const resolved = await this.#resolveValueSetByCanonical(url, version);
285
281
  if (resolved) {
286
- // await this.#ensureComposeIncludes(resolved);
287
- this.#clearInlineExpansion(resolved);
282
+ await this.#ensureComposeIncludes(resolved);
288
283
  this.#scheduleBackgroundExpansion(resolved, { reason: 'fetch-valueset-resolved' });
289
284
  return resolved;
290
285
  }
@@ -294,8 +289,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
294
289
  key = `${url}|${version}`;
295
290
  if (this.valueSetMap.has(key)) {
296
291
  const vs = this.valueSetMap.get(key);
297
- // await this.#ensureComposeIncludes(vs);
298
- this.#clearInlineExpansion(vs);
292
+ await this.#ensureComposeIncludes(vs);
299
293
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-init' });
300
294
  return vs;
301
295
  }
@@ -306,8 +300,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
306
300
  key = `${url}|${majorMinor}`;
307
301
  if (this.valueSetMap.has(key)) {
308
302
  const vs = this.valueSetMap.get(key);
309
- // await this.#ensureComposeIncludes(vs);
310
- this.#clearInlineExpansion(vs);
303
+ await this.#ensureComposeIncludes(vs);
311
304
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-init-mm' });
312
305
  return vs;
313
306
  }
@@ -316,20 +309,20 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
316
309
 
317
310
  if (this.valueSetMap.has(url)) {
318
311
  const vs = this.valueSetMap.get(url);
319
- // await this.#ensureComposeIncludes(vs);
320
- this.#clearInlineExpansion(vs);
312
+ await this.#ensureComposeIncludes(vs);
321
313
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-init-url' });
322
314
  return vs;
323
315
  }
324
316
 
317
+ // Remember that this URL is not in OCL to skip future lookups
318
+ this._negativeUrlCache.add(url);
325
319
  return null;
326
320
  }
327
321
 
328
322
  async fetchValueSetById(id) {
329
323
  const local = this.#getLocalValueSetById(id);
330
324
  if (local) {
331
- // await this.#ensureComposeIncludes(local);
332
- this.#clearInlineExpansion(local);
325
+ await this.#ensureComposeIncludes(local);
333
326
  this.#scheduleBackgroundExpansion(local, { reason: 'fetch-valueset-by-id' });
334
327
  return local;
335
328
  }
@@ -337,8 +330,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
337
330
  await this.initialize();
338
331
 
339
332
  const vs = this.#getLocalValueSetById(id);
340
- // await this.#ensureComposeIncludes(vs);
341
- this.#clearInlineExpansion(vs);
333
+ await this.#ensureComposeIncludes(vs);
342
334
  this.#scheduleBackgroundExpansion(vs, { reason: 'fetch-valueset-by-id-init' });
343
335
  return vs;
344
336
  }
@@ -901,6 +893,24 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
901
893
  }
902
894
  }
903
895
 
896
+ #composeFromExpansion(expansion) {
897
+ if (!expansion || !Array.isArray(expansion.contains)) {
898
+ return null;
899
+ }
900
+ const systemConcepts = new Map();
901
+ for (const entry of expansion.contains) {
902
+ if (!entry.system || !entry.code) continue;
903
+ if (!systemConcepts.has(entry.system)) {
904
+ systemConcepts.set(entry.system, []);
905
+ }
906
+ systemConcepts.get(entry.system).push({ code: entry.code });
907
+ }
908
+ if (systemConcepts.size === 0) {
909
+ return null;
910
+ }
911
+ return { include: Array.from(systemConcepts.entries()).map(([system, concepts]) => ({ system, concept: concepts })) };
912
+ }
913
+
904
914
  #expansionCacheKey(vs, paramsKey) {
905
915
  const base = this.#valueSetBaseKey(vs);
906
916
  if (!base) {
@@ -924,7 +934,7 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
924
934
  }
925
935
  }
926
936
 
927
- #applyCachedExpansion(vs, paramsKey) {
937
+ #applyCachedCompose(vs, paramsKey) {
928
938
  if (!vs || !vs.jsonObj) {
929
939
  return;
930
940
  }
@@ -935,25 +945,20 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
935
945
  }
936
946
 
937
947
  const cached = this.backgroundExpansionCache.get(cacheKey);
938
- if (!cached || !cached.expansion) {
948
+ if (!cached || !cached.compose) {
939
949
  return;
940
950
  }
941
951
 
942
952
  if (!this.#isCachedExpansionValid(vs, cached)) {
943
953
  this.backgroundExpansionCache.delete(cacheKey);
944
- if (vs.jsonObj.expansion) {
945
- delete vs.jsonObj.expansion;
946
- }
947
- console.log(`[OCL-ValueSet] Cached ValueSet expansion invalidated: ${cacheKey}`);
948
954
  return;
949
955
  }
950
956
 
951
- if (vs.jsonObj.expansion) {
957
+ if (vs.jsonObj.compose && Array.isArray(vs.jsonObj.compose.include) && vs.jsonObj.compose.include.length > 0) {
952
958
  return;
953
959
  }
954
960
 
955
- vs.jsonObj.expansion = structuredClone(cached.expansion);
956
- console.log(`[OCL-ValueSet] ValueSet expansion restored from cache: ${cacheKey}`);
961
+ vs.jsonObj.compose = structuredClone(cached.compose);
957
962
  }
958
963
 
959
964
  #scheduleBackgroundExpansion(vs, options = {}) {
@@ -970,13 +975,10 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
970
975
  const cached = this.backgroundExpansionCache.get(cacheKey);
971
976
  if (cached && !this.#isCachedExpansionValid(vs, cached)) {
972
977
  this.backgroundExpansionCache.delete(cacheKey);
973
- if (vs.jsonObj.expansion) {
974
- delete vs.jsonObj.expansion;
975
- }
976
- console.log(`[OCL-ValueSet] Cached ValueSet expansion invalidated: ${cacheKey}`);
977
978
  }
978
979
 
979
- if (vs.jsonObj.expansion) {
980
+ // Already have a cached compose ready
981
+ if (cached && cached.compose) {
980
982
  return;
981
983
  }
982
984
 
@@ -996,23 +998,15 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
996
998
  : cacheAgeFromFileMs != null
997
999
  ? 'file'
998
1000
  : 'metadata';
999
- console.log(`[OCL-ValueSet] Skipping warm-up for ValueSet ${vs.url} (cold cache age: ${formatCacheAgeMinutes(freshestCacheAgeMs)})`);
1000
- console.log(`[OCL-ValueSet] ValueSet cold cache is fresh, not enqueueing warm-up job (${cacheKey}, source=${freshnessSource})`);
1001
1001
  return;
1002
1002
  }
1003
1003
 
1004
1004
  const jobKey = `vs:${cacheKey}`;
1005
1005
  if (OCLBackgroundJobQueue.isQueuedOrRunning(jobKey)) {
1006
- console.log(`[OCL-ValueSet] ValueSet expansion already queued or running: ${cacheKey}`);
1007
1006
  return;
1008
1007
  }
1009
1008
 
1010
1009
  let queuedJobSize = null;
1011
- const warmupAgeText = freshestCacheAgeMs != null
1012
- ? formatCacheAgeMinutes(freshestCacheAgeMs)
1013
- : 'no cold cache';
1014
- console.log(`[OCL-ValueSet] Enqueueing warm-up for ValueSet ${vs.url} (cold cache age: ${warmupAgeText})`);
1015
- console.log(`[OCL-ValueSet] ValueSet expansion enqueued: ${cacheKey}`);
1016
1010
  OCLBackgroundJobQueue.enqueue(
1017
1011
  jobKey,
1018
1012
  'ValueSet expansion',
@@ -1033,7 +1027,6 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
1033
1027
  }
1034
1028
 
1035
1029
  async #runBackgroundExpansion(vs, cacheKey, paramsKey = 'default', knownConceptCount = null) {
1036
- console.log(`[OCL-ValueSet] ValueSet expansion started: ${cacheKey}`);
1037
1030
  const progressState = { processed: 0, total: null };
1038
1031
  this.backgroundExpansionProgress.set(cacheKey, progressState);
1039
1032
  try {
@@ -1053,51 +1046,46 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
1053
1046
  );
1054
1047
  }
1055
1048
 
1056
- const expansion = await this.#buildBackgroundExpansion(vs, progressState);
1057
- if (!expansion) {
1049
+ const compose = await this.#buildBackgroundCompose(vs, progressState);
1050
+ if (!compose) {
1058
1051
  return;
1059
1052
  }
1060
1053
 
1061
- progressState.processed = expansion.total || progressState.processed;
1054
+ const conceptCount = Array.isArray(compose.include)
1055
+ ? compose.include.reduce((sum, inc) => sum + (Array.isArray(inc.concept) ? inc.concept.length : 0), 0)
1056
+ : 0;
1057
+ progressState.processed = conceptCount || progressState.processed;
1062
1058
  if (typeof progressState.total !== 'number' || !Number.isFinite(progressState.total) || progressState.total <= 0) {
1063
- progressState.total = expansion.total || 0;
1059
+ progressState.total = conceptCount || 0;
1064
1060
  }
1065
1061
 
1066
1062
  const metadataSignature = this.#valueSetMetadataSignature(vs);
1067
1063
  const dependencyChecksums = this.#valueSetDependencyChecksums(vs);
1068
1064
 
1069
- // Compute custom fingerprint and compare with cold cache
1070
- const newFingerprint = computeValueSetExpansionFingerprint(expansion);
1065
+ // Compute fingerprint and compare with cold cache
1066
+ const newFingerprint = computeValueSetExpansionFingerprint(compose);
1071
1067
  const oldFingerprint = this.valueSetFingerprints.get(cacheKey);
1072
1068
 
1073
- if (oldFingerprint && newFingerprint === oldFingerprint) {
1074
- console.log(`[OCL-ValueSet] ValueSet expansion fingerprint unchanged: ${cacheKey} (fingerprint=${newFingerprint?.substring(0, 8)})`);
1075
- } else {
1076
- if (oldFingerprint) {
1077
- console.log(`[OCL-ValueSet] ValueSet expansion fingerprint changed: ${cacheKey} (${oldFingerprint?.substring(0, 8)} -> ${newFingerprint?.substring(0, 8)})`);
1078
- console.log(`[OCL-ValueSet] Replacing cold cache with new hot cache: ${cacheKey}`);
1079
- } else {
1080
- console.log(`[OCL-ValueSet] Computed fingerprint for ValueSet expansion: ${cacheKey} (fingerprint=${newFingerprint?.substring(0, 8)})`);
1081
- }
1082
-
1083
- // Save to cold cache
1084
- const savedFingerprint = await this.#saveColdCacheForValueSet(vs, expansion, metadataSignature, dependencyChecksums, paramsKey);
1069
+ if (!oldFingerprint || newFingerprint !== oldFingerprint) {
1070
+ const savedFingerprint = await this.#saveColdCacheForValueSet(vs, compose, metadataSignature, dependencyChecksums, paramsKey);
1085
1071
  if (savedFingerprint) {
1086
1072
  this.valueSetFingerprints.set(cacheKey, savedFingerprint);
1087
1073
  }
1088
1074
  }
1089
1075
 
1090
1076
  this.backgroundExpansionCache.set(cacheKey, {
1091
- expansion,
1077
+ compose,
1092
1078
  metadataSignature,
1093
1079
  dependencyChecksums,
1094
1080
  createdAt: Date.now()
1095
1081
  });
1096
- // Keep expansions in provider-managed cache only.
1097
- // Inline expansion on ValueSet bypasses $expand filtering in worker pipeline.
1098
1082
 
1099
- console.log(`[OCL-ValueSet] ValueSet expansion completed and cached: ${cacheKey}`);
1100
- console.log(`[OCL-ValueSet] ValueSet now available in cache: ${cacheKey}`);
1083
+ // Apply compose to the ValueSet so the expand engine can use it
1084
+ vs.jsonObj.compose = structuredClone(compose);
1085
+ // Ensure no stale inline expansion
1086
+ delete vs.jsonObj.expansion;
1087
+
1088
+ console.log(`[OCL-ValueSet] compose cached: ${vs.url} (${conceptCount} concepts)`);
1101
1089
  } catch (error) {
1102
1090
  console.error(`[OCL-ValueSet] ValueSet background expansion failed: ${cacheKey}: ${error.message}`);
1103
1091
  } finally {
@@ -1105,17 +1093,15 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
1105
1093
  }
1106
1094
  }
1107
1095
 
1108
- async #buildBackgroundExpansion(vs, progressState = null) {
1096
+ async #buildBackgroundCompose(vs, progressState = null) {
1109
1097
  const meta = this.#getCollectionMeta(vs);
1110
1098
  if (!meta || !meta.conceptsUrl) {
1111
1099
  return null;
1112
1100
  }
1113
1101
 
1114
- const contains = [];
1115
- let offset = 0; // Moved this line up
1116
- // Agrupa conceitos por system
1102
+ let offset = 0;
1117
1103
  const systemConcepts = new Map();
1118
- // Removed duplicate offset declaration
1104
+ let totalCount = 0;
1119
1105
  while (true) {
1120
1106
  const batch = await this.#fetchCollectionConcepts(meta, {
1121
1107
  count: CONCEPT_PAGE_SIZE,
@@ -1132,55 +1118,30 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
1132
1118
  if (!entry?.system || !entry?.code) {
1133
1119
  continue;
1134
1120
  }
1135
- const out = {
1136
- system: entry.system,
1137
- code: entry.code
1138
- };
1139
- if (entry.display) {
1140
- out.display = entry.display;
1141
- }
1142
- if (entry.definition) {
1143
- out.definition = entry.definition;
1144
- }
1145
- if (entry.inactive === true) {
1146
- out.inactive = true;
1147
- }
1148
- if (Array.isArray(entry.designation) && entry.designation.length > 0) {
1149
- out.designation = entry.designation
1150
- .filter(d => d && d.value)
1151
- .map(d => ({
1152
- language: d.language,
1153
- value: d.value
1154
- }));
1155
- }
1156
- contains.push(out);
1157
- // Agrupa por system
1158
1121
  if (!systemConcepts.has(entry.system)) {
1159
1122
  systemConcepts.set(entry.system, []);
1160
1123
  }
1161
1124
  systemConcepts.get(entry.system).push(entry.code);
1125
+ totalCount++;
1162
1126
  }
1163
1127
  if (progressState) {
1164
- progressState.processed = contains.length;
1128
+ progressState.processed = totalCount;
1165
1129
  }
1166
1130
  if (entries.length < CONCEPT_PAGE_SIZE) {
1167
1131
  break;
1168
1132
  }
1169
1133
  offset += entries.length;
1170
1134
  }
1171
- // Popular compose.include para cada system
1172
- if (!vs.jsonObj.compose) {
1173
- vs.jsonObj.compose = { include: [] };
1135
+
1136
+ if (systemConcepts.size === 0) {
1137
+ return null;
1174
1138
  }
1175
- vs.jsonObj.compose.include = Array.from(systemConcepts.entries()).map(([system, codes]) => ({
1176
- system,
1177
- concept: codes.map(code => ({ code }))
1178
- }));
1139
+
1179
1140
  return {
1180
- timestamp: new Date().toISOString(),
1181
- identifier: `urn:uuid:${crypto.randomUUID()}`,
1182
- total: contains.length,
1183
- contains
1141
+ include: Array.from(systemConcepts.entries()).map(([system, codes]) => ({
1142
+ system,
1143
+ concept: codes.map(code => ({ code }))
1144
+ }))
1184
1145
  };
1185
1146
  }
1186
1147
 
@@ -1354,36 +1315,55 @@ class OCLValueSetProvider extends AbstractValueSetProvider {
1354
1315
  }
1355
1316
 
1356
1317
  async #fetchOrganizationIds() {
1357
- const endpoint = '/orgs/';
1358
- console.log(`[OCL-ValueSet] Loading organizations from: ${this.baseUrl}${endpoint}`);
1359
- const orgs = await this.#fetchAllPages(endpoint);
1318
+ // Return cached result if available
1319
+ if (this._organizationIdsCache) {
1320
+ return this._organizationIdsCache;
1321
+ }
1360
1322
 
1361
- const ids = [];
1362
- const seen = new Set();
1363
- for (const org of orgs || []) {
1364
- if (!org || typeof org !== 'object') {
1365
- continue;
1366
- }
1323
+ // Deduplicate concurrent requests
1324
+ if (this._organizationIdsFetchPromise) {
1325
+ return this._organizationIdsFetchPromise;
1326
+ }
1367
1327
 
1368
- const id = org.id || org.mnemonic || org.short_code || org.shortCode || org.name || null;
1369
- if (!id) {
1370
- continue;
1328
+ this._organizationIdsFetchPromise = (async () => {
1329
+ const endpoint = '/orgs/';
1330
+ console.log(`[OCL-ValueSet] Loading organizations from: ${this.baseUrl}${endpoint}`);
1331
+ const orgs = await this.#fetchAllPages(endpoint);
1332
+
1333
+ const ids = [];
1334
+ const seen = new Set();
1335
+ for (const org of orgs || []) {
1336
+ if (!org || typeof org !== 'object') {
1337
+ continue;
1338
+ }
1339
+
1340
+ const id = org.id || org.mnemonic || org.short_code || org.shortCode || org.name || null;
1341
+ if (!id) {
1342
+ continue;
1343
+ }
1344
+
1345
+ const normalized = String(id).trim();
1346
+ if (!normalized || seen.has(normalized)) {
1347
+ continue;
1348
+ }
1349
+
1350
+ seen.add(normalized);
1351
+ ids.push(normalized);
1371
1352
  }
1372
1353
 
1373
- const normalized = String(id).trim();
1374
- if (!normalized || seen.has(normalized)) {
1375
- continue;
1354
+ if (ids.length === 0 && this.org) {
1355
+ ids.push(this.org);
1376
1356
  }
1377
1357
 
1378
- seen.add(normalized);
1379
- ids.push(normalized);
1380
- }
1358
+ this._organizationIdsCache = ids;
1359
+ return ids;
1360
+ })();
1381
1361
 
1382
- if (ids.length === 0 && this.org) {
1383
- ids.push(this.org);
1362
+ try {
1363
+ return await this._organizationIdsFetchPromise;
1364
+ } finally {
1365
+ this._organizationIdsFetchPromise = null;
1384
1366
  }
1385
-
1386
- return ids;
1387
1367
  }
1388
1368
 
1389
1369
  #collectionIdentity(collection) {
@@ -154,7 +154,7 @@ class ResourceCache {
154
154
  }
155
155
  }
156
156
  if (this.stats) {
157
- this.stats.task("Client Cache", `Pruned ${i} of ${this.cache.size} entries`);
157
+ this.stats.taskDone("Client Cache", `Pruned ${i} of ${this.cache.size} entries`);
158
158
  }
159
159
  }
160
160
 
@@ -347,12 +347,12 @@ class ExpansionCache {
347
347
  if (heapUsed > this.memoryThresholdBytes) {
348
348
  const i = this.evictOldestHalf();
349
349
  if (this.stats) {
350
- this.stats.task('Expansion Cache', `Checked Memory Pressure: evicted half (${i} entries)`);
350
+ this.stats.taskDone('Expansion Cache', `Checked Memory Pressure: evicted half (${i} entries)`);
351
351
  }
352
352
  return true;
353
353
  }
354
354
  if (this.stats) {
355
- this.stats.task('Expansion Cache', `Checked Memory Pressure - OK (${this.cache.size} entries)`);
355
+ this.stats.taskDone('Expansion Cache', `Checked Memory Pressure - OK (${this.cache.size} entries)`);
356
356
  }
357
357
  return false;
358
358
  }
package/tx/provider.js CHANGED
@@ -49,7 +49,8 @@ class Provider {
49
49
  */
50
50
  conceptMapProviders;
51
51
 
52
- contentSources;
52
+ packageSources;
53
+ externalSources;
53
54
 
54
55
  baseUrl = null;
55
56
  path;
@@ -268,9 +269,9 @@ class Provider {
268
269
 
269
270
 
270
271
 
271
- async findConceptMapForTranslation(opContext, conceptMaps, sourceSystem, sourceScope, targetScope, targetSystem) {
272
+ async findConceptMapForTranslation(opContext, conceptMaps, sourceSystem, sourceScope, targetScope, targetSystem, sourceCode = null) {
272
273
  for (let cmp of this.conceptMapProviders) {
273
- await cmp.findConceptMapForTranslation(opContext, conceptMaps, sourceSystem, sourceScope, targetScope, targetSystem);
274
+ await cmp.findConceptMapForTranslation(opContext, conceptMaps, sourceSystem, sourceScope, targetScope, targetSystem, sourceCode);
274
275
  }
275
276
  if (sourceSystem && targetSystem) {
276
277
  let uris = new Set();
@@ -581,10 +581,15 @@ class SnomedConceptList {
581
581
  return { found: result, index };
582
582
  }
583
583
 
584
+ getConceptByCount(index) {
585
+ return this.getConcept(index * SnomedConceptList.CONCEPT_SIZE);
586
+ }
587
+
584
588
  getConcept(index) {
585
589
  this.#checkPostBuildAccess(index);
586
590
 
587
591
  return {
592
+ index : index,
588
593
  identity: this.master.readBigUInt64LE(index + 0),
589
594
  flags: this.master.readUInt8(index + 8),
590
595
  parents: this.master.readUInt32LE(index + 9),