autoicd 0.2.0__py3-none-any.whl

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.
autoicd/__init__.py ADDED
@@ -0,0 +1,50 @@
1
+ """AutoICD API — Python SDK.
2
+
3
+ Official Python SDK for the AutoICD API: clinical text to ICD-10-CM diagnosis codes.
4
+ """
5
+
6
+ from .client import AutoICD
7
+ from .errors import (
8
+ AuthenticationError,
9
+ AutoICDError,
10
+ NotFoundError,
11
+ RateLimit,
12
+ RateLimitError,
13
+ )
14
+ from .types import (
15
+ AnonymizeResponse,
16
+ ChapterInfo,
17
+ CodeDetail,
18
+ CodeDetailFull,
19
+ CodeMatch,
20
+ CodeOptions,
21
+ CodeSearchResponse,
22
+ CodeTermInfo,
23
+ CodingEntity,
24
+ CodingResponse,
25
+ PIIEntity,
26
+ SearchOptions,
27
+ )
28
+
29
+ __all__ = [
30
+ "AutoICD",
31
+ # Errors
32
+ "AutoICDError",
33
+ "AuthenticationError",
34
+ "RateLimitError",
35
+ "NotFoundError",
36
+ # Types
37
+ "CodeOptions",
38
+ "CodeMatch",
39
+ "CodingEntity",
40
+ "CodingResponse",
41
+ "SearchOptions",
42
+ "CodeDetail",
43
+ "CodeDetailFull",
44
+ "ChapterInfo",
45
+ "CodeSearchResponse",
46
+ "CodeTermInfo",
47
+ "PIIEntity",
48
+ "AnonymizeResponse",
49
+ "RateLimit",
50
+ ]
autoicd/client.py ADDED
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+ from typing import Any
5
+ from urllib.parse import quote, urlencode
6
+
7
+ import httpx
8
+
9
+ from .errors import (
10
+ AuthenticationError,
11
+ AutoICDError,
12
+ NotFoundError,
13
+ RateLimit,
14
+ RateLimitError,
15
+ )
16
+ from .types import (
17
+ AnonymizeResponse,
18
+ ChapterInfo,
19
+ CodeDetail,
20
+ CodeDetailFull,
21
+ CodeMatch,
22
+ CodeOptions,
23
+ CodeSearchResponse,
24
+ CodeTermInfo,
25
+ CodingEntity,
26
+ CodingResponse,
27
+ PIIEntity,
28
+ SearchOptions,
29
+ )
30
+
31
+ _DEFAULT_BASE_URL = "https://autoicdapi.com"
32
+ _DEFAULT_TIMEOUT = 30.0
33
+
34
+
35
+ class Codes:
36
+ """Sub-resource for ICD-10-CM code lookups."""
37
+
38
+ def __init__(self, client: AutoICD) -> None:
39
+ self._client = client
40
+
41
+ def search(
42
+ self, query: str, options: SearchOptions | None = None
43
+ ) -> CodeSearchResponse:
44
+ """Search ICD-10-CM codes by description."""
45
+ params: dict[str, str] = {"q": query}
46
+ if options:
47
+ if options.limit is not None:
48
+ params["limit"] = str(options.limit)
49
+ if options.offset is not None:
50
+ params["offset"] = str(options.offset)
51
+ data = self._client._get(f"/api/v1/codes/search?{urlencode(params)}")
52
+ return CodeSearchResponse(
53
+ query=data["query"],
54
+ count=data["count"],
55
+ codes=[CodeDetail(**c) for c in data["codes"]],
56
+ )
57
+
58
+ def get(self, code: str) -> CodeDetailFull:
59
+ """Get full details for an ICD-10-CM code.
60
+
61
+ Returns comprehensive info including synonyms (SNOMED CT, UMLS),
62
+ hierarchy (parent/children), and chapter/block classification.
63
+ """
64
+ data = self._client._get(f"/api/v1/codes/{quote(code, safe='')}")
65
+ return _parse_code_detail_full(data)
66
+
67
+ def terms(self, code: str) -> list[CodeTermInfo]:
68
+ """Get indexed terms and synonyms for an ICD-10-CM code."""
69
+ data = self._client._get(f"/api/v1/codes/{quote(code, safe='')}/terms")
70
+ return [CodeTermInfo(**t) for t in data]
71
+
72
+
73
+ class AutoICD:
74
+ """Client for the AutoICD API.
75
+
76
+ Args:
77
+ api_key: Your API key (starts with ``sk_``).
78
+ base_url: API base URL (default ``https://autoicdapi.com``).
79
+ timeout: Request timeout in seconds (default 30).
80
+ http_client: Optional ``httpx.Client`` instance for custom configuration.
81
+ """
82
+
83
+ def __init__(
84
+ self,
85
+ *,
86
+ api_key: str,
87
+ base_url: str = _DEFAULT_BASE_URL,
88
+ timeout: float = _DEFAULT_TIMEOUT,
89
+ http_client: httpx.Client | None = None,
90
+ ) -> None:
91
+ if not api_key:
92
+ raise ValueError("api_key must be a non-empty string")
93
+
94
+ self._api_key = api_key
95
+ self._base_url = base_url.rstrip("/")
96
+ self._timeout = timeout
97
+ self._owns_client = http_client is None
98
+ self._http = http_client or httpx.Client(timeout=self._timeout)
99
+ self.codes = Codes(self)
100
+ self.last_rate_limit: RateLimit | None = None
101
+
102
+ def close(self) -> None:
103
+ """Close the underlying HTTP client (only if we created it)."""
104
+ if self._owns_client:
105
+ self._http.close()
106
+
107
+ def __enter__(self) -> AutoICD:
108
+ return self
109
+
110
+ def __exit__(self, *_: Any) -> None:
111
+ self.close()
112
+
113
+ # ── Public methods ──────────────────────────────────────────────
114
+
115
+ def code(
116
+ self, text: str, options: CodeOptions | None = None
117
+ ) -> CodingResponse:
118
+ """Code clinical text to ICD-10-CM diagnoses.
119
+
120
+ Args:
121
+ text: Clinical note or free-text input.
122
+ options: Optional coding parameters.
123
+ """
124
+ body: dict[str, Any] = {"text": text}
125
+ if options:
126
+ if options.top_k is not None:
127
+ body["top_k"] = options.top_k
128
+ if options.include_negated is not None:
129
+ body["include_negated"] = options.include_negated
130
+ if options.strategy is not None:
131
+ body["strategy"] = options.strategy
132
+ data = self._post("/api/v1/code", body)
133
+ return _parse_coding_response(data)
134
+
135
+ def anonymize(self, text: str) -> AnonymizeResponse:
136
+ """De-identify PHI/PII in clinical text.
137
+
138
+ Args:
139
+ text: Clinical note containing PHI.
140
+ """
141
+ data = self._post("/api/v1/anonymize", {"text": text})
142
+ return AnonymizeResponse(
143
+ original_text=data["original_text"],
144
+ anonymized_text=data["anonymized_text"],
145
+ pii_count=data["pii_count"],
146
+ pii_entities=[PIIEntity(**e) for e in data["pii_entities"]],
147
+ )
148
+
149
+ # ── HTTP internals ──────────────────────────────────────────────
150
+
151
+ def _get(self, path: str) -> Any:
152
+ return self._request("GET", path)
153
+
154
+ def _post(self, path: str, body: dict[str, Any]) -> Any:
155
+ return self._request("POST", path, body=body)
156
+
157
+ def _request(
158
+ self,
159
+ method: str,
160
+ path: str,
161
+ body: dict[str, Any] | None = None,
162
+ ) -> Any:
163
+ url = f"{self._base_url}{path}"
164
+ headers = {
165
+ "Authorization": f"Bearer {self._api_key}",
166
+ "Content-Type": "application/json",
167
+ "Accept": "application/json",
168
+ }
169
+
170
+ response = self._http.request(
171
+ method,
172
+ url,
173
+ headers=headers,
174
+ json=body,
175
+ timeout=self._timeout,
176
+ )
177
+
178
+ # Parse rate limit headers
179
+ self._parse_rate_limit(response.headers)
180
+
181
+ # Success
182
+ if 200 <= response.status_code < 300:
183
+ return response.json()
184
+
185
+ # Error handling
186
+ try:
187
+ error_body = response.json()
188
+ message = error_body.get("error", response.text)
189
+ except Exception:
190
+ message = response.text
191
+
192
+ if response.status_code == 401:
193
+ raise AuthenticationError(message)
194
+ if response.status_code == 404:
195
+ raise NotFoundError(message)
196
+ if response.status_code == 429:
197
+ rl = self.last_rate_limit or RateLimit(
198
+ limit=0, remaining=0, reset_at=datetime.now(timezone.utc)
199
+ )
200
+ raise RateLimitError(message, rate_limit=rl)
201
+
202
+ raise AutoICDError(response.status_code, message)
203
+
204
+ def _parse_rate_limit(self, headers: httpx.Headers) -> None:
205
+ limit = headers.get("X-RateLimit-Limit")
206
+ remaining = headers.get("X-RateLimit-Remaining")
207
+ reset_at = headers.get("X-RateLimit-Reset")
208
+
209
+ if limit is not None and remaining is not None and reset_at is not None:
210
+ self.last_rate_limit = RateLimit(
211
+ limit=int(limit),
212
+ remaining=int(remaining),
213
+ reset_at=datetime.fromisoformat(reset_at),
214
+ )
215
+ else:
216
+ self.last_rate_limit = None
217
+
218
+
219
+ # ── Response parsing helpers ────────────────────────────────────────
220
+
221
+
222
+ def _parse_code_match(data: dict[str, Any]) -> CodeMatch:
223
+ return CodeMatch(
224
+ code=data["code"],
225
+ description=data["description"],
226
+ similarity=data["similarity"],
227
+ confidence=data["confidence"],
228
+ matched_term=data["matched_term"],
229
+ )
230
+
231
+
232
+ def _parse_entity(data: dict[str, Any]) -> CodingEntity:
233
+ return CodingEntity(
234
+ entity_text=data["entity_text"],
235
+ entity_start=data["entity_start"],
236
+ entity_end=data["entity_end"],
237
+ negated=data["negated"],
238
+ historical=data["historical"],
239
+ family_history=data["family_history"],
240
+ uncertain=data["uncertain"],
241
+ severity=data.get("severity"),
242
+ codes=[_parse_code_match(c) for c in data.get("codes", [])],
243
+ merged_from=data.get("merged_from"),
244
+ corrected_from=data.get("corrected_from"),
245
+ )
246
+
247
+
248
+ def _parse_coding_response(data: dict[str, Any]) -> CodingResponse:
249
+ return CodingResponse(
250
+ text=data["text"],
251
+ provider=data["provider"],
252
+ strategy=data["strategy"],
253
+ entity_count=data["entity_count"],
254
+ entities=[_parse_entity(e) for e in data.get("entities", [])],
255
+ )
256
+
257
+
258
+ def _parse_code_detail_full(data: dict[str, Any]) -> CodeDetailFull:
259
+ parent_data = data.get("parent")
260
+ parent = CodeDetail(**parent_data) if parent_data else None
261
+
262
+ children = [CodeDetail(**c) for c in data.get("children", [])]
263
+
264
+ chapter_data = data.get("chapter")
265
+ chapter = ChapterInfo(**chapter_data) if chapter_data else None
266
+
267
+ return CodeDetailFull(
268
+ code=data["code"],
269
+ short_description=data["short_description"],
270
+ long_description=data["long_description"],
271
+ is_billable=data["is_billable"],
272
+ synonyms=data.get("synonyms", {}),
273
+ parent=parent,
274
+ children=children,
275
+ chapter=chapter,
276
+ block=data.get("block"),
277
+ )
autoicd/errors.py ADDED
@@ -0,0 +1,43 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from datetime import datetime
5
+
6
+
7
+ @dataclass
8
+ class RateLimit:
9
+ """Rate limit info from API response headers."""
10
+
11
+ limit: int
12
+ remaining: int
13
+ reset_at: datetime
14
+
15
+
16
+ class AutoICDError(Exception):
17
+ """Base exception for AutoICD API errors."""
18
+
19
+ def __init__(self, status: int, message: str) -> None:
20
+ self.status = status
21
+ super().__init__(message)
22
+
23
+
24
+ class AuthenticationError(AutoICDError):
25
+ """Raised when the API key is invalid or revoked (401)."""
26
+
27
+ def __init__(self, message: str = "Invalid API key") -> None:
28
+ super().__init__(401, message)
29
+
30
+
31
+ class RateLimitError(AutoICDError):
32
+ """Raised when the request limit is exceeded (429)."""
33
+
34
+ def __init__(self, message: str, rate_limit: RateLimit) -> None:
35
+ self.rate_limit = rate_limit
36
+ super().__init__(429, message)
37
+
38
+
39
+ class NotFoundError(AutoICDError):
40
+ """Raised when a resource is not found (404)."""
41
+
42
+ def __init__(self, message: str = "Resource not found") -> None:
43
+ super().__init__(404, message)
autoicd/types.py ADDED
@@ -0,0 +1,210 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Literal
5
+
6
+
7
+ # ── Coding ──────────────────────────────────────────────────────────
8
+
9
+
10
+ @dataclass
11
+ class CodeOptions:
12
+ """Options for the ``code()`` method."""
13
+
14
+ top_k: int | None = None
15
+ """Number of ICD-10 candidates per entity (1-25, default 5)."""
16
+
17
+ include_negated: bool | None = None
18
+ """Include negated conditions in results (default True)."""
19
+
20
+ strategy: Literal["individual", "merged"] | None = None
21
+ """Entity extraction strategy (default "individual")."""
22
+
23
+
24
+ @dataclass
25
+ class CodeMatch:
26
+ """A single ranked ICD-10 candidate."""
27
+
28
+ code: str
29
+ """ICD-10-CM code (e.g. ``"E11.21"``)."""
30
+
31
+ description: str
32
+ """Official code description."""
33
+
34
+ similarity: float
35
+ """0-1 cosine similarity score."""
36
+
37
+ confidence: Literal["high", "moderate"]
38
+ """Confidence level."""
39
+
40
+ matched_term: str
41
+ """The index term that produced this match."""
42
+
43
+
44
+ @dataclass
45
+ class CodingEntity:
46
+ """An extracted diagnosis entity with ICD-10 candidates."""
47
+
48
+ entity_text: str
49
+ """Extracted text span."""
50
+
51
+ entity_start: int
52
+ """Character offset start."""
53
+
54
+ entity_end: int
55
+ """Character offset end."""
56
+
57
+ negated: bool
58
+ """Whether the condition was negated."""
59
+
60
+ historical: bool
61
+ """Whether this is historical/resolved."""
62
+
63
+ family_history: bool
64
+ """Whether this is a family member's condition."""
65
+
66
+ uncertain: bool
67
+ """Whether the entity is hedged/uncertain."""
68
+
69
+ severity: str | None
70
+ """Severity qualifier (e.g. ``"severe"``)."""
71
+
72
+ codes: list[CodeMatch] = field(default_factory=list)
73
+ """Ranked ICD-10 candidates."""
74
+
75
+ merged_from: list[str] | None = None
76
+ """Source texts if merged."""
77
+
78
+ corrected_from: str | None = None
79
+ """Original text before spell correction."""
80
+
81
+
82
+ @dataclass
83
+ class CodingResponse:
84
+ """Complete coding result."""
85
+
86
+ text: str
87
+ """Input text that was processed."""
88
+
89
+ provider: str
90
+ """AI provider used for code matching."""
91
+
92
+ strategy: str
93
+ """Strategy used."""
94
+
95
+ entity_count: int
96
+ """Total number of entities."""
97
+
98
+ entities: list[CodingEntity] = field(default_factory=list)
99
+ """Extracted entities sorted by position."""
100
+
101
+
102
+ # ── Code Search ─────────────────────────────────────────────────────
103
+
104
+
105
+ @dataclass
106
+ class SearchOptions:
107
+ """Options for ``codes.search()``."""
108
+
109
+ limit: int | None = None
110
+ """1-100 results per page (default 20)."""
111
+
112
+ offset: int | None = None
113
+ """Pagination offset (default 0)."""
114
+
115
+
116
+ @dataclass
117
+ class CodeDetail:
118
+ """Basic details for an ICD-10-CM code."""
119
+
120
+ code: str
121
+ short_description: str
122
+ long_description: str
123
+ is_billable: bool
124
+
125
+
126
+ @dataclass
127
+ class ChapterInfo:
128
+ """ICD-10-CM chapter classification."""
129
+
130
+ number: int
131
+ """Chapter number (1-22)."""
132
+
133
+ range: str
134
+ """Code range (e.g. ``"E00-E89"``)."""
135
+
136
+ title: str
137
+ """Chapter title."""
138
+
139
+
140
+ @dataclass
141
+ class CodeDetailFull(CodeDetail):
142
+ """Comprehensive details for an ICD-10-CM code including hierarchy and synonyms."""
143
+
144
+ synonyms: dict[str, list[str]] = field(default_factory=dict)
145
+ """Synonyms grouped by source: ``"snomed"``, ``"umls"``, ``"icd10_augmented"``."""
146
+
147
+ parent: CodeDetail | None = None
148
+ """Parent code in the ICD-10 hierarchy, or ``None`` for top-level categories."""
149
+
150
+ children: list[CodeDetail] = field(default_factory=list)
151
+ """Direct child codes in the ICD-10 hierarchy."""
152
+
153
+ chapter: ChapterInfo | None = None
154
+ """ICD-10-CM chapter this code belongs to."""
155
+
156
+ block: str | None = None
157
+ """Code block range (e.g. ``"E08-E13"``)."""
158
+
159
+
160
+ @dataclass
161
+ class CodeSearchResponse:
162
+ """Search results for ICD-10-CM codes."""
163
+
164
+ query: str
165
+ count: int
166
+ codes: list[CodeDetail] = field(default_factory=list)
167
+
168
+
169
+ @dataclass
170
+ class CodeTermInfo:
171
+ """An indexed term for an ICD-10-CM code."""
172
+
173
+ term: str
174
+ """The term text."""
175
+
176
+ term_type: str
177
+ """Term type (e.g. ``"long_desc"``, ``"synonym"``)."""
178
+
179
+
180
+ # ── Anonymization ───────────────────────────────────────────────────
181
+
182
+
183
+ @dataclass
184
+ class PIIEntity:
185
+ """A detected PII entity."""
186
+
187
+ text: str
188
+ """Original PII text."""
189
+
190
+ start: int
191
+ """Character offset start."""
192
+
193
+ end: int
194
+ """Character offset end."""
195
+
196
+ label: str
197
+ """PII type: NAME, DATE, SSN, PHONE, EMAIL, ADDRESS, MRN, AGE."""
198
+
199
+ replacement: str
200
+ """Replacement placeholder (e.g. ``"[NAME]"``)."""
201
+
202
+
203
+ @dataclass
204
+ class AnonymizeResponse:
205
+ """Result of PHI de-identification."""
206
+
207
+ original_text: str
208
+ anonymized_text: str
209
+ pii_count: int
210
+ pii_entities: list[PIIEntity] = field(default_factory=list)
@@ -0,0 +1,307 @@
1
+ Metadata-Version: 2.4
2
+ Name: autoicd
3
+ Version: 0.2.0
4
+ Summary: Official Python SDK for the AutoICD API — clinical text to ICD-10-CM diagnosis codes, powered by AI and medical NLP
5
+ Project-URL: Homepage, https://autoicdapi.com
6
+ Project-URL: Documentation, https://autoicdapi.com/docs
7
+ Project-URL: Repository, https://github.com/fcggamou/autoicd-python
8
+ Project-URL: Issues, https://github.com/fcggamou/autoicd-python/issues
9
+ Author-email: AutoICD <info@autoicdapi.com>
10
+ License-Expression: MIT
11
+ License-File: LICENSE
12
+ Keywords: autoicd,clinical-decision-support,clinical-nlp,diagnosis-codes,ehr,emr,health-tech,hipaa,icd-10,icd-10-cm,medical-billing,medical-coding,medical-nlp,phi-deidentification,revenue-cycle-management
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Healthcare Industry
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Medical Science Apps.
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.10
25
+ Requires-Dist: httpx>=0.27
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.5; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ # AutoICD API — Python SDK
32
+
33
+ [![PyPI version](https://img.shields.io/pypi/v/autoicd.svg)](https://pypi.org/project/autoicd/)
34
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
35
+ [![Python](https://img.shields.io/badge/Python-3.10+-blue.svg)](https://www.python.org/)
36
+
37
+ Official Python SDK for the [AutoICD API](https://autoicdapi.com) — clinical text to ICD-10-CM diagnosis codes, powered by AI and medical NLP.
38
+
39
+ Single dependency (`httpx`). Works in **Python 3.10+**.
40
+
41
+ > Built for EHR integrations, health-tech platforms, medical billing, clinical decision support, and revenue cycle management.
42
+
43
+ ---
44
+
45
+ ## Why AutoICD API
46
+
47
+ | | |
48
+ |---|---|
49
+ | **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 |
50
+ | **74,000+ ICD-10-CM Codes** | Full 2025 code set enriched with SNOMED CT synonyms for comprehensive matching |
51
+ | **Negation & Context Detection** | Knows the difference between "patient has diabetes" and "patient denies diabetes" — flags negated, historical, uncertain, and family-history mentions |
52
+ | **PHI De-identification** | HIPAA-compliant anonymization of names, dates, SSNs, phone numbers, emails, addresses, MRNs, and ages |
53
+ | **Confidence Scoring** | Every code match includes a similarity score and confidence level so you can set your own acceptance thresholds |
54
+ | **Spell Correction** | Handles misspellings in clinical text — "diabeties" still maps to the right code |
55
+ | **Fully Typed** | Complete type annotations for all requests and responses |
56
+
57
+ ---
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pip install autoicd
63
+ ```
64
+
65
+ <details>
66
+ <summary>uv / poetry / pdm</summary>
67
+
68
+ ```bash
69
+ uv add autoicd
70
+ poetry add autoicd
71
+ pdm add autoicd
72
+ ```
73
+
74
+ </details>
75
+
76
+ ---
77
+
78
+ ## Quick Start
79
+
80
+ ```python
81
+ from autoicd import AutoICD
82
+
83
+ client = AutoICD(api_key="sk_...")
84
+
85
+ result = client.code(
86
+ "Patient has type 2 diabetes and essential hypertension"
87
+ )
88
+
89
+ for entity in result.entities:
90
+ print(entity.entity_text, "→", entity.codes[0].code)
91
+ # "type 2 diabetes" → "E11.9"
92
+ # "essential hypertension" → "I10"
93
+ ```
94
+
95
+ ---
96
+
97
+ ## Features
98
+
99
+ ### Automated ICD-10 Medical Coding
100
+
101
+ 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.
102
+
103
+ ```python
104
+ result = client.code(
105
+ "History of severe COPD with acute exacerbation. Patient denies chest pain."
106
+ )
107
+
108
+ for entity in result.entities:
109
+ print(entity.entity_text)
110
+ print(f" Negated: {entity.negated}")
111
+ print(f" Historical: {entity.historical}")
112
+ for match in entity.codes:
113
+ print(
114
+ f" {match.code} — {match.description} "
115
+ f"({match.confidence}, {match.similarity * 100:.1f}%)"
116
+ )
117
+ ```
118
+
119
+ Fine-tune results with coding options:
120
+
121
+ ```python
122
+ from autoicd import CodeOptions
123
+
124
+ result = client.code(
125
+ "Patient presents with acute bronchitis and chest pain",
126
+ options=CodeOptions(
127
+ top_k=3, # Top 3 ICD-10 candidates per entity (default: 5)
128
+ strategy="merged", # "individual" or "merged" entity strategy
129
+ include_negated=False, # Exclude negated conditions from results
130
+ ),
131
+ )
132
+ ```
133
+
134
+ ### ICD-10 Code Search
135
+
136
+ Search the full ICD-10-CM 2025 code set by description. Perfect for building code lookup UIs, autocomplete fields, and validation workflows.
137
+
138
+ ```python
139
+ results = client.codes.search("diabetes mellitus")
140
+ # results.codes → [CodeDetail(code="E11.9", short_description="...", ...), ...]
141
+
142
+ from autoicd import SearchOptions
143
+ results = client.codes.search("heart failure", options=SearchOptions(limit=5))
144
+ ```
145
+
146
+ ### ICD-10 Code Details
147
+
148
+ Get full details for any ICD-10-CM code — descriptions, billable status, and indexed terms.
149
+
150
+ ```python
151
+ detail = client.codes.get("E11.9")
152
+ print(detail.code) # "E11.9"
153
+ print(detail.long_description) # "Type 2 diabetes mellitus without complications"
154
+ print(detail.is_billable) # True
155
+ ```
156
+
157
+ ### ICD-10 Code Terms & Synonyms
158
+
159
+ Retrieve all indexed terms and synonyms for a code — includes SNOMED CT mappings and clinical variants.
160
+
161
+ ```python
162
+ terms = client.codes.terms("E11.9")
163
+ for t in terms:
164
+ print(t.term, f"({t.term_type})")
165
+ # "Type 2 diabetes mellitus without complications" (long_desc)
166
+ # "adult onset diabetes" (synonym)
167
+ # ...
168
+ ```
169
+
170
+ ### PHI De-identification
171
+
172
+ 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.
173
+
174
+ ```python
175
+ result = client.anonymize(
176
+ "John Smith, DOB 01/15/1980, MRN 123456, has COPD"
177
+ )
178
+
179
+ print(result.anonymized_text)
180
+ # "[NAME], DOB [DATE], MRN [MRN], has COPD"
181
+
182
+ print(result.pii_count) # 3
183
+ print(result.pii_entities) # [PIIEntity(text="John Smith", label="NAME", ...), ...]
184
+ ```
185
+
186
+ ---
187
+
188
+ ## Use Cases
189
+
190
+ - **EHR / EMR Integration** — Auto-code clinical notes as providers type, reducing manual coding burden
191
+ - **Medical Billing & RCM** — Accelerate claim submission with accurate ICD-10 codes
192
+ - **Clinical Decision Support** — Map patient conditions to standardized codes for analytics and alerts
193
+ - **Health-Tech SaaS** — Add ICD-10 coding to your platform without building ML infrastructure
194
+ - **Clinical Research** — Extract and standardize diagnoses from unstructured medical records
195
+ - **Insurance & Payer Systems** — Validate and suggest diagnosis codes during claims processing
196
+ - **Telehealth Platforms** — Generate diagnosis codes from visit notes and transcriptions
197
+
198
+ ---
199
+
200
+ ## Error Handling
201
+
202
+ ```python
203
+ from autoicd import (
204
+ AutoICD,
205
+ AuthenticationError,
206
+ RateLimitError,
207
+ NotFoundError,
208
+ )
209
+
210
+ try:
211
+ result = client.code("...")
212
+ except AuthenticationError:
213
+ # Invalid or revoked API key (401)
214
+ ...
215
+ except RateLimitError as e:
216
+ # Request limit exceeded (429)
217
+ print(e.rate_limit.remaining, e.rate_limit.reset_at)
218
+ except NotFoundError:
219
+ # ICD-10 code not found (404)
220
+ ...
221
+ ```
222
+
223
+ Rate limit info is available after every request:
224
+
225
+ ```python
226
+ client.code("...")
227
+ print(client.last_rate_limit)
228
+ # RateLimit(limit=1000, remaining=987, reset_at=datetime(...))
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Configuration
234
+
235
+ ```python
236
+ client = AutoICD(
237
+ api_key="sk_...", # Required — get yours at https://autoicdapi.com
238
+ base_url="https://...", # Default: https://autoicdapi.com
239
+ timeout=60.0, # Default: 30.0 seconds
240
+ http_client=httpx.Client(...), # Custom httpx client (for proxies, mTLS, etc.)
241
+ )
242
+ ```
243
+
244
+ Use as a context manager for automatic cleanup:
245
+
246
+ ```python
247
+ with AutoICD(api_key="sk_...") as client:
248
+ result = client.code("Patient has diabetes")
249
+ ```
250
+
251
+ ---
252
+
253
+ ## API Reference
254
+
255
+ Full REST API documentation at [autoicdapi.com/docs](https://autoicdapi.com/docs).
256
+
257
+ | Method | Description |
258
+ |--------|-------------|
259
+ | `client.code(text, options?)` | Code clinical text to ICD-10-CM diagnoses |
260
+ | `client.anonymize(text)` | De-identify PHI/PII in clinical text |
261
+ | `client.codes.search(query, options?)` | Search ICD-10-CM codes by description |
262
+ | `client.codes.get(code)` | Get details for an ICD-10-CM code |
263
+ | `client.codes.terms(code)` | Get indexed terms/synonyms for a code |
264
+
265
+ ---
266
+
267
+ ## Types
268
+
269
+ All request and response types are exported:
270
+
271
+ ```python
272
+ from autoicd import (
273
+ CodingResponse,
274
+ CodingEntity,
275
+ CodeMatch,
276
+ CodeOptions,
277
+ CodeDetail,
278
+ CodeSearchResponse,
279
+ CodeTermInfo,
280
+ AnonymizeResponse,
281
+ PIIEntity,
282
+ RateLimit,
283
+ SearchOptions,
284
+ )
285
+ ```
286
+
287
+ ---
288
+
289
+ ## Requirements
290
+
291
+ - **Python 3.10+**
292
+ - An API key from [autoicdapi.com](https://autoicdapi.com)
293
+
294
+ ---
295
+
296
+ ## Links
297
+
298
+ - [AutoICD API](https://autoicdapi.com) — Homepage and API key management
299
+ - [API Documentation](https://autoicdapi.com/docs) — Full REST API reference
300
+ - [TypeScript SDK](https://www.npmjs.com/package/autoicd) — `npm install autoicd`
301
+ - [ICD-10-CM 2025 Code Set](https://www.cms.gov/medicare/coding-billing/icd-10-codes) — Official CMS reference
302
+
303
+ ---
304
+
305
+ ## License
306
+
307
+ MIT
@@ -0,0 +1,8 @@
1
+ autoicd/__init__.py,sha256=ip3RVonlUsVkD-DAHT8cp9uSahsMBpdgnhk6CmoRBkM,924
2
+ autoicd/client.py,sha256=aCV7cPOGQUjJTIEdu7jLLOBoRIYHh28gWFBHagXB4YE,9013
3
+ autoicd/errors.py,sha256=ivpEuy_SkTbagS4fnAd2nCdWN_sWuOAuFk4S0sRD__0,1100
4
+ autoicd/types.py,sha256=fhjVc2Oy7Rqmssbf59I5QgcxwbzgPTRmu-cjTDnevjM,5018
5
+ autoicd-0.2.0.dist-info/METADATA,sha256=JHnk21XJvAirId0ynFoSzPL_M2E_3sm3-f5w37ylv7Q,9490
6
+ autoicd-0.2.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
7
+ autoicd-0.2.0.dist-info/licenses/LICENSE,sha256=IYkACTgDzY__lf7SsIcjrjOtil429XVYegIOrZ_9Su8,1061
8
+ autoicd-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -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.