# icd10-am

A lightweight, zero-dependency Python library for querying the **ICD-10-AM** (Australian Modification) code hierarchy.

> **ICD-10-AM** is the Australian clinical modification of ICD-10, maintained by the Australian Institute of Health and Welfare (AIHW) and used across Australian hospitals and health services. This library is built specifically for ICD-10-AM and is **not** a generic ICD-10 library.

---

## Installation

```bash
pip install icd10-am
```

---

## Quick Start

```python
from icd10am import ICD10

icd = ICD10()

icd.code_exists("A00.1")           # True
icd.get_parent("A00.1")            # "A00"
icd.get_parent("A00")              # "Chapter I"
icd.get_chapter("A00.1")           # "Chapter I"
icd.get_children("A00")            # ["A00.0", "A00.1", "A00.9"]
icd.get_range(1)                   # "A00–B99"
icd.get_description("A00.1")       # "Cholera due to Vibrio cholerae 01, biovar eltor"
icd.list_chapter_codes(1)          # ["A00", "A00.0", ..., "B99.9"]
```

---

## Data Model

The ICD-10-AM hierarchy has three levels:

```
Chapter I
└── A00  ·  Cholera
    ├── A00.0  ·  Cholera due to Vibrio cholerae 01, biovar cholerae
    ├── A00.1  ·  Cholera due to Vibrio cholerae 01, biovar eltor
    └── A00.9  ·  Cholera, unspecified
```

Chapter keys are stored as `"Chapter I"`, `"Chapter II"`, etc.

---

## Input Flexibility

All methods normalise input automatically before lookup:

| Input | Resolved as |
|---|---|
| `"a00.1"` | `"A00.1"` |
| `"A001"` | `"A00.1"` |
| `"A0012"` | `"A00.12"` |
| `"1"` or `1` | `"Chapter I"` |
| `"12"` | `"Chapter XII"` |
| `"chapter 6"` | `"Chapter VI"` |
| `"Chapter VI"` | `"Chapter VI"` *(unchanged)* |

You can also call `normalize_code()` directly:

```python
from icd10am import normalize_code

normalize_code("a001")      # "A00.1"
normalize_code("chapter 6") # "Chapter VI"
```

---

## API Reference

### `ICD10(map_path?)`

Loads the bundled ICD-10-AM map by default. Pass a custom path to use your own map file.
Raises `MapNotFoundError` if the file is not found.

---

### `code_exists(code) → bool`

Returns `True` if the ICD-10-AM code is in the map, `False` otherwise. Never raises.

```python
icd.code_exists("A00.1")  # True
icd.code_exists("Z999")   # False
```

---

### `get_description(code) → str`

Returns the human-readable ICD-10-AM description of any code or chapter key.

```python
icd.get_description("A00")         # "Cholera"
icd.get_description("A00.1")       # "Cholera due to Vibrio cholerae 01, biovar eltor"
icd.get_description("Chapter I")   # "CERTAIN INFECTIOUS AND PARASITIC DISEASES (A00–B99)"
icd.get_description(1)             # same as above
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | code not in map |

---

### `get_parent(code) → str`

Returns the parent key one level up the hierarchy.

```python
icd.get_parent("A00.1")     # "A00"
icd.get_parent("A00")       # "Chapter I"
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | code not in map |
| `NoParentError` | called on a chapter key (top of hierarchy) |

---

### `get_chapter(code) → str`

Returns the chapter key for any code, at any level.

```python
icd.get_chapter("A00.1")     # "Chapter I"
icd.get_chapter("A00")       # "Chapter I"
icd.get_chapter("Chapter I") # "Chapter I"
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | code not in map |

---

### `get_children(code) → list[str]`

Returns direct children. Returns `[]` for child codes (leaf nodes).

```python
icd.get_children("Chapter I") # ["A00", "A01", ...]
icd.get_children("A00")       # ["A00.0", "A00.1", "A00.9"]
icd.get_children("A00.1")     # []
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | code not in map |

---

### `get_range(chapter_key) → str`

Returns the code range of a chapter.

```python
icd.get_range(1)             # "A00–B99"
icd.get_range("Chapter VI")  # "G00–G99"
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | chapter key not in map |
| `NotAChapterError` | key is a parent or child code, not a chapter |

---

### `list_chapter_codes(chapter_key) → list[str]`

Returns every parent and child code in a chapter.

```python
icd.list_chapter_codes(1)  # ["A00", "A00.0", "A00.1", ..., "B99.9"]
```

| Raises | When |
|---|---|
| `CodeNotFoundError` | chapter key not in map |
| `NotAChapterError` | key is a parent or child code, not a chapter |

---

## Exceptions

All exceptions inherit from `ICD10Error`:

```python
from icd10am import (
    ICD10Error,
    CodeNotFoundError,
    NoParentError,
    NotAChapterError,
    MapNotFoundError,
)
```

```python
try:
    icd.get_parent("Chapter I")
except NoParentError as e:
    print(e.code)   # "Chapter I"

try:
    icd.get_range("A00")
except NotAChapterError as e:
    print(e.key)    # "A00"

try:
    icd.get_children("Z999")
except CodeNotFoundError as e:
    print(e.code)   # "Z999"
```

Each exception exposes the offending value as an attribute (`.code`, `.key`, `.path`).

---

## License

MIT