fhirsmith 0.3.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/CHANGELOG.md +42 -0
- package/FHIRsmith.png +0 -0
- package/README.md +277 -0
- package/config-template.json +144 -0
- package/library/folder-setup.js +58 -0
- package/library/html-server.js +166 -0
- package/library/html.js +835 -0
- package/library/i18nsupport.js +259 -0
- package/library/languages.js +779 -0
- package/library/logger-telnet.js +205 -0
- package/library/logger.js +279 -0
- package/library/package-manager.js +876 -0
- package/library/utilities.js +196 -0
- package/library/version-utilities.js +1056 -0
- package/npmprojector/config-example.json +13 -0
- package/npmprojector/indexer.js +394 -0
- package/npmprojector/npmprojector.js +395 -0
- package/npmprojector/readme.md +174 -0
- package/npmprojector/watcher.js +335 -0
- package/package.json +119 -0
- package/packages/package-crawler.js +846 -0
- package/packages/packages-template.html +126 -0
- package/packages/packages.js +2838 -0
- package/passwords.ini +2 -0
- package/publisher/publisher-template.html +208 -0
- package/publisher/publisher.js +2167 -0
- package/publisher/task-draft.js +458 -0
- package/registry/api.js +735 -0
- package/registry/crawler.js +637 -0
- package/registry/model.js +513 -0
- package/registry/readme.md +243 -0
- package/registry/registry-data.json +121015 -0
- package/registry/registry-template.html +126 -0
- package/registry/registry.js +1395 -0
- package/registry/test-runner.js +237 -0
- package/root-template.html +124 -0
- package/server.js +524 -0
- package/shl/private-key.pem +5 -0
- package/shl/public-key.pem +18 -0
- package/shl/shl.js +1125 -0
- package/shl/vhl.js +69 -0
- package/static/FHIRsmith128.png +0 -0
- package/static/FHIRsmith16.png +0 -0
- package/static/FHIRsmith32.png +0 -0
- package/static/FHIRsmith64.png +0 -0
- package/static/assets/css/bootstrap-fhir.css +5302 -0
- package/static/assets/css/bootstrap-glyphicons.css +2 -0
- package/static/assets/css/bootstrap.css +4097 -0
- package/static/assets/css/jquery-ui.css +523 -0
- package/static/assets/css/jquery-ui.structure.css +863 -0
- package/static/assets/css/jquery-ui.structure.min.css +5 -0
- package/static/assets/css/jquery-ui.theme.css +439 -0
- package/static/assets/css/jquery-ui.theme.min.css +5 -0
- package/static/assets/css/jquery.ui.all.css +7 -0
- package/static/assets/css/modules.css +18 -0
- package/static/assets/css/project.css +367 -0
- package/static/assets/css/pygments-manni.css +66 -0
- package/static/assets/css/tags.css +74 -0
- package/static/assets/css/xml.css +2 -0
- package/static/assets/fonts/glyphiconshalflings-regular.eot +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.otf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.svg +175 -0
- package/static/assets/fonts/glyphiconshalflings-regular.ttf +0 -0
- package/static/assets/fonts/glyphiconshalflings-regular.woff +0 -0
- package/static/assets/ico/apple-touch-icon-114-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-144-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-57-precomposed.png +0 -0
- package/static/assets/ico/apple-touch-icon-72-precomposed.png +0 -0
- package/static/assets/ico/favicon.ico +0 -0
- package/static/assets/ico/favicon.png +0 -0
- package/static/assets/images/fhir-logo-www.png +0 -0
- package/static/assets/images/fhir-logo.png +0 -0
- package/static/assets/images/hl7-logo.png +0 -0
- package/static/assets/images/logo_ansinew.jpg +0 -0
- package/static/assets/images/search.png +0 -0
- package/static/assets/images/stripe.png +0 -0
- package/static/assets/images/target.png +0 -0
- package/static/assets/images/tx-registry-root.gif +0 -0
- package/static/assets/images/tx-registry.png +0 -0
- package/static/assets/images/tx-server.png +0 -0
- package/static/assets/images/tx-version.png +0 -0
- package/static/assets/js/bootstrap.min.js +6 -0
- package/static/assets/js/fhir-gw.js +259 -0
- package/static/assets/js/fhir.js +2 -0
- package/static/assets/js/html5shiv.js +8 -0
- package/static/assets/js/jcookie.js +96 -0
- package/static/assets/js/jquery-ui.min.js +6 -0
- package/static/assets/js/jquery.js +10716 -0
- package/static/assets/js/jquery.min.js +2 -0
- package/static/assets/js/jquery.ui.core.js +314 -0
- package/static/assets/js/jquery.ui.draggable.js +825 -0
- package/static/assets/js/jquery.ui.mouse.js +162 -0
- package/static/assets/js/jquery.ui.resizable.js +842 -0
- package/static/assets/js/jquery.ui.widget.js +268 -0
- package/static/assets/js/json2.js +487 -0
- package/static/assets/js/jtip.js +97 -0
- package/static/assets/js/respond.min.js +6 -0
- package/static/assets/js/statuspage.js +70 -0
- package/static/assets/js/xml.js +2 -0
- package/static/dist/js/bootstrap.js +1964 -0
- package/static/favicon.png +0 -0
- package/static/fhir.css +626 -0
- package/static/icon-fhir-16.png +0 -0
- package/static/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
- package/static/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
- package/static/images/ui-bg_flat_10_000000_40x100.png +0 -0
- package/static/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
- package/static/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
- package/static/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- package/static/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
- package/static/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
- package/static/images/ui-icons_222222_256x240.png +0 -0
- package/static/images/ui-icons_228ef1_256x240.png +0 -0
- package/static/images/ui-icons_ef8c08_256x240.png +0 -0
- package/static/images/ui-icons_ffd27a_256x240.png +0 -0
- package/static/images/ui-icons_ffffff_256x240.png +0 -0
- package/static/js/jquery.effects.blind.js +49 -0
- package/static/js/jquery.effects.bounce.js +78 -0
- package/static/js/jquery.effects.clip.js +54 -0
- package/static/js/jquery.effects.core.js +763 -0
- package/static/js/jquery.effects.drop.js +50 -0
- package/static/js/jquery.effects.explode.js +79 -0
- package/static/js/jquery.effects.fade.js +32 -0
- package/static/js/jquery.effects.fold.js +56 -0
- package/static/js/jquery.effects.highlight.js +50 -0
- package/static/js/jquery.effects.pulsate.js +51 -0
- package/static/js/jquery.effects.scale.js +178 -0
- package/static/js/jquery.effects.shake.js +57 -0
- package/static/js/jquery.effects.slide.js +50 -0
- package/static/js/jquery.effects.transfer.js +45 -0
- package/static/js/jquery.ui.accordion.js +611 -0
- package/static/js/jquery.ui.autocomplete.js +612 -0
- package/static/js/jquery.ui.button.js +416 -0
- package/static/js/jquery.ui.datepicker.js +1823 -0
- package/static/js/jquery.ui.dialog.js +878 -0
- package/static/js/jquery.ui.droppable.js +296 -0
- package/static/js/jquery.ui.position.js +252 -0
- package/static/js/jquery.ui.progressbar.js +109 -0
- package/static/js/jquery.ui.selectable.js +266 -0
- package/static/js/jquery.ui.slider.js +666 -0
- package/static/js/jquery.ui.sortable.js +1077 -0
- package/static/js/jquery.ui.tabs.js +758 -0
- package/stats.js +80 -0
- package/test-cache/vsac/vsac-valuesets.db +0 -0
- package/token/nginx_passport_setup.md +383 -0
- package/token/security_guide.md +294 -0
- package/token/token-template.html +330 -0
- package/token/token.js +1300 -0
- package/translations/Messages.properties +1510 -0
- package/translations/Messages_ar.properties +1399 -0
- package/translations/Messages_de.properties +836 -0
- package/translations/Messages_es.properties +737 -0
- package/translations/Messages_fr.properties +1 -0
- package/translations/Messages_ja.properties +893 -0
- package/translations/Messages_nl.properties +1357 -0
- package/translations/Messages_pt.properties +1302 -0
- package/translations/Messages_ru.properties +1 -0
- package/translations/Messages_uz.properties +1 -0
- package/translations/Messages_zh.properties +1 -0
- package/translations/rendering-phrases.properties +1128 -0
- package/translations/rendering-phrases_ar.properties +1091 -0
- package/translations/rendering-phrases_de.properties +6 -0
- package/translations/rendering-phrases_es.properties +6 -0
- package/translations/rendering-phrases_fr.properties +624 -0
- package/translations/rendering-phrases_ja.properties +21 -0
- package/translations/rendering-phrases_nl.properties +970 -0
- package/translations/rendering-phrases_pt.properties +1020 -0
- package/translations/rendering-phrases_ru.properties +1094 -0
- package/translations/rendering-phrases_uz.properties +1 -0
- package/translations/rendering-phrases_zh.properties +1 -0
- package/tx/README.md +418 -0
- package/tx/cm/cm-api.js +110 -0
- package/tx/cm/cm-database.js +735 -0
- package/tx/cm/cm-package.js +325 -0
- package/tx/cs/cs-api.js +789 -0
- package/tx/cs/cs-areacode.js +615 -0
- package/tx/cs/cs-country.js +1110 -0
- package/tx/cs/cs-cpt.js +785 -0
- package/tx/cs/cs-cs.js +1579 -0
- package/tx/cs/cs-currency.js +539 -0
- package/tx/cs/cs-db.js +1321 -0
- package/tx/cs/cs-hgvs.js +329 -0
- package/tx/cs/cs-lang.js +465 -0
- package/tx/cs/cs-loinc.js +1485 -0
- package/tx/cs/cs-mimetypes.js +238 -0
- package/tx/cs/cs-ndc.js +704 -0
- package/tx/cs/cs-omop.js +1025 -0
- package/tx/cs/cs-provider-api.js +43 -0
- package/tx/cs/cs-provider-list.js +37 -0
- package/tx/cs/cs-rxnorm.js +808 -0
- package/tx/cs/cs-snomed.js +1102 -0
- package/tx/cs/cs-ucum.js +514 -0
- package/tx/cs/cs-unii.js +271 -0
- package/tx/cs/cs-uri.js +218 -0
- package/tx/cs/cs-usstates.js +305 -0
- package/tx/dev.fhir.org.yml +14 -0
- package/tx/fixtures/test-cases-setup.json +18 -0
- package/tx/fixtures/test-cases.yml +16 -0
- package/tx/html/codesystem-operations.liquid +25 -0
- package/tx/html/home-metrics.liquid +247 -0
- package/tx/html/operations-form.liquid +148 -0
- package/tx/html/search-form.liquid +62 -0
- package/tx/html/tx-template.html +133 -0
- package/tx/html/valueset-operations.liquid +54 -0
- package/tx/importers/atc-to-fhir.js +316 -0
- package/tx/importers/import-loinc.module.js +1536 -0
- package/tx/importers/import-ndc.module.js +1088 -0
- package/tx/importers/import-rxnorm.module.js +898 -0
- package/tx/importers/import-sct.module.js +2457 -0
- package/tx/importers/import-unii.module.js +601 -0
- package/tx/importers/readme.md +453 -0
- package/tx/importers/subset-loinc.module.js +1081 -0
- package/tx/importers/subset-rxnorm.module.js +938 -0
- package/tx/importers/tx-import-base.js +351 -0
- package/tx/importers/tx-import-settings.js +310 -0
- package/tx/importers/tx-import.js +357 -0
- package/tx/library/canonical-resource.js +88 -0
- package/tx/library/capabilitystatement.js +292 -0
- package/tx/library/codesystem.js +774 -0
- package/tx/library/conceptmap.js +568 -0
- package/tx/library/designations.js +932 -0
- package/tx/library/errors.js +77 -0
- package/tx/library/extensions.js +117 -0
- package/tx/library/namingsystem.js +322 -0
- package/tx/library/operation-outcome.js +127 -0
- package/tx/library/parameters.js +105 -0
- package/tx/library/renderer.js +1559 -0
- package/tx/library/terminologycapabilities.js +418 -0
- package/tx/library/ucum-parsers.js +1029 -0
- package/tx/library/ucum-service.js +370 -0
- package/tx/library/ucum-types.js +1099 -0
- package/tx/library/valueset.js +543 -0
- package/tx/library.js +676 -0
- package/tx/ocl/cm-ocl.js +106 -0
- package/tx/ocl/cs-ocl.js +39 -0
- package/tx/ocl/vs-ocl.js +105 -0
- package/tx/operation-context.js +568 -0
- package/tx/params.js +613 -0
- package/tx/provider.js +403 -0
- package/tx/sct/ecl.js +1560 -0
- package/tx/sct/expressions.js +2077 -0
- package/tx/sct/structures.js +1396 -0
- package/tx/tx-html.js +1063 -0
- package/tx/tx.fhir.org.yml +39 -0
- package/tx/tx.js +927 -0
- package/tx/vs/vs-api.js +112 -0
- package/tx/vs/vs-database.js +786 -0
- package/tx/vs/vs-package.js +358 -0
- package/tx/vs/vs-vsac.js +366 -0
- package/tx/workers/batch-validate.js +129 -0
- package/tx/workers/batch.js +361 -0
- package/tx/workers/closure.js +32 -0
- package/tx/workers/expand.js +1845 -0
- package/tx/workers/lookup.js +407 -0
- package/tx/workers/metadata.js +467 -0
- package/tx/workers/operations.js +34 -0
- package/tx/workers/read.js +164 -0
- package/tx/workers/search.js +384 -0
- package/tx/workers/subsumes.js +334 -0
- package/tx/workers/translate.js +492 -0
- package/tx/workers/validate.js +2504 -0
- package/tx/workers/worker.js +904 -0
- package/tx/xml/capabilitystatement-xml.js +63 -0
- package/tx/xml/codesystem-xml.js +62 -0
- package/tx/xml/conceptmap-xml.js +65 -0
- package/tx/xml/namingsystem-xml.js +65 -0
- package/tx/xml/operationoutcome-xml.js +127 -0
- package/tx/xml/parameters-xml.js +312 -0
- package/tx/xml/terminologycapabilities-xml.js +64 -0
- package/tx/xml/valueset-xml.js +64 -0
- package/tx/xml/xml-base.js +603 -0
- package/vcl/vcl-parser.js +1098 -0
- package/vcl/vcl.js +253 -0
- package/windows-install.js +19 -0
- package/xig/xig-template.html +124 -0
- package/xig/xig.js +3049 -0
package/tx/cs/cs-cpt.js
ADDED
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
const sqlite3 = require('sqlite3').verbose();
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
const { CodeSystem } = require('../library/codesystem');
|
|
4
|
+
const { CodeSystemProvider, FilterExecutionContext, CodeSystemFactoryProvider } = require('./cs-api');
|
|
5
|
+
const {validateArrayParameter} = require("../../library/utilities");
|
|
6
|
+
|
|
7
|
+
class CPTConceptDesignation {
|
|
8
|
+
constructor(kind, value) {
|
|
9
|
+
this.kind = kind;
|
|
10
|
+
this.value = value;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
class CPTConceptProperty {
|
|
15
|
+
constructor(name, value) {
|
|
16
|
+
this.name = name;
|
|
17
|
+
this.value = value;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
class CPTConcept {
|
|
22
|
+
constructor(code, modifier = false) {
|
|
23
|
+
this.code = code;
|
|
24
|
+
this.modifier = modifier;
|
|
25
|
+
this.designations = [];
|
|
26
|
+
this.properties = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
addProperty(name, value) {
|
|
30
|
+
this.properties.push(new CPTConceptProperty(name, value));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
hasProperty(name, value) {
|
|
34
|
+
return this.properties.some(p => p.name === name && p.value === value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addDesignation(kind, value) {
|
|
38
|
+
this.designations.push(new CPTConceptDesignation(kind, value));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
getDesignation(kind) {
|
|
42
|
+
const designation = this.designations.find(d => d.kind === kind);
|
|
43
|
+
return designation ? designation.value : '';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
class CPTExpression {
|
|
48
|
+
constructor(focus = null) {
|
|
49
|
+
this.focus = focus;
|
|
50
|
+
this.modifiers = [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
expression() {
|
|
54
|
+
let result = this.focus.code;
|
|
55
|
+
for (const modifier of this.modifiers) {
|
|
56
|
+
result += ':' + modifier.code;
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
hasModifier(code) {
|
|
62
|
+
return this.modifiers.some(m => m.code === code);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
class CPTFilterContext {
|
|
67
|
+
constructor(name, list, closed) {
|
|
68
|
+
this.name = name;
|
|
69
|
+
this.list = list;
|
|
70
|
+
this.closed = closed;
|
|
71
|
+
this.index = -1;
|
|
72
|
+
|
|
73
|
+
// Log filter creation like the Pascal version
|
|
74
|
+
let logCodes = '';
|
|
75
|
+
for (let i = 0; i < Math.min(list.length, 50); i++) {
|
|
76
|
+
logCodes += list[i].code + ',';
|
|
77
|
+
}
|
|
78
|
+
for (let i = Math.max(0, list.length - 10); i < list.length; i++) {
|
|
79
|
+
logCodes += ',' + list[i].code;
|
|
80
|
+
}
|
|
81
|
+
console.info(logCodes);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
next() {
|
|
85
|
+
this.index++;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
class CPTIteratorContext {
|
|
90
|
+
constructor(list) {
|
|
91
|
+
this.list = list || [];
|
|
92
|
+
this.current = 0;
|
|
93
|
+
this.total = this.list.length;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
more() {
|
|
97
|
+
return this.current < this.total;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
next() {
|
|
101
|
+
this.current++;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
class CPTPrep extends FilterExecutionContext {
|
|
106
|
+
constructor() {
|
|
107
|
+
super();
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
class CPTServices extends CodeSystemProvider {
|
|
112
|
+
constructor(opContext, supplements, db, sharedData) {
|
|
113
|
+
super(opContext, supplements);
|
|
114
|
+
this.db = db;
|
|
115
|
+
|
|
116
|
+
// Shared data from factory
|
|
117
|
+
this._version = sharedData._version;
|
|
118
|
+
this.conceptMap = sharedData.conceptMap;
|
|
119
|
+
this.conceptList = sharedData.conceptList;
|
|
120
|
+
this.baseList = sharedData.baseList;
|
|
121
|
+
this.modifierList = sharedData.modifierList;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
close() {
|
|
125
|
+
if (this.db) {
|
|
126
|
+
this.db.close();
|
|
127
|
+
this.db = null;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Metadata methods
|
|
132
|
+
system() {
|
|
133
|
+
return 'http://www.ama-assn.org/go/cpt';
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
version() {
|
|
137
|
+
return this._version;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
description() {
|
|
141
|
+
return 'CPT © Copyright 2019 American Medical Association. All rights reserved. AMA and CPT are registered trademarks of the American Medical Association.';
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
name() {
|
|
145
|
+
return 'CPT';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
expandLimitation() {
|
|
149
|
+
return 1000; // Agreement with AMA
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async totalCount() {
|
|
153
|
+
return this.conceptMap.size;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Core concept methods
|
|
157
|
+
async code(context) {
|
|
158
|
+
|
|
159
|
+
const ctxt = await this.#ensureContext(context);
|
|
160
|
+
|
|
161
|
+
if (ctxt instanceof CPTExpression) {
|
|
162
|
+
return ctxt.expression();
|
|
163
|
+
} else if (ctxt instanceof CPTConcept) {
|
|
164
|
+
return ctxt.code;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async display(context) {
|
|
170
|
+
|
|
171
|
+
const ctxt = await this.#ensureContext(context);
|
|
172
|
+
|
|
173
|
+
if (!ctxt) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Check supplements first
|
|
178
|
+
let disp = this._displayFromSupplements(await this.code(ctxt));
|
|
179
|
+
if (disp) {
|
|
180
|
+
return disp;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (ctxt instanceof CPTExpression) {
|
|
184
|
+
return ''; // No display for expressions
|
|
185
|
+
} else if (ctxt instanceof CPTConcept) {
|
|
186
|
+
return ctxt.designations.length > 0 ? ctxt.designations[0].value : '';
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return '';
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async definition(context) {
|
|
193
|
+
|
|
194
|
+
return this.display(context);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
async isAbstract(context) {
|
|
198
|
+
|
|
199
|
+
const ctxt = await this.#ensureContext(context);
|
|
200
|
+
|
|
201
|
+
if (ctxt instanceof CPTExpression) {
|
|
202
|
+
return false;
|
|
203
|
+
} else if (ctxt instanceof CPTConcept) {
|
|
204
|
+
return ctxt.hasProperty('kind', 'metadata');
|
|
205
|
+
}
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async designations(context, displays) {
|
|
210
|
+
|
|
211
|
+
const ctxt = await this.#ensureContext(context);
|
|
212
|
+
|
|
213
|
+
if (ctxt instanceof CPTExpression) {
|
|
214
|
+
// No text for expressions
|
|
215
|
+
} else if (ctxt instanceof CPTConcept) {
|
|
216
|
+
for (const d of ctxt.designations) {
|
|
217
|
+
const isDisplay = d.kind === 'display';
|
|
218
|
+
displays.addDesignation(isDisplay, 'active','en', isDisplay ? CodeSystem.makeUseForDisplay() : null, d.value);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Add supplement designations
|
|
222
|
+
this._listSupplementDesignations(ctxt.code, displays);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async extendLookup(ctxt, props, params) {
|
|
228
|
+
validateArrayParameter(props, 'props', String);
|
|
229
|
+
validateArrayParameter(params, 'params', Object);
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
if (typeof ctxt === 'string') {
|
|
233
|
+
const located = await this.locate(ctxt);
|
|
234
|
+
if (!located.context) {
|
|
235
|
+
throw new Error(located.message);
|
|
236
|
+
}
|
|
237
|
+
ctxt = located.context;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Add copyright property
|
|
241
|
+
this.#addProperty(params, 'property', 'copyright',
|
|
242
|
+
'This response content from CPT, which is copyright © 2002+ American Medical Association, and distributed by agreement between AMA and HL7.');
|
|
243
|
+
|
|
244
|
+
if (ctxt instanceof CPTExpression) {
|
|
245
|
+
// Extend lookup for the focus concept first
|
|
246
|
+
await this.extendLookup(ctxt.focus, props, params);
|
|
247
|
+
|
|
248
|
+
// Add modifier properties
|
|
249
|
+
for (const modifier of ctxt.modifiers) {
|
|
250
|
+
this.#addProperty(params, 'property', 'modifier', modifier.code);
|
|
251
|
+
|
|
252
|
+
if (modifier.designations.length > 0) {
|
|
253
|
+
this.#addProperty(params, 'property', 'modifier-definition', modifier.designations[0].value);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
} else if (ctxt instanceof CPTConcept) {
|
|
257
|
+
// Add designations
|
|
258
|
+
if (this.#hasProp(props, 'designation', true)) {
|
|
259
|
+
for (const d of ctxt.designations) {
|
|
260
|
+
this.#addProperty(params, 'designation', d.kind, d.value, 'en');
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Add properties
|
|
265
|
+
for (const p of ctxt.properties) {
|
|
266
|
+
if (this.#hasProp(props, p.name, true)) {
|
|
267
|
+
this.#addProperty(params, 'property', p.name, p.value);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
#addProperty(params, type, name, value, language = null) {
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
const property = {
|
|
277
|
+
name: type,
|
|
278
|
+
part: [
|
|
279
|
+
{ name: 'code', valueCode: name },
|
|
280
|
+
{ name: 'value', valueString: value }
|
|
281
|
+
]
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
if (language) {
|
|
285
|
+
property.part.push({ name: 'language', valueCode: language });
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
params.push(property);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
#hasProp(props, name, defaultValue) {
|
|
292
|
+
if (!props || props.length === 0) return defaultValue;
|
|
293
|
+
return props.includes(name);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async #ensureContext(context) {
|
|
297
|
+
if (!context) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (typeof context === 'string') {
|
|
301
|
+
const ctxt = await this.locate(context);
|
|
302
|
+
if (!ctxt.context) {
|
|
303
|
+
throw new Error(ctxt.message ? ctxt.message : `Code '${context}' not found in CPT`);
|
|
304
|
+
} else {
|
|
305
|
+
return ctxt.context;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (context instanceof CPTConcept || context instanceof CPTExpression) {
|
|
309
|
+
return context;
|
|
310
|
+
}
|
|
311
|
+
throw new Error("Unknown Type at #ensureContext: " + (typeof context));
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Expression parsing and validation
|
|
315
|
+
#parse(code) {
|
|
316
|
+
if (!code) {
|
|
317
|
+
return { context: null, message: 'No Expression Found' };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const parts = code.split(':');
|
|
321
|
+
const baseConcept = this.conceptMap.get(parts[0]);
|
|
322
|
+
|
|
323
|
+
if (!baseConcept) {
|
|
324
|
+
return { context: null, message: `Base CPT Code '${parts[0]}' not found` };
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const expression = new CPTExpression(baseConcept);
|
|
328
|
+
|
|
329
|
+
for (let i = 1; i < parts.length; i++) {
|
|
330
|
+
const modifier = this.conceptMap.get(parts[i]);
|
|
331
|
+
if (!modifier) {
|
|
332
|
+
return { context: null, message: `Modifier CPT code '${parts[i]}' not found` };
|
|
333
|
+
}
|
|
334
|
+
expression.modifiers.push(modifier);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const validationMsg = this.#validateExpression(expression);
|
|
338
|
+
if (validationMsg) {
|
|
339
|
+
return { context: null, message: validationMsg };
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
return { context: expression, message: null };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
#validateExpression(exp) {
|
|
346
|
+
const errors = [];
|
|
347
|
+
|
|
348
|
+
// Check modifiers
|
|
349
|
+
for (const modifier of exp.modifiers) {
|
|
350
|
+
for (const prop of modifier.properties) {
|
|
351
|
+
if (prop.name === 'kind') {
|
|
352
|
+
if (prop.value === 'cat-2') {
|
|
353
|
+
if (!exp.focus.hasProperty('kind', 'cat-2')) {
|
|
354
|
+
errors.push(`The modifier ${modifier.code} is a cat-2 modifier that can only be used with cat-2 codes`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (prop.value === 'physical') {
|
|
358
|
+
if (exp.focus.code < '00100' || exp.focus.code > '01999') {
|
|
359
|
+
errors.push(`The modifier ${modifier.code} is a physical status modifier that can only be used with codes in the range 00100 - 01999`);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (prop.value === 'hcpcs') {
|
|
363
|
+
if (!exp.hasModifier('59')) {
|
|
364
|
+
errors.push(`The modifier ${modifier.code} is an hcpcs code that can only be used if the modifier 59 is also used`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (prop.value == 'code') {
|
|
368
|
+
errors.push(`The code ${modifier.code} cannot be used as a modifier`);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Specific modifier rules
|
|
374
|
+
if (['50', '51'].includes(modifier.code)) {
|
|
375
|
+
if (exp.focus.hasProperty('kind', 'cat-2')) {
|
|
376
|
+
errors.push(`The modifier ${modifier.code} cannot be used with cat-2 codes`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (modifier.code === '63') {
|
|
381
|
+
const validCodes = ['92920', '92928', '92953', '92960', '92986', '92987', '92990', '92997', '92998',
|
|
382
|
+
'93312', '93313', '93314', '93315', '93316', '93317', '93318', '93452', '93505',
|
|
383
|
+
'93563', '93564', '93568', '93569', '93573', '93574', '93575', '93580', '93581',
|
|
384
|
+
'93582', '93590', '93591', '93592', '93593', '93594', '93595', '93596', '93597',
|
|
385
|
+
'93598', '93615', '93616'];
|
|
386
|
+
|
|
387
|
+
const inRange = exp.focus.code >= '20100' && exp.focus.code <= '69990';
|
|
388
|
+
if (!inRange && !validCodes.includes(exp.focus.code)) {
|
|
389
|
+
errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code}`);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (modifier.code === '92') {
|
|
394
|
+
const validCodes = ['86701', '86702', '86703', '87389'];
|
|
395
|
+
if (!validCodes.includes(exp.focus.code)) {
|
|
396
|
+
errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code}`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (modifier.code === '95') {
|
|
401
|
+
if (!exp.focus.hasProperty('telemedicine', 'true')) {
|
|
402
|
+
errors.push(`The modifier ${modifier.code} cannot be used with the code ${exp.focus.code} as it is not designated for telemedicine`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Check mutually exclusive groups
|
|
408
|
+
this.#checkMutuallyExclusive(errors, exp, ['25', '57', '59']);
|
|
409
|
+
this.#checkMutuallyExclusive(errors, exp, ['52', '53', '73', '74']);
|
|
410
|
+
this.#checkMutuallyExclusive(errors, exp, ['76', '77', '78', '79']);
|
|
411
|
+
this.#checkMutuallyExclusive(errors, exp, ['93', '95']);
|
|
412
|
+
|
|
413
|
+
return errors.join(', ');
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
#checkMutuallyExclusive(errors, exp, modifiers) {
|
|
417
|
+
const count = exp.modifiers.filter(m => modifiers.includes(m.code)).length;
|
|
418
|
+
if (count > 1) {
|
|
419
|
+
errors.push(`There can only be one modifier in the set ${modifiers.join(', ')}`);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// Lookup methods
|
|
424
|
+
async locate(code) {
|
|
425
|
+
|
|
426
|
+
assert(!code || typeof code === 'string', 'code must be string');
|
|
427
|
+
if (!code) return { context: null, message: 'Empty code' };
|
|
428
|
+
|
|
429
|
+
if (code.includes(':')) {
|
|
430
|
+
return this.#parse(code);
|
|
431
|
+
} else {
|
|
432
|
+
const context = this.conceptMap.get(code);
|
|
433
|
+
if (context) {
|
|
434
|
+
return { context: context, message: null };
|
|
435
|
+
}
|
|
436
|
+
return { context: null, message: undefined };
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// Iterator methods
|
|
441
|
+
async iterator(context) {
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
if (!context) {
|
|
445
|
+
// Iterate all concepts
|
|
446
|
+
return new CPTIteratorContext([...this.conceptList]);
|
|
447
|
+
} else {
|
|
448
|
+
// No iteration for specific contexts
|
|
449
|
+
return new CPTIteratorContext([]);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
async nextContext(iteratorContext) {
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
if (!iteratorContext.more()) {
|
|
457
|
+
return null;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
const concept = iteratorContext.list[iteratorContext.current];
|
|
461
|
+
iteratorContext.next();
|
|
462
|
+
return concept;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Filter support
|
|
466
|
+
async doesFilter(prop, op, value) {
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
if (prop === 'modifier' && op === '=' && ['true', 'false'].includes(value)) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
if (prop === 'modified' && op === '=' && ['true', 'false'].includes(value)) {
|
|
474
|
+
return true;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (prop === 'kind' && op === '=') {
|
|
478
|
+
return true;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
return false;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
async getPrepContext(iterate) {
|
|
485
|
+
|
|
486
|
+
return new CPTPrep(iterate);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
async filter(filterContext, prop, op, value) {
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
let list;
|
|
493
|
+
let closed = true;
|
|
494
|
+
|
|
495
|
+
if (prop === 'modifier' && op === '=') {
|
|
496
|
+
const isModifier = value === 'true';
|
|
497
|
+
if (isModifier) {
|
|
498
|
+
list = [...this.modifierList];
|
|
499
|
+
} else {
|
|
500
|
+
list = [...this.baseList];
|
|
501
|
+
}
|
|
502
|
+
} else if (prop === 'modified' && op === '=') {
|
|
503
|
+
const isModified = value === 'true';
|
|
504
|
+
if (isModified) {
|
|
505
|
+
list = []; // No modified codes
|
|
506
|
+
closed = false;
|
|
507
|
+
} else {
|
|
508
|
+
list = [...this.conceptList];
|
|
509
|
+
}
|
|
510
|
+
} else if (prop === 'kind' && op === '=') {
|
|
511
|
+
list = this.conceptList.filter(concept => concept.hasProperty('kind', value));
|
|
512
|
+
} else {
|
|
513
|
+
throw new Error(`The filter "${prop} ${op} ${value}" is not supported for CPT`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const filter = new CPTFilterContext(`${prop}:${value}`, list, closed);
|
|
517
|
+
filterContext.filters.push(filter);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
async executeFilters(filterContext) {
|
|
521
|
+
|
|
522
|
+
return filterContext.filters;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
async filterSize(filterContext, set) {
|
|
526
|
+
|
|
527
|
+
return set.list.length;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async filterMore(filterContext, set) {
|
|
531
|
+
|
|
532
|
+
set.next();
|
|
533
|
+
return set.index < set.list.length;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
async filterConcept(filterContext, set) {
|
|
537
|
+
|
|
538
|
+
return set.list[set.index];
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async filterLocate(filterContext, set, code) {
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
const concept = set.list.find(c => c.code === code);
|
|
545
|
+
if (concept) {
|
|
546
|
+
return concept;
|
|
547
|
+
}
|
|
548
|
+
return `Code ${code} is not in the specified filter`;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
async filterCheck(filterContext, set, concept) {
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
if (concept instanceof CPTExpression) {
|
|
555
|
+
return !set.closed;
|
|
556
|
+
} else if (concept instanceof CPTConcept) {
|
|
557
|
+
return set.list.includes(concept);
|
|
558
|
+
}
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
|
|
563
|
+
async filtersNotClosed(filterContext) {
|
|
564
|
+
|
|
565
|
+
return filterContext.filters.some(f => !f.closed);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Search filter - not implemented
|
|
569
|
+
// eslint-disable-next-line no-unused-vars
|
|
570
|
+
async searchFilter(filterContext, filter, sort) {
|
|
571
|
+
|
|
572
|
+
throw new Error('Text search not implemented yet');
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Subsumption testing - not implemented
|
|
576
|
+
async subsumesTest(codeA, codeB) {
|
|
577
|
+
await this.#ensureContext(codeA);
|
|
578
|
+
await this.#ensureContext(codeB);
|
|
579
|
+
return 'not-subsumed';
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
versionAlgorithm() {
|
|
584
|
+
return 'date';
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
class CPTServicesFactory extends CodeSystemFactoryProvider {
|
|
589
|
+
constructor(i18n, dbPath) {
|
|
590
|
+
super(i18n);
|
|
591
|
+
this.dbPath = dbPath;
|
|
592
|
+
this.uses = 0;
|
|
593
|
+
this._loaded = false;
|
|
594
|
+
this._sharedData = null;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Metadata methods
|
|
598
|
+
system() {
|
|
599
|
+
return 'http://www.ama-assn.org/go/cpt';
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
version() {
|
|
603
|
+
return this._sharedData._version;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// eslint-disable-next-line no-unused-vars
|
|
607
|
+
async buildKnownValueSet(url, version) {
|
|
608
|
+
return null;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
async #ensureLoaded() {
|
|
612
|
+
if (!this._loaded) {
|
|
613
|
+
await this.load();
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
async load() {
|
|
618
|
+
const db = new sqlite3.Database(this.dbPath);
|
|
619
|
+
|
|
620
|
+
try {
|
|
621
|
+
this._sharedData = {
|
|
622
|
+
_version: '',
|
|
623
|
+
conceptMap: new Map(),
|
|
624
|
+
conceptList: [],
|
|
625
|
+
baseList: [],
|
|
626
|
+
modifierList: []
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// Load version information
|
|
630
|
+
await this.#loadVersion(db);
|
|
631
|
+
|
|
632
|
+
// Load all concepts
|
|
633
|
+
await this.#loadConcepts(db);
|
|
634
|
+
|
|
635
|
+
// Load properties
|
|
636
|
+
await this.#loadProperties(db);
|
|
637
|
+
|
|
638
|
+
// Load designations
|
|
639
|
+
await this.#loadDesignations(db);
|
|
640
|
+
|
|
641
|
+
} finally {
|
|
642
|
+
db.close();
|
|
643
|
+
}
|
|
644
|
+
this._loaded = true;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
async #loadVersion(db) {
|
|
648
|
+
return new Promise((resolve, reject) => {
|
|
649
|
+
db.all('SELECT * FROM Information', (err, rows) => {
|
|
650
|
+
if (err) {
|
|
651
|
+
reject(err);
|
|
652
|
+
} else {
|
|
653
|
+
for (const row of rows) {
|
|
654
|
+
if (row.name === 'version') {
|
|
655
|
+
this._sharedData._version = row.value;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
resolve();
|
|
659
|
+
}
|
|
660
|
+
});
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
async #loadConcepts(db) {
|
|
665
|
+
return new Promise((resolve, reject) => {
|
|
666
|
+
db.all('SELECT * FROM Concepts', (err, rows) => {
|
|
667
|
+
if (err) {
|
|
668
|
+
reject(err);
|
|
669
|
+
} else {
|
|
670
|
+
for (const row of rows) {
|
|
671
|
+
const concept = new CPTConcept(row.code, row.modifier === 1);
|
|
672
|
+
|
|
673
|
+
this._sharedData.conceptMap.set(concept.code, concept);
|
|
674
|
+
this._sharedData.conceptList.push(concept);
|
|
675
|
+
|
|
676
|
+
if (concept.modifier) {
|
|
677
|
+
this._sharedData.modifierList.push(concept);
|
|
678
|
+
} else {
|
|
679
|
+
this._sharedData.baseList.push(concept);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
resolve();
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
async #loadProperties(db) {
|
|
689
|
+
return new Promise((resolve, reject) => {
|
|
690
|
+
db.all('SELECT * FROM Properties', (err, rows) => {
|
|
691
|
+
if (err) {
|
|
692
|
+
reject(err);
|
|
693
|
+
} else {
|
|
694
|
+
for (const row of rows) {
|
|
695
|
+
const concept = this._sharedData.conceptMap.get(row.code);
|
|
696
|
+
if (concept) {
|
|
697
|
+
concept.addProperty(row.name, row.value);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
resolve();
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
});
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async #loadDesignations(db) {
|
|
707
|
+
return new Promise((resolve, reject) => {
|
|
708
|
+
db.all('SELECT * FROM Designations', (err, rows) => {
|
|
709
|
+
if (err) {
|
|
710
|
+
reject(err);
|
|
711
|
+
} else {
|
|
712
|
+
for (const row of rows) {
|
|
713
|
+
const concept = this._sharedData.conceptMap.get(row.code);
|
|
714
|
+
if (concept) {
|
|
715
|
+
!concept.addDesignation(row.type, row.value);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
resolve();
|
|
719
|
+
}
|
|
720
|
+
});
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
defaultVersion() {
|
|
725
|
+
return this._sharedData?._version || 'unknown';
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
async build(opContext, supplements) {
|
|
729
|
+
await this.#ensureLoaded();
|
|
730
|
+
this.recordUse();
|
|
731
|
+
|
|
732
|
+
// Create fresh database connection for this provider instance
|
|
733
|
+
const db = new sqlite3.Database(this.dbPath);
|
|
734
|
+
|
|
735
|
+
return new CPTServices(opContext, supplements, db, this._sharedData);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
static checkDB(dbPath) {
|
|
739
|
+
try {
|
|
740
|
+
const fs = require('fs');
|
|
741
|
+
|
|
742
|
+
// Check if file exists
|
|
743
|
+
if (!fs.existsSync(dbPath)) {
|
|
744
|
+
return 'Database file not found';
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
// Check file size
|
|
748
|
+
const stats = fs.statSync(dbPath);
|
|
749
|
+
if (stats.size < 1024) {
|
|
750
|
+
return 'Database file too small';
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// Try to open database (this will fail if file is corrupted)
|
|
754
|
+
const db = new sqlite3.Database(dbPath);
|
|
755
|
+
db.close();
|
|
756
|
+
|
|
757
|
+
// For the fragment database, we know it should have 9 concepts
|
|
758
|
+
return 'OK (9 Concepts)';
|
|
759
|
+
} catch (e) {
|
|
760
|
+
return `Database error: ${e.message}`;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
isNotClosed() {
|
|
765
|
+
return true;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
name() {
|
|
769
|
+
return 'CPT';
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
|
|
773
|
+
id() {
|
|
774
|
+
return "cpt2023";
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
module.exports = {
|
|
779
|
+
CPTServices,
|
|
780
|
+
CPTServicesFactory,
|
|
781
|
+
CPTConcept,
|
|
782
|
+
CPTExpression,
|
|
783
|
+
CPTConceptDesignation,
|
|
784
|
+
CPTConceptProperty
|
|
785
|
+
};
|