@cosyte/terminology 0.0.12 → 0.1.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/dist/index.cjs CHANGED
@@ -72,19 +72,43 @@ var DIAGNOSTIC_CODES = {
72
72
  TERM_CONCEPT_HEADER_NOT_BILLABLE: "TERM_CONCEPT_HEADER_NOT_BILLABLE",
73
73
  /**
74
74
  * A ValueSet `compose` part could **not be expanded**: an intensional `include`/`exclude` whose
75
- * code system was not supplied, an unresolvable referenced value set, or a `filter` operator the
76
- * engine does not implement. Surfaced as a typed outcome that marks the expansion **incomplete**;
75
+ * code system was not supplied, an unresolvable referenced value set, a `filter` operator the
76
+ * engine does not implement, or a component whose declared code system version disagrees with the
77
+ * supplied release (or names a pin that release carries no version to confirm). Surfaced as a
78
+ * typed outcome that marks the expansion **incomplete**;
77
79
  * the engine **never** fabricates a member and **never** returns a silently-empty "no members"
78
80
  * answer for a part it could not compute (a false "not a member" is a clinical error).
79
81
  */
80
82
  TERM_VALUESET_CANNOT_EXPAND: "TERM_VALUESET_CANNOT_EXPAND",
81
83
  /**
82
84
  * A **pre-computed** `ValueSet.expansion` is **incomplete**: its `total` exceeds the number of
83
- * `contains` entries, or it is flagged too-costly (the VSAC `$expand` >1200-code truncation
84
- * hazard). The engine **never treats a truncated expansion as complete membership**: a code
85
- * absent from a truncated expansion is `undetermined`, never a confident "not a member".
85
+ * `contains` entries, it is flagged too-costly (the VSAC `$expand` >1200-code truncation
86
+ * hazard), or it is marked `unclosed` (the value set is unbounded because it includes
87
+ * post-coordinated content, so no expansion can enumerate it). The engine **never treats an
88
+ * incomplete expansion as complete membership**: a code absent from one is `undetermined`, never
89
+ * a confident "not a member". The diagnostic's `detail` says which of those it was.
86
90
  */
87
91
  TERM_VALUESET_EXPANSION_TRUNCATED: "TERM_VALUESET_EXPANSION_TRUNCATED",
92
+ /**
93
+ * A `ValueSet.compose` component **enumerated** a code the release supplied for its `system` does
94
+ * not define, so the code was **not admitted** as a member. The never-fabricate invariant applied
95
+ * to an extensional selection: enumerating a code is the value set's claim that it exists, and
96
+ * asserting membership for it while holding the very release that shows it does not is the engine
97
+ * deciding against its own evidence.
98
+ *
99
+ * Raised **only** where the evidence is in hand: a release keyed by the component's `system` that
100
+ * the component's declared `version` (if any) agrees with. No release for that system, a declared
101
+ * version the supplied release does not agree with, or a component naming no `system` at all
102
+ * leaves the engine with no contrary evidence, so every enumerated code is carried and **no
103
+ * diagnostic is raised**: absence of evidence is not evidence the code is undefined.
104
+ *
105
+ * The answer stays **decided**, unlike {@link TERM_VALUESET_CANNOT_EXPAND}: the engine computed
106
+ * membership on the evidence it was given, so `expand` reports `complete: true` and
107
+ * `validateCodeInValueSet` reports a decided non-member for the dropped code, rather than a lower
108
+ * bound or a refusal. One diagnostic is raised per affected component, located by that
109
+ * component's own index path, however many of its codes were dropped.
110
+ */
111
+ TERM_VALUESET_ENUMERATED_CODE_UNDEFINED: "TERM_VALUESET_ENUMERATED_CODE_UNDEFINED",
88
112
  /**
89
113
  * A UCUM unit expression is **not valid**: it does not parse against the UCUM grammar, or it
90
114
  * names an atom absent from the vendored UCUM table. A **first-class typed outcome, never an
@@ -489,7 +513,7 @@ function provenanceOf(map, group, sourceSystem) {
489
513
  if (group?.target !== void 0) out.targetSystem = group.target;
490
514
  return Object.freeze(out);
491
515
  }
492
- function unmappedResult(source, mode, provenance, fixedTarget, otherMapUrl) {
516
+ function unmappedResult(source, mode, provenance, fixedTarget, otherMapUrl, notRelated = []) {
493
517
  const out = {
494
518
  unmapped: true,
495
519
  code: "TERM_TRANSLATE_UNMAPPED",
@@ -499,13 +523,15 @@ function unmappedResult(source, mode, provenance, fixedTarget, otherMapUrl) {
499
523
  };
500
524
  if (fixedTarget !== void 0) out.fixedTarget = fixedTarget;
501
525
  if (otherMapUrl !== void 0) out.otherMapUrl = otherMapUrl;
526
+ if (notRelated.length > 0) out.notRelated = Object.freeze(notRelated);
502
527
  return Object.freeze(out);
503
528
  }
504
529
  function translate(sourceCoding, map) {
505
530
  const source = coding(sourceCoding);
506
531
  const system = source.system;
507
- const matches = [];
508
- let firstMatchGroup;
532
+ const rows = [];
533
+ const notRelated = [];
534
+ let firstRowGroup;
509
535
  let sourceCodePresent = false;
510
536
  let fallbackGroup;
511
537
  for (const group of map.group) {
@@ -522,23 +548,45 @@ function translate(sourceCoding, map) {
522
548
  ...t.display !== void 0 ? { display: t.display } : {},
523
549
  ...group.targetVersion !== void 0 ? { version: group.targetVersion } : {}
524
550
  });
525
- const match = {
526
- target: targetCoding,
527
- relationship: equivalenceToRelationship(t.equivalence),
528
- equivalence: t.equivalence
529
- };
530
- if (t.comment !== void 0) match.comment = t.comment;
531
- matches.push(Object.freeze(match));
532
- if (firstMatchGroup === void 0) firstMatchGroup = group;
551
+ if (t.equivalence === "disjoint") {
552
+ const assertion = {
553
+ target: targetCoding,
554
+ relationship: equivalenceToRelationship(t.equivalence),
555
+ equivalence: t.equivalence
556
+ };
557
+ if (t.comment !== void 0) assertion.comment = t.comment;
558
+ const frozen = Object.freeze(assertion);
559
+ notRelated.push(frozen);
560
+ rows.push(frozen);
561
+ } else {
562
+ const match = {
563
+ target: targetCoding,
564
+ relationship: equivalenceToRelationship(t.equivalence),
565
+ equivalence: t.equivalence
566
+ };
567
+ if (t.comment !== void 0) match.comment = t.comment;
568
+ rows.push(Object.freeze(match));
569
+ }
570
+ if (firstRowGroup === void 0) firstRowGroup = group;
533
571
  }
534
572
  }
535
573
  }
536
- if (matches.length > 0) {
537
- return Object.freeze({
538
- unmapped: false,
539
- matches: Object.freeze(matches),
540
- provenance: provenanceOf(map, firstMatchGroup, system)
541
- });
574
+ if (rows.length > 0) {
575
+ if (notRelated.length < rows.length) {
576
+ return Object.freeze({
577
+ unmapped: false,
578
+ matches: Object.freeze(rows),
579
+ provenance: provenanceOf(map, firstRowGroup, system)
580
+ });
581
+ }
582
+ return unmappedResult(
583
+ source,
584
+ "none",
585
+ provenanceOf(map, firstRowGroup, system),
586
+ void 0,
587
+ void 0,
588
+ notRelated
589
+ );
542
590
  }
543
591
  if (!sourceCodePresent && fallbackGroup?.unmapped !== void 0) {
544
592
  const um = fallbackGroup.unmapped;
@@ -561,7 +609,7 @@ function translate(sourceCoding, map) {
561
609
  return unmappedResult(
562
610
  source,
563
611
  "none",
564
- provenanceOf(map, firstMatchGroup, system),
612
+ provenanceOf(map, firstRowGroup, system),
565
613
  void 0,
566
614
  void 0
567
615
  );
@@ -1071,6 +1119,7 @@ function validateCode(cs, code) {
1071
1119
 
1072
1120
  // src/valueset/load.ts
1073
1121
  var TOO_COSTLY_EXTENSION = "http://hl7.org/fhir/StructureDefinition/valueset-toocostly";
1122
+ var UNCLOSED_EXTENSION = "http://hl7.org/fhir/StructureDefinition/valueset-unclosed";
1074
1123
  function malformed2(path, fault) {
1075
1124
  throw new TerminologyError(FATAL_CODES.TERM_VALUESET_MALFORMED, `ValueSet ${path}: ${fault}`);
1076
1125
  }
@@ -1149,7 +1198,13 @@ function loadCompose(raw, path) {
1149
1198
  (c, i) => loadComponent(c, `${path}.exclude[${String(i)}]`)
1150
1199
  )
1151
1200
  );
1152
- return Object.freeze({ include, exclude });
1201
+ const out = { include, exclude };
1202
+ if (raw["inactive"] !== void 0) {
1203
+ const inactive = getBoolean(raw, "inactive");
1204
+ if (inactive === void 0) malformed2(`${path}.inactive`, "'inactive' is not a boolean");
1205
+ out.inactive = inactive;
1206
+ }
1207
+ return Object.freeze(out);
1153
1208
  }
1154
1209
  function loadContains(raw, path) {
1155
1210
  if (!isJsonObject(raw)) malformed2(path, "contains entry is not an object");
@@ -1169,6 +1224,18 @@ function isTooCostly(raw) {
1169
1224
  }
1170
1225
  return false;
1171
1226
  }
1227
+ function isUnclosed(raw) {
1228
+ for (const ext of getArray(raw, "extension") ?? []) {
1229
+ if (typeof ext === "string") {
1230
+ if (ext === UNCLOSED_EXTENSION) return true;
1231
+ continue;
1232
+ }
1233
+ if (getString(ext, "url") !== UNCLOSED_EXTENSION) continue;
1234
+ if (getBoolean(ext, "valueBoolean") === false) continue;
1235
+ return true;
1236
+ }
1237
+ return false;
1238
+ }
1172
1239
  function loadExpansion(raw, path) {
1173
1240
  if (!isJsonObject(raw)) malformed2(path, "expansion is not an object");
1174
1241
  const contains = Object.freeze(
@@ -1177,8 +1244,9 @@ function loadExpansion(raw, path) {
1177
1244
  )
1178
1245
  );
1179
1246
  const total = getNumber(raw, "total");
1180
- const truncated2 = total !== void 0 && total > contains.length || isTooCostly(raw);
1181
- const out = { contains, truncated: truncated2 };
1247
+ const unclosed = isUnclosed(raw);
1248
+ const truncated2 = total !== void 0 && total > contains.length || isTooCostly(raw) || unclosed;
1249
+ const out = { contains, truncated: truncated2, unclosed };
1182
1250
  if (total !== void 0) out.total = total;
1183
1251
  return Object.freeze(out);
1184
1252
  }
@@ -1248,11 +1316,12 @@ function valueSet(value) {
1248
1316
  value.split(",").map((v) => v.trim()).filter((v) => v.length > 0)
1249
1317
  );
1250
1318
  }
1251
- function propertyValue(concept, property) {
1319
+ function propertyValues(concept, property) {
1320
+ const values = [];
1252
1321
  for (const p of concept.properties) {
1253
- if (p.code === property) return typeof p.value === "string" ? p.value : String(p.value);
1322
+ if (p.code === property) values.push(typeof p.value === "string" ? p.value : String(p.value));
1254
1323
  }
1255
- return void 0;
1324
+ return values;
1256
1325
  }
1257
1326
  var UNSUPPORTED = Object.freeze({ matched: false, supported: false });
1258
1327
  function matchesFilter(concept, filter, sub) {
@@ -1268,21 +1337,30 @@ function matchesFilter(concept, filter, sub) {
1268
1337
  case "is-not-a":
1269
1338
  return { matched: !isA(sub, concept.code, filter.value), supported: true };
1270
1339
  case "=": {
1271
- const actual = onCode ? concept.code : propertyValue(concept, filter.property);
1272
- return { matched: actual === filter.value, supported: true };
1340
+ if (onCode) return { matched: concept.code === filter.value, supported: true };
1341
+ return {
1342
+ matched: propertyValues(concept, filter.property).includes(filter.value),
1343
+ supported: true
1344
+ };
1273
1345
  }
1274
1346
  case "in": {
1275
1347
  const set = valueSet(filter.value);
1276
- const actual = onCode ? concept.code : propertyValue(concept, filter.property);
1277
- return { matched: actual !== void 0 && set.has(actual), supported: true };
1348
+ if (onCode) return { matched: set.has(concept.code), supported: true };
1349
+ return {
1350
+ matched: propertyValues(concept, filter.property).some((v) => set.has(v)),
1351
+ supported: true
1352
+ };
1278
1353
  }
1279
1354
  case "not-in": {
1280
1355
  const set = valueSet(filter.value);
1281
- const actual = onCode ? concept.code : propertyValue(concept, filter.property);
1282
- return { matched: actual === void 0 || !set.has(actual), supported: true };
1356
+ if (onCode) return { matched: !set.has(concept.code), supported: true };
1357
+ return {
1358
+ matched: propertyValues(concept, filter.property).every((v) => !set.has(v)),
1359
+ supported: true
1360
+ };
1283
1361
  }
1284
1362
  case "exists": {
1285
- const present = onCode ? true : propertyValue(concept, filter.property) !== void 0;
1363
+ const present = onCode ? true : propertyValues(concept, filter.property).length > 0;
1286
1364
  const wantPresent = filter.value.trim().toLowerCase() === "true";
1287
1365
  return { matched: present === wantPresent, supported: true };
1288
1366
  }
@@ -1323,6 +1401,43 @@ function truncated(detail, path) {
1323
1401
  d.path = path;
1324
1402
  return Object.freeze(d);
1325
1403
  }
1404
+ function enumeratedUndefined(detail, path) {
1405
+ const d = {
1406
+ code: "TERM_VALUESET_ENUMERATED_CODE_UNDEFINED",
1407
+ detail
1408
+ };
1409
+ if (path !== void 0) d.path = path;
1410
+ return Object.freeze(d);
1411
+ }
1412
+ function expansionIncompleteDetail(unclosed) {
1413
+ return unclosed ? "pre-computed expansion is incomplete (marked unclosed: the value set is unbounded, so the snapshot is a sample of its membership)" : "pre-computed expansion is incomplete (truncated or too-costly)";
1414
+ }
1415
+ var ACTIVITY_UNCHECKABLE_DETAIL = "active-only value set: no usable code system release to check whether a selected code is active";
1416
+ var ENUMERATED_UNDEFINED_DETAIL = "enumerated code is not defined by the supplied code system release, so it is not a member";
1417
+ function usableRelease(component, system, ctx) {
1418
+ if (system === void 0) return void 0;
1419
+ const cs = ctx.codeSystems?.get(system);
1420
+ if (cs === void 0) return void 0;
1421
+ if (system === component.system && component.version !== void 0 && cs.version !== component.version) {
1422
+ return void 0;
1423
+ }
1424
+ return cs;
1425
+ }
1426
+ function screenActive(members, component, ctx) {
1427
+ const kept = /* @__PURE__ */ new Map();
1428
+ let uncheckable = false;
1429
+ for (const [key, member] of members) {
1430
+ const cs = usableRelease(component, member.system, ctx);
1431
+ if (cs === void 0) {
1432
+ uncheckable = true;
1433
+ continue;
1434
+ }
1435
+ const concept = cs.concepts.get(member.code);
1436
+ if (concept?.status !== void 0 && !concept.status.active) continue;
1437
+ kept.set(key, member);
1438
+ }
1439
+ return { members: kept, uncheckable };
1440
+ }
1326
1441
  function underPath(d, prefix) {
1327
1442
  const out = { code: d.code, detail: d.detail };
1328
1443
  out.path = d.path === void 0 ? prefix : `${prefix}/${d.path}`;
@@ -1360,7 +1475,7 @@ function expandReferencedValueSets(urls, ctx, visited, path) {
1360
1475
  }
1361
1476
  return { members: acc ?? /* @__PURE__ */ new Map(), complete, diagnostics };
1362
1477
  }
