fhirsmith 0.17.0 → 0.18.1

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 (131) hide show
  1. package/CHANGELOG.md +113 -0
  2. package/README.md +80 -13
  3. package/config-template.json +106 -1
  4. package/ips/ai-client.js +263 -0
  5. package/ips/composer.js +805 -0
  6. package/ips/editor-schema.js +289 -0
  7. package/ips/editor.js +290 -0
  8. package/ips/fhirpath-ips.js +397 -0
  9. package/ips/ips-codeable-concepts.md +1742 -0
  10. package/ips/ips-template.html +125 -0
  11. package/ips/ips-valuesets.js +159 -0
  12. package/ips/ips.js +1736 -0
  13. package/ips/membership.js +179 -0
  14. package/ips/pipeline.js +1012 -0
  15. package/ips/presenter.js +974 -0
  16. package/ips/profile-editor.js +578 -0
  17. package/ips/profiles.js +506 -0
  18. package/ips/readme.md +460 -0
  19. package/ips/render.js +1482 -0
  20. package/ips/search-params.js +83 -0
  21. package/ips/search.js +667 -0
  22. package/ips/share.js +178 -0
  23. package/ips/store.js +650 -0
  24. package/ips/terminology.js +395 -0
  25. package/ips/tx-client.js +202 -0
  26. package/ips/valuesets/CodeSystem-absent-unknown-uv-ips.json +206 -0
  27. package/ips/valuesets/ValueSet-absent-or-unknown-immunizations-uv-ips.json +59 -0
  28. package/ips/valuesets/ValueSet-ips-allergy-code.json +59 -0
  29. package/ips/valuesets/ValueSet-ips-allergy-manifestation.json +356 -0
  30. package/ips/valuesets/ValueSet-ips-allergy-substance.json +37 -0
  31. package/ips/valuesets/ValueSet-ips-body-site.json +26 -0
  32. package/ips/valuesets/ValueSet-ips-clinical-finding-or-none.json +35 -0
  33. package/ips/valuesets/ValueSet-ips-clinical-finding.json +26 -0
  34. package/ips/valuesets/ValueSet-ips-condition-code.json +72 -0
  35. package/ips/valuesets/ValueSet-ips-condition-severity.json +30 -0
  36. package/ips/valuesets/ValueSet-ips-condition-stage.json +26 -0
  37. package/ips/valuesets/ValueSet-ips-exposure-route.json +26 -0
  38. package/ips/valuesets/ValueSet-ips-imaging-procedure.json +25 -0
  39. package/ips/valuesets/ValueSet-ips-immunization-status-reason.json +43 -0
  40. package/ips/valuesets/ValueSet-ips-international-codes.json +25 -0
  41. package/ips/valuesets/ValueSet-ips-medication-ingredient.json +26 -0
  42. package/ips/valuesets/ValueSet-ips-medication-status-reason.json +37 -0
  43. package/ips/valuesets/ValueSet-ips-medication.json +50 -0
  44. package/ips/valuesets/ValueSet-ips-observation-code.json +18 -0
  45. package/ips/valuesets/ValueSet-ips-observation-interpretation.json +18 -0
  46. package/ips/valuesets/ValueSet-ips-procedure-followup.json +51 -0
  47. package/ips/valuesets/ValueSet-ips-procedure-outcome.json +30 -0
  48. package/ips/valuesets/ValueSet-ips-procedure-reason.json +37 -0
  49. package/ips/valuesets/ValueSet-ips-procedure.json +116 -0
  50. package/ips/valuesets/ValueSet-ips-route.json +46 -0
  51. package/ips/valuesets/ValueSet-ips-target-disease.json +171 -0
  52. package/ips/valuesets/ValueSet-ips-vaccine.json +31 -0
  53. package/ips/valuesets/elements.json +44 -0
  54. package/library/daily-report.js +432 -0
  55. package/library/fhir-search-values.js +138 -0
  56. package/library/html-server.js +32 -23
  57. package/library/mailer.js +77 -0
  58. package/library/narrative-check.js +21 -2
  59. package/library/npm-audit.js +25 -0
  60. package/library/package-manager.js +4 -2
  61. package/library/sqlite-session-store.js +29 -1
  62. package/package.json +8 -3
  63. package/packages/packages.js +34 -3
  64. package/publisher/publisher.js +29 -0
  65. package/registry/auth-store.js +366 -0
  66. package/registry/auth.js +872 -0
  67. package/registry/crawler.js +41 -1
  68. package/registry/documentation/api.md +162 -0
  69. package/registry/documentation/configuration.md +93 -0
  70. package/registry/documentation/documents.md +74 -0
  71. package/registry/documentation/internals.md +151 -0
  72. package/registry/documentation/readme.md +100 -0
  73. package/registry/documentation/security.md +199 -0
  74. package/registry/documents.js +309 -0
  75. package/registry/openapi.yaml +30 -0
  76. package/registry/registry-template.html +1 -1
  77. package/registry/registry.js +230 -44
  78. package/registry/uptime.js +154 -0
  79. package/server.js +105 -44
  80. package/shl/shl.js +64 -9
  81. package/stats-db.js +93 -0
  82. package/stats.js +158 -1
  83. package/testing/search.js +2 -108
  84. package/testing/store.js +5 -0
  85. package/testing/testing.js +20 -0
  86. package/token/token-template.html +5 -4
  87. package/token/token.js +17 -10
  88. package/translations/Messages.properties +10 -0
  89. package/tx/README.md +52 -0
  90. package/tx/closure/closure-store.js +14 -0
  91. package/tx/cs/cs-api.js +92 -14
  92. package/tx/cs/cs-cs.js +64 -3
  93. package/tx/cs/cs-icd11.js +366 -112
  94. package/tx/cs/cs-icf.js +431 -0
  95. package/tx/cs/cs-loinc.js +296 -27
  96. package/tx/cs/cs-omop.js +10 -3
  97. package/tx/cs/cs-snomed.js +1210 -80
  98. package/tx/cs/cs-ucum.js +6 -0
  99. package/tx/cs/icd11-expressions.js +191 -78
  100. package/tx/importers/csv-records.js +35 -0
  101. package/tx/importers/export-icd11-codesystem.js +58 -12
  102. package/tx/importers/icd11-schema.md +9 -5
  103. package/tx/importers/import-loinc.module.js +177 -41
  104. package/tx/importers/import-sct.module.js +66 -1
  105. package/tx/importers/subset-loinc.module.js +25 -6
  106. package/tx/library/codesystem.js +4 -1
  107. package/tx/library/designations.js +23 -30
  108. package/tx/library.js +98 -4
  109. package/tx/openapi-operations.js +6 -1
  110. package/tx/params.js +8 -1
  111. package/tx/provider.js +24 -6
  112. package/tx/sct/documentation.md +251 -0
  113. package/tx/sct/ecl.js +758 -46
  114. package/tx/sct/expressions.js +113 -52
  115. package/tx/sct/structures.js +8 -2
  116. package/tx/syndication/syndication-feed.js +218 -0
  117. package/tx/syndication/syndication-store.js +415 -0
  118. package/tx/tx-html.js +15 -1
  119. package/tx/tx.fhir.org.yml +3 -4
  120. package/tx/tx.js +144 -2
  121. package/tx/vs/vs-vsac.js +25 -6
  122. package/tx/workers/closure.js +20 -0
  123. package/tx/workers/expand.js +35 -8
  124. package/tx/workers/lookup.js +61 -6
  125. package/tx/workers/subsumes.js +1 -1
  126. package/tx/workers/translate.js +166 -15
  127. package/tx/workers/validate.js +138 -6
  128. package/utilities/make-sct-loinc-subset.js +250 -0
  129. package/utilities/make-sct-test-extension.js +264 -0
  130. package/xig/xig.js +32 -0
  131. package/registry/readme.md +0 -124
