hcc-meat-coder / docs /ARCHITECTURE.md
chraos's picture
Claude Opus 4.7 (1M context)
Initial scaffold: HCC-MEAT Coder v0.1.0
c6cfe5f
|
Raw History Blame Contribute Delete
5.17 kB
# Architecture
## Data contract (single source of truth)
[`app/pipeline/types.py`](../app/pipeline/types.py) is the authority. Every
component consumes and emits instances of these pydantic models. If you need
to add a field, add it here first and the components will fail loudly until
they handle it.
Key shapes:
- `Note` β€” free-text input + optional section list.
- `Span` β€” typed offset ranges (Condition/Medication/Lab/...) with negation + temporality.
- `ICD10Link` β€” (Condition span, ICD-10 code, linker confidence).
- `HCCCandidate` β€” HCC id, RAF weight, hierarchy state, ICD-10 links, confidence (raw + calibrated), MEAT evidence, risk.
- `MEATEvidence` β€” category (M/E/A/T), quoted text + offsets, NLI entailment, `verified` flag.
- `RiskAssessment` β€” level (low/medium/high), score, transparent reason list, named component scores.
- `CodingResult` β€” the whole thing, serializable.
## Component map
| # | Component | Inputs | Outputs | Real or stub |
|---|---|---|---|---|
| A | Section Segmenter ([a_segmenter.py](../app/pipeline/components/a_segmenter.py)) | Note | Note with sections | Rule-based (real, narrow) |
| B | Clinical NER ([b_ner.py](../app/pipeline/components/b_ner.py)) | Note | `list[Span]` | Dictionary baseline (stub) |
| C | Negation + Temporality ([c_negation.py](../app/pipeline/components/c_negation.py)) | Note + spans | Spans with flags; `is_attributable()` filter | Rule-based (real) |
| D | ICD-10 Linker ([d_linker.py](../app/pipeline/components/d_linker.py)) | Spans | `list[ICD10Link]` | Exact + alias match (stub) |
| E | HCC Crosswalk ([e_crosswalk.py](../app/pipeline/components/e_crosswalk.py)) | Links | HCC candidates + trumping | **Real** (DuckDB + CSV) |
| F | MEAT Extractor ([f_meat.py](../app/pipeline/components/f_meat.py)) | Note + candidate | `list[MEATEvidence]` | Rule-based (stub) |
| G | Calibrator ([g_calibrator.py](../app/pipeline/components/g_calibrator.py)) | HCC + raw conf | Calibrated conf | Identity (stub) |
| H | Over-coding Risk ([h_risk.py](../app/pipeline/components/h_risk.py)) | Candidate | `RiskAssessment` | **Real** (transparent heuristic) |
| I | Verifier ([i_verifier.py](../app/pipeline/components/i_verifier.py)) | Note + candidate | Candidate with verified/dropped evidence | **Real** (substring) + NLI hook |
## Orchestration order
```
A segmenter
β†’ B NER
β†’ C negation/temporality β†’ filter non-attributable spans
β†’ D linker (conditions only)
β†’ E crosswalk + trumping
β†˜
F MEAT extractor (per candidate)
β†’ I verifier (per candidate)
↙
G calibrator (per candidate)
β†’ H risk (per candidate)
β†’ rank: non-trumped first, then calibrated_conf Γ— RAF_weight
```
Notes:
- I (verifier) sits **between F and G** β€” a candidate with zero verified MEAT
evidence is still kept (with an empty evidence list), because risk heuristic H
needs to see that emptiness and flag the candidate accordingly. Dropping the
candidate silently would undercount the suspect list a coder needs to see.
- G (calibrator) runs late because it doesn't affect rank tie-breakers inside E,
but it does affect the final ranking H reads.
## Why hierarchy trumping is handled inside component E
Trumping requires the full candidate set; it's not a per-span decision. Running
it inside the crosswalk (`HCCCrosswalk.apply_trumping`) keeps the orchestrator
code short and the crosswalk tests self-contained.
## Why MEAT evidence belongs on HCCCandidate (not on Span)
One MEAT evidence row justifies one HCC. The same sentence can justify Assess
for HCC-18 and Monitor for HCC-85. Binding MEATEvidence to HCC rather than to
span avoids a many-to-many association table.
## API shape
Endpoints in `app/api/server.py`:
- `POST /api/code` β€” `{text, note_id}` β†’ `CodingResult`. Single source of truth
for the frontend.
- `GET /api/gallery` β€” returns precomputed `GalleryItem[]` for the preloaded
demo cases. Computed once at app startup so clicking a gallery entry is instant.
- `GET /api/healthz` β€” `{status: "ok", version}`. Used by the Docker healthcheck.
- `GET /api/reference/hcc` β€” HCC label table for UI tooltips linking to CMS.
## Frontend
`ui/src/App.tsx` is a controller holding `noteText`, `result`, `selectedHCC`,
and the gallery. Three presentational components:
- `NotePanel` β€” textarea + highlight preview (MEAT quotes underlined by category).
- `HCCList` β€” ranked HCC suggestions; trumped ones dimmed.
- `DrillIn` β€” selected HCC's MEAT-by-category, risk reasons, ICD-10 support.
State flows strictly one-way. The only network call besides gallery is
`codeNote()` from `ui/src/lib/api.ts`.
## Deployment target
Single Docker image (`Dockerfile`) for an HF **Docker Space**:
- Stage 1 (node:20-alpine): build the React UI.
- Stage 2 (python:3.11-slim): install minimal Python deps, copy `app/`,
`eval_harness/`, `data/reference/`, and the built UI at `ui/dist/`.
- Uvicorn serves FastAPI on :7860 and the UI via `StaticFiles` mount.
- HF Spaces auto-builds the Dockerfile; the Python CI runs on GitHub Actions
without building the image.