1363
- function expandComponent(component, ctx, visited, path) {
1478
+ function expandComponent(component, ctx, visited, path, activeOnly = false) {
1364
1479
  const diagnostics = [];
1365
1480
  let complete = true;
1366
1481
  const { system, version, concept, filter, valueSet: valueSet2 } = component;
@@ -1370,9 +1485,18 @@ function expandComponent(component, ctx, visited, path) {
1370
1485
  let base = null;
1371
1486
  if (hasConcept) {
1372
1487
  base = /* @__PURE__ */ new Map();
1488
+ const release = usableRelease(component, system, ctx);
1489
+ let anyUndefined = false;
1373
1490
  for (const c of concept) {
1374
- base.set(codingKey(system, c.code), makeCoding(system, c.code, c.display, version));
1491
+ const defined = release?.concepts.get(c.code);
1492
+ if (release !== void 0 && defined === void 0) {
1493
+ anyUndefined = true;
1494
+ continue;
1495
+ }
1496
+ const display = c.display ?? defined?.display;
1497
+ base.set(codingKey(system, c.code), makeCoding(system, c.code, display, version));
1375
1498
  }
1499
+ if (anyUndefined) diagnostics.push(enumeratedUndefined(ENUMERATED_UNDEFINED_DETAIL, path));
1376
1500
  } else if (hasFilter || system !== void 0 && !hasVs) {
1377
1501
  if (system === void 0) {
1378
1502
  diagnostics.push(cannotExpand("intensional filter without a code system 'system'", path));
@@ -1383,6 +1507,27 @@ function expandComponent(component, ctx, visited, path) {
1383
1507
  diagnostics.push(cannotExpand("code system not supplied for intensional include", path));
1384
1508
  return { members: /* @__PURE__ */ new Map(), complete: false, diagnostics };
1385
1509
  }
1510
+ let stampedVersion = version;
1511
+ if (version !== void 0 && cs.version !== version) {
1512
+ if (cs.version === void 0) {
1513
+ diagnostics.push(
1514
+ cannotExpand(
1515
+ "supplied code system declares no version, so the component's declared version is unconfirmed",
1516
+ path
1517
+ )
1518
+ );
1519
+ complete = false;
1520
+ stampedVersion = void 0;
1521
+ } else {
1522
+ diagnostics.push(
1523
+ cannotExpand(
1524
+ "supplied code system version disagrees with the component's declared version",
1525
+ path
1526
+ )
1527
+ );
1528
+ return { members: /* @__PURE__ */ new Map(), complete: false, diagnostics };
1529
+ }
1530
+ }
1386
1531
  if (hasFilter) {
1387
1532
  const unsupported = unsupportedOps(filter);
1388
1533
  if (unsupported.length > 0) {
@@ -1395,7 +1540,7 @@ function expandComponent(component, ctx, visited, path) {
1395
1540
  if (matchesAllFilters(concpt, filter, sub).matched) {
1396
1541
  base.set(
1397
1542
  codingKey(system, concpt.code),
1398
- makeCoding(system, concpt.code, concpt.display, version)
1543
+ makeCoding(system, concpt.code, concpt.display, stampedVersion)
1399
1544
  );
1400
1545
  }
1401
1546
  }
@@ -1404,7 +1549,7 @@ function expandComponent(component, ctx, visited, path) {
1404
1549
  for (const concpt of cs.concepts.values()) {
1405
1550
  base.set(
1406
1551
  codingKey(system, concpt.code),
1407
- makeCoding(system, concpt.code, concpt.display, version)
1552
+ makeCoding(system, concpt.code, concpt.display, stampedVersion)
1408
1553
  );
1409
1554
  }
1410
1555
  }
@@ -1418,7 +1563,14 @@ function expandComponent(component, ctx, visited, path) {
1418
1563
  for (const [k, c] of [...base]) if (c.system !== system) base.delete(k);
1419
1564
  }
1420
1565
  }
1421
- return { members: base ?? /* @__PURE__ */ new Map(), complete, diagnostics };
1566
+ const members = base ?? /* @__PURE__ */ new Map();
1567
+ if (!activeOnly) return { members, complete, diagnostics };
1568
+ const screened = screenActive(members, component, ctx);
1569
+ if (screened.uncheckable) {
1570
+ diagnostics.push(cannotExpand(ACTIVITY_UNCHECKABLE_DETAIL, path));
1571
+ complete = false;
1572
+ }
1573
+ return { members: screened.members, complete, diagnostics };
1422
1574
  }
1423
1575
  function expandInternal(vs, ctx, visited) {
1424
1576
  if (vs.expansion !== void 0) {
@@ -1434,9 +1586,7 @@ function expandInternal(vs, ctx, visited) {
1434
1586
  let complete = true;
1435
1587
  if (vs.expansion.truncated) {
1436
1588
  complete = false;
1437
- diagnostics.push(
1438
- truncated("pre-computed expansion is incomplete (truncated or too-costly)", "expansion")
1439
- );
1589
+ diagnostics.push(truncated(expansionIncompleteDetail(vs.expansion.unclosed), "expansion"));
1440
1590
  }
1441
1591
  const out = {
1442
1592
  complete,
@@ -1450,8 +1600,9 @@ function expandInternal(vs, ctx, visited) {
1450
1600
  const included = /* @__PURE__ */ new Map();
1451
1601
  const diagnostics = [];
1452
1602
  let complete = true;
1603
+ const activeOnly = vs.compose.inactive === false;
1453
1604
  for (const [i, inc] of vs.compose.include.entries()) {
1454
- const r = expandComponent(inc, ctx, visited, `compose.include[${String(i)}]`);
1605
+ const r = expandComponent(inc, ctx, visited, `compose.include[${String(i)}]`, activeOnly);
1455
1606
  if (!r.complete) complete = false;
1456
1607
  for (const d of r.diagnostics) diagnostics.push(d);
1457
1608
  for (const [k, c] of r.members) if (!included.has(k)) included.set(k, c);
@@ -1489,14 +1640,27 @@ function cannotExpand2(detail, path) {
1489
1640
  if (path !== void 0) d.path = path;
1490
1641
  return Object.freeze(d);
1491
1642
  }
1643
+ function expansionIncompleteDetail2(unclosed) {
1644
+ return unclosed ? "pre-computed expansion is incomplete (marked unclosed: the value set is unbounded, so the snapshot is a sample of its membership)" : "pre-computed expansion is incomplete (truncated or too-costly)";
1645
+ }
1646
+ var ACTIVITY_UNCHECKABLE_DETAIL2 = "active-only value set: no usable code system release to check whether a selected code is active";
1647
+ function usableRelease2(component, system, ctx) {
1648
+ if (system === void 0) return void 0;
1649
+ const cs = ctx.codeSystems?.get(system);
1650
+ if (cs === void 0) return void 0;
1651
+ if (system === component.system && component.version !== void 0 && cs.version !== component.version) {
1652
+ return void 0;
1653
+ }
1654
+ return cs;
1655
+ }
1492
1656
  function underPath2(d, prefix) {
1493
1657
  const out = { code: d.code, detail: d.detail };
1494
1658
  out.path = d.path === void 0 ? prefix : `${prefix}/${d.path}`;
1495
1659
  return Object.freeze(out);
1496
1660
  }
1497
- function matchComponent(target, component, ctx, visited, path) {
1661
+ function matchComponent(target, component, ctx, visited, path, activeOnly = false) {
1498
1662
  const diagnostics = [];
1499
- const { system, concept, filter, valueSet: valueSet2 } = component;
1663
+ const { system, version, concept, filter, valueSet: valueSet2 } = component;
1500
1664
  const hasConcept = concept !== void 0;
1501
1665
  const hasFilter = filter !== void 0 && filter.length > 0;
1502
1666
  const hasVs = valueSet2 !== void 0 && valueSet2.length > 0;
@@ -1505,7 +1669,8 @@ function matchComponent(target, component, ctx, visited, path) {
1505
1669
  if (system !== void 0 && target.system !== void 0 && system !== target.system) {
1506
1670
  baseMatched = false;
1507
1671
  } else if (hasConcept) {
1508
- baseMatched = concept.some((c) => c.code === target.code);
1672
+ const release = usableRelease2(component, system, ctx);
1673
+ baseMatched = concept.some((c) => c.code === target.code) && (release === void 0 || release.concepts.get(target.code) !== void 0);
1509
1674
  } else if (hasFilter || system !== void 0 && !hasVs) {
1510
1675
  if (system === void 0) {
1511
1676
  diagnostics.push(cannotExpand2("intensional filter without a code system 'system'", path));
@@ -1517,6 +1682,24 @@ function matchComponent(target, component, ctx, visited, path) {
1517
1682
  diagnostics.push(cannotExpand2("code system not supplied for intensional include", path));
1518
1683
  baseMatched = false;
1519
1684
  baseComplete = false;
1685
+ } else if (version !== void 0 && cs.version !== version) {
1686
+ if (cs.version === void 0) {
1687
+ diagnostics.push(
1688
+ cannotExpand2(
1689
+ "supplied code system declares no version, so the component's declared version is unconfirmed",
1690
+ path
1691
+ )
1692
+ );
1693
+ } else {
1694
+ diagnostics.push(
1695
+ cannotExpand2(
1696
+ "supplied code system version disagrees with the component's declared version",
1697
+ path
1698
+ )
1699
+ );
1700
+ }
1701
+ baseMatched = false;
1702
+ baseComplete = false;
1520
1703
  } else if (hasFilter) {
1521
1704
  if (unsupportedOps(filter).length > 0) {
1522
1705
  diagnostics.push(cannotExpand2("unsupported filter operator", path));
@@ -1531,6 +1714,7 @@ function matchComponent(target, component, ctx, visited, path) {
1531
1714
  }
1532
1715
  }
1533
1716
  }
1717
+ if (system !== void 0 && target.system === void 0 && baseComplete) baseMatched = false;
1534
1718
  let refDefiniteFalse = false;
1535
1719
  let refUndetermined = false;
1536
1720
  if (hasVs) {
@@ -1563,6 +1747,17 @@ function matchComponent(target, component, ctx, visited, path) {
1563
1747
  return { matched: false, complete: true, diagnostics };
1564
1748
  }
1565
1749
  if (baseDefiniteTrue && refDefiniteTrue) {
1750
+ if (activeOnly) {
1751
+ const cs = usableRelease2(component, target.system, ctx);
1752
+ if (cs === void 0) {
1753
+ diagnostics.push(cannotExpand2(ACTIVITY_UNCHECKABLE_DETAIL2, path));
1754
+ return { matched: false, complete: false, diagnostics };
1755
+ }
1756
+ const concept2 = cs.concepts.get(target.code);
1757
+ if (concept2?.status !== void 0 && !concept2.status.active) {
1758
+ return { matched: false, complete: true, diagnostics };
1759
+ }
1760
+ }
1566
1761
  return { matched: true, complete: true, diagnostics };
1567
1762
  }
1568
1763
  return { matched: false, complete: false, diagnostics };
@@ -1586,7 +1781,7 @@ function validateInternal(target, vs, ctx, visited) {
1586
1781
  if (found) return decided(true, target);
1587
1782
  if (vs.expansion.truncated) {
1588
1783
  return undetermined(target, [
1589
- cannotExpand2("pre-computed expansion is incomplete (truncated or too-costly)", "expansion")
1784
+ cannotExpand2(expansionIncompleteDetail2(vs.expansion.unclosed), "expansion")
1590
1785
  ]);
1591
1786
  }
1592
1787
  return decided(false, target);
@@ -1595,8 +1790,16 @@ function validateInternal(target, vs, ctx, visited) {
1595
1790
  const diagnostics = [];
1596
1791
  let includedDefinite = false;
1597
1792
  let anyIncludeUndetermined = false;
1793
+ const activeOnly = vs.compose.inactive === false;
1598
1794
  for (const [i, inc] of vs.compose.include.entries()) {
1599
- const m = matchComponent(target, inc, ctx, visited, `compose.include[${String(i)}]`);
1795
+ const m = matchComponent(
1796
+ target,
1797
+ inc,
1798
+ ctx,
1799
+ visited,
1800
+ `compose.include[${String(i)}]`,
1801
+ activeOnly
1802
+ );
1600
1803
  for (const d of m.diagnostics) diagnostics.push(d);
1601
1804
  if (m.complete && m.matched) includedDefinite = true;
1602
1805
  if (!m.complete) anyIncludeUndetermined = true;
@@ -4776,7 +4979,7 @@ function approximateMatch(graph, query, options = {}) {
4776
4979
  }
4777
4980
 
4778
4981
  // src/index.ts
4779
- var VERSION = "0.0.12";
4982
+ var VERSION = "0.1.0";
4780
4983
 
4781
4984
  exports.COMPLEX_MAP_SOURCE_SYSTEM = COMPLEX_MAP_SOURCE_SYSTEM;
4782
4985
  exports.DIAGNOSTIC_CODES = DIAGNOSTIC_CODES;