@@ -0,0 +1,431 @@
1
+ /**
2
+ * ICF (http://hl7.org/fhir/sid/icf).
3
+ *
4
+ * The content is an ordinary CodeSystem resource (fhir.tx.support has 2001a and 2017a), so
5
+ * this extends FhirCodeSystemProvider rather than being a factory with its own data:
6
+ * FhirCodeSystemProvider.create() picks this class by url.
7
+ *
8
+ * What ICF adds is a grammar: a category may be followed by '.' and qualifier digits, one per
9
+ * position, and what the positions are depends on the component - the first letter of the code
10
+ * (see QUALIFIERS):
11
+ * b body functions extent
12
+ * s body structures extent, nature, localization
13
+ * d activities/participation performance, capacity, then optionally capacity with
14
+ * assistance and performance without assistance
15
+ * e environmental factors one: a barrier after '.', or a facilitator after '+' (in
16
+ * place of the '.') - e310.2, e310+3
17
+ * Each digit must be a value of its position's scale, which the code system carries as the
18
+ * concepts q1-n (extent of impairment), q2-n (nature), q3-n (localization), q4-n (extent of
19
+ * problem), q5-n (barrier) and q6-n (facilitator, which has no 9), under the group concepts
20
+ * q1..q6. A position cannot be omitted; 8 (not specified) is
21
+ * what fills it, and 8 says nothing in the display. Each position is reported by $lookup as the
22
+ * property named in QUALIFIERS.
23
+ *
24
+ * Qualifiers are not mandatory at the code system level: a plain category is still a valid
25
+ * code. Qualified codes cannot be enumerated, so the code system is not closed.
26
+ *
27
+ * Value sets control qualifiers with filters:
28
+ * <qualifier property> exists true|false - the qualifier is required / not allowed. 8 (not
29
+ * specified) counts as absent, so it never satisfies exists = true, and always satisfies
30
+ * exists = false. A qualifier the code's component does not have is absent
31
+ * qualified = true|false - qualified codes only / no qualified codes at all (b1100.888
32
+ * is qualified, though it says nothing)
33
+ * These combine with the other filters in an include as usual. A qualified code is in any
34
+ * other filter's results when its category is.
35
+ */
36
+
37
+ const { CodeSystem } = require('../library/codesystem');
38
+ const { Issue } = require('../library/operation-outcome');
39
+ const { FhirCodeSystemProvider, FhirCodeSystemProviderContext, FhirCodeSystemProviderFilterContext } = require('./cs-cs');
40
+
41
+ /**
42
+ * The qualifier positions of each component, by the separator that introduces them: the
43
+ * property that reports the qualifier, the scale (group concept) its values come from, and the
44
+ * label that introduces its value in the display (none where the value says what it is).
45
+ * Only environmental factors take '+', which makes the qualifier a facilitator.
46
+ */
47
+ const QUALIFIERS = {
48
+ b: {
49
+ '.': [{ property: 'extent', scale: 'q1' }]
50
+ },
51
+ s: {
52
+ '.': [
53
+ { property: 'extent', scale: 'q1' },
54
+ { property: 'nature', scale: 'q2' },
55
+ { property: 'localization', scale: 'q3' }
56
+ ]
57
+ },
58
+ d: {
59
+ '.': [
60
+ { property: 'performance', scale: 'q4', label: 'performance' },
61
+ { property: 'capacity', scale: 'q4', label: 'capacity' },
62
+ { property: 'capacity-with-assistance', scale: 'q4', label: 'capacity with assistance' },
63
+ { property: 'performance-without-assistance', scale: 'q4', label: 'performance without assistance' }
64
+ ]
65
+ },
66
+ e: {
67
+ '.': [{ property: 'barrier', scale: 'q5' }],
68
+ '+': [{ property: 'facilitator', scale: 'q6' }]
69
+ }
70
+ };
71
+
72
+ const COMPONENT_NAMES = { b: 'body functions', s: 'body structures', d: 'activities and participation', e: 'environmental factors' };
73
+ const COMPONENT_CODES = { b: 'a body functions (b) code', s: 'a body structures (s) code', d: 'an activities and participation (d) code', e: 'an environmental factors (e) code' };
74
+
75
+ const QUALIFIER_PROPERTIES = new Set(Object.values(QUALIFIERS).flatMap(c => Object.values(c)).flatMap(q => q.map(p => p.property)));
76
+
77
+ /**
78
+ * A category with qualifiers. code and concept are the category's, so whatever is inherited
79
+ * from FhirCodeSystemProvider describes the category; qualifiedCode is the code as a whole.
80
+ */
81
+ class IcfQualifiedContext extends FhirCodeSystemProviderContext {
82
+ /**
83
+ * @param {string} qualifiedCode - e.g. s7300.183
84
+ * @param {Object} concept - the category's concept
85
+ * @param {{position: number, digit: string, code: string, concept: Object, property: string}[]} qualifiers
86
+ */
87
+ constructor(qualifiedCode, concept, qualifiers) {
88
+ super(concept.code, concept);
89
+ this.qualifiedCode = qualifiedCode;
90
+ this.qualifiers = qualifiers;
91
+ }
92
+ }
93
+
94
+ /**
95
+ * The result of a qualifier filter. test() decides membership for a located code, qualified or
96
+ * not. concepts (what iteration walks) holds only the plain categories that pass, since
97
+ * qualified codes cannot be listed; closesQualified marks the one filter (qualified = false)
98
+ * that admits no qualified code, so an include with it is closed again.
99
+ */
100
+ class IcfQualifierFilter extends FhirCodeSystemProviderFilterContext {
101
+ constructor(test, closesQualified) {
102
+ super();
103
+ this.test = test;
104
+ this.closesQualified = closesQualified;
105
+ }
106
+ }
107
+
108
+ function propertyValue(concept, code) {
109
+ const p = (concept.property || []).find(p => p.code === code);
110
+ return p ? (p.valueCode ?? p.valueBoolean ?? p.valueString) : undefined;
111
+ }
112
+
113
+ class IcfCodeSystemProvider extends FhirCodeSystemProvider {
114
+
115
+ /** Qualified codes are a grammar: they cannot all be listed. */
116
+ isNotClosed() {
117
+ return true;
118
+ }
119
+
120
+ // -- locating -------------------------------------------------------------------------
121
+
122
+ /**
123
+ * A code in the code system, or a selectable category followed by '.' (or, for an
124
+ * environmental factor, '+') and its qualifier digits. An unknown category is a plain miss
125
+ * (no message); a known category with bad qualifiers says what is wrong with them.
126
+ */
127
+ async locate(code) {
128
+ const base = await super.locate(code);
129
+ if (base.context || typeof code !== 'string') {
130
+ return base;
131
+ }
132
+ const sep = code.search(/[.+]/);
133
+ if (sep <= 0) {
134
+ return base;
135
+ }
136
+ const separator = code[sep];
137
+ const stemCode = code.substring(0, sep);
138
+ const digits = code.substring(sep + 1);
139
+ const stem = this.codeSystem.getConceptByCode(stemCode);
140
+ if (!stem) {
141
+ return { context: null, message: undefined };
142
+ }
143
+ if (!this.#isQualifiable(stem)) {
144
+ return { context: null, message: `The ICF code '${code}' is not valid: qualifiers can only follow a selectable ICF category, and '${stemCode}' is not one` };
145
+ }
146
+ const component = stemCode[0];
147
+ const positions = (QUALIFIERS[component] || {})[separator];
148
+ if (!positions) {
149
+ return { context: null, message: separator === '+'
150
+ ? `The ICF code '${code}' is not valid: only an environmental factor (e) can be a facilitator, written with '+'`
151
+ : `The ICF code '${code}' is not valid: qualifiers on ${COMPONENT_NAMES[component] || 'these'} codes are not supported` };
152
+ }
153
+ const names = positions.map(q => q.property).join(', ');
154
+ if (!new RegExp(`^[0-9]{1,${positions.length}}$`).test(digits)) {
155
+ return { context: null, message: positions.length === 1
156
+ ? `The ICF code '${code}' is not valid: ${COMPONENT_CODES[component]} takes one qualifier digit after the '${separator}' (${names})`
157
+ : `The ICF code '${code}' is not valid: ${COMPONENT_CODES[component]} takes 1 to ${positions.length} qualifier digits after the '${separator}' (${names})` };
158
+ }
159
+ const qualifiers = [];
160
+ for (let i = 0; i < digits.length; i++) {
161
+ const position = positions[i];
162
+ const concept = this.codeSystem.getConceptByCode(position.scale + '-' + digits[i]);
163
+ if (!concept) {
164
+ return { context: null, message: `The ICF code '${code}' is not valid: '${digits[i]}' is not a valid value for qualifier ${i + 1} (${position.property})` };
165
+ }
166
+ qualifiers.push({ position: i + 1, digit: digits[i], code: concept.code, concept, property: position.property, label: position.label });
167
+ }
168
+ return { context: new IcfQualifiedContext(code, stem, qualifiers), message: null };
169
+ }
170
+
171
+ /** A selectable primary category: not a heading, and not a qualifier value. */
172
+ #isQualifiable(concept) {
173
+ const kind = propertyValue(concept, 'kind');
174
+ if (kind !== undefined && kind !== 'primary') {
175
+ return false;
176
+ }
177
+ return propertyValue(concept, 'notSelectable') !== true;
178
+ }
179
+
180
+ async #resolve(context) {
181
+ if (typeof context === 'string') {
182
+ const r = await this.locate(context);
183
+ if (!r.context) {
184
+ throw new Error(r.message ? r.message : `Code '${context}' not found in CodeSystem '${this.system()}'`);
185
+ }
186
+ return r.context;
187
+ }
188
+ return context || null;
189
+ }
190
+
191
+ // -- concept information --------------------------------------------------------------
192
+
193
+ async code(context) {
194
+ const ctxt = await this.#resolve(context);
195
+ return ctxt instanceof IcfQualifiedContext ? ctxt.qualifiedCode : super.code(ctxt);
196
+ }
197
+
198
+ /** The category's display, then the qualifiers' displays, leaving out any 8 (not specified). */
199
+ async display(context) {
200
+ const ctxt = await this.#resolve(context);
201
+ const display = await super.display(ctxt);
202
+ if (!(ctxt instanceof IcfQualifiedContext)) {
203
+ return display;
204
+ }
205
+ const parts = ctxt.qualifiers.filter(q => q.digit !== '8')
206
+ .map(q => (q.label ? q.label + ' ' : '') + (q.concept.display || '').trim());
207
+ return parts.length > 0 ? display + ': ' + parts.join(', ') : display;
208
+ }
209
+
210
+ /** A qualified code has one designation: its display. The category's translations do not describe it. */
211
+ async designations(context, displays) {
212
+ const ctxt = await this.#resolve(context);
213
+ if (!(ctxt instanceof IcfQualifiedContext)) {
214
+ return super.designations(ctxt, displays);
215
+ }
216
+ const lang = this.defaultLanguage ? this.defaultLanguage.toString() : null;
217
+ displays.addDesignation(true, 'active', lang, CodeSystem.makeUseForDisplay(), await this.display(ctxt));
218
+ }
219
+
220
+ /**
221
+ * A qualified code has the category's properties, less parent (its parent is the category),
222
+ * plus one property per qualifier, named by the qualifier's scale (extent, nature, localization).
223
+ */
224
+ async properties(context) {
225
+ const ctxt = await this.#resolve(context);
226
+ const props = await super.properties(ctxt);
227
+ if (!(ctxt instanceof IcfQualifiedContext)) {
228
+ return props;
229
+ }
230
+ const result = props.filter(p => p.code !== 'parent');
231
+ for (const q of ctxt.qualifiers) {
232
+ result.push({ code: q.property, valueCode: q.code, definition: this.getPropertyDefinition(this.codeSystem.jsonObj, q.property) });
233
+ }
234
+ return result;
235
+ }
236
+
237
+ async parent(context) {
238
+ const ctxt = await this.#resolve(context);
239
+ return ctxt instanceof IcfQualifiedContext ? ctxt.code : super.parent(ctxt);
240
+ }
241
+
242
+ async sameConcept(a, b) {
243
+ const ca = await this.#resolve(a);
244
+ const cb = await this.#resolve(b);
245
+ if (!ca || !cb) {
246
+ return false;
247
+ }
248
+ return (await this.code(ca)) === (await this.code(cb));
249
+ }
250
+
251
+ /** As for any concept, but a qualified code has no children. */
252
+ async extendLookup(ctxt, props, params) {
253
+ const start = params.length;
254
+ await super.extendLookup(ctxt, props, params);
255
+ if (ctxt instanceof IcfQualifiedContext) {
256
+ for (let i = params.length - 1; i >= start; i--) {
257
+ if (params[i].name === 'property' && params[i].part?.[0]?.valueCode === 'child') {
258
+ params.splice(i, 1);
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ // -- hierarchy ------------------------------------------------------------------------
265
+
266
+ /**
267
+ * Whether `general` subsumes `specific` or is it: a qualified code sits under its category,
268
+ * and so under everything the category does. Qualified codes are related only to themselves.
269
+ */
270
+ #isA(specific, general) {
271
+ if (general instanceof IcfQualifiedContext) {
272
+ return specific instanceof IcfQualifiedContext && specific.qualifiedCode === general.qualifiedCode;
273
+ }
274
+ const code = specific.code; // the category, for a qualified code
275
+ if (code === general.code) {
276
+ return true;
277
+ }
278
+ return this.codeSystem.isDescendantOf(code, general.code);
279
+ }
280
+
281
+ async locateIsA(code, parent, disallowSelf = false) {
282
+ const codeResult = await this.locate(code);
283
+ if (!codeResult.context) {
284
+ return codeResult;
285
+ }
286
+ const parentResult = await this.locate(parent);
287
+ if (!(codeResult.context instanceof IcfQualifiedContext) && !(parentResult.context instanceof IcfQualifiedContext)) {
288
+ return super.locateIsA(code, parent, disallowSelf);
289
+ }
290
+ if (!parentResult.context) {
291
+ return { context: null, message: `Parent code '${parent}' not found in CodeSystem '${this.system()}'` };
292
+ }
293
+ if (code === parent) {
294
+ return disallowSelf ? { context: null, message: `Code '${code}' cannot be the same as its parent` } : codeResult;
295
+ }
296
+ if (this.#isA(codeResult.context, parentResult.context)) {
297
+ return codeResult;
298
+ }
299
+ return { context: null, message: `Code '${code}' is not a descendant of '${parent}'` };
300
+ }
301
+
302
+ async subsumesTest(codeA, codeB) {
303
+ const a = (await this.locate(codeA)).context;
304
+ if (!a) {
305
+ throw new Error(`Unknown Code "${codeA}"`);
306
+ }
307
+ const b = (await this.locate(codeB)).context;
308
+ if (!b) {
309
+ throw new Error(`Unknown Code "${codeB}"`);
310
+ }
311
+ if (!(a instanceof IcfQualifiedContext) && !(b instanceof IcfQualifiedContext)) {
312
+ return super.subsumesTest(codeA, codeB);
313
+ }
314
+ if ((await this.code(a)) === (await this.code(b))) {
315
+ return 'equivalent';
316
+ }
317
+ if (this.#isA(b, a)) {
318
+ return 'subsumes';
319
+ }
320
+ if (this.#isA(a, b)) {
321
+ return 'subsumed-by';
322
+ }
323
+ return 'not-subsumed';
324
+ }
325
+
326
+ /** A qualified code has no children. */
327
+ async iterator(context) {
328
+ if (context) {
329
+ const ctxt = await this.#resolve(context);
330
+ if (ctxt instanceof IcfQualifiedContext) {
331
+ return { type: 'children', parentCode: ctxt.qualifiedCode, codes: [], current: 0, total: 0 };
332
+ }
333
+ return super.iterator(ctxt);
334
+ }
335
+ return super.iterator(context);
336
+ }
337
+
338
+ // -- filters --------------------------------------------------------------------------
339
+
340
+ #isQualifierFilter(prop, op) {
341
+ return (op === 'exists' && QUALIFIER_PROPERTIES.has(prop)) || (prop === 'qualified' && op === '=');
342
+ }
343
+
344
+ async doesFilter(prop, op, value) {
345
+ if (this.#isQualifierFilter(prop, op)) {
346
+ return value === 'true' || value === 'false';
347
+ }
348
+ return super.doesFilter(prop, op, value);
349
+ }
350
+
351
+ async filter(filterContext, forIteration, prop, op, value, fc) {
352
+ if (!this.#isQualifierFilter(prop, op)) {
353
+ return super.filter(filterContext, forIteration, prop, op, value, fc);
354
+ }
355
+ if (value !== 'true' && value !== 'false') {
356
+ throw new Issue('error', 'exception', null, 'FILTER_NOT_UNDERSTOOD',
357
+ this.opContext.i18n.translate('FILTER_NOT_UNDERSTOOD', this.opContext.langs, [prop, op, value]), 'vs-invalid', 422);
358
+ }
359
+ const want = value === 'true';
360
+ let test;
361
+ if (prop === 'qualified') {
362
+ test = (ctxt) => (ctxt instanceof IcfQualifiedContext) === want;
363
+ } else {
364
+ test = (ctxt) => {
365
+ const q = ctxt instanceof IcfQualifiedContext ? ctxt.qualifiers.find(q => q.property === prop) : undefined;
366
+ return (q !== undefined && q.digit !== '8') === want;
367
+ };
368
+ }
369
+ const results = new IcfQualifierFilter(test, prop === 'qualified' && !want);
370
+ // the plain categories that pass: none for exists = true or qualified = true
371
+ for (const code of this.codeSystem.getAllCodes()) {
372
+ const concept = this.codeSystem.getConceptByCode(code);
373
+ if (concept && this.#isPrimary(concept) && test(new FhirCodeSystemProviderContext(code, concept))) {
374
+ results.add(concept);
375
+ }
376
+ }
377
+ if (!filterContext.filters) {
378
+ filterContext.filters = [];
379
+ }
380
+ filterContext.filters.push(results);
381
+ return results;
382
+ }
383
+
384
+ /** A primary ICF category (heading or not), as opposed to a qualifier value. */
385
+ #isPrimary(concept) {
386
+ const kind = propertyValue(concept, 'kind');
387
+ return kind === undefined || kind === 'primary';
388
+ }
389
+
390
+ /** Every include can admit qualified codes, unless it has a qualified = false filter. */
391
+ async filtersNotClosed(filterContext) {
392
+ return !(filterContext.filters || []).some(f => f instanceof IcfQualifierFilter && f.closesQualified);
393
+ }
394
+
395
+ async filterCheck(filterContext, set, concept) {
396
+ if (set instanceof IcfQualifierFilter && concept instanceof IcfQualifiedContext) {
397
+ return set.test(concept) ? true : `Code '${concept.qualifiedCode}' does not meet the qualifier rules`;
398
+ }
399
+ return super.filterCheck(filterContext, set, concept);
400
+ }
401
+
402
+ /**
403
+ * A qualified code is in a filter's results when its category is: it is-a the category,
404
+ * and has the category's properties. (filterCheck needs nothing extra: it checks the
405
+ * context's concept, which is the category's.)
406
+ */
407
+ async filterLocate(filterContext, set, code) {
408
+ if (set instanceof IcfQualifierFilter) {
409
+ const loc = await this.locate(code);
410
+ if (!loc.context) {
411
+ return null;
412
+ }
413
+ return (await this.filterCheck(filterContext, set, loc.context)) === true ? loc.context : null;
414
+ }
415
+ const found = await super.filterLocate(filterContext, set, code);
416
+ if (found) {
417
+ return found;
418
+ }
419
+ const loc = await this.locate(code);
420
+ if (!(loc.context instanceof IcfQualifiedContext)) {
421
+ return found;
422
+ }
423
+ const stem = await super.filterLocate(filterContext, set, loc.context.code);
424
+ return stem && typeof stem !== 'string' ? loc.context : null;
425
+ }
426
+ }
427
+
428
+ module.exports = {
429
+ IcfCodeSystemProvider,
430
+ IcfQualifiedContext
431
+ };