autoicd-js 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/LICENSE +21 -0
- package/README.md +268 -0
- package/dist/index.d.mts +216 -0
- package/dist/index.d.ts +216 -0
- package/dist/index.js +237 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +206 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +71 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fede
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
# AutoICD API — TypeScript SDK
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/autoicd)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.typescriptlang.org/)
|
|
6
|
+
|
|
7
|
+
Official TypeScript SDK for the [AutoICD API](https://autoicdapi.com) — clinical text to ICD-10-CM diagnosis codes, powered by medical NLP and SapBERT embeddings.
|
|
8
|
+
|
|
9
|
+
Zero dependencies. Works in **Node.js 18+**, **Deno**, **Bun**, and **edge runtimes**.
|
|
10
|
+
|
|
11
|
+
> Built for EHR integrations, health-tech platforms, medical billing, clinical decision support, and revenue cycle management.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Why AutoICD API
|
|
16
|
+
|
|
17
|
+
| | |
|
|
18
|
+
|---|---|
|
|
19
|
+
| **AI-Powered ICD-10 Coding** | Clinical NLP extracts diagnoses from free-text notes and maps them to ICD-10-CM codes — no manual lookup required |
|
|
20
|
+
| **74,000+ ICD-10-CM Codes** | Full 2025 code set enriched with SNOMED CT synonyms for comprehensive matching |
|
|
21
|
+
| **Negation & Context Detection** | Knows the difference between "patient has diabetes" and "patient denies diabetes" — flags negated, historical, uncertain, and family-history mentions |
|
|
22
|
+
| **PHI De-identification** | HIPAA-compliant anonymization of names, dates, SSNs, phone numbers, emails, addresses, MRNs, and ages |
|
|
23
|
+
| **Confidence Scoring** | Every code match includes a similarity score and confidence level so you can set your own acceptance thresholds |
|
|
24
|
+
| **Spell Correction** | Handles misspellings in clinical text — "diabeties" still maps to the right code |
|
|
25
|
+
| **Fully Type-Safe** | Complete TypeScript definitions for all requests and responses |
|
|
26
|
+
| **Zero Dependencies** | Lightweight, no bloat, no supply-chain risk |
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Install
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install autoicd
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
<details>
|
|
37
|
+
<summary>yarn / pnpm / bun</summary>
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yarn add autoicd
|
|
41
|
+
pnpm add autoicd
|
|
42
|
+
bun add autoicd
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
</details>
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
import { AutoICD } from "autoicd";
|
|
53
|
+
|
|
54
|
+
const autoicd = new AutoICD({ apiKey: "sk_..." });
|
|
55
|
+
|
|
56
|
+
const result = await autoicd.code(
|
|
57
|
+
"Patient has type 2 diabetes and essential hypertension"
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
for (const entity of result.entities) {
|
|
61
|
+
console.log(entity.entity_text, "→", entity.codes[0]?.code);
|
|
62
|
+
}
|
|
63
|
+
// "type 2 diabetes" → "E11.9"
|
|
64
|
+
// "essential hypertension" → "I10"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Features
|
|
70
|
+
|
|
71
|
+
### Automated ICD-10 Medical Coding
|
|
72
|
+
|
|
73
|
+
Extract diagnosis entities from clinical notes and map them to ICD-10-CM codes. Each entity includes ranked candidates with confidence scores, negation status, and context flags.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
const result = await autoicd.code(
|
|
77
|
+
"History of severe COPD with acute exacerbation. Patient denies chest pain."
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
for (const entity of result.entities) {
|
|
81
|
+
console.log(entity.entity_text);
|
|
82
|
+
console.log(` Negated: ${entity.negated}`);
|
|
83
|
+
console.log(` Historical: ${entity.historical}`);
|
|
84
|
+
for (const match of entity.codes) {
|
|
85
|
+
console.log(
|
|
86
|
+
` ${match.code} — ${match.description} (${match.confidence}, ${(match.similarity * 100).toFixed(1)}%)`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Fine-tune results with coding options:
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
const result = await autoicd.code(
|
|
96
|
+
"Patient presents with acute bronchitis and chest pain",
|
|
97
|
+
{
|
|
98
|
+
topK: 3, // Top 3 ICD-10 candidates per entity (default: 5)
|
|
99
|
+
strategy: "merged", // "individual" or "merged" entity strategy
|
|
100
|
+
includeNegated: false, // Exclude negated conditions from results
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### ICD-10 Code Search
|
|
106
|
+
|
|
107
|
+
Search the full ICD-10-CM 2025 code set by description. Perfect for building code lookup UIs, autocomplete fields, and validation workflows.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
const results = await autoicd.codes.search("diabetes mellitus");
|
|
111
|
+
// results.codes → [{ code: "E11.9", short_description: "...", long_description: "...", is_billable: true }, ...]
|
|
112
|
+
|
|
113
|
+
const results = await autoicd.codes.search("heart failure", { limit: 5 });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### ICD-10 Code Details
|
|
117
|
+
|
|
118
|
+
Get full details for any ICD-10-CM code — descriptions, billable status, and indexed terms.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const detail = await autoicd.codes.get("E11.9");
|
|
122
|
+
console.log(detail.code); // "E11.9"
|
|
123
|
+
console.log(detail.long_description); // "Type 2 diabetes mellitus without complications"
|
|
124
|
+
console.log(detail.is_billable); // true
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ICD-10 Code Terms & Synonyms
|
|
128
|
+
|
|
129
|
+
Retrieve all indexed terms and synonyms for a code — includes SNOMED CT mappings and clinical variants.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const terms = await autoicd.codes.terms("E11.9");
|
|
133
|
+
terms.forEach((t) => console.log(t.term, `(${t.term_type})`));
|
|
134
|
+
// "Type 2 diabetes mellitus without complications" (long_desc)
|
|
135
|
+
// "adult onset diabetes" (synonym)
|
|
136
|
+
// ...
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### PHI De-identification
|
|
140
|
+
|
|
141
|
+
Strip protected health information from clinical notes before storage or analysis. HIPAA-compliant de-identification for names, dates, SSNs, phone numbers, emails, addresses, MRNs, and ages.
|
|
142
|
+
|
|
143
|
+
```ts
|
|
144
|
+
const result = await autoicd.anonymize(
|
|
145
|
+
"John Smith, DOB 01/15/1980, MRN 123456, has COPD"
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
console.log(result.anonymized_text);
|
|
149
|
+
// "[NAME], DOB [DATE], MRN [MRN], has COPD"
|
|
150
|
+
|
|
151
|
+
console.log(result.pii_count); // 3
|
|
152
|
+
console.log(result.pii_entities); // [{ text: "John Smith", label: "NAME", ... }, ...]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Use Cases
|
|
158
|
+
|
|
159
|
+
- **EHR / EMR Integration** — Auto-code clinical notes as providers type, reducing manual coding burden
|
|
160
|
+
- **Medical Billing & RCM** — Accelerate claim submission with accurate ICD-10 codes
|
|
161
|
+
- **Clinical Decision Support** — Map patient conditions to standardized codes for analytics and alerts
|
|
162
|
+
- **Health-Tech SaaS** — Add ICD-10 coding to your platform without building ML infrastructure
|
|
163
|
+
- **Clinical Research** — Extract and standardize diagnoses from unstructured medical records
|
|
164
|
+
- **Insurance & Payer Systems** — Validate and suggest diagnosis codes during claims processing
|
|
165
|
+
- **Telehealth Platforms** — Generate diagnosis codes from visit notes and transcriptions
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Error Handling
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
import {
|
|
173
|
+
AutoICD,
|
|
174
|
+
AuthenticationError,
|
|
175
|
+
RateLimitError,
|
|
176
|
+
NotFoundError,
|
|
177
|
+
} from "autoicd";
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
await autoicd.code("...");
|
|
181
|
+
} catch (err) {
|
|
182
|
+
if (err instanceof AuthenticationError) {
|
|
183
|
+
// Invalid or revoked API key (401)
|
|
184
|
+
} else if (err instanceof RateLimitError) {
|
|
185
|
+
// Request limit exceeded (429)
|
|
186
|
+
console.log(err.rateLimit.remaining, err.rateLimit.resetAt);
|
|
187
|
+
} else if (err instanceof NotFoundError) {
|
|
188
|
+
// ICD-10 code not found (404)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Rate limit info is available after every request:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
await autoicd.code("...");
|
|
197
|
+
console.log(autoicd.lastRateLimit);
|
|
198
|
+
// { limit: 1000, remaining: 987, resetAt: Date }
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Configuration
|
|
204
|
+
|
|
205
|
+
```ts
|
|
206
|
+
const autoicd = new AutoICD({
|
|
207
|
+
apiKey: "sk_...", // Required — get yours at https://autoicdapi.com
|
|
208
|
+
baseURL: "https://...", // Default: https://autoicdapi.com
|
|
209
|
+
timeout: 60_000, // Default: 30000ms
|
|
210
|
+
fetch: customFetch, // Custom fetch (for testing or non-standard runtimes)
|
|
211
|
+
});
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## API Reference
|
|
217
|
+
|
|
218
|
+
Full REST API documentation at [autoicdapi.com/docs](https://autoicdapi.com/docs).
|
|
219
|
+
|
|
220
|
+
| Method | Description |
|
|
221
|
+
|--------|-------------|
|
|
222
|
+
| `autoicd.code(text, options?)` | Code clinical text to ICD-10-CM diagnoses |
|
|
223
|
+
| `autoicd.anonymize(text)` | De-identify PHI/PII in clinical text |
|
|
224
|
+
| `autoicd.codes.search(query, options?)` | Search ICD-10-CM codes by description |
|
|
225
|
+
| `autoicd.codes.get(code)` | Get details for an ICD-10-CM code |
|
|
226
|
+
| `autoicd.codes.terms(code)` | Get indexed terms/synonyms for a code |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## TypeScript Types
|
|
231
|
+
|
|
232
|
+
All request and response types are exported:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import type {
|
|
236
|
+
CodingResponse,
|
|
237
|
+
CodingEntity,
|
|
238
|
+
CodeMatch,
|
|
239
|
+
CodeOptions,
|
|
240
|
+
CodeDetail,
|
|
241
|
+
CodeSearchResponse,
|
|
242
|
+
CodeTermInfo,
|
|
243
|
+
AnonymizeResponse,
|
|
244
|
+
PIIEntity,
|
|
245
|
+
RateLimit,
|
|
246
|
+
} from "autoicd";
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
## Requirements
|
|
252
|
+
|
|
253
|
+
- **Node.js 18+**, Deno, Bun, or any runtime with `fetch` support
|
|
254
|
+
- An API key from [autoicdapi.com](https://autoicdapi.com)
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Links
|
|
259
|
+
|
|
260
|
+
- [AutoICD API](https://autoicdapi.com) — Homepage and API key management
|
|
261
|
+
- [API Documentation](https://autoicdapi.com/docs) — Full REST API reference
|
|
262
|
+
- [ICD-10-CM 2025 Code Set](https://www.cms.gov/medicare/coding-billing/icd-10-codes) — Official CMS reference
|
|
263
|
+
|
|
264
|
+
---
|
|
265
|
+
|
|
266
|
+
## License
|
|
267
|
+
|
|
268
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
interface AutoICDOptions {
|
|
2
|
+
/** API key (starts with `sk_`). */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL. Defaults to `https://autoicdapi.com`. */
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
/** Default timeout in milliseconds. Defaults to 30_000. */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/** Custom fetch implementation (for testing or non-standard runtimes). */
|
|
9
|
+
fetch?: typeof globalThis.fetch;
|
|
10
|
+
}
|
|
11
|
+
interface CodeOptions {
|
|
12
|
+
/** Number of top ICD-10 candidates per entity (1-25). Defaults to 5. */
|
|
13
|
+
topK?: number;
|
|
14
|
+
/** Include negated entities in results. Defaults to true. */
|
|
15
|
+
includeNegated?: boolean;
|
|
16
|
+
/** Matching strategy. Defaults to `"individual"`. */
|
|
17
|
+
strategy?: "individual" | "merged";
|
|
18
|
+
}
|
|
19
|
+
interface CodeMatch {
|
|
20
|
+
/** ICD-10-CM code (e.g., `"E11.21"`). */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Official code description. */
|
|
23
|
+
description: string;
|
|
24
|
+
/** Cosine similarity score (0-1). */
|
|
25
|
+
similarity: number;
|
|
26
|
+
/** `"high"` if above high-confidence threshold, else `"moderate"`. */
|
|
27
|
+
confidence: "high" | "moderate";
|
|
28
|
+
/** The index term that produced this match. */
|
|
29
|
+
matched_term: string;
|
|
30
|
+
}
|
|
31
|
+
interface CodingEntity {
|
|
32
|
+
/** Entity text as extracted from the input. */
|
|
33
|
+
entity_text: string;
|
|
34
|
+
/** Character offset start in input text. */
|
|
35
|
+
entity_start: number;
|
|
36
|
+
/** Character offset end in input text. */
|
|
37
|
+
entity_end: number;
|
|
38
|
+
/** Whether this entity was negated in context. */
|
|
39
|
+
negated: boolean;
|
|
40
|
+
/** Entity refers to a past/resolved condition. */
|
|
41
|
+
historical: boolean;
|
|
42
|
+
/** Entity refers to a family member's condition. */
|
|
43
|
+
family_history: boolean;
|
|
44
|
+
/** Entity is hedged or uncertain. */
|
|
45
|
+
uncertain: boolean;
|
|
46
|
+
/** Severity modifier if detected (e.g., `"severe"`). */
|
|
47
|
+
severity: string | null;
|
|
48
|
+
/** Ranked ICD-10 code candidates. */
|
|
49
|
+
codes: CodeMatch[];
|
|
50
|
+
/** Source entity texts if this result was created by merging consecutive entities. */
|
|
51
|
+
merged_from?: string[] | null;
|
|
52
|
+
/** Original text before spell correction (null if no correction). */
|
|
53
|
+
corrected_from?: string | null;
|
|
54
|
+
}
|
|
55
|
+
interface CodingResponse {
|
|
56
|
+
/** The input text that was processed. */
|
|
57
|
+
text: string;
|
|
58
|
+
/** Coding provider used (e.g., `"sapbert"`). */
|
|
59
|
+
provider: string;
|
|
60
|
+
/** Matching strategy used. */
|
|
61
|
+
strategy: string;
|
|
62
|
+
/** Total number of entities in results. */
|
|
63
|
+
entity_count: number;
|
|
64
|
+
/** Coding results per entity, sorted by position in text. */
|
|
65
|
+
entities: CodingEntity[];
|
|
66
|
+
}
|
|
67
|
+
interface SearchOptions {
|
|
68
|
+
/** Maximum number of results (1-100). Defaults to 20. */
|
|
69
|
+
limit?: number;
|
|
70
|
+
/** Number of results to skip (for pagination). Defaults to 0. */
|
|
71
|
+
offset?: number;
|
|
72
|
+
}
|
|
73
|
+
interface CodeDetail {
|
|
74
|
+
/** ICD-10-CM code. */
|
|
75
|
+
code: string;
|
|
76
|
+
/** Abbreviated description. */
|
|
77
|
+
short_description: string;
|
|
78
|
+
/** Full official description. */
|
|
79
|
+
long_description: string;
|
|
80
|
+
/** Whether this is a billable (leaf) code. */
|
|
81
|
+
is_billable: boolean;
|
|
82
|
+
}
|
|
83
|
+
interface CodeSearchResponse {
|
|
84
|
+
/** The search query that was used. */
|
|
85
|
+
query: string;
|
|
86
|
+
/** Number of results returned. */
|
|
87
|
+
count: number;
|
|
88
|
+
/** Matching ICD-10 codes. */
|
|
89
|
+
codes: CodeDetail[];
|
|
90
|
+
}
|
|
91
|
+
interface CodeTermInfo {
|
|
92
|
+
/** The term text that was embedded. */
|
|
93
|
+
term: string;
|
|
94
|
+
/** Origin of this term (e.g., `"long_desc"`, `"synonym"`). */
|
|
95
|
+
term_type: string;
|
|
96
|
+
}
|
|
97
|
+
interface PIIEntity {
|
|
98
|
+
/** Original PII text. */
|
|
99
|
+
text: string;
|
|
100
|
+
/** Character offset start. */
|
|
101
|
+
start: number;
|
|
102
|
+
/** Character offset end. */
|
|
103
|
+
end: number;
|
|
104
|
+
/** PII category (NAME, DATE, SSN, PHONE, EMAIL, ADDRESS, MRN, AGE). */
|
|
105
|
+
label: string;
|
|
106
|
+
/** Replacement token (e.g., `"[NAME]"`). */
|
|
107
|
+
replacement: string;
|
|
108
|
+
}
|
|
109
|
+
interface AnonymizeResponse {
|
|
110
|
+
/** The input text that was processed. */
|
|
111
|
+
original_text: string;
|
|
112
|
+
/** Text with PII replaced by type labels. */
|
|
113
|
+
anonymized_text: string;
|
|
114
|
+
/** Number of PII entities detected. */
|
|
115
|
+
pii_count: number;
|
|
116
|
+
/** Detected PII spans with original offsets. */
|
|
117
|
+
pii_entities: PIIEntity[];
|
|
118
|
+
}
|
|
119
|
+
interface RateLimit {
|
|
120
|
+
/** Total requests allowed in the current period. */
|
|
121
|
+
limit: number;
|
|
122
|
+
/** Requests remaining. */
|
|
123
|
+
remaining: number;
|
|
124
|
+
/** UTC timestamp when the limit resets. */
|
|
125
|
+
resetAt: Date;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
declare class AutoICD {
|
|
129
|
+
private readonly apiKey;
|
|
130
|
+
private readonly baseURL;
|
|
131
|
+
private readonly timeout;
|
|
132
|
+
private readonly _fetch;
|
|
133
|
+
/** Rate limit info from the most recent API response. */
|
|
134
|
+
lastRateLimit: RateLimit | null;
|
|
135
|
+
/** Sub-resource for ICD-10 code lookup. */
|
|
136
|
+
readonly codes: Codes;
|
|
137
|
+
constructor(options: AutoICDOptions);
|
|
138
|
+
/**
|
|
139
|
+
* Code clinical text to ICD-10-CM diagnoses.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const result = await autoicd.code("Patient has type 2 diabetes");
|
|
144
|
+
* for (const entity of result.entities) {
|
|
145
|
+
* console.log(entity.entity_text, entity.codes[0]?.code);
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
code(text: string, options?: CodeOptions): Promise<CodingResponse>;
|
|
150
|
+
/**
|
|
151
|
+
* Anonymize PHI/PII in clinical text.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const result = await autoicd.anonymize("John Smith, DOB 01/15/1980, has COPD");
|
|
156
|
+
* console.log(result.anonymized_text);
|
|
157
|
+
* // "[NAME], DOB [DATE], has COPD"
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
anonymize(text: string): Promise<AnonymizeResponse>;
|
|
161
|
+
/** @internal */
|
|
162
|
+
get<T>(path: string): Promise<T>;
|
|
163
|
+
/** @internal */
|
|
164
|
+
post<T>(path: string, body: Record<string, unknown>): Promise<T>;
|
|
165
|
+
private request;
|
|
166
|
+
}
|
|
167
|
+
declare class Codes {
|
|
168
|
+
private readonly client;
|
|
169
|
+
constructor(client: AutoICD);
|
|
170
|
+
/**
|
|
171
|
+
* Search ICD-10 codes by description.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* const results = await autoicd.codes.search("diabetes mellitus");
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
search(query: string, options?: SearchOptions): Promise<CodeSearchResponse>;
|
|
179
|
+
/**
|
|
180
|
+
* Get full details for a single ICD-10 code.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const detail = await autoicd.codes.get("E11.9");
|
|
185
|
+
* console.log(detail.long_description);
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
get(code: string): Promise<CodeDetail>;
|
|
189
|
+
/**
|
|
190
|
+
* Get all indexed terms/synonyms for an ICD-10 code.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const terms = await autoicd.codes.terms("E11.9");
|
|
195
|
+
* terms.forEach(t => console.log(t.term, t.term_type));
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
terms(code: string): Promise<CodeTermInfo[]>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
declare class AutoICDError extends Error {
|
|
202
|
+
readonly status: number;
|
|
203
|
+
constructor(status: number, message: string);
|
|
204
|
+
}
|
|
205
|
+
declare class AuthenticationError extends AutoICDError {
|
|
206
|
+
constructor(message?: string);
|
|
207
|
+
}
|
|
208
|
+
declare class RateLimitError extends AutoICDError {
|
|
209
|
+
readonly rateLimit: RateLimit;
|
|
210
|
+
constructor(message: string, rateLimit: RateLimit);
|
|
211
|
+
}
|
|
212
|
+
declare class NotFoundError extends AutoICDError {
|
|
213
|
+
constructor(message?: string);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { type AnonymizeResponse, AuthenticationError, AutoICD, AutoICDError, type AutoICDOptions, type CodeDetail, type CodeMatch, type CodeOptions, type CodeSearchResponse, type CodeTermInfo, type CodingEntity, type CodingResponse, NotFoundError, type PIIEntity, type RateLimit, RateLimitError, type SearchOptions };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
interface AutoICDOptions {
|
|
2
|
+
/** API key (starts with `sk_`). */
|
|
3
|
+
apiKey: string;
|
|
4
|
+
/** Base URL. Defaults to `https://autoicdapi.com`. */
|
|
5
|
+
baseURL?: string;
|
|
6
|
+
/** Default timeout in milliseconds. Defaults to 30_000. */
|
|
7
|
+
timeout?: number;
|
|
8
|
+
/** Custom fetch implementation (for testing or non-standard runtimes). */
|
|
9
|
+
fetch?: typeof globalThis.fetch;
|
|
10
|
+
}
|
|
11
|
+
interface CodeOptions {
|
|
12
|
+
/** Number of top ICD-10 candidates per entity (1-25). Defaults to 5. */
|
|
13
|
+
topK?: number;
|
|
14
|
+
/** Include negated entities in results. Defaults to true. */
|
|
15
|
+
includeNegated?: boolean;
|
|
16
|
+
/** Matching strategy. Defaults to `"individual"`. */
|
|
17
|
+
strategy?: "individual" | "merged";
|
|
18
|
+
}
|
|
19
|
+
interface CodeMatch {
|
|
20
|
+
/** ICD-10-CM code (e.g., `"E11.21"`). */
|
|
21
|
+
code: string;
|
|
22
|
+
/** Official code description. */
|
|
23
|
+
description: string;
|
|
24
|
+
/** Cosine similarity score (0-1). */
|
|
25
|
+
similarity: number;
|
|
26
|
+
/** `"high"` if above high-confidence threshold, else `"moderate"`. */
|
|
27
|
+
confidence: "high" | "moderate";
|
|
28
|
+
/** The index term that produced this match. */
|
|
29
|
+
matched_term: string;
|
|
30
|
+
}
|
|
31
|
+
interface CodingEntity {
|
|
32
|
+
/** Entity text as extracted from the input. */
|
|
33
|
+
entity_text: string;
|
|
34
|
+
/** Character offset start in input text. */
|
|
35
|
+
entity_start: number;
|
|
36
|
+
/** Character offset end in input text. */
|
|
37
|
+
entity_end: number;
|
|
38
|
+
/** Whether this entity was negated in context. */
|
|
39
|
+
negated: boolean;
|
|
40
|
+
/** Entity refers to a past/resolved condition. */
|
|
41
|
+
historical: boolean;
|
|
42
|
+
/** Entity refers to a family member's condition. */
|
|
43
|
+
family_history: boolean;
|
|
44
|
+
/** Entity is hedged or uncertain. */
|
|
45
|
+
uncertain: boolean;
|
|
46
|
+
/** Severity modifier if detected (e.g., `"severe"`). */
|
|
47
|
+
severity: string | null;
|
|
48
|
+
/** Ranked ICD-10 code candidates. */
|
|
49
|
+
codes: CodeMatch[];
|
|
50
|
+
/** Source entity texts if this result was created by merging consecutive entities. */
|
|
51
|
+
merged_from?: string[] | null;
|
|
52
|
+
/** Original text before spell correction (null if no correction). */
|
|
53
|
+
corrected_from?: string | null;
|
|
54
|
+
}
|
|
55
|
+
interface CodingResponse {
|
|
56
|
+
/** The input text that was processed. */
|
|
57
|
+
text: string;
|
|
58
|
+
/** Coding provider used (e.g., `"sapbert"`). */
|
|
59
|
+
provider: string;
|
|
60
|
+
/** Matching strategy used. */
|
|
61
|
+
strategy: string;
|
|
62
|
+
/** Total number of entities in results. */
|
|
63
|
+
entity_count: number;
|
|
64
|
+
/** Coding results per entity, sorted by position in text. */
|
|
65
|
+
entities: CodingEntity[];
|
|
66
|
+
}
|
|
67
|
+
interface SearchOptions {
|
|
68
|
+
/** Maximum number of results (1-100). Defaults to 20. */
|
|
69
|
+
limit?: number;
|
|
70
|
+
/** Number of results to skip (for pagination). Defaults to 0. */
|
|
71
|
+
offset?: number;
|
|
72
|
+
}
|
|
73
|
+
interface CodeDetail {
|
|
74
|
+
/** ICD-10-CM code. */
|
|
75
|
+
code: string;
|
|
76
|
+
/** Abbreviated description. */
|
|
77
|
+
short_description: string;
|
|
78
|
+
/** Full official description. */
|
|
79
|
+
long_description: string;
|
|
80
|
+
/** Whether this is a billable (leaf) code. */
|
|
81
|
+
is_billable: boolean;
|
|
82
|
+
}
|
|
83
|
+
interface CodeSearchResponse {
|
|
84
|
+
/** The search query that was used. */
|
|
85
|
+
query: string;
|
|
86
|
+
/** Number of results returned. */
|
|
87
|
+
count: number;
|
|
88
|
+
/** Matching ICD-10 codes. */
|
|
89
|
+
codes: CodeDetail[];
|
|
90
|
+
}
|
|
91
|
+
interface CodeTermInfo {
|
|
92
|
+
/** The term text that was embedded. */
|
|
93
|
+
term: string;
|
|
94
|
+
/** Origin of this term (e.g., `"long_desc"`, `"synonym"`). */
|
|
95
|
+
term_type: string;
|
|
96
|
+
}
|
|
97
|
+
interface PIIEntity {
|
|
98
|
+
/** Original PII text. */
|
|
99
|
+
text: string;
|
|
100
|
+
/** Character offset start. */
|
|
101
|
+
start: number;
|
|
102
|
+
/** Character offset end. */
|
|
103
|
+
end: number;
|
|
104
|
+
/** PII category (NAME, DATE, SSN, PHONE, EMAIL, ADDRESS, MRN, AGE). */
|
|
105
|
+
label: string;
|
|
106
|
+
/** Replacement token (e.g., `"[NAME]"`). */
|
|
107
|
+
replacement: string;
|
|
108
|
+
}
|
|
109
|
+
interface AnonymizeResponse {
|
|
110
|
+
/** The input text that was processed. */
|
|
111
|
+
original_text: string;
|
|
112
|
+
/** Text with PII replaced by type labels. */
|
|
113
|
+
anonymized_text: string;
|
|
114
|
+
/** Number of PII entities detected. */
|
|
115
|
+
pii_count: number;
|
|
116
|
+
/** Detected PII spans with original offsets. */
|
|
117
|
+
pii_entities: PIIEntity[];
|
|
118
|
+
}
|
|
119
|
+
interface RateLimit {
|
|
120
|
+
/** Total requests allowed in the current period. */
|
|
121
|
+
limit: number;
|
|
122
|
+
/** Requests remaining. */
|
|
123
|
+
remaining: number;
|
|
124
|
+
/** UTC timestamp when the limit resets. */
|
|
125
|
+
resetAt: Date;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
declare class AutoICD {
|
|
129
|
+
private readonly apiKey;
|
|
130
|
+
private readonly baseURL;
|
|
131
|
+
private readonly timeout;
|
|
132
|
+
private readonly _fetch;
|
|
133
|
+
/** Rate limit info from the most recent API response. */
|
|
134
|
+
lastRateLimit: RateLimit | null;
|
|
135
|
+
/** Sub-resource for ICD-10 code lookup. */
|
|
136
|
+
readonly codes: Codes;
|
|
137
|
+
constructor(options: AutoICDOptions);
|
|
138
|
+
/**
|
|
139
|
+
* Code clinical text to ICD-10-CM diagnoses.
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* const result = await autoicd.code("Patient has type 2 diabetes");
|
|
144
|
+
* for (const entity of result.entities) {
|
|
145
|
+
* console.log(entity.entity_text, entity.codes[0]?.code);
|
|
146
|
+
* }
|
|
147
|
+
* ```
|
|
148
|
+
*/
|
|
149
|
+
code(text: string, options?: CodeOptions): Promise<CodingResponse>;
|
|
150
|
+
/**
|
|
151
|
+
* Anonymize PHI/PII in clinical text.
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* ```ts
|
|
155
|
+
* const result = await autoicd.anonymize("John Smith, DOB 01/15/1980, has COPD");
|
|
156
|
+
* console.log(result.anonymized_text);
|
|
157
|
+
* // "[NAME], DOB [DATE], has COPD"
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
anonymize(text: string): Promise<AnonymizeResponse>;
|
|
161
|
+
/** @internal */
|
|
162
|
+
get<T>(path: string): Promise<T>;
|
|
163
|
+
/** @internal */
|
|
164
|
+
post<T>(path: string, body: Record<string, unknown>): Promise<T>;
|
|
165
|
+
private request;
|
|
166
|
+
}
|
|
167
|
+
declare class Codes {
|
|
168
|
+
private readonly client;
|
|
169
|
+
constructor(client: AutoICD);
|
|
170
|
+
/**
|
|
171
|
+
* Search ICD-10 codes by description.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```ts
|
|
175
|
+
* const results = await autoicd.codes.search("diabetes mellitus");
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
search(query: string, options?: SearchOptions): Promise<CodeSearchResponse>;
|
|
179
|
+
/**
|
|
180
|
+
* Get full details for a single ICD-10 code.
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```ts
|
|
184
|
+
* const detail = await autoicd.codes.get("E11.9");
|
|
185
|
+
* console.log(detail.long_description);
|
|
186
|
+
* ```
|
|
187
|
+
*/
|
|
188
|
+
get(code: string): Promise<CodeDetail>;
|
|
189
|
+
/**
|
|
190
|
+
* Get all indexed terms/synonyms for an ICD-10 code.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const terms = await autoicd.codes.terms("E11.9");
|
|
195
|
+
* terms.forEach(t => console.log(t.term, t.term_type));
|
|
196
|
+
* ```
|
|
197
|
+
*/
|
|
198
|
+
terms(code: string): Promise<CodeTermInfo[]>;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
declare class AutoICDError extends Error {
|
|
202
|
+
readonly status: number;
|
|
203
|
+
constructor(status: number, message: string);
|
|
204
|
+
}
|
|
205
|
+
declare class AuthenticationError extends AutoICDError {
|
|
206
|
+
constructor(message?: string);
|
|
207
|
+
}
|
|
208
|
+
declare class RateLimitError extends AutoICDError {
|
|
209
|
+
readonly rateLimit: RateLimit;
|
|
210
|
+
constructor(message: string, rateLimit: RateLimit);
|
|
211
|
+
}
|
|
212
|
+
declare class NotFoundError extends AutoICDError {
|
|
213
|
+
constructor(message?: string);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
export { type AnonymizeResponse, AuthenticationError, AutoICD, AutoICDError, type AutoICDOptions, type CodeDetail, type CodeMatch, type CodeOptions, type CodeSearchResponse, type CodeTermInfo, type CodingEntity, type CodingResponse, NotFoundError, type PIIEntity, type RateLimit, RateLimitError, type SearchOptions };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthenticationError: () => AuthenticationError,
|
|
24
|
+
AutoICD: () => AutoICD,
|
|
25
|
+
AutoICDError: () => AutoICDError,
|
|
26
|
+
NotFoundError: () => NotFoundError,
|
|
27
|
+
RateLimitError: () => RateLimitError
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/errors.ts
|
|
32
|
+
var AutoICDError = class extends Error {
|
|
33
|
+
status;
|
|
34
|
+
constructor(status, message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = "AutoICDError";
|
|
37
|
+
this.status = status;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
var AuthenticationError = class extends AutoICDError {
|
|
41
|
+
constructor(message = "Invalid API key") {
|
|
42
|
+
super(401, message);
|
|
43
|
+
this.name = "AuthenticationError";
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
var RateLimitError = class extends AutoICDError {
|
|
47
|
+
rateLimit;
|
|
48
|
+
constructor(message, rateLimit) {
|
|
49
|
+
super(429, message);
|
|
50
|
+
this.name = "RateLimitError";
|
|
51
|
+
this.rateLimit = rateLimit;
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var NotFoundError = class extends AutoICDError {
|
|
55
|
+
constructor(message = "Resource not found") {
|
|
56
|
+
super(404, message);
|
|
57
|
+
this.name = "NotFoundError";
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// src/client.ts
|
|
62
|
+
var DEFAULT_BASE_URL = "https://autoicdapi.com";
|
|
63
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
64
|
+
var AutoICD = class {
|
|
65
|
+
apiKey;
|
|
66
|
+
baseURL;
|
|
67
|
+
timeout;
|
|
68
|
+
_fetch;
|
|
69
|
+
/** Rate limit info from the most recent API response. */
|
|
70
|
+
lastRateLimit = null;
|
|
71
|
+
/** Sub-resource for ICD-10 code lookup. */
|
|
72
|
+
codes;
|
|
73
|
+
constructor(options) {
|
|
74
|
+
if (!options.apiKey) {
|
|
75
|
+
throw new Error("apiKey is required");
|
|
76
|
+
}
|
|
77
|
+
this.apiKey = options.apiKey;
|
|
78
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
79
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
80
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
81
|
+
this.codes = new Codes(this);
|
|
82
|
+
}
|
|
83
|
+
// ─── Public Methods ───
|
|
84
|
+
/**
|
|
85
|
+
* Code clinical text to ICD-10-CM diagnoses.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* const result = await autoicd.code("Patient has type 2 diabetes");
|
|
90
|
+
* for (const entity of result.entities) {
|
|
91
|
+
* console.log(entity.entity_text, entity.codes[0]?.code);
|
|
92
|
+
* }
|
|
93
|
+
* ```
|
|
94
|
+
*/
|
|
95
|
+
async code(text, options) {
|
|
96
|
+
return this.post("/api/v1/code", {
|
|
97
|
+
text,
|
|
98
|
+
top_k: options?.topK,
|
|
99
|
+
include_negated: options?.includeNegated,
|
|
100
|
+
strategy: options?.strategy
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Anonymize PHI/PII in clinical text.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* const result = await autoicd.anonymize("John Smith, DOB 01/15/1980, has COPD");
|
|
109
|
+
* console.log(result.anonymized_text);
|
|
110
|
+
* // "[NAME], DOB [DATE], has COPD"
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
async anonymize(text) {
|
|
114
|
+
return this.post("/api/v1/anonymize", { text });
|
|
115
|
+
}
|
|
116
|
+
// ─── Internal HTTP ───
|
|
117
|
+
/** @internal */
|
|
118
|
+
async get(path) {
|
|
119
|
+
return this.request("GET", path);
|
|
120
|
+
}
|
|
121
|
+
/** @internal */
|
|
122
|
+
async post(path, body) {
|
|
123
|
+
const clean = Object.fromEntries(
|
|
124
|
+
Object.entries(body).filter(([, v]) => v !== void 0)
|
|
125
|
+
);
|
|
126
|
+
return this.request("POST", path, clean);
|
|
127
|
+
}
|
|
128
|
+
async request(method, path, body) {
|
|
129
|
+
const url = `${this.baseURL}${path}`;
|
|
130
|
+
const controller = new AbortController();
|
|
131
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
132
|
+
try {
|
|
133
|
+
const res = await this._fetch(url, {
|
|
134
|
+
method,
|
|
135
|
+
headers: {
|
|
136
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
137
|
+
"Content-Type": "application/json",
|
|
138
|
+
Accept: "application/json"
|
|
139
|
+
},
|
|
140
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
141
|
+
signal: controller.signal
|
|
142
|
+
});
|
|
143
|
+
this.lastRateLimit = parseRateLimit(res.headers);
|
|
144
|
+
if (res.ok) {
|
|
145
|
+
return await res.json();
|
|
146
|
+
}
|
|
147
|
+
const errorBody = await res.json().catch(() => null);
|
|
148
|
+
const message = errorBody?.error ?? `HTTP ${res.status}`;
|
|
149
|
+
if (res.status === 401) throw new AuthenticationError(message);
|
|
150
|
+
if (res.status === 404) throw new NotFoundError(message);
|
|
151
|
+
if (res.status === 429) {
|
|
152
|
+
throw new RateLimitError(
|
|
153
|
+
message,
|
|
154
|
+
this.lastRateLimit ?? {
|
|
155
|
+
limit: errorBody?.limit ?? 0,
|
|
156
|
+
remaining: 0,
|
|
157
|
+
resetAt: errorBody?.resetAt ? new Date(errorBody.resetAt) : /* @__PURE__ */ new Date()
|
|
158
|
+
}
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
throw new AutoICDError(res.status, message);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
if (err instanceof AutoICDError) throw err;
|
|
164
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
165
|
+
throw new AutoICDError(0, `Request timed out after ${this.timeout}ms`);
|
|
166
|
+
}
|
|
167
|
+
throw err;
|
|
168
|
+
} finally {
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
var Codes = class {
|
|
174
|
+
constructor(client) {
|
|
175
|
+
this.client = client;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Search ICD-10 codes by description.
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* ```ts
|
|
182
|
+
* const results = await autoicd.codes.search("diabetes mellitus");
|
|
183
|
+
* ```
|
|
184
|
+
*/
|
|
185
|
+
async search(query, options) {
|
|
186
|
+
const params = new URLSearchParams({ q: query });
|
|
187
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
188
|
+
if (options?.offset !== void 0) params.set("offset", String(options.offset));
|
|
189
|
+
return this.client.get(`/api/v1/codes/search?${params}`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Get full details for a single ICD-10 code.
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* const detail = await autoicd.codes.get("E11.9");
|
|
197
|
+
* console.log(detail.long_description);
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
async get(code) {
|
|
201
|
+
return this.client.get(`/api/v1/codes/${encodeURIComponent(code)}`);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Get all indexed terms/synonyms for an ICD-10 code.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```ts
|
|
208
|
+
* const terms = await autoicd.codes.terms("E11.9");
|
|
209
|
+
* terms.forEach(t => console.log(t.term, t.term_type));
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
async terms(code) {
|
|
213
|
+
return this.client.get(
|
|
214
|
+
`/api/v1/codes/${encodeURIComponent(code)}/terms`
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
};
|
|
218
|
+
function parseRateLimit(headers) {
|
|
219
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
220
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
221
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
222
|
+
if (!limit || !remaining || !reset) return null;
|
|
223
|
+
return {
|
|
224
|
+
limit: parseInt(limit, 10),
|
|
225
|
+
remaining: parseInt(remaining, 10),
|
|
226
|
+
resetAt: new Date(reset)
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
230
|
+
0 && (module.exports = {
|
|
231
|
+
AuthenticationError,
|
|
232
|
+
AutoICD,
|
|
233
|
+
AutoICDError,
|
|
234
|
+
NotFoundError,
|
|
235
|
+
RateLimitError
|
|
236
|
+
});
|
|
237
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/client.ts"],"sourcesContent":["export { AutoICD } from \"./client.js\";\n\nexport type {\n AutoICDOptions,\n CodeOptions,\n CodeMatch,\n CodingEntity,\n CodingResponse,\n SearchOptions,\n CodeDetail,\n CodeSearchResponse,\n CodeTermInfo,\n PIIEntity,\n AnonymizeResponse,\n RateLimit,\n} from \"./types.js\";\n\nexport {\n AutoICDError,\n AuthenticationError,\n RateLimitError,\n NotFoundError,\n} from \"./errors.js\";\n","import type { RateLimit } from \"./types.js\";\n\nexport class AutoICDError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"AutoICDError\";\n this.status = status;\n }\n}\n\nexport class AuthenticationError extends AutoICDError {\n constructor(message = \"Invalid API key\") {\n super(401, message);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends AutoICDError {\n readonly rateLimit: RateLimit;\n\n constructor(message: string, rateLimit: RateLimit) {\n super(429, message);\n this.name = \"RateLimitError\";\n this.rateLimit = rateLimit;\n }\n}\n\nexport class NotFoundError extends AutoICDError {\n constructor(message = \"Resource not found\") {\n super(404, message);\n this.name = \"NotFoundError\";\n }\n}\n","import type {\n AutoICDOptions,\n CodeOptions,\n CodingResponse,\n SearchOptions,\n CodeSearchResponse,\n CodeDetail,\n CodeTermInfo,\n AnonymizeResponse,\n RateLimit,\n ErrorBody,\n} from \"./types.js\";\nimport {\n AutoICDError,\n AuthenticationError,\n RateLimitError,\n NotFoundError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://autoicdapi.com\";\nconst DEFAULT_TIMEOUT = 30_000;\n\nexport class AutoICD {\n private readonly apiKey: string;\n private readonly baseURL: string;\n private readonly timeout: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n /** Rate limit info from the most recent API response. */\n lastRateLimit: RateLimit | null = null;\n\n /** Sub-resource for ICD-10 code lookup. */\n readonly codes: Codes;\n\n constructor(options: AutoICDOptions) {\n if (!options.apiKey) {\n throw new Error(\"apiKey is required\");\n }\n this.apiKey = options.apiKey;\n this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this._fetch = options.fetch ?? globalThis.fetch;\n this.codes = new Codes(this);\n }\n\n // ─── Public Methods ───\n\n /**\n * Code clinical text to ICD-10-CM diagnoses.\n *\n * @example\n * ```ts\n * const result = await autoicd.code(\"Patient has type 2 diabetes\");\n * for (const entity of result.entities) {\n * console.log(entity.entity_text, entity.codes[0]?.code);\n * }\n * ```\n */\n async code(text: string, options?: CodeOptions): Promise<CodingResponse> {\n return this.post<CodingResponse>(\"/api/v1/code\", {\n text,\n top_k: options?.topK,\n include_negated: options?.includeNegated,\n strategy: options?.strategy,\n });\n }\n\n /**\n * Anonymize PHI/PII in clinical text.\n *\n * @example\n * ```ts\n * const result = await autoicd.anonymize(\"John Smith, DOB 01/15/1980, has COPD\");\n * console.log(result.anonymized_text);\n * // \"[NAME], DOB [DATE], has COPD\"\n * ```\n */\n async anonymize(text: string): Promise<AnonymizeResponse> {\n return this.post<AnonymizeResponse>(\"/api/v1/anonymize\", { text });\n }\n\n // ─── Internal HTTP ───\n\n /** @internal */\n async get<T>(path: string): Promise<T> {\n return this.request<T>(\"GET\", path);\n }\n\n /** @internal */\n async post<T>(path: string, body: Record<string, unknown>): Promise<T> {\n // Strip undefined values so the API receives clean JSON\n const clean = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined)\n );\n return this.request<T>(\"POST\", path, clean);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: Record<string, unknown>\n ): Promise<T> {\n const url = `${this.baseURL}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await this._fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n // Parse rate limit headers\n this.lastRateLimit = parseRateLimit(res.headers);\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n // Error responses\n const errorBody = (await res.json().catch(() => null)) as ErrorBody | null;\n const message = errorBody?.error ?? `HTTP ${res.status}`;\n\n if (res.status === 401) throw new AuthenticationError(message);\n if (res.status === 404) throw new NotFoundError(message);\n if (res.status === 429) {\n throw new RateLimitError(\n message,\n this.lastRateLimit ?? {\n limit: errorBody?.limit ?? 0,\n remaining: 0,\n resetAt: errorBody?.resetAt ? new Date(errorBody.resetAt) : new Date(),\n }\n );\n }\n\n throw new AutoICDError(res.status, message);\n } catch (err) {\n if (err instanceof AutoICDError) throw err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new AutoICDError(0, `Request timed out after ${this.timeout}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n// ─── Codes Sub-resource ───\n\nclass Codes {\n constructor(private readonly client: AutoICD) {}\n\n /**\n * Search ICD-10 codes by description.\n *\n * @example\n * ```ts\n * const results = await autoicd.codes.search(\"diabetes mellitus\");\n * ```\n */\n async search(query: string, options?: SearchOptions): Promise<CodeSearchResponse> {\n const params = new URLSearchParams({ q: query });\n if (options?.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options?.offset !== undefined) params.set(\"offset\", String(options.offset));\n return this.client.get<CodeSearchResponse>(`/api/v1/codes/search?${params}`);\n }\n\n /**\n * Get full details for a single ICD-10 code.\n *\n * @example\n * ```ts\n * const detail = await autoicd.codes.get(\"E11.9\");\n * console.log(detail.long_description);\n * ```\n */\n async get(code: string): Promise<CodeDetail> {\n return this.client.get<CodeDetail>(`/api/v1/codes/${encodeURIComponent(code)}`);\n }\n\n /**\n * Get all indexed terms/synonyms for an ICD-10 code.\n *\n * @example\n * ```ts\n * const terms = await autoicd.codes.terms(\"E11.9\");\n * terms.forEach(t => console.log(t.term, t.term_type));\n * ```\n */\n async terms(code: string): Promise<CodeTermInfo[]> {\n return this.client.get<CodeTermInfo[]>(\n `/api/v1/codes/${encodeURIComponent(code)}/terms`\n );\n }\n}\n\n// ─── Helpers ───\n\nfunction parseRateLimit(headers: Headers): RateLimit | null {\n const limit = headers.get(\"X-RateLimit-Limit\");\n const remaining = headers.get(\"X-RateLimit-Remaining\");\n const reset = headers.get(\"X-RateLimit-Reset\");\n\n if (!limit || !remaining || !reset) return null;\n\n return {\n limit: parseInt(limit, 10),\n remaining: parseInt(remaining, 10),\n resetAt: new Date(reset),\n };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var AutoICDError = class extends Error {
|
|
3
|
+
status;
|
|
4
|
+
constructor(status, message) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "AutoICDError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
var AuthenticationError = class extends AutoICDError {
|
|
11
|
+
constructor(message = "Invalid API key") {
|
|
12
|
+
super(401, message);
|
|
13
|
+
this.name = "AuthenticationError";
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
var RateLimitError = class extends AutoICDError {
|
|
17
|
+
rateLimit;
|
|
18
|
+
constructor(message, rateLimit) {
|
|
19
|
+
super(429, message);
|
|
20
|
+
this.name = "RateLimitError";
|
|
21
|
+
this.rateLimit = rateLimit;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
var NotFoundError = class extends AutoICDError {
|
|
25
|
+
constructor(message = "Resource not found") {
|
|
26
|
+
super(404, message);
|
|
27
|
+
this.name = "NotFoundError";
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// src/client.ts
|
|
32
|
+
var DEFAULT_BASE_URL = "https://autoicdapi.com";
|
|
33
|
+
var DEFAULT_TIMEOUT = 3e4;
|
|
34
|
+
var AutoICD = class {
|
|
35
|
+
apiKey;
|
|
36
|
+
baseURL;
|
|
37
|
+
timeout;
|
|
38
|
+
_fetch;
|
|
39
|
+
/** Rate limit info from the most recent API response. */
|
|
40
|
+
lastRateLimit = null;
|
|
41
|
+
/** Sub-resource for ICD-10 code lookup. */
|
|
42
|
+
codes;
|
|
43
|
+
constructor(options) {
|
|
44
|
+
if (!options.apiKey) {
|
|
45
|
+
throw new Error("apiKey is required");
|
|
46
|
+
}
|
|
47
|
+
this.apiKey = options.apiKey;
|
|
48
|
+
this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
49
|
+
this.timeout = options.timeout ?? DEFAULT_TIMEOUT;
|
|
50
|
+
this._fetch = options.fetch ?? globalThis.fetch;
|
|
51
|
+
this.codes = new Codes(this);
|
|
52
|
+
}
|
|
53
|
+
// ─── Public Methods ───
|
|
54
|
+
/**
|
|
55
|
+
* Code clinical text to ICD-10-CM diagnoses.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```ts
|
|
59
|
+
* const result = await autoicd.code("Patient has type 2 diabetes");
|
|
60
|
+
* for (const entity of result.entities) {
|
|
61
|
+
* console.log(entity.entity_text, entity.codes[0]?.code);
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
async code(text, options) {
|
|
66
|
+
return this.post("/api/v1/code", {
|
|
67
|
+
text,
|
|
68
|
+
top_k: options?.topK,
|
|
69
|
+
include_negated: options?.includeNegated,
|
|
70
|
+
strategy: options?.strategy
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Anonymize PHI/PII in clinical text.
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```ts
|
|
78
|
+
* const result = await autoicd.anonymize("John Smith, DOB 01/15/1980, has COPD");
|
|
79
|
+
* console.log(result.anonymized_text);
|
|
80
|
+
* // "[NAME], DOB [DATE], has COPD"
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
async anonymize(text) {
|
|
84
|
+
return this.post("/api/v1/anonymize", { text });
|
|
85
|
+
}
|
|
86
|
+
// ─── Internal HTTP ───
|
|
87
|
+
/** @internal */
|
|
88
|
+
async get(path) {
|
|
89
|
+
return this.request("GET", path);
|
|
90
|
+
}
|
|
91
|
+
/** @internal */
|
|
92
|
+
async post(path, body) {
|
|
93
|
+
const clean = Object.fromEntries(
|
|
94
|
+
Object.entries(body).filter(([, v]) => v !== void 0)
|
|
95
|
+
);
|
|
96
|
+
return this.request("POST", path, clean);
|
|
97
|
+
}
|
|
98
|
+
async request(method, path, body) {
|
|
99
|
+
const url = `${this.baseURL}${path}`;
|
|
100
|
+
const controller = new AbortController();
|
|
101
|
+
const timer = setTimeout(() => controller.abort(), this.timeout);
|
|
102
|
+
try {
|
|
103
|
+
const res = await this._fetch(url, {
|
|
104
|
+
method,
|
|
105
|
+
headers: {
|
|
106
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
107
|
+
"Content-Type": "application/json",
|
|
108
|
+
Accept: "application/json"
|
|
109
|
+
},
|
|
110
|
+
body: body ? JSON.stringify(body) : void 0,
|
|
111
|
+
signal: controller.signal
|
|
112
|
+
});
|
|
113
|
+
this.lastRateLimit = parseRateLimit(res.headers);
|
|
114
|
+
if (res.ok) {
|
|
115
|
+
return await res.json();
|
|
116
|
+
}
|
|
117
|
+
const errorBody = await res.json().catch(() => null);
|
|
118
|
+
const message = errorBody?.error ?? `HTTP ${res.status}`;
|
|
119
|
+
if (res.status === 401) throw new AuthenticationError(message);
|
|
120
|
+
if (res.status === 404) throw new NotFoundError(message);
|
|
121
|
+
if (res.status === 429) {
|
|
122
|
+
throw new RateLimitError(
|
|
123
|
+
message,
|
|
124
|
+
this.lastRateLimit ?? {
|
|
125
|
+
limit: errorBody?.limit ?? 0,
|
|
126
|
+
remaining: 0,
|
|
127
|
+
resetAt: errorBody?.resetAt ? new Date(errorBody.resetAt) : /* @__PURE__ */ new Date()
|
|
128
|
+
}
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
throw new AutoICDError(res.status, message);
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err instanceof AutoICDError) throw err;
|
|
134
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
135
|
+
throw new AutoICDError(0, `Request timed out after ${this.timeout}ms`);
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
} finally {
|
|
139
|
+
clearTimeout(timer);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
var Codes = class {
|
|
144
|
+
constructor(client) {
|
|
145
|
+
this.client = client;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Search ICD-10 codes by description.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* ```ts
|
|
152
|
+
* const results = await autoicd.codes.search("diabetes mellitus");
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
async search(query, options) {
|
|
156
|
+
const params = new URLSearchParams({ q: query });
|
|
157
|
+
if (options?.limit !== void 0) params.set("limit", String(options.limit));
|
|
158
|
+
if (options?.offset !== void 0) params.set("offset", String(options.offset));
|
|
159
|
+
return this.client.get(`/api/v1/codes/search?${params}`);
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Get full details for a single ICD-10 code.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* const detail = await autoicd.codes.get("E11.9");
|
|
167
|
+
* console.log(detail.long_description);
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
async get(code) {
|
|
171
|
+
return this.client.get(`/api/v1/codes/${encodeURIComponent(code)}`);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get all indexed terms/synonyms for an ICD-10 code.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* ```ts
|
|
178
|
+
* const terms = await autoicd.codes.terms("E11.9");
|
|
179
|
+
* terms.forEach(t => console.log(t.term, t.term_type));
|
|
180
|
+
* ```
|
|
181
|
+
*/
|
|
182
|
+
async terms(code) {
|
|
183
|
+
return this.client.get(
|
|
184
|
+
`/api/v1/codes/${encodeURIComponent(code)}/terms`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
function parseRateLimit(headers) {
|
|
189
|
+
const limit = headers.get("X-RateLimit-Limit");
|
|
190
|
+
const remaining = headers.get("X-RateLimit-Remaining");
|
|
191
|
+
const reset = headers.get("X-RateLimit-Reset");
|
|
192
|
+
if (!limit || !remaining || !reset) return null;
|
|
193
|
+
return {
|
|
194
|
+
limit: parseInt(limit, 10),
|
|
195
|
+
remaining: parseInt(remaining, 10),
|
|
196
|
+
resetAt: new Date(reset)
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
export {
|
|
200
|
+
AuthenticationError,
|
|
201
|
+
AutoICD,
|
|
202
|
+
AutoICDError,
|
|
203
|
+
NotFoundError,
|
|
204
|
+
RateLimitError
|
|
205
|
+
};
|
|
206
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/client.ts"],"sourcesContent":["import type { RateLimit } from \"./types.js\";\n\nexport class AutoICDError extends Error {\n readonly status: number;\n\n constructor(status: number, message: string) {\n super(message);\n this.name = \"AutoICDError\";\n this.status = status;\n }\n}\n\nexport class AuthenticationError extends AutoICDError {\n constructor(message = \"Invalid API key\") {\n super(401, message);\n this.name = \"AuthenticationError\";\n }\n}\n\nexport class RateLimitError extends AutoICDError {\n readonly rateLimit: RateLimit;\n\n constructor(message: string, rateLimit: RateLimit) {\n super(429, message);\n this.name = \"RateLimitError\";\n this.rateLimit = rateLimit;\n }\n}\n\nexport class NotFoundError extends AutoICDError {\n constructor(message = \"Resource not found\") {\n super(404, message);\n this.name = \"NotFoundError\";\n }\n}\n","import type {\n AutoICDOptions,\n CodeOptions,\n CodingResponse,\n SearchOptions,\n CodeSearchResponse,\n CodeDetail,\n CodeTermInfo,\n AnonymizeResponse,\n RateLimit,\n ErrorBody,\n} from \"./types.js\";\nimport {\n AutoICDError,\n AuthenticationError,\n RateLimitError,\n NotFoundError,\n} from \"./errors.js\";\n\nconst DEFAULT_BASE_URL = \"https://autoicdapi.com\";\nconst DEFAULT_TIMEOUT = 30_000;\n\nexport class AutoICD {\n private readonly apiKey: string;\n private readonly baseURL: string;\n private readonly timeout: number;\n private readonly _fetch: typeof globalThis.fetch;\n\n /** Rate limit info from the most recent API response. */\n lastRateLimit: RateLimit | null = null;\n\n /** Sub-resource for ICD-10 code lookup. */\n readonly codes: Codes;\n\n constructor(options: AutoICDOptions) {\n if (!options.apiKey) {\n throw new Error(\"apiKey is required\");\n }\n this.apiKey = options.apiKey;\n this.baseURL = (options.baseURL ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n this.timeout = options.timeout ?? DEFAULT_TIMEOUT;\n this._fetch = options.fetch ?? globalThis.fetch;\n this.codes = new Codes(this);\n }\n\n // ─── Public Methods ───\n\n /**\n * Code clinical text to ICD-10-CM diagnoses.\n *\n * @example\n * ```ts\n * const result = await autoicd.code(\"Patient has type 2 diabetes\");\n * for (const entity of result.entities) {\n * console.log(entity.entity_text, entity.codes[0]?.code);\n * }\n * ```\n */\n async code(text: string, options?: CodeOptions): Promise<CodingResponse> {\n return this.post<CodingResponse>(\"/api/v1/code\", {\n text,\n top_k: options?.topK,\n include_negated: options?.includeNegated,\n strategy: options?.strategy,\n });\n }\n\n /**\n * Anonymize PHI/PII in clinical text.\n *\n * @example\n * ```ts\n * const result = await autoicd.anonymize(\"John Smith, DOB 01/15/1980, has COPD\");\n * console.log(result.anonymized_text);\n * // \"[NAME], DOB [DATE], has COPD\"\n * ```\n */\n async anonymize(text: string): Promise<AnonymizeResponse> {\n return this.post<AnonymizeResponse>(\"/api/v1/anonymize\", { text });\n }\n\n // ─── Internal HTTP ───\n\n /** @internal */\n async get<T>(path: string): Promise<T> {\n return this.request<T>(\"GET\", path);\n }\n\n /** @internal */\n async post<T>(path: string, body: Record<string, unknown>): Promise<T> {\n // Strip undefined values so the API receives clean JSON\n const clean = Object.fromEntries(\n Object.entries(body).filter(([, v]) => v !== undefined)\n );\n return this.request<T>(\"POST\", path, clean);\n }\n\n private async request<T>(\n method: string,\n path: string,\n body?: Record<string, unknown>\n ): Promise<T> {\n const url = `${this.baseURL}${path}`;\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeout);\n\n try {\n const res = await this._fetch(url, {\n method,\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: body ? JSON.stringify(body) : undefined,\n signal: controller.signal,\n });\n\n // Parse rate limit headers\n this.lastRateLimit = parseRateLimit(res.headers);\n\n if (res.ok) {\n return (await res.json()) as T;\n }\n\n // Error responses\n const errorBody = (await res.json().catch(() => null)) as ErrorBody | null;\n const message = errorBody?.error ?? `HTTP ${res.status}`;\n\n if (res.status === 401) throw new AuthenticationError(message);\n if (res.status === 404) throw new NotFoundError(message);\n if (res.status === 429) {\n throw new RateLimitError(\n message,\n this.lastRateLimit ?? {\n limit: errorBody?.limit ?? 0,\n remaining: 0,\n resetAt: errorBody?.resetAt ? new Date(errorBody.resetAt) : new Date(),\n }\n );\n }\n\n throw new AutoICDError(res.status, message);\n } catch (err) {\n if (err instanceof AutoICDError) throw err;\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new AutoICDError(0, `Request timed out after ${this.timeout}ms`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n }\n}\n\n// ─── Codes Sub-resource ───\n\nclass Codes {\n constructor(private readonly client: AutoICD) {}\n\n /**\n * Search ICD-10 codes by description.\n *\n * @example\n * ```ts\n * const results = await autoicd.codes.search(\"diabetes mellitus\");\n * ```\n */\n async search(query: string, options?: SearchOptions): Promise<CodeSearchResponse> {\n const params = new URLSearchParams({ q: query });\n if (options?.limit !== undefined) params.set(\"limit\", String(options.limit));\n if (options?.offset !== undefined) params.set(\"offset\", String(options.offset));\n return this.client.get<CodeSearchResponse>(`/api/v1/codes/search?${params}`);\n }\n\n /**\n * Get full details for a single ICD-10 code.\n *\n * @example\n * ```ts\n * const detail = await autoicd.codes.get(\"E11.9\");\n * console.log(detail.long_description);\n * ```\n */\n async get(code: string): Promise<CodeDetail> {\n return this.client.get<CodeDetail>(`/api/v1/codes/${encodeURIComponent(code)}`);\n }\n\n /**\n * Get all indexed terms/synonyms for an ICD-10 code.\n *\n * @example\n * ```ts\n * const terms = await autoicd.codes.terms(\"E11.9\");\n * terms.forEach(t => console.log(t.term, t.term_type));\n * ```\n */\n async terms(code: string): Promise<CodeTermInfo[]> {\n return this.client.get<CodeTermInfo[]>(\n `/api/v1/codes/${encodeURIComponent(code)}/terms`\n );\n }\n}\n\n// ─── Helpers ───\n\nfunction parseRateLimit(headers: Headers): RateLimit | null {\n const limit = headers.get(\"X-RateLimit-Limit\");\n const remaining = headers.get(\"X-RateLimit-Remaining\");\n const reset = headers.get(\"X-RateLimit-Reset\");\n\n if (!limit || !remaining || !reset) return null;\n\n return {\n limit: parseInt(limit, 10),\n remaining: parseInt(remaining, 10),\n resetAt: new Date(reset),\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "autoicd-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "ICD-10 medical coding SDK — convert clinical text to ICD-10-CM diagnosis codes with AI-powered NLP. Automated medical coding, PHI de-identification, and ICD-10 code search for EHR, billing, and health-tech apps.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"icd-10",
|
|
29
|
+
"icd10",
|
|
30
|
+
"icd-10-cm",
|
|
31
|
+
"medical-coding",
|
|
32
|
+
"medical-billing",
|
|
33
|
+
"clinical-nlp",
|
|
34
|
+
"clinical-text",
|
|
35
|
+
"diagnosis-codes",
|
|
36
|
+
"healthcare",
|
|
37
|
+
"health-tech",
|
|
38
|
+
"ehr",
|
|
39
|
+
"emr",
|
|
40
|
+
"nlp",
|
|
41
|
+
"phi-deidentification",
|
|
42
|
+
"hipaa",
|
|
43
|
+
"medical-ai",
|
|
44
|
+
"revenue-cycle-management",
|
|
45
|
+
"sapbert",
|
|
46
|
+
"autoicd",
|
|
47
|
+
"autoicd-api"
|
|
48
|
+
],
|
|
49
|
+
"author": "AutoICD",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"homepage": "https://autoicdapi.com",
|
|
52
|
+
"repository": {
|
|
53
|
+
"type": "git",
|
|
54
|
+
"url": "https://github.com/fcggamou/autoicd-js"
|
|
55
|
+
},
|
|
56
|
+
"bugs": {
|
|
57
|
+
"url": "https://github.com/fcggamou/autoicd-js/issues"
|
|
58
|
+
},
|
|
59
|
+
"funding": {
|
|
60
|
+
"type": "individual",
|
|
61
|
+
"url": "https://autoicdapi.com"
|
|
62
|
+
},
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=18"
|
|
65
|
+
},
|
|
66
|
+
"devDependencies": {
|
|
67
|
+
"tsup": "^8.0.0",
|
|
68
|
+
"typescript": "^5.5.0",
|
|
69
|
+
"vitest": "^2.0.0"
|
|
70
|
+
}
|
|
71
|
+
}
|