fhirsmith 0.18.2 → 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 +149 -0
- 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 +132 -59
- package/ips/nationality.js +120 -0
- package/ips/pipeline.js +74 -21
- package/ips/presenter.js +1 -1
- package/ips/readme.md +132 -25
- package/ips/render.js +466 -77
- package/ips/store.js +79 -22
- 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/crawler.js +84 -108
- package/registry/documentation/configuration.md +34 -0
- package/registry/registry-template.html +5 -8
- package/registry/registry.js +5 -4
- 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 +154 -4
- package/tx/closure/closure-store.js +126 -3
- package/tx/cm/cm-database.js +69 -57
- package/tx/cm/cm-package.js +29 -1
- 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 +148 -14
- 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 +185 -66
- 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 +608 -64
- 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 +57 -58
- 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/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 +278 -42
- package/tx/resource-cache-sqlite.js +1050 -0
- package/tx/sct/ecl.js +47 -6
- 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 +166 -13
- 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 +27 -4
- package/tx/vs/vs-vsac.js +648 -100
- 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-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");
|
|
@@ -56,7 +56,8 @@ function conceptSearchTexts(concept) {
|
|
|
56
56
|
display: concept.display ? searchText(concept.display) : null,
|
|
57
57
|
definition: concept.definition ? foldSearchText(concept.definition) : null,
|
|
58
58
|
designations: Array.isArray(concept.designation)
|
|
59
|
-
? concept.designation.filter(designation => designation.value)
|
|
59
|
+
? concept.designation.filter(designation => designation.value)
|
|
60
|
+
.map(designation => ({ ...searchText(designation.value), language: designation.language || null }))
|
|
60
61
|
: []
|
|
61
62
|
};
|
|
62
63
|
SEARCH_TEXTS.set(concept, texts);
|
|
@@ -83,9 +84,16 @@ function wordsRatio(words, text) {
|
|
|
83
84
|
* Context class for FHIR CodeSystem provider concepts
|
|
84
85
|
*/
|
|
85
86
|
class FhirCodeSystemProviderContext {
|
|
86
|
-
|
|
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) {
|
|
87
94
|
this.code = code;
|
|
88
95
|
this.concept = concept;
|
|
96
|
+
this.reserved = reserved;
|
|
89
97
|
}
|
|
90
98
|
}
|
|
91
99
|
|
|
@@ -190,6 +198,16 @@ function providerClassFor(url) {
|
|
|
190
198
|
}
|
|
191
199
|
|
|
192
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
|
+
|
|
193
211
|
/**
|
|
194
212
|
* The provider for a CodeSystem resource: a FhirCodeSystemProvider, or the subclass
|
|
195
213
|
* registered for its url. Always use this rather than the constructor.
|
|
@@ -464,6 +482,16 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
464
482
|
};
|
|
465
483
|
}
|
|
466
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
|
+
|
|
467
495
|
return {
|
|
468
496
|
context: null,
|
|
469
497
|
message: undefined
|
|
@@ -511,6 +539,11 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
511
539
|
if (supplementDisplay) {
|
|
512
540
|
return supplementDisplay;
|
|
513
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
|
+
}
|
|
514
547
|
|
|
515
548
|
// Use language-aware display logic
|
|
516
549
|
if (this.opContext.langs && !this.opContext.langs.isEnglishOrNothing()) {
|
|
@@ -774,8 +807,17 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
774
807
|
}
|
|
775
808
|
}
|
|
776
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
|
+
|
|
777
819
|
// Add supplement designations
|
|
778
|
-
this._listSupplementDesignations(ctxt.code, displays);
|
|
820
|
+
this._listSupplementDesignations(ctxt.code, displays, ctxt.reserved);
|
|
779
821
|
}
|
|
780
822
|
|
|
781
823
|
/**
|
|
@@ -1132,9 +1174,10 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1132
1174
|
* @param {boolean} iterate - True if results will be iterated
|
|
1133
1175
|
* @returns {FilterExecutionContext} Filter context
|
|
1134
1176
|
*/
|
|
1135
|
-
async getPrepContext(iterate) {
|
|
1136
|
-
|
|
1137
|
-
|
|
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;
|
|
1138
1181
|
}
|
|
1139
1182
|
|
|
1140
1183
|
/**
|
|
@@ -1161,7 +1204,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1161
1204
|
}
|
|
1162
1205
|
// Supported hierarchy filters
|
|
1163
1206
|
if ((prop === 'concept' || prop === 'code') &&
|
|
1164
|
-
['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)) {
|
|
1165
1208
|
return true;
|
|
1166
1209
|
}
|
|
1167
1210
|
|
|
@@ -1304,13 +1347,16 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1304
1347
|
const results = new FhirCodeSystemProviderFilterContext();
|
|
1305
1348
|
const searchTerm = foldSearchText(filter.filter);
|
|
1306
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);
|
|
1307
1353
|
|
|
1308
1354
|
// Search through all concepts
|
|
1309
1355
|
const allConcepts = this.codeSystem.getAllConcepts();
|
|
1310
1356
|
|
|
1311
1357
|
for (const concept of allConcepts) {
|
|
1312
1358
|
this.opContext.deadCheck('cs:searchFilter');
|
|
1313
|
-
const rating = this._calculateSearchRating(concept, searchTerm, searchWords);
|
|
1359
|
+
const rating = this._calculateSearchRating(concept, searchTerm, searchWords, lang);
|
|
1314
1360
|
if (rating > 0) {
|
|
1315
1361
|
results.add(concept, rating);
|
|
1316
1362
|
}
|
|
@@ -1337,8 +1383,20 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1337
1383
|
* @returns {number} Rating (0 = no match, higher = better match)
|
|
1338
1384
|
* @private
|
|
1339
1385
|
*/
|
|
1340
|
-
_calculateSearchRating(concept, searchTerm, searchWords) {
|
|
1341
|
-
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
|
+
}
|
|
1342
1400
|
const display = texts.display;
|
|
1343
1401
|
let rating = 0;
|
|
1344
1402
|
|
|
@@ -1389,6 +1447,55 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1389
1447
|
return rating;
|
|
1390
1448
|
}
|
|
1391
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
|
+
|
|
1392
1499
|
/**
|
|
1393
1500
|
* Execute a value set filter
|
|
1394
1501
|
* @param {FilterExecutionContext} filterContext - Filter context
|
|
@@ -1479,7 +1586,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1479
1586
|
*/
|
|
1480
1587
|
_isCacheableFilter(prop, op) {
|
|
1481
1588
|
if (prop === 'concept' || prop === 'code') {
|
|
1482
|
-
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';
|
|
1483
1590
|
}
|
|
1484
1591
|
if (prop === 'child' && op === 'exists') {
|
|
1485
1592
|
return true;
|
|
@@ -1518,6 +1625,19 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1518
1625
|
// Find all descendants of the specified code
|
|
1519
1626
|
const includeRoot = (op === 'is-a');
|
|
1520
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
|
+
}
|
|
1521
1641
|
} else if (op === 'child-of') {
|
|
1522
1642
|
// Find all descendants of the specified code
|
|
1523
1643
|
await this._addChildren(results, value);
|
|
@@ -1547,6 +1667,20 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1547
1667
|
}
|
|
1548
1668
|
}
|
|
1549
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
|
+
}
|
|
1550
1684
|
else if (op === '=') {
|
|
1551
1685
|
// Exact match
|
|
1552
1686
|
const concept = this.codeSystem.getConceptByCode(value);
|
|
@@ -1557,7 +1691,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1557
1691
|
else if (op === 'regex') {
|
|
1558
1692
|
// Regular expression match
|
|
1559
1693
|
try {
|
|
1560
|
-
const regex = regexUtilities.compile('^' + value + '$');
|
|
1694
|
+
const regex = regexUtilities.compile('^(?:' + value + ')$'); // the whole value: a|b is ^(?:a|b)$, not ^a|b$
|
|
1561
1695
|
const allCodes = this.codeSystem.getAllCodes();
|
|
1562
1696
|
for (const code of allCodes) {
|
|
1563
1697
|
this.opContext.deadCheck('cs:conceptFilter:regex');
|
|
@@ -1705,7 +1839,7 @@ class FhirCodeSystemProvider extends BaseCSServices {
|
|
|
1705
1839
|
}
|
|
1706
1840
|
else if (op === 'regex') {
|
|
1707
1841
|
try {
|
|
1708
|
-
const regex = regexUtilities.compile('^' + value + '$');
|
|
1842
|
+
const regex = regexUtilities.compile('^(?:' + value + ')$'); // the whole value: a|b is ^(?:a|b)$, not ^a|b$
|
|
1709
1843
|
return properties.some(p => regex.test(this._getPropertyValue(p)));
|
|
1710
1844
|
} catch (error) {
|
|
1711
1845
|
return false;
|
package/tx/cs/cs-currency.js
CHANGED
package/tx/cs/cs-icd11.js
CHANGED
|
@@ -22,6 +22,7 @@ const { ICD11Expression, parseExpression, axisLabel } = require('./icd11-express
|
|
|
22
22
|
const { Issue } = require('../library/operation-outcome');
|
|
23
23
|
const { DesignationUse } = require('../library/designations');
|
|
24
24
|
const assert = require('assert');
|
|
25
|
+
const { guarded } = require('../library/sqlite-utilities');
|
|
25
26
|
|
|
26
27
|
// WHO's own vocabulary for the kinds of text an entity carries, used as designation.use.
|
|
27
28
|
const SCHEMA_NS = 'http://id.who.int/icd/schema';
|
|
@@ -100,10 +101,7 @@ class ICD11Services extends BaseCSServices {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
close() {
|
|
103
|
-
|
|
104
|
-
this.db.close();
|
|
105
|
-
this.db = null;
|
|
106
|
-
}
|
|
104
|
+
this.closeDb();
|
|
107
105
|
}
|
|
108
106
|
|
|
109
107
|
// -- small promise wrappers, so no query below carries callback boilerplate -----------
|
|
@@ -116,7 +114,7 @@ class ICD11Services extends BaseCSServices {
|
|
|
116
114
|
|
|
117
115
|
#all(sql, params = []) {
|
|
118
116
|
return new Promise((resolve, reject) => {
|
|
119
|
-
this.db.all(sql, params, (err, rows) => err ? reject(err) : resolve(rows || []));
|
|
117
|
+
this.db.all(sql, params, guarded(reject, (err, rows) => err ? reject(err) : resolve(rows || [])));
|
|
120
118
|
});
|
|
121
119
|
}
|
|
122
120
|
|
|
@@ -993,12 +991,22 @@ class ICD11ServicesFactory extends CodeSystemFactoryProvider {
|
|
|
993
991
|
|
|
994
992
|
id() { return `icd11-${this.systemCode}`; }
|
|
995
993
|
|
|
994
|
+
/**
|
|
995
|
+
* The WHO browser page for the concept, as the ICD-API gives it (browserUrl, kept in the
|
|
996
|
+
* database: https://icd.who.int/browse/2026-01/mms/en#257068234). A postcoordinated
|
|
997
|
+
* expression links to its stem.
|
|
998
|
+
*/
|
|
999
|
+
codeLink(context) {
|
|
1000
|
+
const concept = context && context.tokens ? context.tokens[0]?.concept : context;
|
|
1001
|
+
return concept && concept.browserUrl ? concept.browserUrl : undefined;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
996
1004
|
async load() {
|
|
997
1005
|
if (this._loaded) return;
|
|
998
1006
|
const db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY);
|
|
999
1007
|
try {
|
|
1000
1008
|
const get = (sql, p) => new Promise((res, rej) =>
|
|
1001
|
-
db.get(sql, p, (e, r) => e ? rej(e) : res(r)));
|
|
1009
|
+
db.get(sql, p, guarded(rej, (e, r) => e ? rej(e) : res(r))));
|
|
1002
1010
|
const sys = await get('SELECT * FROM system WHERE code = ?', [this.systemCode]);
|
|
1003
1011
|
if (!sys) {
|
|
1004
1012
|
throw new Error(`ICD-11 database ${this.dbPath} has no '${this.systemCode}' code system`);
|
|
@@ -1007,7 +1015,7 @@ class ICD11ServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1007
1015
|
const langs = await get("SELECT value FROM meta WHERE key = 'languages'", []);
|
|
1008
1016
|
// per-language titles; a database built before system_title existed has none
|
|
1009
1017
|
const all = (sql, p) => new Promise((res, rej) =>
|
|
1010
|
-
db.all(sql, p, (e, r) => e ? rej(e) : res(r || [])));
|
|
1018
|
+
db.all(sql, p, guarded(rej, (e, r) => e ? rej(e) : res(r || []))));
|
|
1011
1019
|
const titles = {};
|
|
1012
1020
|
const hasTitles = await get(
|
|
1013
1021
|
"SELECT 1 x FROM sqlite_master WHERE type = 'table' AND name = 'system_title'", []);
|
|
@@ -1054,8 +1062,8 @@ class ICD11ServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1054
1062
|
|
|
1055
1063
|
const db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY);
|
|
1056
1064
|
try {
|
|
1057
|
-
const get = (sql, p) => new Promise((res, rej) => db.get(sql, p, (e, r) => e ? rej(e) : res(r)));
|
|
1058
|
-
const all = (sql, p) => new Promise((res, rej) => db.all(sql, p, (e, r) => e ? rej(e) : res(r || [])));
|
|
1065
|
+
const get = (sql, p) => new Promise((res, rej) => db.get(sql, p, guarded(rej, (e, r) => e ? rej(e) : res(r))));
|
|
1066
|
+
const all = (sql, p) => new Promise((res, rej) => db.all(sql, p, guarded(rej, (e, r) => e ? rej(e) : res(r || []))));
|
|
1059
1067
|
const scale = await get(
|
|
1060
1068
|
`SELECT ps.id, ps.axis, ps.axis_name, ps.required, ps.allow_multiple,
|
|
1061
1069
|
c.entity_id, c.code AS stem_code, c.uri AS stem_uri
|
|
@@ -1100,14 +1108,14 @@ class ICD11ServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1100
1108
|
/** Which code systems a given database holds, so the library can register one each. */
|
|
1101
1109
|
static listSystems(dbPath) {
|
|
1102
1110
|
return new Promise((resolve, reject) => {
|
|
1103
|
-
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, (err) => {
|
|
1111
|
+
const db = new sqlite3.Database(dbPath, sqlite3.OPEN_READONLY, guarded(reject, (err) => {
|
|
1104
1112
|
if (err) { reject(new Error(`cannot open ICD-11 database ${dbPath}: ${err.message}`)); return; }
|
|
1105
|
-
db.all('SELECT code, url, version, title FROM system ORDER BY code', (e, rows) => {
|
|
1113
|
+
db.all('SELECT code, url, version, title FROM system ORDER BY code', guarded(reject, (e, rows) => {
|
|
1106
1114
|
db.close();
|
|
1107
1115
|
if (e) reject(new Error(`${dbPath} is not an ICD-11 database: ${e.message}`));
|
|
1108
1116
|
else resolve(rows || []);
|
|
1109
|
-
});
|
|
1110
|
-
});
|
|
1117
|
+
}));
|
|
1118
|
+
}));
|
|
1111
1119
|
});
|
|
1112
1120
|
}
|
|
1113
1121
|
}
|
package/tx/cs/cs-icf.js
CHANGED
|
@@ -195,9 +195,23 @@ class IcfCodeSystemProvider extends FhirCodeSystemProvider {
|
|
|
195
195
|
return ctxt instanceof IcfQualifiedContext ? ctxt.qualifiedCode : super.code(ctxt);
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
-
/**
|
|
198
|
+
/**
|
|
199
|
+
* A qualified code's display is the one an extension-codes supplement in scope gives it, if one
|
|
200
|
+
* does. Otherwise it is generated: the category's display, then the qualifiers' displays, leaving
|
|
201
|
+
* out any 8 (not specified).
|
|
202
|
+
*/
|
|
199
203
|
async display(context) {
|
|
200
204
|
const ctxt = await this.#resolve(context);
|
|
205
|
+
if (ctxt instanceof IcfQualifiedContext) {
|
|
206
|
+
const ext = this._extensionCodeDisplay(ctxt.qualifiedCode);
|
|
207
|
+
if (ext) {
|
|
208
|
+
return ext;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return this.#generatedDisplay(ctxt);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
async #generatedDisplay(ctxt) {
|
|
201
215
|
const display = await super.display(ctxt);
|
|
202
216
|
if (!(ctxt instanceof IcfQualifiedContext)) {
|
|
203
217
|
return display;
|
|
@@ -207,14 +221,19 @@ class IcfCodeSystemProvider extends FhirCodeSystemProvider {
|
|
|
207
221
|
return parts.length > 0 ? display + ': ' + parts.join(', ') : display;
|
|
208
222
|
}
|
|
209
223
|
|
|
210
|
-
/**
|
|
224
|
+
/**
|
|
225
|
+
* A qualified code's designations: the display an extension-codes supplement in scope gives it
|
|
226
|
+
* (first, so it is the preferred display), and its generated display, which stays a valid one. The
|
|
227
|
+
* category's translations do not describe it.
|
|
228
|
+
*/
|
|
211
229
|
async designations(context, displays) {
|
|
212
230
|
const ctxt = await this.#resolve(context);
|
|
213
231
|
if (!(ctxt instanceof IcfQualifiedContext)) {
|
|
214
232
|
return super.designations(ctxt, displays);
|
|
215
233
|
}
|
|
234
|
+
this._listSupplementDesignations(ctxt.qualifiedCode, displays, true);
|
|
216
235
|
const lang = this.defaultLanguage ? this.defaultLanguage.toString() : null;
|
|
217
|
-
displays.addDesignation(true, 'active', lang, CodeSystem.makeUseForDisplay(), await this
|
|
236
|
+
displays.addDesignation(true, 'active', lang, CodeSystem.makeUseForDisplay(), await this.#generatedDisplay(ctxt));
|
|
218
237
|
}
|
|
219
238
|
|
|
220
239
|
/**
|
package/tx/cs/cs-lang.js
CHANGED
|
@@ -75,7 +75,7 @@ class IETFLanguageCodeFilter {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
/** Why a code did not match, for the caller. @returns {string} */
|
|
78
|
-
describe(code
|
|
78
|
+
describe(code) {
|
|
79
79
|
const action = this.status ? 'does not contain' : 'contains';
|
|
80
80
|
const requirement = this.status ? 'required' : 'not allowed';
|
|
81
81
|
return `The language code ${code} ${action} a ${this.component}, and it is ${requirement}`;
|
|
@@ -101,7 +101,7 @@ class IETFLanguageEqualityFilter {
|
|
|
101
101
|
return value.toLowerCase() === this.value.toLowerCase();
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
describe(code
|
|
104
|
+
describe(code) {
|
|
105
105
|
return `The language code ${code} does not have a ${this.component} of '${this.value}'`;
|
|
106
106
|
}
|
|
107
107
|
}
|