@cyanheads/medical-codes-mcp-server 0.1.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/AGENTS.md +375 -0
- package/CLAUDE.md +375 -0
- package/Dockerfile +124 -0
- package/LICENSE +201 -0
- package/README.md +328 -0
- package/changelog/0.1.x/0.1.0.md +24 -0
- package/changelog/template.md +127 -0
- package/data/medical-codes.db +0 -0
- package/dist/config/server-config.d.ts +23 -0
- package/dist/config/server-config.d.ts.map +1 -0
- package/dist/config/server-config.js +51 -0
- package/dist/config/server-config.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +54 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp-server/tools/definitions/_render.d.ts +26 -0
- package/dist/mcp-server/tools/definitions/_render.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/_render.js +45 -0
- package/dist/mcp-server/tools/definitions/_render.js.map +1 -0
- package/dist/mcp-server/tools/definitions/browse-hierarchy.tool.d.ts +51 -0
- package/dist/mcp-server/tools/definitions/browse-hierarchy.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/browse-hierarchy.tool.js +131 -0
- package/dist/mcp-server/tools/definitions/browse-hierarchy.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/check-code.tool.d.ts +42 -0
- package/dist/mcp-server/tools/definitions/check-code.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/check-code.tool.js +91 -0
- package/dist/mcp-server/tools/definitions/check-code.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/get-code.tool.d.ts +52 -0
- package/dist/mcp-server/tools/definitions/get-code.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/get-code.tool.js +165 -0
- package/dist/mcp-server/tools/definitions/get-code.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/list-systems.tool.d.ts +21 -0
- package/dist/mcp-server/tools/definitions/list-systems.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/list-systems.tool.js +81 -0
- package/dist/mcp-server/tools/definitions/list-systems.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/map-codes.tool.d.ts +55 -0
- package/dist/mcp-server/tools/definitions/map-codes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/map-codes.tool.js +132 -0
- package/dist/mcp-server/tools/definitions/map-codes.tool.js.map +1 -0
- package/dist/mcp-server/tools/definitions/search-codes.tool.d.ts +48 -0
- package/dist/mcp-server/tools/definitions/search-codes.tool.d.ts.map +1 -0
- package/dist/mcp-server/tools/definitions/search-codes.tool.js +133 -0
- package/dist/mcp-server/tools/definitions/search-codes.tool.js.map +1 -0
- package/dist/services/code-index/code-index-service.d.ts +185 -0
- package/dist/services/code-index/code-index-service.d.ts.map +1 -0
- package/dist/services/code-index/code-index-service.js +530 -0
- package/dist/services/code-index/code-index-service.js.map +1 -0
- package/dist/services/code-index/detect.d.ts +23 -0
- package/dist/services/code-index/detect.d.ts.map +1 -0
- package/dist/services/code-index/detect.js +58 -0
- package/dist/services/code-index/detect.js.map +1 -0
- package/dist/services/code-index/schema.d.ts +55 -0
- package/dist/services/code-index/schema.d.ts.map +1 -0
- package/dist/services/code-index/schema.js +129 -0
- package/dist/services/code-index/schema.js.map +1 -0
- package/dist/services/code-index/types.d.ts +71 -0
- package/dist/services/code-index/types.d.ts.map +1 -0
- package/dist/services/code-index/types.js +24 -0
- package/dist/services/code-index/types.js.map +1 -0
- package/package.json +92 -0
- package/server.json +125 -0
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview The code-index service — the single source of truth for the
|
|
3
|
+
* server. Opens the bundled SQLite + FTS5 database read-only at startup and
|
|
4
|
+
* exposes typed query methods the six tools compose: decode, search, validate,
|
|
5
|
+
* crosswalk, and hierarchy-browse. No network, no tenant state — the corpus is
|
|
6
|
+
* global, read-only, and built at package-build time.
|
|
7
|
+
* @module services/code-index/code-index-service
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync } from 'node:fs';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import { internalError } from '@cyanheads/mcp-ts-core/errors';
|
|
13
|
+
import { logger, requestContextService, runtimeCaps } from '@cyanheads/mcp-ts-core/utils';
|
|
14
|
+
import { getServerConfig } from '../../config/server-config.js';
|
|
15
|
+
import { detectSystems } from './detect.js';
|
|
16
|
+
import { displayCode, icd10cmParent, storageCode } from './schema.js';
|
|
17
|
+
import { DRUG_DIRECTIONS, SYSTEM_IDS, } from './types.js';
|
|
18
|
+
/**
|
|
19
|
+
* Open the bundled DB read-only with whichever driver the runtime provides, and
|
|
20
|
+
* normalize it to {@link SqliteDb}. Bun uses the built-in `bun:sqlite`; Node and
|
|
21
|
+
* the Vitest worker (which runs as Node) use the `better-sqlite3` optional dep.
|
|
22
|
+
* Both are loaded via variable-specifier dynamic import so the project typechecks
|
|
23
|
+
* without `bun-types` and builds without `better-sqlite3` resolved at compile time.
|
|
24
|
+
*/
|
|
25
|
+
async function openDriver(dbPath) {
|
|
26
|
+
if (runtimeCaps.isBun) {
|
|
27
|
+
const BUN_SQLITE = 'bun:sqlite';
|
|
28
|
+
const { Database } = (await import(BUN_SQLITE));
|
|
29
|
+
const db = new Database(dbPath, { readonly: true });
|
|
30
|
+
return { query: (sql) => db.query(sql), close: () => db.close() };
|
|
31
|
+
}
|
|
32
|
+
const BETTER_SQLITE3 = 'better-sqlite3';
|
|
33
|
+
let mod;
|
|
34
|
+
try {
|
|
35
|
+
mod = (await import(BETTER_SQLITE3));
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
throw internalError('medical-codes-mcp-server needs a SQLite driver: the built-in `bun:sqlite` (run with Bun — the published Docker image and npm bin both do) or the `better-sqlite3` optional dependency for Node. Neither was found.', { dbPath }, { cause: err });
|
|
39
|
+
}
|
|
40
|
+
const db = new mod.default(dbPath, { readonly: true, fileMustExist: true });
|
|
41
|
+
return { query: (sql) => db.prepare(sql), close: () => db.close() };
|
|
42
|
+
}
|
|
43
|
+
const FALLBACK_DB_FILENAME = 'medical-codes.db';
|
|
44
|
+
/**
|
|
45
|
+
* Resolve the bundled DB path. An explicit `MEDCODE_DB_PATH` wins; otherwise the
|
|
46
|
+
* packaged `data/medical-codes.db`, resolved relative to this module's location
|
|
47
|
+
* so it works from both `src/` (tests, bun) and `dist/` (production build).
|
|
48
|
+
*/
|
|
49
|
+
function resolveDbPath() {
|
|
50
|
+
const configured = getServerConfig().dbPath;
|
|
51
|
+
if (configured)
|
|
52
|
+
return configured;
|
|
53
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
54
|
+
// here is …/src/services/code-index or …/dist/services/code-index → up 3 to root.
|
|
55
|
+
return join(here, '..', '..', '..', 'data', FALLBACK_DB_FILENAME);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read-only handle over the bundled medical-codes index. Constructed once in
|
|
59
|
+
* `setup()`. All query methods are synchronous SQLite reads — fast enough to
|
|
60
|
+
* call inline from handlers.
|
|
61
|
+
*/
|
|
62
|
+
export class CodeIndexService {
|
|
63
|
+
db;
|
|
64
|
+
dbPath;
|
|
65
|
+
constructor(db, dbPath) {
|
|
66
|
+
this.db = db;
|
|
67
|
+
this.dbPath = dbPath;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Open the bundled DB read-only and assert it is a populated build. Fails fast
|
|
71
|
+
* (throws) on a missing file, an unreadable DB, or an empty `build_meta` — a
|
|
72
|
+
* broken bundle should crash loudly at startup, not serve empty results.
|
|
73
|
+
*/
|
|
74
|
+
static async open() {
|
|
75
|
+
const dbPath = resolveDbPath();
|
|
76
|
+
if (!existsSync(dbPath)) {
|
|
77
|
+
throw internalError(`Bundled code index not found at ${dbPath}. The package ships data/medical-codes.db; set MEDCODE_DB_PATH to override, or rebuild it with \`bun run scripts/build-index.ts\`.`, { dbPath });
|
|
78
|
+
}
|
|
79
|
+
const db = await openDriver(dbPath);
|
|
80
|
+
const meta = db.query('SELECT COUNT(*) AS n FROM build_meta').get();
|
|
81
|
+
if (!meta || meta.n === 0) {
|
|
82
|
+
db.close();
|
|
83
|
+
throw internalError(`Bundled code index at ${dbPath} has no build_meta rows — the database is empty or corrupt.`, { dbPath });
|
|
84
|
+
}
|
|
85
|
+
logger.info('Code index opened', requestContextService.createRequestContext({
|
|
86
|
+
operation: 'CodeIndexOpen',
|
|
87
|
+
dbPath,
|
|
88
|
+
systems: meta.n,
|
|
89
|
+
}));
|
|
90
|
+
return new CodeIndexService(db, dbPath);
|
|
91
|
+
}
|
|
92
|
+
/** Map a raw `codes` row (snake_case, 0/1 ints) to a typed CodeRow. */
|
|
93
|
+
static toCodeRow(raw) {
|
|
94
|
+
return {
|
|
95
|
+
system: raw.system,
|
|
96
|
+
code: raw.code,
|
|
97
|
+
shortDesc: raw.short_desc ?? null,
|
|
98
|
+
longDesc: raw.long_desc ?? null,
|
|
99
|
+
billable: Number(raw.billable ?? 0),
|
|
100
|
+
header: Number(raw.header ?? 0),
|
|
101
|
+
chapter: raw.chapter ?? null,
|
|
102
|
+
parent: raw.parent ?? null,
|
|
103
|
+
effective: raw.effective ?? null,
|
|
104
|
+
terminated: raw.terminated ?? null,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** Project a CodeRow into the display shape tools return. */
|
|
108
|
+
static decode(row) {
|
|
109
|
+
return {
|
|
110
|
+
system: row.system,
|
|
111
|
+
code: displayCode(row.system, row.code),
|
|
112
|
+
shortDescription: row.shortDesc,
|
|
113
|
+
description: row.longDesc ?? row.shortDesc,
|
|
114
|
+
billable: row.billable === 1,
|
|
115
|
+
header: row.header === 1,
|
|
116
|
+
chapter: row.chapter,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/** Lexically-plausible systems for a raw code (no DB hit). */
|
|
120
|
+
detectSystem(rawCode) {
|
|
121
|
+
return detectSystems(rawCode);
|
|
122
|
+
}
|
|
123
|
+
/** Fetch the raw row for a code in a specific system, or null. */
|
|
124
|
+
getRow(code, system) {
|
|
125
|
+
const raw = this.db
|
|
126
|
+
.query('SELECT * FROM codes WHERE system = ? AND code = ?')
|
|
127
|
+
.get(system, code);
|
|
128
|
+
return raw ? CodeIndexService.toCodeRow(raw) : null;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Find which systems actually contain a code string. Narrows by lexical shape
|
|
132
|
+
* first (cheap), then confirms membership in the DB. The lexical candidates
|
|
133
|
+
* may overlap (a letter+4-digit code is shaped like both ICD-10-CM and HCPCS);
|
|
134
|
+
* DB membership is the real disambiguator — a code present in exactly one
|
|
135
|
+
* system is unambiguous regardless of shape overlap.
|
|
136
|
+
*/
|
|
137
|
+
resolveSystems(rawCode, explicit) {
|
|
138
|
+
const code = storageCode(rawCode);
|
|
139
|
+
const candidates = explicit ? [explicit] : detectSystems(rawCode);
|
|
140
|
+
return candidates.filter((sys) => this.getRow(code, sys) !== null);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Decode one code. Returns `{ kind: 'found' }` with the row, `'ambiguous'`
|
|
144
|
+
* with the colliding systems, or `'not_found'`. When `system` is given it is
|
|
145
|
+
* authoritative; otherwise membership across detected systems decides.
|
|
146
|
+
*/
|
|
147
|
+
getByCode(rawCode, system) {
|
|
148
|
+
const code = storageCode(rawCode);
|
|
149
|
+
const present = this.resolveSystems(rawCode, system);
|
|
150
|
+
const [sys] = present;
|
|
151
|
+
if (!sys)
|
|
152
|
+
return { kind: 'not_found' };
|
|
153
|
+
if (present.length > 1)
|
|
154
|
+
return { kind: 'ambiguous', systems: present };
|
|
155
|
+
const row = this.getRow(code, sys);
|
|
156
|
+
return row ? { kind: 'found', row } : { kind: 'not_found' };
|
|
157
|
+
}
|
|
158
|
+
/** Immediate children of a code within its system (prefix hierarchy). */
|
|
159
|
+
childrenOf(system, parentCode) {
|
|
160
|
+
const rows = this.db
|
|
161
|
+
.query('SELECT * FROM codes WHERE system = ? AND parent = ? ORDER BY code LIMIT ?')
|
|
162
|
+
.all(system, parentCode, getServerConfig().maxResults);
|
|
163
|
+
return rows.map((r) => CodeIndexService.decode(CodeIndexService.toCodeRow(r)));
|
|
164
|
+
}
|
|
165
|
+
/** Decode with parent + immediate children attached. */
|
|
166
|
+
getByCodeWithHierarchy(row) {
|
|
167
|
+
const base = CodeIndexService.decode(row);
|
|
168
|
+
const parentStorage = row.system === 'ICD10CM' ? icd10cmParent(row.code) : row.parent;
|
|
169
|
+
const children = this.childrenOf(row.system, row.code);
|
|
170
|
+
return {
|
|
171
|
+
...base,
|
|
172
|
+
parent: parentStorage ? displayCode(row.system, parentStorage) : null,
|
|
173
|
+
children,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Full-text search over code descriptions. Translates the user's text into a
|
|
178
|
+
* safe FTS5 MATCH expression (every token required, prefix-matched), applies
|
|
179
|
+
* the system/billable/chapter filters, and caps at `limit`.
|
|
180
|
+
*/
|
|
181
|
+
searchFts(queryText, filters) {
|
|
182
|
+
const match = toFtsMatch(queryText);
|
|
183
|
+
if (!match)
|
|
184
|
+
return [];
|
|
185
|
+
const where = ['codes_fts MATCH ?'];
|
|
186
|
+
const params = [match];
|
|
187
|
+
if (filters.system) {
|
|
188
|
+
where.push('c.system = ?');
|
|
189
|
+
params.push(filters.system);
|
|
190
|
+
}
|
|
191
|
+
if (filters.billableOnly) {
|
|
192
|
+
where.push('c.billable = 1');
|
|
193
|
+
}
|
|
194
|
+
if (filters.chapter) {
|
|
195
|
+
where.push('c.chapter = ?');
|
|
196
|
+
params.push(filters.chapter);
|
|
197
|
+
}
|
|
198
|
+
params.push(filters.limit);
|
|
199
|
+
const rows = this.db
|
|
200
|
+
.query(`SELECT c.* FROM codes_fts f
|
|
201
|
+
JOIN codes c ON c.system = f.system AND c.code = f.code
|
|
202
|
+
WHERE ${where.join(' AND ')}
|
|
203
|
+
ORDER BY bm25(codes_fts) LIMIT ?`)
|
|
204
|
+
.all(...params);
|
|
205
|
+
return rows.map((r) => CodeIndexService.decode(CodeIndexService.toCodeRow(r)));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Validate a code's existence, currency, and billability. Existence vs.
|
|
209
|
+
* validity are split: a non-billable or terminated code is a *successful*
|
|
210
|
+
* status with a why-not, NOT a failure. Only a code absent from every detected
|
|
211
|
+
* system is `unknown`.
|
|
212
|
+
*/
|
|
213
|
+
checkCode(rawCode, system) {
|
|
214
|
+
const code = storageCode(rawCode);
|
|
215
|
+
const present = this.resolveSystems(rawCode, system);
|
|
216
|
+
if (present.length > 1)
|
|
217
|
+
return { kind: 'ambiguous', systems: present };
|
|
218
|
+
if (present.length === 0) {
|
|
219
|
+
// Echo a best-guess system for the caller's orientation, if shape suggests one.
|
|
220
|
+
const detected = system ?? detectSystems(rawCode)[0];
|
|
221
|
+
return {
|
|
222
|
+
kind: 'resolved',
|
|
223
|
+
result: {
|
|
224
|
+
system: detected ?? 'ICD10CM',
|
|
225
|
+
code: rawCode.trim().toUpperCase(),
|
|
226
|
+
status: 'unknown',
|
|
227
|
+
whyNot: detected
|
|
228
|
+
? `No ${detected} code matches "${rawCode.trim()}" in the bundled release.`
|
|
229
|
+
: `"${rawCode.trim()}" does not match the shape of any bundled code system.`,
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
// Exactly one system remains (zero and >1 handled above); resolveSystems only
|
|
234
|
+
// returns systems whose row exists, so getRow cannot be null here.
|
|
235
|
+
const [sys] = present;
|
|
236
|
+
const row = sys ? this.getRow(code, sys) : null;
|
|
237
|
+
if (!row)
|
|
238
|
+
return { kind: 'resolved', result: { system: sys ?? 'ICD10CM', code, status: 'unknown' } };
|
|
239
|
+
const display = displayCode(row.system, row.code);
|
|
240
|
+
if (row.terminated) {
|
|
241
|
+
return {
|
|
242
|
+
kind: 'resolved',
|
|
243
|
+
result: {
|
|
244
|
+
system: row.system,
|
|
245
|
+
code: display,
|
|
246
|
+
status: 'terminated',
|
|
247
|
+
whyNot: `Code terminated effective ${formatDate(row.terminated)}; no longer valid for current claims.`,
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (row.header === 1) {
|
|
252
|
+
return {
|
|
253
|
+
kind: 'resolved',
|
|
254
|
+
result: {
|
|
255
|
+
system: row.system,
|
|
256
|
+
code: display,
|
|
257
|
+
status: 'valid_header',
|
|
258
|
+
whyNot: 'Valid category/header, but not billable — submit a more specific child code instead.',
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
if (row.billable === 1) {
|
|
263
|
+
return {
|
|
264
|
+
kind: 'resolved',
|
|
265
|
+
result: { system: row.system, code: display, status: 'valid_billable' },
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
kind: 'resolved',
|
|
270
|
+
result: {
|
|
271
|
+
system: row.system,
|
|
272
|
+
code: display,
|
|
273
|
+
status: 'valid_not_billable',
|
|
274
|
+
whyNot: 'Valid code, but not flagged billable in this release — verify a more specific code is not required before submitting.',
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Crosswalk a value across systems or within a hierarchy. v1 supports the
|
|
280
|
+
* hierarchy directions (`parents`/`children`); the drug directions resolve
|
|
281
|
+
* against the RxNorm tables, which are empty until phase 2 — the tool guards
|
|
282
|
+
* for that before calling.
|
|
283
|
+
*/
|
|
284
|
+
mapCode(from, direction, system) {
|
|
285
|
+
if (direction === 'parents' || direction === 'children') {
|
|
286
|
+
const present = this.resolveSystems(from, system);
|
|
287
|
+
const [sys] = present;
|
|
288
|
+
if (!sys)
|
|
289
|
+
return { kind: 'source_not_found' };
|
|
290
|
+
if (present.length > 1)
|
|
291
|
+
return { kind: 'ambiguous', systems: present };
|
|
292
|
+
const code = storageCode(from);
|
|
293
|
+
const row = this.getRow(code, sys);
|
|
294
|
+
if (!row)
|
|
295
|
+
return { kind: 'source_not_found' };
|
|
296
|
+
if (direction === 'children') {
|
|
297
|
+
const kids = this.childrenOf(sys, code);
|
|
298
|
+
return {
|
|
299
|
+
kind: 'ok',
|
|
300
|
+
resolvedSystem: sys,
|
|
301
|
+
hits: kids.map((k) => ({
|
|
302
|
+
source: sys,
|
|
303
|
+
system: sys,
|
|
304
|
+
value: k.code,
|
|
305
|
+
...(k.description ? { description: k.description } : {}),
|
|
306
|
+
})),
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// parents — single immediate parent for prefix systems; PCS has none.
|
|
310
|
+
const parentStorage = sys === 'ICD10CM' ? icd10cmParent(code) : row.parent;
|
|
311
|
+
if (!parentStorage)
|
|
312
|
+
return { kind: 'ok', resolvedSystem: sys, hits: [] };
|
|
313
|
+
const parentRow = this.getRow(parentStorage, sys);
|
|
314
|
+
if (!parentRow)
|
|
315
|
+
return { kind: 'ok', resolvedSystem: sys, hits: [] };
|
|
316
|
+
const parentDesc = parentRow.longDesc ?? parentRow.shortDesc;
|
|
317
|
+
return {
|
|
318
|
+
kind: 'ok',
|
|
319
|
+
resolvedSystem: sys,
|
|
320
|
+
hits: [
|
|
321
|
+
{
|
|
322
|
+
source: sys,
|
|
323
|
+
system: sys,
|
|
324
|
+
value: displayCode(sys, parentRow.code),
|
|
325
|
+
...(parentDesc ? { description: parentDesc } : {}),
|
|
326
|
+
},
|
|
327
|
+
],
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
// Drug directions — RxNorm-backed (phase 2). The tool short-circuits these
|
|
331
|
+
// with `direction_unavailable` when the RxNorm tables are unbuilt; this
|
|
332
|
+
// implementation is live the moment the tables are populated.
|
|
333
|
+
return this.mapDrug(from, direction);
|
|
334
|
+
}
|
|
335
|
+
/** Whether the RxNorm tables carry any rows (i.e. phase 2 is bundled). */
|
|
336
|
+
hasRxNorm() {
|
|
337
|
+
const row = this.db.query('SELECT COUNT(*) AS n FROM rxnorm_rel').get();
|
|
338
|
+
return (row?.n ?? 0) > 0;
|
|
339
|
+
}
|
|
340
|
+
/** RxNorm crosswalk resolution. */
|
|
341
|
+
mapDrug(from, direction) {
|
|
342
|
+
const value = from.trim();
|
|
343
|
+
switch (direction) {
|
|
344
|
+
case 'name_to_rxcui': {
|
|
345
|
+
// Escape LIKE wildcards in the user value so `%` / `_` / `\` match
|
|
346
|
+
// literally instead of acting as operators (an unescaped `%` would match
|
|
347
|
+
// every drug name). Bind the escaped term inside `%…%` and declare the
|
|
348
|
+
// escape char with ESCAPE.
|
|
349
|
+
const term = `%${escapeLike(value)}%`;
|
|
350
|
+
const rows = this.db
|
|
351
|
+
.query(`SELECT code, long_desc, short_desc FROM codes
|
|
352
|
+
WHERE system = 'RXNORM' AND (long_desc LIKE ? ESCAPE '\\' OR short_desc LIKE ? ESCAPE '\\')
|
|
353
|
+
ORDER BY length(code) LIMIT ?`)
|
|
354
|
+
.all(term, term, getServerConfig().maxResults);
|
|
355
|
+
if (rows.length === 0)
|
|
356
|
+
return { kind: 'source_not_found' };
|
|
357
|
+
return {
|
|
358
|
+
kind: 'ok',
|
|
359
|
+
resolvedSystem: 'RXNORM',
|
|
360
|
+
hits: rows.map((r) => ({
|
|
361
|
+
source: 'RXNORM',
|
|
362
|
+
system: 'RXNORM',
|
|
363
|
+
value: r.code,
|
|
364
|
+
...(r.long_desc || r.short_desc
|
|
365
|
+
? { description: (r.long_desc ?? r.short_desc) }
|
|
366
|
+
: {}),
|
|
367
|
+
})),
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
case 'ndc_to_rxcui': {
|
|
371
|
+
const ndc = value.replace(/[^0-9]/g, '');
|
|
372
|
+
const rows = this.db.query('SELECT rxcui FROM ndc_map WHERE ndc = ?').all(ndc);
|
|
373
|
+
if (rows.length === 0)
|
|
374
|
+
return { kind: 'source_not_found' };
|
|
375
|
+
return {
|
|
376
|
+
kind: 'ok',
|
|
377
|
+
resolvedSystem: 'RXNORM',
|
|
378
|
+
hits: rows.map((r) => ({ source: 'NDC', system: 'RXNORM', value: r.rxcui })),
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
case 'rxcui_to_ndc': {
|
|
382
|
+
const rows = this.db.query('SELECT ndc FROM ndc_map WHERE rxcui = ?').all(value);
|
|
383
|
+
if (rows.length === 0)
|
|
384
|
+
return { kind: 'source_not_found' };
|
|
385
|
+
return {
|
|
386
|
+
kind: 'ok',
|
|
387
|
+
resolvedSystem: 'RXNORM',
|
|
388
|
+
hits: rows.map((r) => ({ source: 'NDC', system: null, value: r.ndc })),
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
case 'rxcui_to_ingredients':
|
|
392
|
+
case 'rxcui_to_brands': {
|
|
393
|
+
const rel = direction === 'rxcui_to_ingredients' ? 'has_ingredient' : 'has_tradename';
|
|
394
|
+
const rows = this.db
|
|
395
|
+
.query('SELECT rel, target, target_type FROM rxnorm_rel WHERE rxcui = ? AND rel = ?')
|
|
396
|
+
.all(value, rel);
|
|
397
|
+
if (rows.length === 0)
|
|
398
|
+
return { kind: 'source_not_found' };
|
|
399
|
+
return {
|
|
400
|
+
kind: 'ok',
|
|
401
|
+
resolvedSystem: 'RXNORM',
|
|
402
|
+
hits: rows.map((r) => ({
|
|
403
|
+
source: r.rel,
|
|
404
|
+
system: 'RXNORM',
|
|
405
|
+
value: r.target,
|
|
406
|
+
...(r.targetType ? { description: r.targetType } : {}),
|
|
407
|
+
})),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
default:
|
|
411
|
+
return { kind: 'source_not_found' };
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
/** True when `direction` needs the RxNorm tables. */
|
|
415
|
+
static isDrugDirection(direction) {
|
|
416
|
+
return DRUG_DIRECTIONS.includes(direction);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Browse a system's hierarchy. With no `node`: top-level entries (3-char
|
|
420
|
+
* ICD-10-CM categories grouped by chapter letter, HCPCS range buckets, PCS
|
|
421
|
+
* section axis values). With a `node`: that node's children — for ICD-10-PCS,
|
|
422
|
+
* the valid next-position axis values rather than prefix children.
|
|
423
|
+
*/
|
|
424
|
+
browse(system, node, limit) {
|
|
425
|
+
if (system === 'ICD10PCS') {
|
|
426
|
+
return this.browsePcs(node, limit);
|
|
427
|
+
}
|
|
428
|
+
if (!node) {
|
|
429
|
+
// Top level: codes with no parent (roots). For ICD10CM these are 3-char
|
|
430
|
+
// categories; for HCPCS the single-letter range buckets (if seeded) or the
|
|
431
|
+
// distinct chapters.
|
|
432
|
+
const rows = this.db
|
|
433
|
+
.query('SELECT * FROM codes WHERE system = ? AND parent IS NULL ORDER BY code LIMIT ?')
|
|
434
|
+
.all(system, limit);
|
|
435
|
+
return {
|
|
436
|
+
kind: 'codes',
|
|
437
|
+
codes: rows.map((r) => CodeIndexService.decode(CodeIndexService.toCodeRow(r))),
|
|
438
|
+
};
|
|
439
|
+
}
|
|
440
|
+
const code = storageCode(node);
|
|
441
|
+
if (!this.getRow(code, system))
|
|
442
|
+
return { kind: 'unknown_node' };
|
|
443
|
+
return { kind: 'codes', codes: this.childrenOf(system, code) };
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* PCS axis browse. Only the position-1 Section axis is baked into `pcs_axes`
|
|
447
|
+
* (positions 2-7 are context-dependent on the full preceding axis path and a
|
|
448
|
+
* flat table can't represent them without storing wrong meanings — see
|
|
449
|
+
* `parseIcd10pcsAxes`). So the top level (no node) returns the 17 Sections;
|
|
450
|
+
* a partial code asks for a deeper position, which has no rows — the caller
|
|
451
|
+
* gets an explicit notice (handled in the tool), not silent-empty or wrong data.
|
|
452
|
+
*/
|
|
453
|
+
browsePcs(node, limit) {
|
|
454
|
+
const partial = node ? storageCode(node) : '';
|
|
455
|
+
if (partial.length >= 7)
|
|
456
|
+
return { kind: 'unknown_node' };
|
|
457
|
+
const position = partial.length + 1; // next axis position to enumerate
|
|
458
|
+
const rows = this.db
|
|
459
|
+
.query('SELECT position, value, meaning FROM pcs_axes WHERE position = ? ORDER BY value LIMIT ?')
|
|
460
|
+
.all(position, limit);
|
|
461
|
+
return { kind: 'axes', axes: rows };
|
|
462
|
+
}
|
|
463
|
+
/** Provenance for every bundled system. */
|
|
464
|
+
listSystems() {
|
|
465
|
+
const rows = this.db.query('SELECT * FROM build_meta ORDER BY system').all();
|
|
466
|
+
const order = new Map(SYSTEM_IDS.map((s, i) => [s, i]));
|
|
467
|
+
return rows
|
|
468
|
+
.map((r) => ({
|
|
469
|
+
system: r.system,
|
|
470
|
+
releaseId: r.release_id,
|
|
471
|
+
effectiveStart: r.effective_start ?? null,
|
|
472
|
+
effectiveEnd: r.effective_end ?? null,
|
|
473
|
+
codeCount: Number(r.code_count ?? 0),
|
|
474
|
+
sourceUrl: r.source_url ?? null,
|
|
475
|
+
builtAt: r.built_at,
|
|
476
|
+
}))
|
|
477
|
+
.sort((a, b) => (order.get(a.system) ?? 99) - (order.get(b.system) ?? 99));
|
|
478
|
+
}
|
|
479
|
+
/** Expose the decode projection for tools that already hold a CodeRow. */
|
|
480
|
+
static project(row) {
|
|
481
|
+
return CodeIndexService.decode(row);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Translate free user text into a safe FTS5 MATCH expression. Strips FTS5
|
|
486
|
+
* operator characters, splits into tokens, and ANDs prefix-matched tokens so
|
|
487
|
+
* "diabetic neuropathy" requires both stems. Returns null when nothing usable
|
|
488
|
+
* remains (caller treats that as an empty result, not an error).
|
|
489
|
+
*/
|
|
490
|
+
export function toFtsMatch(text) {
|
|
491
|
+
const tokens = text
|
|
492
|
+
.toLowerCase()
|
|
493
|
+
.replace(/["()*:^-]/g, ' ')
|
|
494
|
+
.split(/\s+/)
|
|
495
|
+
.map((t) => t.trim())
|
|
496
|
+
.filter((t) => t.length > 0);
|
|
497
|
+
if (tokens.length === 0)
|
|
498
|
+
return null;
|
|
499
|
+
// Quote each token (defuses any residual special handling) and prefix-match.
|
|
500
|
+
return tokens.map((t) => `"${t}"*`).join(' AND ');
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Escape SQL LIKE wildcards (`%`, `_`) and the escape char (`\`) in a
|
|
504
|
+
* user-supplied substring so it matches literally inside a `LIKE '%…%'` pattern.
|
|
505
|
+
* Pair with `ESCAPE '\'` in the query. Without this a value like `%` would match
|
|
506
|
+
* every row; `_` would match any single character.
|
|
507
|
+
*/
|
|
508
|
+
export function escapeLike(value) {
|
|
509
|
+
return value.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
510
|
+
}
|
|
511
|
+
/** Format a YYYYMMDD storage date as YYYY-MM-DD; pass through anything else. */
|
|
512
|
+
function formatDate(yyyymmdd) {
|
|
513
|
+
return /^\d{8}$/.test(yyyymmdd)
|
|
514
|
+
? `${yyyymmdd.slice(0, 4)}-${yyyymmdd.slice(4, 6)}-${yyyymmdd.slice(6, 8)}`
|
|
515
|
+
: yyyymmdd;
|
|
516
|
+
}
|
|
517
|
+
// ─── Init / Accessor ────────────────────────────────────────────────────────
|
|
518
|
+
let _service;
|
|
519
|
+
/** Open the bundled index and cache the handle. Called once from `setup()`. */
|
|
520
|
+
export async function initCodeIndexService() {
|
|
521
|
+
_service = await CodeIndexService.open();
|
|
522
|
+
}
|
|
523
|
+
/** Return the initialized service, throwing if `setup()` hasn't run. */
|
|
524
|
+
export function getCodeIndexService() {
|
|
525
|
+
if (!_service) {
|
|
526
|
+
throw new Error('CodeIndexService not initialized — call initCodeIndexService() in setup()');
|
|
527
|
+
}
|
|
528
|
+
return _service;
|
|
529
|
+
}
|
|
530
|
+
//# sourceMappingURL=code-index-service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-index-service.js","sourceRoot":"","sources":["../../../src/services/code-index/code-index-service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Code-shape auto-detection. Each bundled system has a distinct
|
|
3
|
+
* lexical shape; `detectSystems` returns every system a raw code could belong
|
|
4
|
+
* to so the caller can disambiguate (one match → route directly; multiple →
|
|
5
|
+
* `ambiguous_system`; zero → unknown shape).
|
|
6
|
+
* @module services/code-index/detect
|
|
7
|
+
*/
|
|
8
|
+
import type { SystemId } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* Return every system whose shape the raw code matches, in canonical order.
|
|
11
|
+
* Empty array ⇒ the code matches no known shape. The caller decides what a
|
|
12
|
+
* single vs. multiple match means.
|
|
13
|
+
*
|
|
14
|
+
* Note the deliberate overlaps the design calls out: a 7-character ICD-10-PCS
|
|
15
|
+
* code can also satisfy the ICD-10-CM pattern is NOT possible (CM caps at 3+4=7
|
|
16
|
+
* but requires digits in positions 2-3 and a leading A-Z excluding nothing),
|
|
17
|
+
* but a 5-char HCPCS code and a short ICD-10-CM category never collide because
|
|
18
|
+
* HCPCS requires 4 trailing digits while CM position 2-3 are digits and 4-5 are
|
|
19
|
+
* alphanumeric — `J0120` is HCPCS-only. The real ambiguity is a bare integer
|
|
20
|
+
* (RXCUI) vs nothing else, and a 7-char alphanumeric that is PCS-shaped.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectSystems(rawCode: string): SystemId[];
|
|
23
|
+
//# sourceMappingURL=detect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.d.ts","sourceRoot":"","sources":["../../../src/services/code-index/detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AA2B3C;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,EAAE,CAUzD"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Code-shape auto-detection. Each bundled system has a distinct
|
|
3
|
+
* lexical shape; `detectSystems` returns every system a raw code could belong
|
|
4
|
+
* to so the caller can disambiguate (one match → route directly; multiple →
|
|
5
|
+
* `ambiguous_system`; zero → unknown shape).
|
|
6
|
+
* @module services/code-index/detect
|
|
7
|
+
*/
|
|
8
|
+
import { storageCode } from './schema.js';
|
|
9
|
+
/**
|
|
10
|
+
* ICD-10-CM: a letter, two digits, then optionally a dot and up to four more
|
|
11
|
+
* alphanumerics. Tested against the dot-stripped storage form. The first
|
|
12
|
+
* character is a letter; the next two are digits; trailing characters are
|
|
13
|
+
* alphanumeric. `D` and `U` are valid leading letters in ICD-10-CM.
|
|
14
|
+
*/
|
|
15
|
+
const ICD10CM_RE = /^[A-TV-Z][0-9][0-9AB][0-9A-Z]{0,4}$/;
|
|
16
|
+
/**
|
|
17
|
+
* ICD-10-PCS: exactly 7 characters, alphanumeric, drawn from digits 0-9 and
|
|
18
|
+
* letters A-H,J-N,P-Z (the letters `I` and `O` are excluded by design to avoid
|
|
19
|
+
* confusion with 1 and 0).
|
|
20
|
+
*/
|
|
21
|
+
const ICD10PCS_RE = /^[0-9A-HJ-NP-Z]{7}$/;
|
|
22
|
+
/**
|
|
23
|
+
* HCPCS Level II: one letter A-V, then exactly four digits (e.g. `J0120`,
|
|
24
|
+
* `E0110`). The leading-letter range A-V is what separates it from a generic
|
|
25
|
+
* 5-char alphanumeric.
|
|
26
|
+
*/
|
|
27
|
+
const HCPCS_RE = /^[A-V][0-9]{4}$/;
|
|
28
|
+
/** RXCUI: a pure integer (RxNorm concept identifier). */
|
|
29
|
+
const RXCUI_RE = /^[0-9]+$/;
|
|
30
|
+
/**
|
|
31
|
+
* Return every system whose shape the raw code matches, in canonical order.
|
|
32
|
+
* Empty array ⇒ the code matches no known shape. The caller decides what a
|
|
33
|
+
* single vs. multiple match means.
|
|
34
|
+
*
|
|
35
|
+
* Note the deliberate overlaps the design calls out: a 7-character ICD-10-PCS
|
|
36
|
+
* code can also satisfy the ICD-10-CM pattern is NOT possible (CM caps at 3+4=7
|
|
37
|
+
* but requires digits in positions 2-3 and a leading A-Z excluding nothing),
|
|
38
|
+
* but a 5-char HCPCS code and a short ICD-10-CM category never collide because
|
|
39
|
+
* HCPCS requires 4 trailing digits while CM position 2-3 are digits and 4-5 are
|
|
40
|
+
* alphanumeric — `J0120` is HCPCS-only. The real ambiguity is a bare integer
|
|
41
|
+
* (RXCUI) vs nothing else, and a 7-char alphanumeric that is PCS-shaped.
|
|
42
|
+
*/
|
|
43
|
+
export function detectSystems(rawCode) {
|
|
44
|
+
const code = storageCode(rawCode);
|
|
45
|
+
if (code.length === 0)
|
|
46
|
+
return [];
|
|
47
|
+
const matches = [];
|
|
48
|
+
if (ICD10CM_RE.test(code))
|
|
49
|
+
matches.push('ICD10CM');
|
|
50
|
+
if (ICD10PCS_RE.test(code))
|
|
51
|
+
matches.push('ICD10PCS');
|
|
52
|
+
if (HCPCS_RE.test(code))
|
|
53
|
+
matches.push('HCPCS');
|
|
54
|
+
if (RXCUI_RE.test(code))
|
|
55
|
+
matches.push('RXNORM');
|
|
56
|
+
return matches;
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=detect.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"detect.js","sourceRoot":"","sources":["../../../src/services/code-index/detect.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1C;;;;;GAKG;AACH,MAAM,UAAU,GAAG,qCAAqC,CAAC;AAEzD;;;;GAIG;AACH,MAAM,WAAW,GAAG,qBAAqB,CAAC;AAE1C;;;;GAIG;AACH,MAAM,QAAQ,GAAG,iBAAiB,CAAC;AAEnC,yDAAyD;AACzD,MAAM,QAAQ,GAAG,UAAU,CAAC;AAE5B;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEjC,MAAM,OAAO,GAAe,EAAE,CAAC;IAC/B,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACnD,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACrD,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IAChD,OAAO,OAAO,CAAC;AACjB,CAAC"}
|