fhirsmith 0.18.1 → 1.0.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/ACCESSIBILITY.md +119 -0
- package/CHANGELOG.md +190 -9
- package/CONFIGURATION.md +305 -0
- package/LICENSE +29 -0
- package/README.md +29 -122
- package/cluster.js +1220 -0
- package/cluster.md +707 -0
- package/config-template.json +77 -8
- package/extension-tracker/extension-tracker-template.html +5 -8
- package/extension-tracker/extension-tracker.js +0 -2
- package/folder/folder.js +7 -6
- package/ips/analysis.js +560 -0
- package/ips/ips-template.html +5 -8
- package/ips/ips.js +331 -33
- package/ips/jurisdictions.js +65 -0
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +131 -17
- package/ips/presenter.js +1 -1
- package/ips/profile-editor.js +283 -4
- package/ips/readme.md +170 -20
- package/ips/render.js +769 -109
- package/ips/scripts.js +46 -0
- package/ips/store.js +123 -11
- package/ips/structure.js +247 -0
- package/ips/terminology.js +1 -1
- package/library/body-limit.js +22 -0
- package/library/cache-generation.js +15 -0
- package/library/cgroup-memory.js +133 -1
- package/library/cluster-counts.js +121 -0
- package/library/cluster-events.js +153 -0
- package/library/cluster-metrics.js +193 -0
- package/library/cluster-status.js +468 -0
- package/library/config-reader.js +96 -0
- package/library/daily-report.js +100 -2
- package/library/html-server.js +27 -1
- package/library/i18nsupport.js +23 -1
- package/library/keep-alive.js +18 -0
- package/library/languages.js +17 -3
- package/library/logger.js +31 -0
- package/library/memory-guard.js +428 -0
- package/library/native-memory.js +52 -0
- package/library/npm-audit.js +51 -25
- package/library/openapi-doc.js +7 -5
- package/library/package-manager.js +8 -0
- package/library/role.js +197 -0
- package/library/server-credentials.js +169 -0
- package/library/sqlite-utilities.js +59 -0
- package/library/ssrf-guard.js +100 -0
- package/library/static-response-stats.js +16 -0
- package/library/version-utilities.js +50 -0
- package/library/worker-count.js +192 -0
- package/nginx.md +30 -11
- package/npmprojector/indexer.js +18 -10
- package/package.json +11 -8
- package/packages/feed-check-page.js +117 -0
- package/packages/feed-check.js +279 -0
- package/packages/npm-check.js +57 -3
- package/packages/npm-signing.js +219 -0
- package/packages/openapi.yaml +207 -1
- package/packages/package-crawler.js +322 -41
- package/packages/packages-template.html +6 -8
- package/packages/packages.js +604 -57
- package/packages/readme.md +113 -1
- package/publisher/publisher-template.html +2 -5
- package/publisher/publisher.js +121 -110
- package/publisher/readme.md +75 -0
- package/registry/api.js +179 -1
- package/registry/crawler.js +86 -108
- package/registry/documentation/api.md +68 -2
- package/registry/documentation/configuration.md +34 -0
- package/registry/documentation/internals.md +4 -1
- package/registry/documentation/readme.md +3 -2
- package/registry/model.js +8 -0
- package/registry/openapi.yaml +84 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +58 -6
- package/root-bare-template.html +7 -4
- package/root-template.html +5 -8
- package/server.js +398 -198
- package/shl/shl.js +38 -37
- package/static/a11y/a11y-check.js +832 -0
- package/static/a11y/axe.min.js +12 -0
- package/static/assets/css/bootstrap-fhir.css +15 -15
- package/static/assets/images/flags/au.svg +18 -0
- package/static/assets/images/flags/unknown.svg +11 -0
- package/static/assets/images/flags/us.svg +63 -0
- package/static/assets/images/icon_audit.png +0 -0
- package/static/assets/images/icon_clinical.png +0 -0
- package/static/assets/images/icon_document.png +0 -0
- package/static/assets/images/icon_external.png +0 -0
- package/static/assets/images/icon_medication.png +0 -0
- package/static/assets/images/icon_organization.png +0 -0
- package/static/assets/images/icon_other.png +0 -0
- package/static/assets/images/icon_person.png +0 -0
- package/static/assets/images/icon_section.png +0 -0
- package/stats-db.js +45 -0
- package/stats.js +197 -10
- package/testing/testing-template.html +5 -8
- package/token/nginx_passport_setup.md +1 -1
- package/token/security_guide.md +1 -1
- package/token/token-template.html +2 -5
- package/token/token.js +40 -36
- package/translations/Messages.properties +19 -0
- package/tx/README.md +158 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +50 -16
- package/tx/cs/cs-api.js +284 -19
- package/tx/cs/cs-api.md +41 -4
- package/tx/cs/cs-areacode.js +1 -1
- package/tx/cs/cs-country.js +17 -5
- package/tx/cs/cs-cpt.js +33 -16
- package/tx/cs/cs-cs.js +199 -24
- package/tx/cs/cs-currency.js +1 -1
- package/tx/cs/cs-icd11.js +21 -13
- package/tx/cs/cs-icf.js +22 -3
- package/tx/cs/cs-lang.js +2 -2
- package/tx/cs/cs-loinc.js +193 -67
- package/tx/cs/cs-mimetypes.js +12 -2
- package/tx/cs/cs-ndc.js +40 -25
- package/tx/cs/cs-omop.js +54 -29
- package/tx/cs/cs-rxnorm.js +300 -196
- package/tx/cs/cs-snomed.js +861 -95
- package/tx/cs/cs-ucum.js +14 -4
- package/tx/cs/cs-unii.js +18 -14
- package/tx/cs/cs-virtual.js +210 -0
- package/tx/ecosystem-resolver.js +413 -0
- package/tx/html/cluster-dash-metrics.liquid +26 -0
- package/tx/html/cluster-home-metrics.liquid +31 -0
- package/tx/html/cluster-metrics-script.liquid +251 -0
- package/tx/html/codesystem-operations.liquid +3 -3
- package/tx/html/ecl-panel.liquid +1 -1
- package/tx/html/external-metrics.liquid +17 -0
- package/tx/html/operations-form.liquid +17 -17
- package/tx/html/search-form.liquid +11 -11
- package/tx/html/status-line.liquid +58 -0
- package/tx/html/status-panel.liquid +140 -0
- package/tx/html/status-tasks.liquid +25 -0
- package/tx/html/tx-template.html +2 -5
- package/tx/importers/atc-to-fhir.js +17 -16
- package/tx/importers/export-icd11-codesystem.js +3 -2
- package/tx/importers/import-icd11.js +4 -3
- package/tx/importers/import-loinc.module.js +118 -56
- package/tx/importers/import-ndc.module.js +56 -44
- package/tx/importers/import-rxnorm.module.js +118 -79
- package/tx/importers/import-sct.module.js +125 -61
- package/tx/importers/import-unii.module.js +37 -36
- package/tx/importers/subset-loinc.module.js +49 -48
- package/tx/importers/subset-rxnorm.module.js +29 -28
- package/tx/importers/terminal.js +18 -0
- package/tx/importers/tx-import-base.js +13 -12
- package/tx/importers/tx-import-settings.js +5 -4
- package/tx/importers/tx-import.js +53 -52
- package/tx/library/canonical-resource.js +49 -0
- package/tx/library/codesystem.js +94 -5
- package/tx/library/designations.js +45 -2
- package/tx/library/extensions.js +13 -7
- package/tx/library/implicit-valuesets.js +66 -0
- package/tx/library/renderer.js +77 -21
- package/tx/library/sqlite-utilities.js +2 -0
- package/tx/library/tho-precedence.js +40 -0
- package/tx/library/ucum-parsers.js +11 -1
- package/tx/library/wait-for-cache.js +43 -0
- package/tx/library.js +321 -22
- package/tx/openapi-operations.js +6 -4
- package/tx/operation-context.js +460 -99
- package/tx/params.js +25 -0
- package/tx/problems.js +3 -5
- package/tx/provider.js +314 -67
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/documentation.md +45 -21
- package/tx/sct/ecl.js +81 -17
- package/tx/sct/editions.js +39 -0
- package/tx/sct/expressions.js +10 -1
- package/tx/sct/load-config.js +107 -0
- package/tx/sct/mmap.js +109 -0
- package/tx/sct/structures.js +175 -15
- package/tx/sct/text-index.js +5 -1
- package/tx/syndication/syndication-feed.js +2 -2
- package/tx/tx-html.js +44 -8
- package/tx/tx.fhir.org.yml +8 -8
- package/tx/tx.js +282 -80
- package/tx/usage-tracker.js +39 -11
- package/tx/vs/vs-api.js +19 -0
- package/tx/vs/vs-database.js +386 -62
- package/tx/vs/vs-package.js +44 -17
- package/tx/vs/vs-vsac.js +649 -101
- package/tx/workers/batch-validate.js +37 -10
- package/tx/workers/batch.js +6 -1
- package/tx/workers/cache-control.js +15 -4
- package/tx/workers/closure.js +156 -13
- package/tx/workers/compare.js +8 -5
- package/tx/workers/expand.js +1173 -289
- package/tx/workers/metadata.js +35 -0
- package/tx/workers/search.js +53 -1
- package/tx/workers/supplement-filters.js +145 -0
- package/tx/workers/translate.js +169 -4
- package/tx/workers/validate.js +223 -119
- package/tx/workers/worker.js +123 -31
- package/tx/xversion/xv-valueset.js +7 -4
- package/utilities/submit-test-report.js +3 -1
- package/utilities/tx-stress-test.js +482 -0
- package/vcl/readme.md +37 -0
- package/vcl/vcl-parser.js +15 -0
- package/xig/readme.md +52 -0
- package/xig/xig-template.html +5 -8
- package/xig/xig.js +238 -422
- package/changelog-template.md +0 -22
- package/registry/test-runner.js +0 -237
- package/release.sh +0 -80
- package/test-scripts/repro-re2-wasm-leak.js +0 -34
- package/tsconfig.eslint.json +0 -9
- package/tx/html/dash-metrics.liquid +0 -147
- package/tx/html/home-metrics.liquid +0 -225
package/tx/cs/cs-country.js
CHANGED
|
@@ -73,6 +73,14 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
73
73
|
if (!ctxt) {
|
|
74
74
|
return null;
|
|
75
75
|
}
|
|
76
|
+
// a user-assigned code's display is a placeholder ('User-assigned'): an extension-codes
|
|
77
|
+
// supplement's display for it comes first
|
|
78
|
+
if (ctxt.userDefined) {
|
|
79
|
+
const ext = this._extensionCodeDisplay(ctxt.code);
|
|
80
|
+
if (ext) {
|
|
81
|
+
return ext;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
76
84
|
if (ctxt.display && this.opContext.langs.isEnglishOrNothing()) {
|
|
77
85
|
return ctxt.display;
|
|
78
86
|
}
|
|
@@ -116,11 +124,15 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
116
124
|
const ctxt = await this.#ensureContext(code);
|
|
117
125
|
if (ctxt != null) {
|
|
118
126
|
displays.userDefined = ctxt.userDefined;
|
|
119
|
-
|
|
127
|
+
// when an extension-codes supplement gives a user-assigned code a display, the placeholder
|
|
128
|
+
// ('User-assigned') isn't one of its displays
|
|
129
|
+
if (!(ctxt.userDefined && this._extensionCodeDisplay(ctxt.code))) {
|
|
130
|
+
displays.addDesignation( true, 'active', 'en', CodeSystem.makeUseForDisplay(), ctxt.display);
|
|
131
|
+
}
|
|
120
132
|
if (ctxt.french) {
|
|
121
133
|
displays.addDesignation(true, 'active', 'fr', CodeSystem.makeUseForDisplay(), ctxt.french);
|
|
122
134
|
}
|
|
123
|
-
this._listSupplementDesignations(ctxt.code, displays);
|
|
135
|
+
this._listSupplementDesignations(ctxt.code, displays, ctxt.userDefined);
|
|
124
136
|
}
|
|
125
137
|
}
|
|
126
138
|
|
|
@@ -199,8 +211,8 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
199
211
|
const result = new CountryCodeConceptFilter();
|
|
200
212
|
|
|
201
213
|
try {
|
|
202
|
-
//
|
|
203
|
-
const regex = regexUtilities.compile('^' + value + '$');
|
|
214
|
+
// anchored to the whole code
|
|
215
|
+
const regex = regexUtilities.compile('^(?:' + value + ')$'); // the whole value: a|b is ^(?:a|b)$, not ^a|b$
|
|
204
216
|
|
|
205
217
|
for (const concept of this.codes) {
|
|
206
218
|
if (regex.test(concept.code)) {
|
|
@@ -266,7 +278,7 @@ class CountryCodeServices extends CodeSystemProvider {
|
|
|
266
278
|
if (concept && set.list.includes(concept)) {
|
|
267
279
|
return concept;
|
|
268
280
|
}
|
|
269
|
-
return
|
|
281
|
+
return null;
|
|
270
282
|
}
|
|
271
283
|
|
|
272
284
|
async filterCheck(filterContext, set, concept) {
|
package/tx/cs/cs-cpt.js
CHANGED
|
@@ -4,6 +4,7 @@ const { CodeSystem } = require('../library/codesystem');
|
|
|
4
4
|
const { FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api');
|
|
5
5
|
const {validateArrayParameter} = require("../../library/utilities");
|
|
6
6
|
const {BaseCSServices} = require("./cs-base");
|
|
7
|
+
const { guarded } = require('../library/sqlite-utilities');
|
|
7
8
|
|
|
8
9
|
class CPTConceptDesignation {
|
|
9
10
|
constructor(kind, value) {
|
|
@@ -123,10 +124,7 @@ class CPTServices extends BaseCSServices {
|
|
|
123
124
|
}
|
|
124
125
|
|
|
125
126
|
close() {
|
|
126
|
-
|
|
127
|
-
this.db.close();
|
|
128
|
-
this.db = null;
|
|
129
|
-
}
|
|
127
|
+
this.closeDb();
|
|
130
128
|
}
|
|
131
129
|
|
|
132
130
|
// Metadata methods
|
|
@@ -228,6 +226,15 @@ class CPTServices extends BaseCSServices {
|
|
|
228
226
|
isNotClosed() {
|
|
229
227
|
return true;
|
|
230
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* The concept's properties for $expand: built by extendLookup, so that an expansion reports
|
|
231
|
+
* the same properties $lookup does (see CodeSystemProvider._propertiesFromLookup). Not the
|
|
232
|
+
* copyright, which extendLookup always adds: it is about the code system, not the concept
|
|
233
|
+
*/
|
|
234
|
+
async properties(context, names) {
|
|
235
|
+
return (await this._propertiesFromLookup(context, names)).filter(p => p.code !== 'copyright');
|
|
236
|
+
}
|
|
237
|
+
|
|
231
238
|
async extendLookup(ctxt, props, params) {
|
|
232
239
|
validateArrayParameter(props, 'props', String);
|
|
233
240
|
validateArrayParameter(params, 'params', Object);
|
|
@@ -571,7 +578,8 @@ class CPTServices extends BaseCSServices {
|
|
|
571
578
|
|
|
572
579
|
|
|
573
580
|
|
|
574
|
-
// Subsumption
|
|
581
|
+
// Subsumption: CPT codes have no subsumption relationships, so two codes that both exist are
|
|
582
|
+
// not-subsumed (the caller deals with equal codes: equivalent)
|
|
575
583
|
async subsumesTest(codeA, codeB) {
|
|
576
584
|
await this.#ensureContext(codeA);
|
|
577
585
|
await this.#ensureContext(codeB);
|
|
@@ -585,9 +593,14 @@ class CPTServices extends BaseCSServices {
|
|
|
585
593
|
}
|
|
586
594
|
|
|
587
595
|
class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
588
|
-
|
|
596
|
+
/**
|
|
597
|
+
* @param {Object} [options] - {readOnly: true} in a tx-worker (cluster.md): the file is
|
|
598
|
+
* the master's; this process opens it read-only
|
|
599
|
+
*/
|
|
600
|
+
constructor(i18n, dbPath, options = {}) {
|
|
589
601
|
super(i18n);
|
|
590
602
|
this.dbPath = dbPath;
|
|
603
|
+
this.readOnly = !!options.readOnly;
|
|
591
604
|
this.uses = 0;
|
|
592
605
|
this._loaded = false;
|
|
593
606
|
this._sharedData = null;
|
|
@@ -613,8 +626,12 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
613
626
|
}
|
|
614
627
|
}
|
|
615
628
|
|
|
629
|
+
#open() {
|
|
630
|
+
return this.readOnly ? new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY) : new sqlite3.Database(this.dbPath);
|
|
631
|
+
}
|
|
632
|
+
|
|
616
633
|
async load() {
|
|
617
|
-
const db =
|
|
634
|
+
const db = this.#open();
|
|
618
635
|
|
|
619
636
|
try {
|
|
620
637
|
this._sharedData = {
|
|
@@ -645,7 +662,7 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
645
662
|
|
|
646
663
|
async #loadVersion(db) {
|
|
647
664
|
return new Promise((resolve, reject) => {
|
|
648
|
-
db.all('SELECT * FROM Information', (err, rows) => {
|
|
665
|
+
db.all('SELECT * FROM Information', guarded(reject, (err, rows) => {
|
|
649
666
|
if (err) {
|
|
650
667
|
reject(err);
|
|
651
668
|
} else {
|
|
@@ -656,13 +673,13 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
656
673
|
}
|
|
657
674
|
resolve();
|
|
658
675
|
}
|
|
659
|
-
});
|
|
676
|
+
}));
|
|
660
677
|
});
|
|
661
678
|
}
|
|
662
679
|
|
|
663
680
|
async #loadConcepts(db) {
|
|
664
681
|
return new Promise((resolve, reject) => {
|
|
665
|
-
db.all('SELECT * FROM Concepts', (err, rows) => {
|
|
682
|
+
db.all('SELECT * FROM Concepts', guarded(reject, (err, rows) => {
|
|
666
683
|
if (err) {
|
|
667
684
|
reject(err);
|
|
668
685
|
} else {
|
|
@@ -680,13 +697,13 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
680
697
|
}
|
|
681
698
|
resolve();
|
|
682
699
|
}
|
|
683
|
-
});
|
|
700
|
+
}));
|
|
684
701
|
});
|
|
685
702
|
}
|
|
686
703
|
|
|
687
704
|
async #loadProperties(db) {
|
|
688
705
|
return new Promise((resolve, reject) => {
|
|
689
|
-
db.all('SELECT * FROM Properties', (err, rows) => {
|
|
706
|
+
db.all('SELECT * FROM Properties', guarded(reject, (err, rows) => {
|
|
690
707
|
if (err) {
|
|
691
708
|
reject(err);
|
|
692
709
|
} else {
|
|
@@ -698,13 +715,13 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
698
715
|
}
|
|
699
716
|
resolve();
|
|
700
717
|
}
|
|
701
|
-
});
|
|
718
|
+
}));
|
|
702
719
|
});
|
|
703
720
|
}
|
|
704
721
|
|
|
705
722
|
async #loadDesignations(db) {
|
|
706
723
|
return new Promise((resolve, reject) => {
|
|
707
|
-
db.all('SELECT * FROM Designations', (err, rows) => {
|
|
724
|
+
db.all('SELECT * FROM Designations', guarded(reject, (err, rows) => {
|
|
708
725
|
if (err) {
|
|
709
726
|
reject(err);
|
|
710
727
|
} else {
|
|
@@ -716,7 +733,7 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
716
733
|
}
|
|
717
734
|
resolve();
|
|
718
735
|
}
|
|
719
|
-
});
|
|
736
|
+
}));
|
|
720
737
|
});
|
|
721
738
|
}
|
|
722
739
|
|
|
@@ -729,7 +746,7 @@ class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
|
729
746
|
this.recordUse();
|
|
730
747
|
|
|
731
748
|
// Create fresh database connection for this provider instance
|
|
732
|
-
const db =
|
|
749
|
+
const db = this.#open();
|
|
733
750
|
|
|
734
751
|
return new CPTServices(opContext, supplements, db, this._sharedData);
|
|
735
752
|
}
|
package/tx/cs/cs-cs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const { CodeSystem} = require("../library/codesystem");
|
|
2
|
-
const { CodeSystemFactoryProvider, FilterExecutionContext } = require( "./cs-api");
|
|
2
|
+
const { CodeSystemFactoryProvider, FilterExecutionContext, CodeSystemProvider } = require( "./cs-api");
|
|
3
3
|
const { VersionUtilities } = require("../../library/version-utilities");
|
|
4
4
|
const { Language } = require ("../../library/languages");
|
|
5
5
|
const { validateOptionalParameter, getValuePrimitive, validateArrayParameter} = require("../../library/utilities");
|
|
@@ -19,6 +19,9 @@ const regexUtilities = require("../../library/regex-utilities");
|
|
|
19
19
|
// filter results do not depend on forIteration, so one form is stored per version.
|
|
20
20
|
const FILTER_ANALYSIS = Symbol('filterAnalysis');
|
|
21
21
|
|
|
22
|
+
// The standard concept properties (http://hl7.org/fhir/concept-properties)
|
|
23
|
+
const CONCEPT_PROPERTIES = 'http://hl7.org/fhir/concept-properties#';
|
|
24
|
+
|
|
22
25
|
// The texts of each concept as a text filter compares them, by concept. Folding is
|
|
23
26
|
// the costly part of a search over a large code system, so it is done once per
|
|
24
27
|
// concept, and dropped with the code system.
|
|
@@ -53,7 +56,8 @@ function conceptSearchTexts(concept) {
|
|
|
53
56
|
display: concept.display ? searchText(concept.display) : null,
|
|
54
57
|
definition: concept.definition ? foldSearchText(concept.definition) : null,
|
|
55
58
|
designations: Array.isArray(concept.designation)
|
|
56
|
-
? concept.designation.filter(designation => designation.value)
|
|
59
|
+
? concept.designation.filter(designation => designation.value)
|
|
60
|
+
.map(designation => ({ ...searchText(designation.value), language: designation.language || null }))
|
|
57
61
|
: []
|
|
58
62
|
};
|
|
59
63
|
SEARCH_TEXTS.set(concept, texts);
|
|
@@ -80,9 +84,16 @@ function wordsRatio(words, text) {
|
|
|
80
84
|
* Context class for FHIR CodeSystem provider concepts
|
|
81
85
|
*/
|
|
82
86
|
class FhirCodeSystemProviderContext {
|
|
83
|
-
|
|
87
|
+
/**
|
|
88
|
+
* @param {string} code - the code
|
|
89
|
+
* @param {Object} concept - its definition in the code system
|
|
90
|
+
* @param {boolean} reserved - true if the code system doesn't define the code, but it is in its
|
|
91
|
+
* reserved code space (so the concept is just the code, and supplements may define it)
|
|
92
|
+
*/
|
|
93
|
+
constructor(code, concept, reserved = false) {
|
|
84
94
|
this.code = code;
|
|
85
95
|
this.concept = concept;
|
|
96
|
+
this.reserved = reserved;
|
|
86
97
|
}
|
|
87
98
|
}
|
|
88
99
|
|
|
@@ -187,6 +198,16 @@ function providerClassFor(url) {
|
|
|
187
198
|
}
|
|
188
199
|
|
|
189
200
|
class FhirCodeSystemProvider extends BaseCSServices {
|
|
201
|
+
/**
|
|
202
|
+
* A code system that reserves code spaces for users (codesystem-reservedCodeSpace) has codes that
|
|
203
|
+
* aren't listed in an expansion of all of it
|
|
204
|
+
* @returns {string|null}
|
|
205
|
+
*/
|
|
206
|
+
openCodeSpaceReason() {
|
|
207
|
+
return this.codeSystem.reservedCodeSpaces().length > 0 ?
|
|
208
|
+
"The code system '" + this.system() + "' reserves code spaces for users to define their own codes, and those codes are not in the expansion" : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
190
211
|
/**
|
|
191
212
|
* The provider for a CodeSystem resource: a FhirCodeSystemProvider, or the subclass
|
|
192
213
|
* registered for its url. Always use this rather than the constructor.
|
|
@@ -315,6 +336,46 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
315
336
|
return this.codeSystem.jsonObj.property || null;
|
|
316
337
|
}
|
|
317
338
|
|
|
339
|
+
/**
|
|
340
|
+
* The codes this code system uses for one of the standard concept properties
|
|
341
|
+
* (http://hl7.org/fhir/concept-properties#<name>). A property declared with the
|
|
342
|
+
* standard uri counts whatever local code it was given. A code that the code
|
|
343
|
+
* system declares with a different uri is a different property, and does not
|
|
344
|
+
* count. A code the code system doesn't declare (or declares with no uri) is
|
|
345
|
+
* recognised by the code alone - the standard code, and any legacy aliases
|
|
346
|
+
*
|
|
347
|
+
* @param {string} name - the standard property code (e.g. notSelectable)
|
|
348
|
+
* @param {string[]} aliases - legacy codes also recognised when not declared otherwise
|
|
349
|
+
* @returns {Set<string>} the local codes for the property
|
|
350
|
+
*/
|
|
351
|
+
standardPropertyCodes(name, aliases = []) {
|
|
352
|
+
const uri = CONCEPT_PROPERTIES + name;
|
|
353
|
+
const defs = this.propertyDefinitions() || [];
|
|
354
|
+
const codes = new Set();
|
|
355
|
+
for (const d of defs) {
|
|
356
|
+
if (d.uri === uri) {
|
|
357
|
+
codes.add(d.code);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
for (const c of [name, ...aliases]) {
|
|
361
|
+
if (!defs.some(d => d.code === c && d.uri && d.uri !== uri)) {
|
|
362
|
+
codes.add(c);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return codes;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Whether the code system declares the code of a standard concept property for a
|
|
370
|
+
* property of its own (with a different uri), so that the code is not the standard one
|
|
371
|
+
* @param {string} name - the standard property code
|
|
372
|
+
* @returns {boolean}
|
|
373
|
+
*/
|
|
374
|
+
redefinesStandardProperty(name) {
|
|
375
|
+
const uri = CONCEPT_PROPERTIES + name;
|
|
376
|
+
return (this.propertyDefinitions() || []).some(d => d.code === name && d.uri && d.uri !== uri);
|
|
377
|
+
}
|
|
378
|
+
|
|
318
379
|
/**
|
|
319
380
|
* @param {Languages} languages - Language specification
|
|
320
381
|
* @returns {boolean} Whether any displays are available for the languages
|
|
@@ -421,6 +482,16 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
421
482
|
};
|
|
422
483
|
}
|
|
423
484
|
|
|
485
|
+
// a code the code system doesn't define, in a code space it reserves for users to define their own
|
|
486
|
+
// codes, is a valid code. An extension-codes supplement in scope may define it (display etc); if
|
|
487
|
+
// none does, it has no display
|
|
488
|
+
if (this.codeSystem.isInReservedCodeSpace(code)) {
|
|
489
|
+
return {
|
|
490
|
+
context: new FhirCodeSystemProviderContext(code, { code: code }, true),
|
|
491
|
+
message: null
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
424
495
|
return {
|
|
425
496
|
context: null,
|
|
426
497
|
message: undefined
|
|
@@ -468,6 +539,11 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
468
539
|
if (supplementDisplay) {
|
|
469
540
|
return supplementDisplay;
|
|
470
541
|
}
|
|
542
|
+
// a code in the reserved code space has the display an extension-codes supplement gives it,
|
|
543
|
+
// whatever languages were asked for - or, if none does, the code space's default display
|
|
544
|
+
if (ctxt.reserved) {
|
|
545
|
+
return this._extensionCodeDisplay(ctxt.code) || this.codeSystem.reservedCodeSpaceFor(ctxt.code).defaultDisplay;
|
|
546
|
+
}
|
|
471
547
|
|
|
472
548
|
// Use language-aware display logic
|
|
473
549
|
if (this.opContext.langs && !this.opContext.langs.isEnglishOrNothing()) {
|
|
@@ -524,14 +600,10 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
524
600
|
return false;
|
|
525
601
|
}
|
|
526
602
|
|
|
527
|
-
// Check for
|
|
603
|
+
// Check for the notSelectable property, by whatever code this code system gives it
|
|
528
604
|
if (ctxt.concept.property && Array.isArray(ctxt.concept.property)) {
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
p.code === 'not-selectable' ||
|
|
532
|
-
p.code === 'notSelectable' ||
|
|
533
|
-
p.uri === 'http://hl7.org/fhir/concept-properties#notSelectable'
|
|
534
|
-
);
|
|
605
|
+
const codes = this.standardPropertyCodes('notSelectable', ['abstract', 'not-selectable']);
|
|
606
|
+
const abstractProp = ctxt.concept.property.find(p => codes.has(p.code));
|
|
535
607
|
if (abstractProp && abstractProp.valueBoolean) {
|
|
536
608
|
return true;
|
|
537
609
|
}
|
|
@@ -735,8 +807,17 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
735
807
|
}
|
|
736
808
|
}
|
|
737
809
|
|
|
810
|
+
// a code in the reserved code space that no extension-codes supplement in scope gives a display has
|
|
811
|
+
// its code space's default display ('User-assigned'). It is a placeholder, so (as for ISO 3166's
|
|
812
|
+
// user-assigned codes) another display is not wrong - see validate.js checkDisplays
|
|
813
|
+
if (ctxt.reserved && !this._extensionCodeDisplay(ctxt.code)) {
|
|
814
|
+
displays.userDefined = true;
|
|
815
|
+
const displayLang = this.defaultLanguage ? this.defaultLanguage.toString() : null;
|
|
816
|
+
displays.addDesignation(true, 'active', displayLang, CodeSystem.makeUseForDisplay(), this.codeSystem.reservedCodeSpaceFor(ctxt.code).defaultDisplay);
|
|
817
|
+
}
|
|
818
|
+
|
|
738
819
|
// Add supplement designations
|
|
739
|
-
this._listSupplementDesignations(ctxt.code, displays);
|
|
820
|
+
this._listSupplementDesignations(ctxt.code, displays, ctxt.reserved);
|
|
740
821
|
}
|
|
741
822
|
|
|
742
823
|
/**
|
|
@@ -1093,9 +1174,10 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1093
1174
|
* @param {boolean} iterate - True if results will be iterated
|
|
1094
1175
|
* @returns {FilterExecutionContext} Filter context
|
|
1095
1176
|
*/
|
|
1096
|
-
async getPrepContext(iterate) {
|
|
1097
|
-
|
|
1098
|
-
|
|
1177
|
+
async getPrepContext(iterate, languages = null) {
|
|
1178
|
+
const result = new FilterExecutionContext(iterate);
|
|
1179
|
+
result.languages = languages; // what the request asked for (a text filter searches one language)
|
|
1180
|
+
return result;
|
|
1099
1181
|
}
|
|
1100
1182
|
|
|
1101
1183
|
/**
|
|
@@ -1122,7 +1204,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1122
1204
|
}
|
|
1123
1205
|
// Supported hierarchy filters
|
|
1124
1206
|
if ((prop === 'concept' || prop === 'code') &&
|
|
1125
|
-
['is-a', 'descendent-of', 'is-not-a', 'child-of', 'in', '=', 'regex'].includes(op)) {
|
|
1207
|
+
['is-a', 'descendent-of', 'descendent-leaf', 'is-not-a', 'child-of', 'in', 'not-in', '=', 'regex'].includes(op)) {
|
|
1126
1208
|
return true;
|
|
1127
1209
|
}
|
|
1128
1210
|
|
|
@@ -1265,13 +1347,16 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1265
1347
|
const results = new FhirCodeSystemProviderFilterContext();
|
|
1266
1348
|
const searchTerm = foldSearchText(filter.filter);
|
|
1267
1349
|
const searchWords = searchTerm.split(/[^\p{L}\p{N}]+/u).filter(word => word !== '');
|
|
1350
|
+
// the display, definition and designations in one language only (see searchLanguage),
|
|
1351
|
+
// with the designations that supplements in use give in that language
|
|
1352
|
+
const lang = this.searchLanguage(filterContext.languages);
|
|
1268
1353
|
|
|
1269
1354
|
// Search through all concepts
|
|
1270
1355
|
const allConcepts = this.codeSystem.getAllConcepts();
|
|
1271
1356
|
|
|
1272
1357
|
for (const concept of allConcepts) {
|
|
1273
1358
|
this.opContext.deadCheck('cs:searchFilter');
|
|
1274
|
-
const rating = this._calculateSearchRating(concept, searchTerm, searchWords);
|
|
1359
|
+
const rating = this._calculateSearchRating(concept, searchTerm, searchWords, lang);
|
|
1275
1360
|
if (rating > 0) {
|
|
1276
1361
|
results.add(concept, rating);
|
|
1277
1362
|
}
|
|
@@ -1298,8 +1383,20 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1298
1383
|
* @returns {number} Rating (0 = no match, higher = better match)
|
|
1299
1384
|
* @private
|
|
1300
1385
|
*/
|
|
1301
|
-
_calculateSearchRating(concept, searchTerm, searchWords) {
|
|
1302
|
-
const
|
|
1386
|
+
_calculateSearchRating(concept, searchTerm, searchWords, lang = null) {
|
|
1387
|
+
const all = conceptSearchTexts(concept);
|
|
1388
|
+
let texts = all;
|
|
1389
|
+
if (lang) {
|
|
1390
|
+
const csLang = this.defLang();
|
|
1391
|
+
const inLang = CodeSystemProvider.inSearchLanguage(csLang, lang);
|
|
1392
|
+
texts = {
|
|
1393
|
+
code: all.code,
|
|
1394
|
+
display: inLang ? all.display : null,
|
|
1395
|
+
definition: inLang ? all.definition : null,
|
|
1396
|
+
designations: all.designations.filter(d => CodeSystemProvider.inSearchLanguage(d.language, lang, csLang))
|
|
1397
|
+
.concat(this.#supplementSearchTexts(concept.code, lang))
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1303
1400
|
const display = texts.display;
|
|
1304
1401
|
let rating = 0;
|
|
1305
1402
|
|
|
@@ -1350,6 +1447,55 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1350
1447
|
return rating;
|
|
1351
1448
|
}
|
|
1352
1449
|
|
|
1450
|
+
/**
|
|
1451
|
+
* As for any code system (CodeSystemProvider.searchLanguage), but whether there is text in
|
|
1452
|
+
* a language is decided by what the code system actually has: its own language, a designation
|
|
1453
|
+
* in it, or a supplement's (hasAnyDisplays says yes to English whatever the code system's
|
|
1454
|
+
* language is)
|
|
1455
|
+
*/
|
|
1456
|
+
searchLanguage(languages) {
|
|
1457
|
+
for (const lang of (languages && languages.languages) || []) {
|
|
1458
|
+
if (lang && lang.language && lang.code !== '*' && this.#hasSearchText(lang)) {
|
|
1459
|
+
return lang;
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return new Language(this.defLang());
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
#hasSearchText(lang) {
|
|
1466
|
+
const csLang = this.defLang();
|
|
1467
|
+
if (CodeSystemProvider.inSearchLanguage(csLang, lang)) {
|
|
1468
|
+
return true;
|
|
1469
|
+
}
|
|
1470
|
+
for (const concept of this.codeSystem.getAllConcepts()) {
|
|
1471
|
+
if ((concept.designation || []).some(d => d.value && CodeSystemProvider.inSearchLanguage(d.language, lang, csLang))) {
|
|
1472
|
+
return true;
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
return this._hasSupplementText(lang);
|
|
1476
|
+
}
|
|
1477
|
+
|
|
1478
|
+
// the displays and designations that supplements in use give a code, in the search language
|
|
1479
|
+
#supplementSearchTexts(code, lang) {
|
|
1480
|
+
const result = [];
|
|
1481
|
+
for (const supplement of this.supplements || []) {
|
|
1482
|
+
const concept = supplement.getConceptByCode(code);
|
|
1483
|
+
if (!concept) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
const suppLang = supplement.jsonObj.language || this.defLang();
|
|
1487
|
+
if (concept.display && CodeSystemProvider.inSearchLanguage(suppLang, lang)) {
|
|
1488
|
+
result.push(searchText(concept.display));
|
|
1489
|
+
}
|
|
1490
|
+
for (const d of concept.designation || []) {
|
|
1491
|
+
if (d.value && CodeSystemProvider.inSearchLanguage(d.language, lang, suppLang)) {
|
|
1492
|
+
result.push(searchText(d.value));
|
|
1493
|
+
}
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
return result;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1353
1499
|
/**
|
|
1354
1500
|
* Execute a value set filter
|
|
1355
1501
|
* @param {FilterExecutionContext} filterContext - Filter context
|
|
@@ -1399,9 +1545,10 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1399
1545
|
}
|
|
1400
1546
|
}
|
|
1401
1547
|
|
|
1402
|
-
// Handle known special properties
|
|
1548
|
+
// Handle known special properties - unless the code system uses the code for a
|
|
1549
|
+
// property of its own, in which case the property filter above applies
|
|
1403
1550
|
const knownProperties = ['notSelectable', 'status', 'inactive', 'deprecated'];
|
|
1404
|
-
if (knownProperties.includes(prop)) {
|
|
1551
|
+
if (knownProperties.includes(prop) && !this.redefinesStandardProperty(prop)) {
|
|
1405
1552
|
results = await this._handleKnownPropertyFilter(filterContext, prop, op, value);
|
|
1406
1553
|
}
|
|
1407
1554
|
|
|
@@ -1439,7 +1586,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1439
1586
|
*/
|
|
1440
1587
|
_isCacheableFilter(prop, op) {
|
|
1441
1588
|
if (prop === 'concept' || prop === 'code') {
|
|
1442
|
-
return op === 'is-a' || op === 'descendent-of' || op === 'is-not-a' || op === 'regex';
|
|
1589
|
+
return op === 'is-a' || op === 'descendent-of' || op === 'descendent-leaf' || op === 'is-not-a' || op === 'regex';
|
|
1443
1590
|
}
|
|
1444
1591
|
if (prop === 'child' && op === 'exists') {
|
|
1445
1592
|
return true;
|
|
@@ -1478,6 +1625,19 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1478
1625
|
// Find all descendants of the specified code
|
|
1479
1626
|
const includeRoot = (op === 'is-a');
|
|
1480
1627
|
await this._addDescendants(results, value, includeRoot);
|
|
1628
|
+
} else if (op === 'descendent-leaf') {
|
|
1629
|
+
// R5: the descendants that have no children of their own
|
|
1630
|
+
if (this.codeSystem.getConceptByCode(value)) {
|
|
1631
|
+
for (const code of this.codeSystem.getDescendants(value)) {
|
|
1632
|
+
this.opContext.deadCheck('cs:conceptFilter:descendent-leaf');
|
|
1633
|
+
if (code !== value && this.codeSystem.getChildren(code).length === 0) {
|
|
1634
|
+
const concept = this.codeSystem.getConceptByCode(code);
|
|
1635
|
+
if (concept) {
|
|
1636
|
+
results.add(concept, 0);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1481
1641
|
} else if (op === 'child-of') {
|
|
1482
1642
|
// Find all descendants of the specified code
|
|
1483
1643
|
await this._addChildren(results, value);
|
|
@@ -1507,6 +1667,20 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1507
1667
|
}
|
|
1508
1668
|
}
|
|
1509
1669
|
}
|
|
1670
|
+
else if (op === 'not-in') {
|
|
1671
|
+
// Every code except the listed ones (comma separated) - their descendants are not excluded
|
|
1672
|
+
const excludeSet = new Set(value.split(',').map(c => c.trim()));
|
|
1673
|
+
const allCodes = this.codeSystem.getAllCodes();
|
|
1674
|
+
for (const code of allCodes) {
|
|
1675
|
+
this.opContext.deadCheck('cs:conceptFilter:not-in');
|
|
1676
|
+
if (!excludeSet.has(code)) {
|
|
1677
|
+
const concept = this.codeSystem.getConceptByCode(code);
|
|
1678
|
+
if (concept) {
|
|
1679
|
+
results.add(concept, 0);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1510
1684
|
else if (op === '=') {
|
|
1511
1685
|
// Exact match
|
|
1512
1686
|
const concept = this.codeSystem.getConceptByCode(value);
|
|
@@ -1517,7 +1691,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1517
1691
|
else if (op === 'regex') {
|
|
1518
1692
|
// Regular expression match
|
|
1519
1693
|
try {
|
|
1520
|
-
const regex = regexUtilities.compile('^' + value + '$');
|
|
1694
|
+
const regex = regexUtilities.compile('^(?:' + value + ')$'); // the whole value: a|b is ^(?:a|b)$, not ^a|b$
|
|
1521
1695
|
const allCodes = this.codeSystem.getAllCodes();
|
|
1522
1696
|
for (const code of allCodes) {
|
|
1523
1697
|
this.opContext.deadCheck('cs:conceptFilter:regex');
|
|
@@ -1665,7 +1839,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1665
1839
|
}
|
|
1666
1840
|
else if (op === 'regex') {
|
|
1667
1841
|
try {
|
|
1668
|
-
const regex = regexUtilities.compile('^' + value + '$');
|
|
1842
|
+
const regex = regexUtilities.compile('^(?:' + value + ')$'); // the whole value: a|b is ^(?:a|b)$, not ^a|b$
|
|
1669
1843
|
return properties.some(p => regex.test(this._getPropertyValue(p)));
|
|
1670
1844
|
} catch (error) {
|
|
1671
1845
|
return false;
|
|
@@ -1705,13 +1879,14 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1705
1879
|
async _handleKnownPropertyFilter(filterContext, prop, op, value) {
|
|
1706
1880
|
const results = new FhirCodeSystemProviderFilterContext();
|
|
1707
1881
|
const allConcepts = this.codeSystem.getAllConcepts();
|
|
1882
|
+
const nsCodes = this.standardPropertyCodes('notSelectable', ['abstract', 'not-selectable']);
|
|
1708
1883
|
|
|
1709
1884
|
for (const concept of allConcepts) {
|
|
1710
1885
|
this.opContext.deadCheck('cs:knownPropertyFilter');
|
|
1711
1886
|
let matches = false;
|
|
1712
1887
|
|
|
1713
1888
|
if (prop === 'notSelectable') {
|
|
1714
|
-
const abstractProp = (concept.property || []).find(p =>
|
|
1889
|
+
const abstractProp = (concept.property || []).find(p => nsCodes.has(p.code));
|
|
1715
1890
|
let vv = abstractProp ? String(getValuePrimitive(abstractProp)) : null;
|
|
1716
1891
|
if (op === '=') {
|
|
1717
1892
|
matches = (vv === value);
|
package/tx/cs/cs-currency.js
CHANGED