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-loinc.js
CHANGED
|
@@ -2,12 +2,13 @@ const sqlite3 = require('sqlite3').verbose();
|
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { CodeSystem } = require('../library/codesystem');
|
|
4
4
|
const { Language, Languages} = require('../../library/languages');
|
|
5
|
-
const { CodeSystemFactoryProvider} = require('./cs-api');
|
|
5
|
+
const { CodeSystemFactoryProvider, CodeSystemProvider } = require('./cs-api');
|
|
6
6
|
const { validateOptionalParameter, validateArrayParameter} = require("../../library/utilities");
|
|
7
7
|
const {BaseCSServices} = require("./cs-base");
|
|
8
|
-
const {sqlEscapeString} = require("../../
|
|
8
|
+
const {sqlEscapeString} = require("../../library/sqlite-utilities");
|
|
9
9
|
const regexUtilities = require('../../library/regex-utilities');
|
|
10
10
|
const {ConceptMap} = require("../library/conceptmap");
|
|
11
|
+
const { guarded } = require('../library/sqlite-utilities');
|
|
11
12
|
|
|
12
13
|
// Context kinds matching Pascal enum
|
|
13
14
|
const LoincProviderContextKind = {
|
|
@@ -53,13 +54,16 @@ function mapRelationship(equivalence, fromLoinc) {
|
|
|
53
54
|
}
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// CLASSTYPE: the code and the description the importer stores (see descClassType in
|
|
58
|
+
// import-loinc.module.js - 'Claims attachment', singular), and back
|
|
56
59
|
const classTypes = {
|
|
57
60
|
'1': 'Laboratory class',
|
|
58
61
|
'2': 'Clinical class',
|
|
59
|
-
'3': 'Claims
|
|
62
|
+
'3': 'Claims attachment',
|
|
60
63
|
'4': 'Surveys',
|
|
61
64
|
'Laboratory class' : '1',
|
|
62
65
|
'Clinical class' : '2',
|
|
66
|
+
'Claims attachment' : '3',
|
|
63
67
|
'Claims attachments' : '3',
|
|
64
68
|
'Surveys' : '4'
|
|
65
69
|
};
|
|
@@ -124,6 +128,9 @@ class LoincFilterHolder {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
hasKey(key) {
|
|
131
|
+
if (this.keySet) {
|
|
132
|
+
return this.keySet.has(key);
|
|
133
|
+
}
|
|
127
134
|
// Binary search since keys should be sorted
|
|
128
135
|
let l = 0;
|
|
129
136
|
let r = this.keys.length - 1;
|
|
@@ -142,8 +149,9 @@ class LoincFilterHolder {
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
class LoincPrep {
|
|
145
|
-
constructor() {
|
|
152
|
+
constructor(languages = null) {
|
|
146
153
|
this.filters = [];
|
|
154
|
+
this.languages = languages; // what the request asked for (a text filter searches one language)
|
|
147
155
|
}
|
|
148
156
|
}
|
|
149
157
|
|
|
@@ -168,10 +176,7 @@ class LoincServices extends BaseCSServices {
|
|
|
168
176
|
}
|
|
169
177
|
|
|
170
178
|
close() {
|
|
171
|
-
|
|
172
|
-
this.db.close();
|
|
173
|
-
this.db = null;
|
|
174
|
-
}
|
|
179
|
+
this.closeDb();
|
|
175
180
|
}
|
|
176
181
|
|
|
177
182
|
// Metadata methods
|
|
@@ -336,6 +341,14 @@ class LoincServices extends BaseCSServices {
|
|
|
336
341
|
|
|
337
342
|
}
|
|
338
343
|
|
|
344
|
+
/**
|
|
345
|
+
* The concept's properties for $expand: built by extendLookup, so that an expansion reports
|
|
346
|
+
* the same properties $lookup does (see CodeSystemProvider._propertiesFromLookup)
|
|
347
|
+
*/
|
|
348
|
+
async properties(context, names) {
|
|
349
|
+
return this._propertiesFromLookup(context, names);
|
|
350
|
+
}
|
|
351
|
+
|
|
339
352
|
async extendLookup(ctxt, props, params) {
|
|
340
353
|
validateArrayParameter(props, 'props', String);
|
|
341
354
|
validateArrayParameter(params, 'params', Object);
|
|
@@ -478,26 +491,36 @@ class LoincServices extends BaseCSServices {
|
|
|
478
491
|
|
|
479
492
|
async #addRelationshipProperties(ctxt, props, params) {
|
|
480
493
|
return new Promise((resolve, reject) => {
|
|
494
|
+
// DISTINCT: databases imported before the importer de-duplicated relationships have the
|
|
495
|
+
// same row more than once - a part listed twice under the same parent in
|
|
496
|
+
// ComponentHierarchyBySystem (on two paths) has two parent rows
|
|
481
497
|
const sql = `
|
|
482
|
-
SELECT RelationshipTypes.Description as Relationship, Codes.Code, Codes.Description as Value
|
|
498
|
+
SELECT DISTINCT RelationshipTypes.Description as Relationship, Codes.Code, Codes.Description as Value
|
|
483
499
|
FROM Relationships, RelationshipTypes, Codes
|
|
484
500
|
WHERE Relationships.SourceKey = ?
|
|
485
501
|
AND Relationships.RelationshipTypeKey = RelationshipTypes.RelationshipTypeKey
|
|
486
502
|
AND Relationships.TargetKey = Codes.CodeKey
|
|
487
503
|
`;
|
|
488
504
|
|
|
489
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
505
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
490
506
|
if (err) {
|
|
491
507
|
reject(err);
|
|
492
508
|
} else {
|
|
493
509
|
for (const row of rows) {
|
|
494
510
|
if (this._hasProp(props, row.Relationship, true)) {
|
|
495
|
-
|
|
511
|
+
if (row.Relationship === 'CLASS') {
|
|
512
|
+
// CLASS is a string property - the class name (e.g. PULM). The Part code for the
|
|
513
|
+
// class is also returned, as partCode (an informal extension to the property)
|
|
514
|
+
this._addStringProperty(params, 'property', row.Relationship, row.Value || row.Code)
|
|
515
|
+
.part.push({name: 'partCode', valueCode: row.Code});
|
|
516
|
+
} else {
|
|
517
|
+
this._addCodeProperty(params, 'property', row.Relationship, row.Code);
|
|
518
|
+
}
|
|
496
519
|
}
|
|
497
520
|
}
|
|
498
521
|
resolve();
|
|
499
522
|
}
|
|
500
|
-
});
|
|
523
|
+
}));
|
|
501
524
|
});
|
|
502
525
|
}
|
|
503
526
|
|
|
@@ -511,14 +534,15 @@ class LoincServices extends BaseCSServices {
|
|
|
511
534
|
AND Properties.PropertyValueKey = PropertyValues.PropertyValueKey
|
|
512
535
|
`;
|
|
513
536
|
|
|
514
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
537
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
515
538
|
if (err) {
|
|
516
539
|
reject(err);
|
|
517
540
|
} else {
|
|
518
541
|
for (const row of rows) {
|
|
519
542
|
if (this._hasProp(props, row.Description, true)) {
|
|
520
543
|
if (row.Description == 'CLASSTYPE') {
|
|
521
|
-
|
|
544
|
+
// the code (1-4), with its description; a description with no known code is the value
|
|
545
|
+
this._addStringProperty(params, 'property', row.Description, classTypes[row.Value] || row.Value)
|
|
522
546
|
.part.push({name: 'description', valueString: row.Value});
|
|
523
547
|
} else {
|
|
524
548
|
this._addStringProperty(params, 'property', row.Description, row.Value);
|
|
@@ -527,7 +551,7 @@ class LoincServices extends BaseCSServices {
|
|
|
527
551
|
}
|
|
528
552
|
resolve();
|
|
529
553
|
}
|
|
530
|
-
});
|
|
554
|
+
}));
|
|
531
555
|
});
|
|
532
556
|
}
|
|
533
557
|
|
|
@@ -535,7 +559,7 @@ class LoincServices extends BaseCSServices {
|
|
|
535
559
|
return new Promise((resolve, reject) => {
|
|
536
560
|
const sql = 'SELECT StatusKey FROM Codes WHERE CodeKey = ? AND StatusKey != 0';
|
|
537
561
|
|
|
538
|
-
this.db.get(sql, [ctxt.key], (err, row) => {
|
|
562
|
+
this.db.get(sql, [ctxt.key], guarded(reject, (err, row) => {
|
|
539
563
|
if (err) {
|
|
540
564
|
reject(err);
|
|
541
565
|
} else if (row) {
|
|
@@ -549,7 +573,7 @@ class LoincServices extends BaseCSServices {
|
|
|
549
573
|
} else {
|
|
550
574
|
resolve();
|
|
551
575
|
}
|
|
552
|
-
});
|
|
576
|
+
}));
|
|
553
577
|
});
|
|
554
578
|
}
|
|
555
579
|
|
|
@@ -577,7 +601,7 @@ class LoincServices extends BaseCSServices {
|
|
|
577
601
|
AND Descriptions.LanguageKey = Languages.LanguageKey
|
|
578
602
|
`;
|
|
579
603
|
|
|
580
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
604
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
581
605
|
if (err) {
|
|
582
606
|
reject(err);
|
|
583
607
|
} else {
|
|
@@ -586,7 +610,7 @@ class LoincServices extends BaseCSServices {
|
|
|
586
610
|
}
|
|
587
611
|
resolve();
|
|
588
612
|
}
|
|
589
|
-
});
|
|
613
|
+
}));
|
|
590
614
|
});
|
|
591
615
|
}
|
|
592
616
|
|
|
@@ -604,7 +628,7 @@ class LoincServices extends BaseCSServices {
|
|
|
604
628
|
ORDER BY DescriptionTypeKey
|
|
605
629
|
`;
|
|
606
630
|
|
|
607
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
631
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
608
632
|
if (err) {
|
|
609
633
|
reject(err);
|
|
610
634
|
} else {
|
|
@@ -617,7 +641,7 @@ class LoincServices extends BaseCSServices {
|
|
|
617
641
|
|
|
618
642
|
resolve(displays);
|
|
619
643
|
}
|
|
620
|
-
});
|
|
644
|
+
}));
|
|
621
645
|
});
|
|
622
646
|
}
|
|
623
647
|
|
|
@@ -651,7 +675,7 @@ class LoincServices extends BaseCSServices {
|
|
|
651
675
|
AND Descriptions.LanguageKey = Languages.LanguageKey
|
|
652
676
|
`;
|
|
653
677
|
|
|
654
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
678
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
655
679
|
if (err) {
|
|
656
680
|
reject(err);
|
|
657
681
|
} else {
|
|
@@ -662,7 +686,7 @@ class LoincServices extends BaseCSServices {
|
|
|
662
686
|
}
|
|
663
687
|
resolve(results);
|
|
664
688
|
}
|
|
665
|
-
});
|
|
689
|
+
}));
|
|
666
690
|
});
|
|
667
691
|
}
|
|
668
692
|
|
|
@@ -676,7 +700,7 @@ class LoincServices extends BaseCSServices {
|
|
|
676
700
|
AND Descriptions.LanguageKey = Languages.LanguageKey
|
|
677
701
|
`;
|
|
678
702
|
|
|
679
|
-
this.db.all(sql, [ctxt.key], (err, rows) => {
|
|
703
|
+
this.db.all(sql, [ctxt.key], guarded(reject, (err, rows) => {
|
|
680
704
|
if (err) {
|
|
681
705
|
reject(err);
|
|
682
706
|
} else {
|
|
@@ -686,7 +710,7 @@ class LoincServices extends BaseCSServices {
|
|
|
686
710
|
}
|
|
687
711
|
resolve(results);
|
|
688
712
|
}
|
|
689
|
-
});
|
|
713
|
+
}));
|
|
690
714
|
});
|
|
691
715
|
}
|
|
692
716
|
|
|
@@ -775,11 +799,17 @@ class LoincServices extends BaseCSServices {
|
|
|
775
799
|
return true;
|
|
776
800
|
}
|
|
777
801
|
|
|
778
|
-
// LIST filter
|
|
802
|
+
// LIST filter: tx.fhir.org's own name for answers-for = [answer list], supported for
|
|
803
|
+
// backwards compatibility only (the defined filters are answers-for and answer-list, UP-645)
|
|
779
804
|
if (prop === 'LIST' && op === '=' && this.codes.has(value)) {
|
|
780
805
|
return true;
|
|
781
806
|
}
|
|
782
807
|
|
|
808
|
+
// answer-list filter: the answer lists (LL) that an answer (LA) is in (UP-645)
|
|
809
|
+
if (prop === 'answer-list' && op === '=') {
|
|
810
|
+
return true;
|
|
811
|
+
}
|
|
812
|
+
|
|
783
813
|
// CLASSSTYPE filter
|
|
784
814
|
if (prop === 'CLASSTYPE' && op === '=' && ["1", "2", "3", "4"].includes(value)) {
|
|
785
815
|
return true;
|
|
@@ -795,6 +825,13 @@ class LoincServices extends BaseCSServices {
|
|
|
795
825
|
return true;
|
|
796
826
|
}
|
|
797
827
|
|
|
828
|
+
// LOINC's own hierarchy filter: ancestor = part (or in parts), the codes the part subsumes
|
|
829
|
+
// in the multi-axial hierarchy. LOINC's implicit value sets (http://loinc.org/vs/[partcode])
|
|
830
|
+
// are defined with it
|
|
831
|
+
if (prop === 'ancestor' && ['=', 'in'].includes(op)) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
|
|
798
835
|
// code filters (VSAC workaround)
|
|
799
836
|
if (prop === 'code' && ['is-a', 'descendent-of', '='].includes(op)) {
|
|
800
837
|
return true;
|
|
@@ -808,8 +845,8 @@ class LoincServices extends BaseCSServices {
|
|
|
808
845
|
return false;
|
|
809
846
|
}
|
|
810
847
|
|
|
811
|
-
async getPrepContext(iterate) {
|
|
812
|
-
return new LoincPrep(
|
|
848
|
+
async getPrepContext(iterate, languages = null) {
|
|
849
|
+
return new LoincPrep(languages);
|
|
813
850
|
}
|
|
814
851
|
|
|
815
852
|
async filter(filterContext, forIteration, prop, op, value) {
|
|
@@ -818,10 +855,49 @@ class LoincServices extends BaseCSServices {
|
|
|
818
855
|
filterContext.filters.push(filter);
|
|
819
856
|
}
|
|
820
857
|
|
|
858
|
+
/**
|
|
859
|
+
* A text filter matches the codes with a name in the search language (see searchLanguage:
|
|
860
|
+
* the request's language if LOINC has it, else English) that contains the text, and the
|
|
861
|
+
* codes that a supplement in use gives a matching designation in that language. In English
|
|
862
|
+
* the name is the display (scanning every English description - short names, consumer
|
|
863
|
+
* names - takes 2+ seconds, against 0.2 for the displays); in another language it is any
|
|
864
|
+
* description in it but RELATEDNAMES2, which is a list of keywords (a property), not a name.
|
|
865
|
+
* LOINC does not rate its matches, so sorted or not they come in display order
|
|
866
|
+
*/
|
|
821
867
|
async searchFilter(filterContext, filterText, sort) { // eslint-disable-line no-unused-vars
|
|
822
868
|
const filter = new LoincFilterHolder();
|
|
823
|
-
|
|
824
|
-
|
|
869
|
+
const lang = this.searchLanguage(filterContext.languages);
|
|
870
|
+
const english = CodeSystemProvider.inSearchLanguage(null, lang);
|
|
871
|
+
const langKeys = [];
|
|
872
|
+
for (const [code, key] of this.langs) {
|
|
873
|
+
if (!english && CodeSystemProvider.inSearchLanguage(code, lang)) {
|
|
874
|
+
langKeys.push(key);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
const text = sqlEscapeString(filterText.filter || '');
|
|
878
|
+
const conds = [];
|
|
879
|
+
if (english) {
|
|
880
|
+
conds.push(`Codes.Description like '%${text}%'`); // the display, which is English
|
|
881
|
+
}
|
|
882
|
+
if (langKeys.length > 0) {
|
|
883
|
+
conds.push(`Codes.CodeKey IN (SELECT CodeKey FROM Descriptions WHERE LanguageKey IN (${langKeys.join(',')})
|
|
884
|
+
AND DescriptionTypeKey != 4 AND Value like '%${text}%')`);
|
|
885
|
+
}
|
|
886
|
+
if (conds.length > 0) {
|
|
887
|
+
await this.#executeSQL(`SELECT Codes.CodeKey as Key FROM Codes WHERE ${conds.join(' OR ')} ORDER BY Codes.Description ASC`, filter);
|
|
888
|
+
} else {
|
|
889
|
+
filter.keys = [];
|
|
890
|
+
}
|
|
891
|
+
const have = new Set(filter.keys);
|
|
892
|
+
for (const code of this._supplementSearchCodes(filterText, lang)) {
|
|
893
|
+
const ctxt = this.codes.get(code);
|
|
894
|
+
if (ctxt && !have.has(ctxt.key)) {
|
|
895
|
+
filter.keys.push(ctxt.key);
|
|
896
|
+
have.add(ctxt.key);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
filter.lsql = 'text'; // checked by key (filterLocate needs it set)
|
|
900
|
+
filter.keySet = new Set(filter.keys); // in display order, not key order, so hasKey can't binary search
|
|
825
901
|
filterContext.filters.push(filter);
|
|
826
902
|
}
|
|
827
903
|
|
|
@@ -840,6 +916,17 @@ class LoincServices extends BaseCSServices {
|
|
|
840
916
|
AND SourceKey IN (SELECT CodeKey FROM Codes WHERE Code = '${this.#sqlWrapString(value)}')
|
|
841
917
|
AND TargetKey = `;
|
|
842
918
|
}
|
|
919
|
+
// answer-list filter: the answer lists (LL) the answer (LA) is in - the inverse of answers-for
|
|
920
|
+
else if (prop === 'answer-list' && op === '=') {
|
|
921
|
+
sql = `SELECT DISTINCT SourceKey as Key FROM Relationships
|
|
922
|
+
WHERE RelationshipTypeKey = ${this.relationships.get('Answer')}
|
|
923
|
+
AND TargetKey IN (SELECT CodeKey FROM Codes WHERE Code = '${this.#sqlWrapString(value)}')
|
|
924
|
+
ORDER BY SourceKey ASC`;
|
|
925
|
+
lsql = `SELECT COUNT(DISTINCT SourceKey) FROM Relationships
|
|
926
|
+
WHERE RelationshipTypeKey = ${this.relationships.get('Answer')}
|
|
927
|
+
AND TargetKey IN (SELECT CodeKey FROM Codes WHERE Code = '${this.#sqlWrapString(value)}')
|
|
928
|
+
AND SourceKey = `;
|
|
929
|
+
}
|
|
843
930
|
// answers-for filter
|
|
844
931
|
else if (prop === 'answers-for' && op === '=') {
|
|
845
932
|
if (value.startsWith('LL')) {
|
|
@@ -1026,6 +1113,14 @@ class LoincServices extends BaseCSServices {
|
|
|
1026
1113
|
else if (prop === 'concept' && ['is-a', 'descendent-of'].includes(op)) {
|
|
1027
1114
|
({ sql, lsql } = this.#hierarchyFilterSql(op, value));
|
|
1028
1115
|
}
|
|
1116
|
+
// ancestor = X is LOINC's name for descendent-of X: "Ancestor includes parents transitively",
|
|
1117
|
+
// and the part itself is not its own ancestor. in is the union over the parts
|
|
1118
|
+
else if (prop === 'ancestor' && op === '=') {
|
|
1119
|
+
({ sql, lsql } = this.#hierarchyFilterSql('descendent-of', value));
|
|
1120
|
+
}
|
|
1121
|
+
else if (prop === 'ancestor' && op === 'in') {
|
|
1122
|
+
({ sql, lsql } = this.#hierarchyFilterSql('descendent-of', null, this.#commaListOfCodes(value)));
|
|
1123
|
+
}
|
|
1029
1124
|
// Concept equal filter (workaround for VSAC misuse)
|
|
1030
1125
|
else if (prop === 'concept' && op === '=') {
|
|
1031
1126
|
sql = `SELECT CodeKey as Key FROM Codes
|
|
@@ -1102,14 +1197,15 @@ class LoincServices extends BaseCSServices {
|
|
|
1102
1197
|
* the file as a graph would, for example, put Glucose in Serum or Plasma under Urinalysis,
|
|
1103
1198
|
* because Protein appears under both.
|
|
1104
1199
|
*/
|
|
1105
|
-
#hierarchyFilterSql(op, value) {
|
|
1106
|
-
|
|
1107
|
-
const
|
|
1108
|
-
|
|
1200
|
+
#hierarchyFilterSql(op, value, codeList = null) {
|
|
1201
|
+
// one code, or (codeList) an already quoted, comma separated list of them
|
|
1202
|
+
const codes = codeList || `'${this.#sqlWrapString(value)}'`;
|
|
1203
|
+
const descendants = `SELECT DISTINCT DescendentKey as Key FROM Closure
|
|
1204
|
+
WHERE AncestorKey IN (SELECT CodeKey FROM Codes WHERE Code IN (${codes}))`;
|
|
1109
1205
|
const set = op === 'is-a'
|
|
1110
1206
|
? `${descendants}
|
|
1111
1207
|
UNION
|
|
1112
|
-
SELECT CodeKey as Key FROM Codes WHERE Code
|
|
1208
|
+
SELECT CodeKey as Key FROM Codes WHERE Code IN (${codes})`
|
|
1113
1209
|
: descendants;
|
|
1114
1210
|
return {
|
|
1115
1211
|
sql: `${set}
|
|
@@ -1121,10 +1217,11 @@ class LoincServices extends BaseCSServices {
|
|
|
1121
1217
|
// Helper method for regex matching
|
|
1122
1218
|
async #findRegexMatches(sql, pattern, valueColumn, keyColumn = 'Key') {
|
|
1123
1219
|
return new Promise((resolve, reject) => {
|
|
1124
|
-
|
|
1220
|
+
// the whole value, as for every regex filter: a|b is ^(?:a|b)$
|
|
1221
|
+
const regex = regexUtilities.compile('^(?:' + pattern + ')$');
|
|
1125
1222
|
const matchingKeys = [];
|
|
1126
1223
|
|
|
1127
|
-
this.db.all(sql, (err, rows) => {
|
|
1224
|
+
this.db.all(sql, guarded(reject, (err, rows) => {
|
|
1128
1225
|
if (err) {
|
|
1129
1226
|
reject(err);
|
|
1130
1227
|
} else {
|
|
@@ -1136,7 +1233,7 @@ class LoincServices extends BaseCSServices {
|
|
|
1136
1233
|
}
|
|
1137
1234
|
resolve(matchingKeys);
|
|
1138
1235
|
}
|
|
1139
|
-
});
|
|
1236
|
+
}));
|
|
1140
1237
|
});
|
|
1141
1238
|
}
|
|
1142
1239
|
|
|
@@ -1150,14 +1247,14 @@ class LoincServices extends BaseCSServices {
|
|
|
1150
1247
|
|
|
1151
1248
|
async #executeSQL(sql, filter) {
|
|
1152
1249
|
return new Promise((resolve, reject) => {
|
|
1153
|
-
this.db.all(sql, (err, rows) => {
|
|
1250
|
+
this.db.all(sql, guarded(reject, (err, rows) => {
|
|
1154
1251
|
if (err) {
|
|
1155
1252
|
reject(err);
|
|
1156
1253
|
} else {
|
|
1157
1254
|
filter.keys = rows.map(row => row.Key).filter(key => key !== 0);
|
|
1158
1255
|
resolve();
|
|
1159
1256
|
}
|
|
1160
|
-
});
|
|
1257
|
+
}));
|
|
1161
1258
|
});
|
|
1162
1259
|
}
|
|
1163
1260
|
|
|
@@ -1166,8 +1263,19 @@ class LoincServices extends BaseCSServices {
|
|
|
1166
1263
|
}
|
|
1167
1264
|
|
|
1168
1265
|
async executeFilters(filterContext) {
|
|
1169
|
-
|
|
1170
|
-
|
|
1266
|
+
const filters = filterContext.filters;
|
|
1267
|
+
if (filters.length <= 1) {
|
|
1268
|
+
return filters;
|
|
1269
|
+
}
|
|
1270
|
+
// Every filter is already a list of keys in memory, so the include is their intersection,
|
|
1271
|
+
// as one set: its size is the include's size (count=0 needs no walk, and so no limit), and
|
|
1272
|
+
// the expander walks only the codes that are in it. In the first filter's order
|
|
1273
|
+
const others = filters.slice(1).map(f => new Set(f.keys));
|
|
1274
|
+
const set = new LoincFilterHolder();
|
|
1275
|
+
set.keys = filters[0].keys.filter(k => others.every(o => o.has(k)));
|
|
1276
|
+
set.keySet = new Set(set.keys);
|
|
1277
|
+
set.lsql = filters.map(f => f.lsql).find(l => l) || 'intersection';
|
|
1278
|
+
return [set];
|
|
1171
1279
|
}
|
|
1172
1280
|
|
|
1173
1281
|
async filterSize(filterContext, set) {
|
|
@@ -1278,9 +1386,14 @@ class LoincServices extends BaseCSServices {
|
|
|
1278
1386
|
}
|
|
1279
1387
|
|
|
1280
1388
|
class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
1281
|
-
|
|
1389
|
+
/**
|
|
1390
|
+
* @param {Object} [options] - {readOnly: true} in a tx-worker (cluster.md): the master
|
|
1391
|
+
* sets the journal mode and makes the indexes; this process opens the file read-only
|
|
1392
|
+
*/
|
|
1393
|
+
constructor(i18n, dbPath, options = {}) {
|
|
1282
1394
|
super(i18n);
|
|
1283
1395
|
this.dbPath = dbPath;
|
|
1396
|
+
this.readOnly = !!options.readOnly;
|
|
1284
1397
|
this.uses = 0;
|
|
1285
1398
|
this._loaded = false;
|
|
1286
1399
|
this._sharedData = null;
|
|
@@ -1305,7 +1418,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1305
1418
|
}
|
|
1306
1419
|
|
|
1307
1420
|
async load() {
|
|
1308
|
-
const db = new sqlite3.Database(this.dbPath);
|
|
1421
|
+
const db = this.readOnly ? new sqlite3.Database(this.dbPath, sqlite3.OPEN_READONLY) : new sqlite3.Database(this.dbPath);
|
|
1309
1422
|
|
|
1310
1423
|
// Enable performance optimizations
|
|
1311
1424
|
await this.#optimizeDatabase(db);
|
|
@@ -1358,6 +1471,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1358
1471
|
async #optimizeDatabase(db) {
|
|
1359
1472
|
return new Promise((resolve, reject) => {
|
|
1360
1473
|
db.serialize(() => {
|
|
1474
|
+
if (this.readOnly) {
|
|
1475
|
+
// this connection's settings only; the file is the master's
|
|
1476
|
+
db.run('PRAGMA cache_size = 10000');
|
|
1477
|
+
db.run('PRAGMA temp_store = MEMORY');
|
|
1478
|
+
db.run('PRAGMA mmap_size = 268435456', (err) => err ? reject(err) : resolve());
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1361
1481
|
db.run('PRAGMA journal_mode = WAL');
|
|
1362
1482
|
db.run('PRAGMA synchronous = NORMAL');
|
|
1363
1483
|
db.run('PRAGMA cache_size = 10000');
|
|
@@ -1377,7 +1497,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1377
1497
|
|
|
1378
1498
|
async #loadLanguages(db) {
|
|
1379
1499
|
return new Promise((resolve, reject) => {
|
|
1380
|
-
db.all('SELECT LanguageKey, Code FROM Languages', (err, rows) => {
|
|
1500
|
+
db.all('SELECT LanguageKey, Code FROM Languages', guarded(reject, (err, rows) => {
|
|
1381
1501
|
if (err) {
|
|
1382
1502
|
reject(err);
|
|
1383
1503
|
} else {
|
|
@@ -1386,13 +1506,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1386
1506
|
}
|
|
1387
1507
|
resolve();
|
|
1388
1508
|
}
|
|
1389
|
-
});
|
|
1509
|
+
}));
|
|
1390
1510
|
});
|
|
1391
1511
|
}
|
|
1392
1512
|
|
|
1393
1513
|
async #loadStatusCodes(db) {
|
|
1394
1514
|
return new Promise((resolve, reject) => {
|
|
1395
|
-
db.all('SELECT StatusKey, Description FROM StatusCodes', (err, rows) => {
|
|
1515
|
+
db.all('SELECT StatusKey, Description FROM StatusCodes', guarded(reject, (err, rows) => {
|
|
1396
1516
|
if (err) {
|
|
1397
1517
|
reject(err);
|
|
1398
1518
|
} else {
|
|
@@ -1402,13 +1522,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1402
1522
|
}
|
|
1403
1523
|
resolve();
|
|
1404
1524
|
}
|
|
1405
|
-
});
|
|
1525
|
+
}));
|
|
1406
1526
|
});
|
|
1407
1527
|
}
|
|
1408
1528
|
|
|
1409
1529
|
async #loadRelationshipTypes(db) {
|
|
1410
1530
|
return new Promise((resolve, reject) => {
|
|
1411
|
-
db.all('SELECT RelationshipTypeKey, Description FROM RelationshipTypes', (err, rows) => {
|
|
1531
|
+
db.all('SELECT RelationshipTypeKey, Description FROM RelationshipTypes', guarded(reject, (err, rows) => {
|
|
1412
1532
|
if (err) {
|
|
1413
1533
|
reject(err);
|
|
1414
1534
|
} else {
|
|
@@ -1417,13 +1537,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1417
1537
|
}
|
|
1418
1538
|
resolve();
|
|
1419
1539
|
}
|
|
1420
|
-
});
|
|
1540
|
+
}));
|
|
1421
1541
|
});
|
|
1422
1542
|
}
|
|
1423
1543
|
|
|
1424
1544
|
async #loadPropertyTypes(db) {
|
|
1425
1545
|
return new Promise((resolve, reject) => {
|
|
1426
|
-
db.all('SELECT PropertyTypeKey, Description FROM PropertyTypes', (err, rows) => {
|
|
1546
|
+
db.all('SELECT PropertyTypeKey, Description FROM PropertyTypes', guarded(reject, (err, rows) => {
|
|
1427
1547
|
if (err) {
|
|
1428
1548
|
reject(err);
|
|
1429
1549
|
} else {
|
|
@@ -1432,14 +1552,14 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1432
1552
|
}
|
|
1433
1553
|
resolve();
|
|
1434
1554
|
}
|
|
1435
|
-
});
|
|
1555
|
+
}));
|
|
1436
1556
|
});
|
|
1437
1557
|
}
|
|
1438
1558
|
|
|
1439
1559
|
async #loadCodes(db) {
|
|
1440
1560
|
return new Promise((resolve, reject) => {
|
|
1441
1561
|
// First get the count to pre-allocate array
|
|
1442
|
-
db.get('SELECT MAX(CodeKey) as maxKey FROM Codes', (err, row) => {
|
|
1562
|
+
db.get('SELECT MAX(CodeKey) as maxKey FROM Codes', guarded(reject, (err, row) => {
|
|
1443
1563
|
if (err) return reject(err);
|
|
1444
1564
|
|
|
1445
1565
|
// Pre-allocate the array to avoid repeated resizing
|
|
@@ -1447,7 +1567,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1447
1567
|
this._sharedData.codeList = new Array(maxKey + 1).fill(null);
|
|
1448
1568
|
|
|
1449
1569
|
// Now load all codes
|
|
1450
|
-
db.all('SELECT CodeKey, Code, Type, Codes.Description, StatusCodes.Description as Status FROM Codes, StatusCodes where StatusCodes.StatusKey = Codes.StatusKey order by Type Asc, CodeKey Asc', (err, rows) => {
|
|
1570
|
+
db.all('SELECT CodeKey, Code, Type, Codes.Description, StatusCodes.Description as Status FROM Codes, StatusCodes where StatusCodes.StatusKey = Codes.StatusKey order by Type Asc, CodeKey Asc', guarded(reject, (err, rows) => {
|
|
1451
1571
|
if (err) return reject(err);
|
|
1452
1572
|
|
|
1453
1573
|
// Batch process rows
|
|
@@ -1469,8 +1589,8 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1469
1589
|
}
|
|
1470
1590
|
}
|
|
1471
1591
|
resolve();
|
|
1472
|
-
});
|
|
1473
|
-
});
|
|
1592
|
+
}));
|
|
1593
|
+
}));
|
|
1474
1594
|
});
|
|
1475
1595
|
}
|
|
1476
1596
|
|
|
@@ -1490,7 +1610,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1490
1610
|
ORDER BY d.CodeKey
|
|
1491
1611
|
`;
|
|
1492
1612
|
|
|
1493
|
-
db.all(sql, (err, rows) => {
|
|
1613
|
+
db.all(sql, guarded(reject, (err, rows) => {
|
|
1494
1614
|
if (err) return reject(err);
|
|
1495
1615
|
|
|
1496
1616
|
// Batch process by CodeKey to reduce lookups
|
|
@@ -1510,7 +1630,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1510
1630
|
}
|
|
1511
1631
|
}
|
|
1512
1632
|
resolve();
|
|
1513
|
-
});
|
|
1633
|
+
}));
|
|
1514
1634
|
});
|
|
1515
1635
|
}
|
|
1516
1636
|
|
|
@@ -1521,12 +1641,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1521
1641
|
}
|
|
1522
1642
|
|
|
1523
1643
|
return new Promise((resolve, reject) => {
|
|
1644
|
+
// DISTINCT, for databases imported with duplicate rows (see #addRelationshipProperties)
|
|
1524
1645
|
const sql = `
|
|
1525
|
-
SELECT SourceKey, TargetKey FROM Relationships
|
|
1646
|
+
SELECT DISTINCT SourceKey, TargetKey FROM Relationships
|
|
1526
1647
|
WHERE RelationshipTypeKey = ${childRelKey}
|
|
1527
1648
|
`;
|
|
1528
1649
|
|
|
1529
|
-
db.all(sql, (err, rows) => {
|
|
1650
|
+
db.all(sql, guarded(reject, (err, rows) => {
|
|
1530
1651
|
if (err) {
|
|
1531
1652
|
reject(err);
|
|
1532
1653
|
} else {
|
|
@@ -1540,13 +1661,13 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1540
1661
|
}
|
|
1541
1662
|
resolve();
|
|
1542
1663
|
}
|
|
1543
|
-
});
|
|
1664
|
+
}));
|
|
1544
1665
|
});
|
|
1545
1666
|
}
|
|
1546
1667
|
|
|
1547
1668
|
async #loadConfig(db) {
|
|
1548
1669
|
return new Promise((resolve, reject) => {
|
|
1549
|
-
db.all('SELECT ConfigKey, Value FROM Config WHERE ConfigKey IN (2, 3)', (err, rows) => {
|
|
1670
|
+
db.all('SELECT ConfigKey, Value FROM Config WHERE ConfigKey IN (2, 3)', guarded(reject, (err, rows) => {
|
|
1550
1671
|
if (err) {
|
|
1551
1672
|
reject(err);
|
|
1552
1673
|
} else {
|
|
@@ -1559,7 +1680,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1559
1680
|
}
|
|
1560
1681
|
resolve();
|
|
1561
1682
|
}
|
|
1562
|
-
});
|
|
1683
|
+
}));
|
|
1563
1684
|
});
|
|
1564
1685
|
}
|
|
1565
1686
|
|
|
@@ -1570,14 +1691,14 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1570
1691
|
// the mapping tables were added with the LOINC 2.83 importer; older databases don't have them
|
|
1571
1692
|
async #loadHasMappings(db) {
|
|
1572
1693
|
return new Promise((resolve, reject) => {
|
|
1573
|
-
db.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'ExternalMappings'", (err, row) => {
|
|
1694
|
+
db.get("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'ExternalMappings'", guarded(reject, (err, row) => {
|
|
1574
1695
|
if (err) {
|
|
1575
1696
|
reject(err);
|
|
1576
1697
|
} else {
|
|
1577
1698
|
this._sharedData.hasMappings = !!row;
|
|
1578
1699
|
resolve();
|
|
1579
1700
|
}
|
|
1580
|
-
});
|
|
1701
|
+
}));
|
|
1581
1702
|
});
|
|
1582
1703
|
}
|
|
1583
1704
|
|
|
@@ -1590,7 +1711,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1590
1711
|
db.all(`SELECT Codes.Code as LoincCode, ExternalCodes.System, ExternalCodes.Code
|
|
1591
1712
|
FROM ExternalMappings, ExternalCodes, Codes
|
|
1592
1713
|
WHERE ExternalMappings.ExternalCodeKey = ExternalCodes.ExternalCodeKey
|
|
1593
|
-
AND ExternalMappings.CodeKey = Codes.CodeKey`, (err, rows) => {
|
|
1714
|
+
AND ExternalMappings.CodeKey = Codes.CodeKey`, guarded(reject, (err, rows) => {
|
|
1594
1715
|
if (err) {
|
|
1595
1716
|
reject(err);
|
|
1596
1717
|
} else {
|
|
@@ -1605,7 +1726,7 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1605
1726
|
}
|
|
1606
1727
|
resolve();
|
|
1607
1728
|
}
|
|
1608
|
-
});
|
|
1729
|
+
}));
|
|
1609
1730
|
});
|
|
1610
1731
|
}
|
|
1611
1732
|
|
|
@@ -1784,6 +1905,11 @@ class LoincServicesFactory extends CodeSystemFactoryProvider {
|
|
|
1784
1905
|
id() {
|
|
1785
1906
|
return "loinc"+this.version();
|
|
1786
1907
|
}
|
|
1908
|
+
|
|
1909
|
+
/** Codes, parts, answers and groups all have a page at loinc.org (as LoincLinker in org.hl7.fhir.core) */
|
|
1910
|
+
codeLink(context) {
|
|
1911
|
+
return context && context.code ? `https://loinc.org/${encodeURIComponent(context.code)}/` : undefined;
|
|
1912
|
+
}
|
|
1787
1913
|
}
|
|
1788
1914
|
|
|
1789
1915
|
module.exports = {
|