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
|
@@ -0,0 +1,1396 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class SnomedStrings {
|
|
5
|
+
constructor(buffer = null) {
|
|
6
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
7
|
+
this.decoder = new TextDecoder('utf-8');
|
|
8
|
+
this.builder = null;
|
|
9
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
getEntry(offset) {
|
|
13
|
+
if (offset > this.master.length) {
|
|
14
|
+
throw new Error('Wrong length index getting snomed name');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Read 2-byte length prefix (little-endian)
|
|
18
|
+
const length = this.master.readUInt16LE(offset);
|
|
19
|
+
|
|
20
|
+
// Bounds check
|
|
21
|
+
if (offset + 2 + length > this.master.length) {
|
|
22
|
+
throw new Error('Wrong length index getting snomed name (2)');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Read UTF-8 bytes and decode to string
|
|
26
|
+
const stringBytes = this.master.subarray(offset + 2, offset + 2 + length);
|
|
27
|
+
return this.decoder.decode(stringBytes);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
startBuild() {
|
|
31
|
+
this.builder = [];
|
|
32
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
reopen() {
|
|
36
|
+
this.builder = [this.master];
|
|
37
|
+
this.currentOffset = this.master.length; // SET: offset to current master length
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
addString(str) {
|
|
41
|
+
if (!this.builder) {
|
|
42
|
+
throw new Error('Must call startBuild() first');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const utf8Bytes = Buffer.from(str, 'utf8');
|
|
46
|
+
|
|
47
|
+
if (utf8Bytes.length > 65535) {
|
|
48
|
+
throw new Error(`Snomed Description too long: ${str}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
52
|
+
const currentOffset = this.currentOffset;
|
|
53
|
+
|
|
54
|
+
// Create length prefix (2 bytes, little-endian)
|
|
55
|
+
const lengthPrefix = Buffer.allocUnsafe(2);
|
|
56
|
+
lengthPrefix.writeUInt16LE(utf8Bytes.length, 0);
|
|
57
|
+
|
|
58
|
+
// Add length prefix + string bytes to builder
|
|
59
|
+
this.builder.push(lengthPrefix, utf8Bytes);
|
|
60
|
+
|
|
61
|
+
// UPDATE: offset for next call
|
|
62
|
+
this.currentOffset += 2 + utf8Bytes.length;
|
|
63
|
+
|
|
64
|
+
return currentOffset;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
doneBuild() {
|
|
68
|
+
if (!this.builder) {
|
|
69
|
+
throw new Error('No build in progress');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.master = Buffer.concat(this.builder);
|
|
73
|
+
this.builder = null;
|
|
74
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
clear() {
|
|
78
|
+
this.master = Buffer.alloc(0);
|
|
79
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
get length() {
|
|
83
|
+
return this.master.length;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
class SnomedWords {
|
|
88
|
+
constructor(buffer = null) {
|
|
89
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
90
|
+
this.builder = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getEntry(index) {
|
|
94
|
+
const offset = index * 5;
|
|
95
|
+
|
|
96
|
+
if (offset > this.master.length - 5) {
|
|
97
|
+
throw new Error('invalid index');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Read 4-byte cardinal (little-endian) + 1-byte flag
|
|
101
|
+
const stringIndex = this.master.readUInt32LE(offset);
|
|
102
|
+
const flags = this.master.readUInt8(offset + 4);
|
|
103
|
+
|
|
104
|
+
return { index: stringIndex, flags };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
getString(index) {
|
|
108
|
+
const entry = this.getEntry(index);
|
|
109
|
+
return entry.index;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
count() {
|
|
113
|
+
return Math.floor(this.master.length / 5);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
startBuild() {
|
|
117
|
+
this.builder = [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
addWord(index, flags) {
|
|
121
|
+
if (!this.builder) {
|
|
122
|
+
throw new Error('Must call startBuild() first');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Create 5-byte record: 4-byte index + 1-byte flag
|
|
126
|
+
const record = Buffer.allocUnsafe(5);
|
|
127
|
+
record.writeUInt32LE(index, 0); // 4-byte cardinal (little-endian)
|
|
128
|
+
record.writeUInt8(flags, 4); // 1-byte flag
|
|
129
|
+
|
|
130
|
+
this.builder.push(record);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
doneBuild() {
|
|
134
|
+
if (!this.builder) {
|
|
135
|
+
throw new Error('No build in progress');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
this.master = Buffer.concat(this.builder);
|
|
139
|
+
this.builder = null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
clear() {
|
|
143
|
+
this.master = Buffer.alloc(0);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
get length() {
|
|
147
|
+
return this.master.length;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class SnomedStems {
|
|
152
|
+
constructor(buffer = null) {
|
|
153
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
154
|
+
this.builder = null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
getEntry(index) {
|
|
158
|
+
const offset = index * 8;
|
|
159
|
+
|
|
160
|
+
if (offset > this.master.length - 7) {
|
|
161
|
+
throw new Error('invalid index');
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Read two 4-byte cardinals (little-endian)
|
|
165
|
+
const stringIndex = this.master.readUInt32LE(offset);
|
|
166
|
+
const reference = this.master.readUInt32LE(offset + 4);
|
|
167
|
+
|
|
168
|
+
return { index: stringIndex, reference };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
getString(index) {
|
|
172
|
+
const entry = this.getEntry(index);
|
|
173
|
+
return entry.index;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
count() {
|
|
177
|
+
return Math.floor(this.master.length / 8);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
startBuild() {
|
|
181
|
+
this.builder = [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
addStem(index, reference) {
|
|
185
|
+
if (!this.builder) {
|
|
186
|
+
throw new Error('Must call startBuild() first');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create 8-byte record: two 4-byte cardinals
|
|
190
|
+
const record = Buffer.allocUnsafe(8);
|
|
191
|
+
record.writeUInt32LE(index, 0); // First 4-byte cardinal
|
|
192
|
+
record.writeUInt32LE(reference, 4); // Second 4-byte cardinal
|
|
193
|
+
|
|
194
|
+
this.builder.push(record);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
doneBuild() {
|
|
198
|
+
if (!this.builder) {
|
|
199
|
+
throw new Error('No build in progress');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
this.master = Buffer.concat(this.builder);
|
|
203
|
+
this.builder = null;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
clear() {
|
|
207
|
+
this.master = Buffer.alloc(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
get length() {
|
|
211
|
+
return this.master.length;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
class SnomedReferences {
|
|
216
|
+
constructor(buffer = null) {
|
|
217
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
218
|
+
this.builder = null;
|
|
219
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
220
|
+
this.MAGIC_NO_CHILDREN = 0xFFFFFFFF; // Assuming this constant - adjust as needed
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
getReferences(index) {
|
|
224
|
+
if (index === this.MAGIC_NO_CHILDREN || index === 0) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Handle incremental building case
|
|
229
|
+
if (this.builder && index >= this.master.length) {
|
|
230
|
+
this.post();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (index >= this.master.length) {
|
|
234
|
+
throw new Error(`Wrong length index getting Snomed list. asked for ${index}, limit is ${this.master.length}`);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Read count of elements
|
|
238
|
+
const count = this.master.readUInt32LE(index);
|
|
239
|
+
|
|
240
|
+
// Bounds check for the full array
|
|
241
|
+
if (index + 4 + count * 4 > this.master.length) {
|
|
242
|
+
throw new Error(`Wrong length index (${index}, ${count}) getting Snomed list (length = ${this.master.length})`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Read the cardinal array
|
|
246
|
+
const result = new Array(count);
|
|
247
|
+
let offset = index + 4;
|
|
248
|
+
|
|
249
|
+
for (let i = 0; i < count; i++) {
|
|
250
|
+
result[i] = this.master.readUInt32LE(offset);
|
|
251
|
+
offset += 4;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
getLength(index) {
|
|
258
|
+
if (index > this.master.length) {
|
|
259
|
+
throw new Error('Wrong length index getting Snomed list');
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return this.master.readUInt32LE(index);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
startBuild() {
|
|
266
|
+
this.builder = [];
|
|
267
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
addReferences(cardinalArray) {
|
|
271
|
+
if (!this.builder) {
|
|
272
|
+
throw new Error('Must call startBuild() first');
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
276
|
+
const currentOffset = this.currentOffset;
|
|
277
|
+
|
|
278
|
+
// Create buffer for count + array data
|
|
279
|
+
const totalBytes = 4 + (cardinalArray.length * 4);
|
|
280
|
+
const record = Buffer.allocUnsafe(totalBytes);
|
|
281
|
+
|
|
282
|
+
// Write count
|
|
283
|
+
record.writeUInt32LE(cardinalArray.length, 0);
|
|
284
|
+
|
|
285
|
+
// Write each cardinal
|
|
286
|
+
let offset = 4;
|
|
287
|
+
for (const value of cardinalArray) {
|
|
288
|
+
record.writeUInt32LE(value, offset);
|
|
289
|
+
offset += 4;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.builder.push(record);
|
|
293
|
+
|
|
294
|
+
// UPDATE: offset for next call
|
|
295
|
+
this.currentOffset += totalBytes;
|
|
296
|
+
|
|
297
|
+
return currentOffset;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
post() {
|
|
301
|
+
if (this.builder) {
|
|
302
|
+
this.master = Buffer.concat([this.master, ...this.builder]);
|
|
303
|
+
this.builder = [];
|
|
304
|
+
this.currentOffset = this.master.length; // UPDATE: offset to new master length
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
doneBuild() {
|
|
309
|
+
if (!this.builder) {
|
|
310
|
+
throw new Error('No build in progress');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
this.master = Buffer.concat([this.master, ...this.builder]);
|
|
314
|
+
this.builder = null;
|
|
315
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
clear() {
|
|
319
|
+
this.master = Buffer.alloc(0);
|
|
320
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
get length() {
|
|
324
|
+
return this.master.length;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
class SnomedDescriptions {
|
|
329
|
+
static DESC_SIZE = 40;
|
|
330
|
+
|
|
331
|
+
// Flag constants
|
|
332
|
+
static FLAG_Active = 0;
|
|
333
|
+
static FLAG_RetiredWithoutStatedReason = 1;
|
|
334
|
+
static FLAG_Duplicate = 2;
|
|
335
|
+
static FLAG_Outdated = 3;
|
|
336
|
+
static FLAG_Ambiguous = 4;
|
|
337
|
+
static FLAG_Erroneous = 5;
|
|
338
|
+
static FLAG_Limited = 6;
|
|
339
|
+
static FLAG_Inappropriate = 7;
|
|
340
|
+
static FLAG_ConceptInactive = 8;
|
|
341
|
+
static FLAG_MovedElswhere = 10;
|
|
342
|
+
static FLAG_PendingMove = 11;
|
|
343
|
+
|
|
344
|
+
constructor(buffer = null) {
|
|
345
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
346
|
+
this.builder = null;
|
|
347
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
count() {
|
|
351
|
+
return Math.floor(this.master.length / SnomedDescriptions.DESC_SIZE);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
getDescription(index) {
|
|
355
|
+
if (index >= this.master.length) {
|
|
356
|
+
throw new Error('Wrong length index getting snomed Desc Details');
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const offset = index;
|
|
360
|
+
|
|
361
|
+
return {
|
|
362
|
+
iDesc: this.master.readUInt32LE(offset + 0),
|
|
363
|
+
active: this.master.readUInt8(offset + 4) !== 0,
|
|
364
|
+
id: this.master.readBigUInt64LE(offset + 5),
|
|
365
|
+
concept: this.master.readUInt32LE(offset + 13),
|
|
366
|
+
module: this.master.readUInt32LE(offset + 17),
|
|
367
|
+
kind: this.master.readUInt32LE(offset + 21),
|
|
368
|
+
caps: this.master.readUInt32LE(offset + 25),
|
|
369
|
+
date: this.master.readUInt16LE(offset + 29),
|
|
370
|
+
lang: this.master.readUInt8(offset + 31),
|
|
371
|
+
refsets: this.master.readUInt32LE(offset + 32),
|
|
372
|
+
valueses: this.master.readUInt32LE(offset + 36)
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
conceptByIndex(index) {
|
|
377
|
+
if (index >= this.master.length) {
|
|
378
|
+
throw new Error('Wrong length index getting snomed Desc Details');
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return this.master.readUInt32LE(index + 13);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
startBuild() {
|
|
385
|
+
this.builder = [];
|
|
386
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
addDescription(iDesc, id, date, concept, module, kind, caps, active, lang) {
|
|
390
|
+
if (!this.builder) {
|
|
391
|
+
throw new Error('Must call startBuild() first');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
395
|
+
const currentOffset = this.currentOffset;
|
|
396
|
+
|
|
397
|
+
// Create 40-byte record
|
|
398
|
+
const record = Buffer.allocUnsafe(SnomedDescriptions.DESC_SIZE);
|
|
399
|
+
|
|
400
|
+
record.writeUInt32LE(iDesc, 0); // 4 bytes
|
|
401
|
+
record.writeUInt8(active ? 1 : 0, 4); // 1 byte
|
|
402
|
+
record.writeBigUInt64LE(BigInt(id), 5); // 8 bytes
|
|
403
|
+
record.writeUInt32LE(concept, 13); // 4 bytes
|
|
404
|
+
record.writeUInt32LE(module, 17); // 4 bytes
|
|
405
|
+
record.writeUInt32LE(kind, 21); // 4 bytes
|
|
406
|
+
record.writeUInt32LE(caps, 25); // 4 bytes
|
|
407
|
+
record.writeUInt16LE(date, 29); // 2 bytes
|
|
408
|
+
record.writeUInt8(lang, 31); // 1 byte
|
|
409
|
+
record.writeUInt32LE(0, 32); // 4 bytes (refsets)
|
|
410
|
+
record.writeUInt32LE(0, 36); // 4 bytes (valueses)
|
|
411
|
+
|
|
412
|
+
this.builder.push(record);
|
|
413
|
+
|
|
414
|
+
// UPDATE: offset for next call
|
|
415
|
+
this.currentOffset += SnomedDescriptions.DESC_SIZE;
|
|
416
|
+
|
|
417
|
+
return currentOffset;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
setRefsets(index, refsets, valueses) {
|
|
421
|
+
if (index >= this.master.length) {
|
|
422
|
+
throw new Error('Wrong length index getting snomed Desc Details');
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (index % SnomedDescriptions.DESC_SIZE !== 0) {
|
|
426
|
+
throw new Error('Index must be aligned to DESC_SIZE');
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
this.master.writeUInt32LE(refsets, index + 32);
|
|
430
|
+
this.master.writeUInt32LE(valueses, index + 36);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
doneBuild() {
|
|
434
|
+
if (!this.builder) {
|
|
435
|
+
throw new Error('No build in progress');
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
this.master = Buffer.concat(this.builder);
|
|
439
|
+
this.builder = null;
|
|
440
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
clear() {
|
|
444
|
+
this.master = Buffer.alloc(0);
|
|
445
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
get length() {
|
|
449
|
+
return this.master.length;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
class SnomedDescriptionIndex {
|
|
454
|
+
constructor(buffer = null) {
|
|
455
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
456
|
+
this.builder = null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
findDescription(identity) {
|
|
460
|
+
// Convert to BigInt if it's not already
|
|
461
|
+
const targetId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
|
|
462
|
+
|
|
463
|
+
let result = false;
|
|
464
|
+
let L = 0;
|
|
465
|
+
let H = Math.floor(this.master.length / 12) - 1;
|
|
466
|
+
|
|
467
|
+
while (L <= H) {
|
|
468
|
+
const I = Math.floor((L + H) / 2);
|
|
469
|
+
const aConcept = this.master.readBigUInt64LE(I * 12);
|
|
470
|
+
|
|
471
|
+
if (aConcept < targetId) {
|
|
472
|
+
L = I + 1;
|
|
473
|
+
} else {
|
|
474
|
+
H = I - 1;
|
|
475
|
+
if (aConcept === targetId) {
|
|
476
|
+
result = true;
|
|
477
|
+
L = I; // Found it, but continue searching left for first occurrence
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
if (result) {
|
|
483
|
+
const index = this.master.readUInt32LE(L * 12 + 8);
|
|
484
|
+
return { found: true, index };
|
|
485
|
+
} else {
|
|
486
|
+
return { found: false, index: 0 };
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
startBuild() {
|
|
491
|
+
this.builder = [];
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
addDescription(id, reference) {
|
|
495
|
+
if (!this.builder) {
|
|
496
|
+
throw new Error('Must call startBuild() first');
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Convert to BigInt if needed
|
|
500
|
+
const bigIntId = typeof id === 'string' ? BigInt(id) : BigInt(id);
|
|
501
|
+
|
|
502
|
+
// Create 12-byte record: 8-byte ID + 4-byte reference
|
|
503
|
+
const record = Buffer.allocUnsafe(12);
|
|
504
|
+
record.writeBigUInt64LE(bigIntId, 0);
|
|
505
|
+
record.writeUInt32LE(reference, 8);
|
|
506
|
+
|
|
507
|
+
this.builder.push(record);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
doneBuild() {
|
|
511
|
+
if (!this.builder) {
|
|
512
|
+
throw new Error('No build in progress');
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
this.master = Buffer.concat(this.builder);
|
|
516
|
+
this.builder = null;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
clear() {
|
|
520
|
+
this.master = Buffer.alloc(0);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
get length() {
|
|
524
|
+
return this.master.length;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// Helper method to get count of entries
|
|
528
|
+
count() {
|
|
529
|
+
return Math.floor(this.master.length / 12);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
class SnomedConceptList {
|
|
534
|
+
static CONCEPT_SIZE = 56;
|
|
535
|
+
static MASK_CONCEPT_STATUS = 0x0F;
|
|
536
|
+
static MASK_CONCEPT_PRIMITIVE = 0x10;
|
|
537
|
+
static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
|
|
538
|
+
|
|
539
|
+
constructor(buffer = null) {
|
|
540
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
541
|
+
this.builder = null;
|
|
542
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// Helper method to check post-build state and bounds
|
|
546
|
+
#checkPostBuildAccess(index) {
|
|
547
|
+
if (this.builder !== null) {
|
|
548
|
+
throw new Error('Cannot call setX methods before doneBuild()');
|
|
549
|
+
}
|
|
550
|
+
if (index >= this.master.length) {
|
|
551
|
+
throw new Error(`Wrong length index ${index} getting snomed Concept Details. Max = ${this.master.length}`);
|
|
552
|
+
}
|
|
553
|
+
if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
|
|
554
|
+
throw new Error(`Wrong length index ${index} getting snomed Concept Details`);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
findConcept(identity) {
|
|
559
|
+
const targetId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
|
|
560
|
+
|
|
561
|
+
let result = false;
|
|
562
|
+
let L = 0;
|
|
563
|
+
let H = Math.floor(this.master.length / SnomedConceptList.CONCEPT_SIZE) - 1;
|
|
564
|
+
|
|
565
|
+
while (L <= H) {
|
|
566
|
+
const I = Math.floor((L + H) / 2);
|
|
567
|
+
const aConcept = this.master.readBigUInt64LE(I * SnomedConceptList.CONCEPT_SIZE);
|
|
568
|
+
|
|
569
|
+
if (aConcept < targetId) {
|
|
570
|
+
L = I + 1;
|
|
571
|
+
} else {
|
|
572
|
+
H = I - 1;
|
|
573
|
+
if (aConcept === targetId) {
|
|
574
|
+
result = true;
|
|
575
|
+
L = I;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const index = L * SnomedConceptList.CONCEPT_SIZE;
|
|
581
|
+
return { found: result, index };
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
getConcept(index) {
|
|
585
|
+
this.#checkPostBuildAccess(index);
|
|
586
|
+
|
|
587
|
+
return {
|
|
588
|
+
identity: this.master.readBigUInt64LE(index + 0),
|
|
589
|
+
flags: this.master.readUInt8(index + 8),
|
|
590
|
+
parents: this.master.readUInt32LE(index + 9),
|
|
591
|
+
descriptions: this.master.readUInt32LE(index + 13),
|
|
592
|
+
inbounds: this.master.readUInt32LE(index + 17),
|
|
593
|
+
outbounds: this.master.readUInt32LE(index + 21),
|
|
594
|
+
effectiveTime: this.master.readUInt16LE(index + 34),
|
|
595
|
+
refsets: this.master.readUInt32LE(index + 44)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
getConceptId(index) {
|
|
600
|
+
if (index >= this.master.length) {
|
|
601
|
+
throw new Error('Wrong length index getting snomed Concept Details');
|
|
602
|
+
}
|
|
603
|
+
return this.master.readBigUInt64LE(index + 0);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
getParent(index) {
|
|
607
|
+
this.#checkPostBuildAccess(index);
|
|
608
|
+
return this.master.readUInt32LE(index + 9);
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
getIdentity(index) {
|
|
612
|
+
this.#checkPostBuildAccess(index);
|
|
613
|
+
return this.master.readBigUInt64LE(index + 0);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
getDescriptions(index) {
|
|
617
|
+
this.#checkPostBuildAccess(index);
|
|
618
|
+
return this.master.readUInt32LE(index + 13);
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
getInbounds(index) {
|
|
622
|
+
this.#checkPostBuildAccess(index);
|
|
623
|
+
return this.master.readUInt32LE(index + 17);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
getOutbounds(index) {
|
|
627
|
+
this.#checkPostBuildAccess(index);
|
|
628
|
+
return this.master.readUInt32LE(index + 21);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
getAllDesc(index) {
|
|
632
|
+
this.#checkPostBuildAccess(index);
|
|
633
|
+
return this.master.readUInt32LE(index + 25);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
getDepth(index) {
|
|
637
|
+
this.#checkPostBuildAccess(index);
|
|
638
|
+
return this.master.readUInt8(index + 29);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
getStems(index) {
|
|
642
|
+
this.#checkPostBuildAccess(index);
|
|
643
|
+
return this.master.readUInt32LE(index + 30);
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
getModuleId(index) {
|
|
647
|
+
this.#checkPostBuildAccess(index);
|
|
648
|
+
return this.master.readUInt32LE(index + 36);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
getStatus(index) {
|
|
652
|
+
this.#checkPostBuildAccess(index);
|
|
653
|
+
return this.master.readUInt32LE(index + 40);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
getRefsets(index) {
|
|
657
|
+
this.#checkPostBuildAccess(index);
|
|
658
|
+
return this.master.readUInt32LE(index + 44);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
getNormalForm(index) {
|
|
662
|
+
this.#checkPostBuildAccess(index);
|
|
663
|
+
return this.master.readUInt32LE(index + 48);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
setNormalFormDuringBuild(index, value) {
|
|
667
|
+
// Special version that can be called during building phase
|
|
668
|
+
// This bypasses the post-build access check for normal forms specifically
|
|
669
|
+
if (index >= this.master.length) {
|
|
670
|
+
throw new Error(`Wrong length index ${index} getting snomed Concept Details. Max = ${this.master.length}`);
|
|
671
|
+
}
|
|
672
|
+
if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
|
|
673
|
+
throw new Error(`Wrong length index ${index} getting snomed Concept Details`);
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
this.master.writeUInt32LE(value, index + 48);
|
|
677
|
+
}
|
|
678
|
+
// Also add a helper to check if a concept exists by index
|
|
679
|
+
conceptExists(index) {
|
|
680
|
+
try {
|
|
681
|
+
if (index >= this.master.length) {
|
|
682
|
+
return false;
|
|
683
|
+
}
|
|
684
|
+
if (index % SnomedConceptList.CONCEPT_SIZE !== 0) {
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
return true;
|
|
688
|
+
} catch (error) {
|
|
689
|
+
return false;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
// Add a helper to get concept identity safely
|
|
694
|
+
getConceptIdentitySafe(index) {
|
|
695
|
+
try {
|
|
696
|
+
if (!this.conceptExists(index)) {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
return this.master.readBigUInt64LE(index + 0);
|
|
700
|
+
} catch (error) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// All the setter methods - these require doneBuild() to have been called
|
|
706
|
+
setParents(index, active, inactive) {
|
|
707
|
+
this.#checkPostBuildAccess(index);
|
|
708
|
+
this.master.writeUInt32LE(active, index + 9);
|
|
709
|
+
this.master.writeUInt32LE(inactive, index + 52);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
setDescriptions(index, value) {
|
|
713
|
+
this.#checkPostBuildAccess(index);
|
|
714
|
+
this.master.writeUInt32LE(value, index + 13);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
setInbounds(index, value) {
|
|
718
|
+
this.#checkPostBuildAccess(index);
|
|
719
|
+
this.master.writeUInt32LE(value, index + 17);
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
setOutbounds(index, value) {
|
|
723
|
+
this.#checkPostBuildAccess(index);
|
|
724
|
+
this.master.writeUInt32LE(value, index + 21);
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
setAllDesc(index, value) {
|
|
728
|
+
this.#checkPostBuildAccess(index);
|
|
729
|
+
this.master.writeUInt32LE(value, index + 25);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
setDepth(index, value) {
|
|
733
|
+
this.#checkPostBuildAccess(index);
|
|
734
|
+
this.master.writeUInt8(value, index + 29);
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
setStems(index, value) {
|
|
738
|
+
this.#checkPostBuildAccess(index);
|
|
739
|
+
this.master.writeUInt32LE(value, index + 30);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
setDate(index, effectiveTime) {
|
|
743
|
+
this.#checkPostBuildAccess(index);
|
|
744
|
+
this.master.writeUInt16LE(effectiveTime, index + 34);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
setModuleId(index, value) {
|
|
748
|
+
this.#checkPostBuildAccess(index);
|
|
749
|
+
this.master.writeUInt32LE(value, index + 36);
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
setStatus(index, value) {
|
|
753
|
+
this.#checkPostBuildAccess(index);
|
|
754
|
+
this.master.writeUInt32LE(value, index + 40);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
setRefsets(index, value) {
|
|
758
|
+
this.#checkPostBuildAccess(index);
|
|
759
|
+
this.master.writeUInt32LE(value, index + 44);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
setNormalForm(index, value) {
|
|
763
|
+
this.#checkPostBuildAccess(index);
|
|
764
|
+
this.master.writeUInt32LE(value, index + 48);
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
setFlag(index, flags) {
|
|
768
|
+
this.#checkPostBuildAccess(index);
|
|
769
|
+
this.master.writeUInt8(flags, index + 8);
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
count() {
|
|
773
|
+
return Math.floor(this.master.length / SnomedConceptList.CONCEPT_SIZE);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
// Build methods
|
|
777
|
+
startBuild() {
|
|
778
|
+
this.builder = [];
|
|
779
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
addConcept(identity, effectiveTime, flags) {
|
|
783
|
+
if (!this.builder) {
|
|
784
|
+
throw new Error('Must call startBuild() first');
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
788
|
+
const currentOffset = this.currentOffset;
|
|
789
|
+
const bigIntId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
|
|
790
|
+
|
|
791
|
+
// Create 56-byte record
|
|
792
|
+
const record = Buffer.allocUnsafe(SnomedConceptList.CONCEPT_SIZE);
|
|
793
|
+
|
|
794
|
+
record.writeBigUInt64LE(bigIntId, 0); // identity
|
|
795
|
+
record.writeUInt8(flags, 8); // flags
|
|
796
|
+
record.writeUInt32LE(0, 9); // active parents
|
|
797
|
+
record.writeUInt32LE(0, 13); // descriptions
|
|
798
|
+
record.writeUInt32LE(0, 17); // inbounds
|
|
799
|
+
record.writeUInt32LE(0, 21); // outbounds
|
|
800
|
+
record.writeUInt32LE(0, 25); // closures
|
|
801
|
+
record.writeUInt8(0, 29); // depth
|
|
802
|
+
record.writeUInt32LE(0, 30); // stems
|
|
803
|
+
record.writeUInt16LE(effectiveTime, 34); // date
|
|
804
|
+
record.writeUInt32LE(0, 36); // moduleId
|
|
805
|
+
record.writeUInt32LE(0, 40); // status
|
|
806
|
+
record.writeUInt32LE(0, 44); // refsets
|
|
807
|
+
record.writeUInt32LE(0, 48); // normal form
|
|
808
|
+
record.writeUInt32LE(0, 52); // inactive parents
|
|
809
|
+
|
|
810
|
+
this.builder.push(record);
|
|
811
|
+
|
|
812
|
+
// UPDATE: offset for next call
|
|
813
|
+
this.currentOffset += SnomedConceptList.CONCEPT_SIZE;
|
|
814
|
+
|
|
815
|
+
return currentOffset;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
doneBuild() {
|
|
819
|
+
if (!this.builder) {
|
|
820
|
+
throw new Error('No build in progress');
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
this.master = Buffer.concat(this.builder);
|
|
824
|
+
this.builder = null; // This enables the setX methods
|
|
825
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
clear() {
|
|
829
|
+
this.master = Buffer.alloc(0);
|
|
830
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
get length() {
|
|
834
|
+
return this.master.length;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
class SnomedRelationshipList {
|
|
839
|
+
static RELATIONSHIP_SIZE = 40;
|
|
840
|
+
static REFSET_SIZE_NOLANG = 28;
|
|
841
|
+
static REFSET_SIZE_LANG = 32;
|
|
842
|
+
|
|
843
|
+
constructor(buffer = null) {
|
|
844
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
845
|
+
this.builder = null;
|
|
846
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
getRelationship(index) {
|
|
850
|
+
if (index >= this.master.length) {
|
|
851
|
+
throw new Error('Wrong length index getting snomed relationship Details');
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
return {
|
|
855
|
+
source: this.master.readUInt32LE(index + 0),
|
|
856
|
+
target: this.master.readUInt32LE(index + 4),
|
|
857
|
+
relType: this.master.readUInt32LE(index + 8),
|
|
858
|
+
module: this.master.readUInt32LE(index + 12),
|
|
859
|
+
kind: this.master.readUInt32LE(index + 16),
|
|
860
|
+
modifier: this.master.readUInt32LE(index + 20),
|
|
861
|
+
date: this.master.readUInt16LE(index + 24),
|
|
862
|
+
active: this.master.readUInt8(index + 26) !== 0,
|
|
863
|
+
defining: this.master.readUInt8(index + 27) !== 0,
|
|
864
|
+
group: this.master.readInt32LE(index + 28), // Signed integer
|
|
865
|
+
identity: this.master.readBigUInt64LE(index + 32)
|
|
866
|
+
};
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
count() {
|
|
870
|
+
return Math.floor(this.master.length / SnomedRelationshipList.RELATIONSHIP_SIZE);
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
// Build methods
|
|
874
|
+
startBuild() {
|
|
875
|
+
this.builder = [];
|
|
876
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
addRelationship(identity, source, target, relType, module, kind, modifier, date, active, defining, group) {
|
|
880
|
+
if (!this.builder) {
|
|
881
|
+
throw new Error('Must call startBuild() first');
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
885
|
+
const currentOffset = this.currentOffset;
|
|
886
|
+
const bigIntId = typeof identity === 'string' ? BigInt(identity) : BigInt(identity);
|
|
887
|
+
|
|
888
|
+
// Create 40-byte record
|
|
889
|
+
const record = Buffer.allocUnsafe(SnomedRelationshipList.RELATIONSHIP_SIZE);
|
|
890
|
+
|
|
891
|
+
record.writeUInt32LE(source, 0);
|
|
892
|
+
record.writeUInt32LE(target, 4);
|
|
893
|
+
record.writeUInt32LE(relType, 8);
|
|
894
|
+
record.writeUInt32LE(module, 12);
|
|
895
|
+
record.writeUInt32LE(kind, 16);
|
|
896
|
+
record.writeUInt32LE(modifier, 20);
|
|
897
|
+
record.writeUInt16LE(date, 24);
|
|
898
|
+
record.writeUInt8(active ? 1 : 0, 26);
|
|
899
|
+
record.writeUInt8(defining ? 1 : 0, 27);
|
|
900
|
+
record.writeInt32LE(group, 28); // Signed integer
|
|
901
|
+
record.writeBigUInt64LE(bigIntId, 32);
|
|
902
|
+
|
|
903
|
+
this.builder.push(record);
|
|
904
|
+
|
|
905
|
+
// UPDATE: offset for next call
|
|
906
|
+
this.currentOffset += SnomedRelationshipList.RELATIONSHIP_SIZE;
|
|
907
|
+
|
|
908
|
+
return currentOffset;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
doneBuild() {
|
|
912
|
+
if (!this.builder) {
|
|
913
|
+
throw new Error('No build in progress');
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
this.master = Buffer.concat(this.builder);
|
|
917
|
+
this.builder = null;
|
|
918
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
clear() {
|
|
922
|
+
this.master = Buffer.alloc(0);
|
|
923
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
get length() {
|
|
927
|
+
return this.master.length;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
class SnomedReferenceSetMembers {
|
|
932
|
+
static MAGIC_NO_CHILDREN = 0xFFFFFFFF;
|
|
933
|
+
|
|
934
|
+
constructor(buffer = null) {
|
|
935
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
936
|
+
this.builder = null;
|
|
937
|
+
this.currentOffset = 0; // ADD: Track offset directly
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
getMemberCount(index) {
|
|
941
|
+
if (index === SnomedReferenceSetMembers.MAGIC_NO_CHILDREN) {
|
|
942
|
+
return 0;
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
if (index > this.master.length) {
|
|
946
|
+
throw new Error('Wrong length index getting Snomed list');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
return this.master.readUInt32LE(index);
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
getMembers(index) {
|
|
953
|
+
if (index === SnomedReferenceSetMembers.MAGIC_NO_CHILDREN) {
|
|
954
|
+
return null;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
if (index > this.master.length) {
|
|
958
|
+
throw new Error(`Wrong length index getting Snomed list. asked for ${index}, limit is ${this.master.length}`);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
// Read count and ids flag
|
|
962
|
+
const count = this.master.readUInt32LE(index);
|
|
963
|
+
const ids = this.master.readUInt8(index + 4) !== 0;
|
|
964
|
+
|
|
965
|
+
let offset = index + 5; // Skip count + ids flag
|
|
966
|
+
const result = new Array(count);
|
|
967
|
+
|
|
968
|
+
for (let i = 0; i < count; i++) {
|
|
969
|
+
const member = {};
|
|
970
|
+
|
|
971
|
+
if (ids) {
|
|
972
|
+
// Read full member with GUID
|
|
973
|
+
member.id = this.master.subarray(offset, offset + 16); // 16-byte GUID as Buffer
|
|
974
|
+
offset += 16;
|
|
975
|
+
member.module = this.master.readUInt32LE(offset);
|
|
976
|
+
offset += 4;
|
|
977
|
+
member.date = this.master.readUInt16LE(offset);
|
|
978
|
+
offset += 2;
|
|
979
|
+
member.kind = this.master.readUInt8(offset);
|
|
980
|
+
offset += 1;
|
|
981
|
+
member.ref = this.master.readUInt32LE(offset);
|
|
982
|
+
offset += 4;
|
|
983
|
+
member.values = this.master.readUInt32LE(offset);
|
|
984
|
+
offset += 4;
|
|
985
|
+
} else {
|
|
986
|
+
// Read basic member without GUID/module/date
|
|
987
|
+
member.id = null;
|
|
988
|
+
member.module = null;
|
|
989
|
+
member.date = null;
|
|
990
|
+
member.kind = this.master.readUInt8(offset);
|
|
991
|
+
offset += 1;
|
|
992
|
+
member.ref = this.master.readUInt32LE(offset);
|
|
993
|
+
offset += 4;
|
|
994
|
+
member.values = this.master.readUInt32LE(offset);
|
|
995
|
+
offset += 4;
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
result[i] = member;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
return result;
|
|
1002
|
+
}
|
|
1003
|
+
|
|
1004
|
+
startBuild() {
|
|
1005
|
+
this.builder = [];
|
|
1006
|
+
this.currentOffset = 0; // RESET: offset when starting build
|
|
1007
|
+
}
|
|
1008
|
+
|
|
1009
|
+
addMembers(ids, membersArray) {
|
|
1010
|
+
if (!this.builder) {
|
|
1011
|
+
throw new Error('Must call startBuild() first');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// FIXED: Use pre-calculated offset instead of reduce
|
|
1015
|
+
const currentOffset = this.currentOffset;
|
|
1016
|
+
|
|
1017
|
+
// Calculate total size needed
|
|
1018
|
+
const count = membersArray.length;
|
|
1019
|
+
const bytesPerMember = ids ? 31 : 9; // 31 bytes with IDs, 9 bytes without
|
|
1020
|
+
const totalSize = 5 + (count * bytesPerMember); // 5 = count(4) + ids_flag(1)
|
|
1021
|
+
|
|
1022
|
+
const record = Buffer.allocUnsafe(totalSize);
|
|
1023
|
+
let offset = 0;
|
|
1024
|
+
|
|
1025
|
+
// Write count and ids flag
|
|
1026
|
+
record.writeUInt32LE(count, offset);
|
|
1027
|
+
offset += 4;
|
|
1028
|
+
record.writeUInt8(ids ? 1 : 0, offset);
|
|
1029
|
+
offset += 1;
|
|
1030
|
+
|
|
1031
|
+
// Write each member
|
|
1032
|
+
for (const member of membersArray) {
|
|
1033
|
+
if (ids) {
|
|
1034
|
+
// Write full member with GUID
|
|
1035
|
+
if (member.id && Buffer.isBuffer(member.id)) {
|
|
1036
|
+
member.id.copy(record, offset, 0, 16);
|
|
1037
|
+
} else if (typeof member.id === 'string') {
|
|
1038
|
+
// Convert GUID string to buffer if needed
|
|
1039
|
+
const guidBuffer = this.#guidStringToBuffer(member.id);
|
|
1040
|
+
guidBuffer.copy(record, offset, 0, 16);
|
|
1041
|
+
} else {
|
|
1042
|
+
// Zero-fill if no GUID provided
|
|
1043
|
+
record.fill(0, offset, offset + 16);
|
|
1044
|
+
}
|
|
1045
|
+
offset += 16;
|
|
1046
|
+
|
|
1047
|
+
record.writeUInt32LE(member.module || 0, offset);
|
|
1048
|
+
offset += 4;
|
|
1049
|
+
record.writeUInt16LE(member.date || 0, offset);
|
|
1050
|
+
offset += 2;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
record.writeUInt8(member.kind || 0, offset);
|
|
1054
|
+
offset += 1;
|
|
1055
|
+
record.writeUInt32LE(member.ref || 0, offset);
|
|
1056
|
+
offset += 4;
|
|
1057
|
+
record.writeUInt32LE(member.values || 0, offset);
|
|
1058
|
+
offset += 4;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
this.builder.push(record);
|
|
1062
|
+
|
|
1063
|
+
// UPDATE: offset for next call
|
|
1064
|
+
this.currentOffset += totalSize;
|
|
1065
|
+
|
|
1066
|
+
return currentOffset;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
// Helper method to convert GUID string to 16-byte buffer
|
|
1070
|
+
#guidStringToBuffer(guidString) {
|
|
1071
|
+
// Remove hyphens and braces from GUID string
|
|
1072
|
+
const cleanGuid = guidString.replace(/[-{}]/g, '');
|
|
1073
|
+
if (cleanGuid.length !== 32) {
|
|
1074
|
+
throw new Error('Invalid GUID format');
|
|
1075
|
+
}
|
|
1076
|
+
return Buffer.from(cleanGuid, 'hex');
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Helper method to convert 16-byte buffer to GUID string
|
|
1080
|
+
guidBufferToString(guidBuffer) {
|
|
1081
|
+
if (!Buffer.isBuffer(guidBuffer) || guidBuffer.length !== 16) {
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
const hex = guidBuffer.toString('hex');
|
|
1085
|
+
return `${hex.substr(0, 8)}-${hex.substr(8, 4)}-${hex.substr(12, 4)}-${hex.substr(16, 4)}-${hex.substr(20, 12)}`;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
doneBuild() {
|
|
1089
|
+
if (!this.builder) {
|
|
1090
|
+
throw new Error('No build in progress');
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
this.master = Buffer.concat(this.builder);
|
|
1094
|
+
this.builder = null;
|
|
1095
|
+
this.currentOffset = 0; // RESET: for potential future builds
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
clear() {
|
|
1099
|
+
this.master = Buffer.alloc(0);
|
|
1100
|
+
this.currentOffset = 0; // RESET: offset when clearing
|
|
1101
|
+
}
|
|
1102
|
+
|
|
1103
|
+
get length() {
|
|
1104
|
+
return this.master.length;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
class SnomedReferenceSetIndex {
|
|
1109
|
+
static REFSET_SIZE_LANG = 32; // Always using lang version
|
|
1110
|
+
static REFSET_SIZE_NOLANG = 28;
|
|
1111
|
+
|
|
1112
|
+
constructor(buffer = null, hasLangs = true) {
|
|
1113
|
+
this.master = buffer ? Buffer.from(buffer) : Buffer.alloc(0);
|
|
1114
|
+
this.builder = null;
|
|
1115
|
+
this.hasLangs = hasLangs;
|
|
1116
|
+
this.recordSize = hasLangs ? SnomedReferenceSetIndex.REFSET_SIZE_LANG : SnomedReferenceSetIndex.REFSET_SIZE_NOLANG;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
getReferenceSet(index) {
|
|
1120
|
+
const byteIndex = index * this.recordSize;
|
|
1121
|
+
|
|
1122
|
+
if (byteIndex >= this.master.length) {
|
|
1123
|
+
throw new Error('Wrong length index getting snomed relationship Details');
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const result = {
|
|
1127
|
+
definition: this.master.readUInt32LE(byteIndex + 0),
|
|
1128
|
+
filename: this.master.readUInt32LE(byteIndex + 4),
|
|
1129
|
+
membersByRef: this.master.readUInt32LE(byteIndex + 8),
|
|
1130
|
+
membersByName: this.master.readUInt32LE(byteIndex + 12),
|
|
1131
|
+
fieldTypes: this.master.readUInt32LE(byteIndex + 16),
|
|
1132
|
+
name: this.master.readUInt32LE(byteIndex + 20),
|
|
1133
|
+
fieldNames: this.master.readUInt32LE(byteIndex + 24)
|
|
1134
|
+
};
|
|
1135
|
+
|
|
1136
|
+
// Only read langs field if hasLangs is true
|
|
1137
|
+
if (this.hasLangs) {
|
|
1138
|
+
result.langs = this.master.readUInt32LE(byteIndex + 28);
|
|
1139
|
+
} else {
|
|
1140
|
+
result.langs = 0;
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
return result;
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
getMembersByConcept(conceptIndex, byName = false) {
|
|
1147
|
+
// Linear search through records looking for matching concept
|
|
1148
|
+
for (let i = 0; i < this.count(); i++) {
|
|
1149
|
+
const offset = i * this.recordSize;
|
|
1150
|
+
const definition = this.master.readUInt32LE(offset + 0);
|
|
1151
|
+
|
|
1152
|
+
if (definition === conceptIndex) {
|
|
1153
|
+
if (byName) {
|
|
1154
|
+
return this.master.readUInt32LE(offset + 12); // membersByName
|
|
1155
|
+
} else {
|
|
1156
|
+
return this.master.readUInt32LE(offset + 8); // membersByRef
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
return 0; // Not found
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
getRefSetByConcept(conceptIndex) {
|
|
1165
|
+
// Linear search through records looking for matching concept, return record index
|
|
1166
|
+
for (let i = 0; i < this.count(); i++) {
|
|
1167
|
+
const offset = i * this.recordSize;
|
|
1168
|
+
const definition = this.master.readUInt32LE(offset + 0);
|
|
1169
|
+
|
|
1170
|
+
if (definition === conceptIndex) {
|
|
1171
|
+
return i; // Return record index, not byte offset
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
return 0; // Not found
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
count() {
|
|
1179
|
+
return Math.floor(this.master.length / this.recordSize);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
// Build methods
|
|
1183
|
+
startBuild() {
|
|
1184
|
+
this.builder = [];
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
addReferenceSet(name, filename, definition, membersByRef, membersByName, fieldTypes, fieldNames, langs) {
|
|
1188
|
+
if (!this.builder) {
|
|
1189
|
+
throw new Error('Must call startBuild() first');
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Create 32-byte record - store in the order used by Pascal AddReferenceSet
|
|
1193
|
+
const record = Buffer.allocUnsafe(this.recordSize);
|
|
1194
|
+
|
|
1195
|
+
record.writeUInt32LE(definition, 0); // First stored
|
|
1196
|
+
record.writeUInt32LE(filename, 4); // Second stored
|
|
1197
|
+
record.writeUInt32LE(membersByRef, 8); // Third stored
|
|
1198
|
+
record.writeUInt32LE(membersByName, 12); // Fourth stored
|
|
1199
|
+
record.writeUInt32LE(fieldTypes, 16); // Fifth stored
|
|
1200
|
+
record.writeUInt32LE(name, 20); // Sixth stored
|
|
1201
|
+
record.writeUInt32LE(fieldNames, 24); // Seventh stored
|
|
1202
|
+
|
|
1203
|
+
if (this.hasLangs) {
|
|
1204
|
+
record.writeUInt32LE(langs, 28); // Eighth stored
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
this.builder.push(record);
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
doneBuild() {
|
|
1211
|
+
if (!this.builder) {
|
|
1212
|
+
throw new Error('No build in progress');
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
this.master = Buffer.concat(this.builder);
|
|
1216
|
+
this.builder = null;
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
clear() {
|
|
1220
|
+
this.master = Buffer.alloc(0);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
get length() {
|
|
1224
|
+
return this.master.length;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1228
|
+
|
|
1229
|
+
class SnomedFileReader {
|
|
1230
|
+
constructor(filePath) {
|
|
1231
|
+
this.filePath = filePath;
|
|
1232
|
+
this.buffer = null;
|
|
1233
|
+
this.offset = 0;
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
async load() {
|
|
1237
|
+
// Read entire file into buffer
|
|
1238
|
+
this.buffer = await fs.promises.readFile(this.filePath);
|
|
1239
|
+
this.offset = 0;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Read a string (Free Pascal TReader format: type byte + length + string bytes)
|
|
1243
|
+
readString() {
|
|
1244
|
+
// Read TValueType enum (1 byte)
|
|
1245
|
+
const valueType = this.buffer.readUInt8(this.offset);
|
|
1246
|
+
this.offset += 1;
|
|
1247
|
+
|
|
1248
|
+
let length;
|
|
1249
|
+
// Based on the actual file format we're seeing:
|
|
1250
|
+
// Type 6 appears to use 1-byte length (not 4-byte as we expected)
|
|
1251
|
+
if (valueType === 6) {
|
|
1252
|
+
length = this.buffer.readUInt8(this.offset); // 1-byte length
|
|
1253
|
+
this.offset += 1;
|
|
1254
|
+
} else {
|
|
1255
|
+
// We'll handle other types when we encounter them
|
|
1256
|
+
throw new Error(`Unknown string type: ${valueType}`);
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
if (length === 0) {
|
|
1260
|
+
return '';
|
|
1261
|
+
}
|
|
1262
|
+
|
|
1263
|
+
const str = this.buffer.toString('utf8', this.offset, this.offset + length);
|
|
1264
|
+
this.offset += length;
|
|
1265
|
+
return str;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// Read a 4-byte integer (TReader format: type byte + integer)
|
|
1269
|
+
readInteger() {
|
|
1270
|
+
// Read type byte first
|
|
1271
|
+
const valueType = this.buffer.readUInt8(this.offset);
|
|
1272
|
+
this.offset += 1;
|
|
1273
|
+
|
|
1274
|
+
let value;
|
|
1275
|
+
switch (valueType) {
|
|
1276
|
+
case 2: // Int8
|
|
1277
|
+
value = this.buffer.readInt8(this.offset);
|
|
1278
|
+
this.offset += 1;
|
|
1279
|
+
break;
|
|
1280
|
+
case 3: // Int16
|
|
1281
|
+
value = this.buffer.readInt16LE(this.offset);
|
|
1282
|
+
this.offset += 2;
|
|
1283
|
+
break;
|
|
1284
|
+
case 4: // Int32
|
|
1285
|
+
value = this.buffer.readInt32LE(this.offset);
|
|
1286
|
+
this.offset += 4;
|
|
1287
|
+
break;
|
|
1288
|
+
default:
|
|
1289
|
+
throw new Error(`Unknown integer type: ${valueType}`);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
return value;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
// Read an 8-byte UInt64 (little-endian)
|
|
1296
|
+
readUInt64() {
|
|
1297
|
+
const value = this.buffer.readBigUInt64LE(this.offset);
|
|
1298
|
+
this.offset += 8;
|
|
1299
|
+
return value;
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Read a byte array (length-prefixed using TReader.ReadInteger format)
|
|
1303
|
+
readBytes() {
|
|
1304
|
+
const length = this.readInteger(); // Use TReader format, not raw bytes
|
|
1305
|
+
const bytes = this.buffer.subarray(this.offset, this.offset + length);
|
|
1306
|
+
this.offset += length;
|
|
1307
|
+
return bytes;
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// Main loading method that matches the Pascal logic
|
|
1311
|
+
async loadSnomedData() {
|
|
1312
|
+
await this.load();
|
|
1313
|
+
|
|
1314
|
+
const result = {
|
|
1315
|
+
cacheVersion: this.readString(), // This is "16" or "17" - the cache format version
|
|
1316
|
+
versionUri: this.readString(),
|
|
1317
|
+
versionDate: this.readString(),
|
|
1318
|
+
isTesting: false,
|
|
1319
|
+
|
|
1320
|
+
// Version-dependent settings
|
|
1321
|
+
hasLangs: false, // Default for older versions
|
|
1322
|
+
isUTF16: false, // Will be determined by version
|
|
1323
|
+
|
|
1324
|
+
// Extract edition and version from URI
|
|
1325
|
+
edition: null,
|
|
1326
|
+
version: null,
|
|
1327
|
+
|
|
1328
|
+
// Structure data
|
|
1329
|
+
strings: this.readBytes(),
|
|
1330
|
+
refs: this.readBytes(),
|
|
1331
|
+
desc: this.readBytes(),
|
|
1332
|
+
words: this.readBytes(),
|
|
1333
|
+
stems: this.readBytes(),
|
|
1334
|
+
concept: this.readBytes(),
|
|
1335
|
+
rel: this.readBytes(),
|
|
1336
|
+
refSetIndex: this.readBytes(),
|
|
1337
|
+
refSetMembers: this.readBytes(),
|
|
1338
|
+
descRef: this.readBytes(),
|
|
1339
|
+
|
|
1340
|
+
// Additional data
|
|
1341
|
+
isAIndex: this.readInteger(),
|
|
1342
|
+
inactiveRoots: [],
|
|
1343
|
+
activeRoots: [],
|
|
1344
|
+
defaultLanguage: null // Will be read last
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
result.isTesting = result.versionUri.includes("/xsct/");
|
|
1348
|
+
// Parse edition and version from URI
|
|
1349
|
+
const uriParts = result.versionUri.split('/');
|
|
1350
|
+
if (uriParts.length >= 7) {
|
|
1351
|
+
result.edition = uriParts[4];
|
|
1352
|
+
result.version = uriParts[6];
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Determine settings based on cache version string (matching Pascal logic)
|
|
1356
|
+
if (result.cacheVersion === '17') { // SNOMED_CACHE_VERSION_CURRENT
|
|
1357
|
+
result.hasLangs = true;
|
|
1358
|
+
} else if (result.cacheVersion === '16') { // Assuming this is UTF8 version for now
|
|
1359
|
+
result.isUTF16 = false;
|
|
1360
|
+
result.hasLangs = false;
|
|
1361
|
+
} else {
|
|
1362
|
+
throw new Error(`Unsupported SNOMED cache version: ${result.cacheVersion}`);
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
const inactiveRootsLength = this.readInteger();
|
|
1366
|
+
|
|
1367
|
+
for (let i = 0; i < inactiveRootsLength; i++) {
|
|
1368
|
+
result.inactiveRoots.push(this.readUInt64());
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// Read active roots array
|
|
1372
|
+
const activeRootsLength = this.readInteger();
|
|
1373
|
+
for (let i = 0; i < activeRootsLength; i++) {
|
|
1374
|
+
result.activeRoots.push(this.readUInt64());
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
// Read default language LAST
|
|
1378
|
+
result.defaultLanguage = this.readInteger();
|
|
1379
|
+
|
|
1380
|
+
return result;
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
module.exports = {
|
|
1385
|
+
SnomedStrings,
|
|
1386
|
+
SnomedWords,
|
|
1387
|
+
SnomedStems,
|
|
1388
|
+
SnomedReferences,
|
|
1389
|
+
SnomedDescriptions,
|
|
1390
|
+
SnomedDescriptionIndex,
|
|
1391
|
+
SnomedConceptList,
|
|
1392
|
+
SnomedRelationshipList,
|
|
1393
|
+
SnomedReferenceSetMembers,
|
|
1394
|
+
SnomedReferenceSetIndex,
|
|
1395
|
+
SnomedFileReader
|
|
1396
|
+
};
|