Evidence-Grounded ICD-10 Coding Assistant
Foundry/AIP tool suggesting ICD-10 codes with verbatim evidence and validation
Project metadata
Overview
An operational tool that reads unstructured clinical notes, suggests ICD-10 diagnosis codes for medical coders, and grounds every suggestion in verbatim evidence plus a validation check against an official code reference. Built on Palantir Foundry and AIP.
Problem
Hospitals bill insurance using ICD-10 diagnosis codes, a standardized system with roughly 70,000 codes. A human medical coder reads long unstructured clinical notes and hand-assigns codes. The work is slow and detail-heavy, and miscoding carries real consequences: under-coding loses revenue, over-coding or incorrect coding triggers claim denials and compliance audits.
Fully automating this with an LLM is dangerous, because a model can confidently produce a code that does not exist or that is not supported by the note.
User and operational decision
- User: a medical coder or Clinical Documentation Integrity (CDI) specialist.
- Decision informed: approve, correct, or escalate the ICD-10 codes for a chart before it goes to billing.
- Effect: the coder shifts from reading an entire chart and coding from scratch to reviewing evidence-backed suggestions and approving them, while retaining full control of the final decision.
This is a tool that informs a decision, not an analytics dashboard.
What it does
For a selected patient:
- Reads the clinical note
- Suggests ICD-10 codes, one per identified diagnosis or codeable finding
- Shows the verbatim sentence from the note justifying each suggestion
- Marks each code validated (green) or unverified (amber) against an official ICD-10 reference
- Lets the coder approve each suggestion, writing that decision back into the data model
Architecture
Two Foundry datasets feed a Pipeline Builder pipeline; the output feeds an ontology of two linked object types; a Workshop application is the coder's surface.
Pipeline, stage by stage
Stage 0: Data preparation (local, pre-upload)
Two source files from the challenge's public data extract:
- PMC_Patients_clean.csv: 167,000+ patient notes (patient_id, patient_uid, patient_note, age, gender)
- icd_10_codes.csv: about 2 GB, containing embedded_description (a stringified vector embedding), icd_10_code, and description
Local reduction in pandas:
Reduced files: patients_sample_50.csv at ~151 KB, icd10_reference.csv at ~6 MB. The 2 GB original was never uploaded.
Stage 1: Extraction
A Pipeline Builder pipeline (icd_extraction) runs a Use LLM node over each patient_note. Model: Claude Sonnet 4.5 (anthropic-claude-4-5-sonnet). The node writes raw model output to a string column, llm_output.
Extraction prompt:
Design intent: the verbatim-quote requirement forces grounding and makes fabricated findings hard; "only code what is documented" is the accuracy guardrail; structured JSON makes the downstream parse and ontology mapping mechanical.
Stage 2: Parse and explode
Raw llm_output is stripped of markdown code fences, parsed as a JSON array of structs (condition, icd10_code, evidence_quote, confidence), then exploded so there is one row per suggestion, carrying patient_id, patient_uid, and patient_note on every row, with a generated unique suggestion_id.
A checkpoint dataset (patients_with_llm_output) preserves the raw per-patient LLM output, so re-running downstream logic never re-invokes the expensive LLM step.
Stage 3: Validation join
Each suggested code is checked against icd10_reference:
- A normalized key norm_code is computed on both sides: uppercase the code, remove all . characters. This handles the dotted vs undotted gap (E11.9 versus E119).
- Suggestions are LEFT JOINed to the reference on norm_code.
- validation_status is validated when a reference match exists, otherwise unverified.
- For validated rows, the reference description is pulled in as canonical_description.
Output dataset validated_suggestions columns: suggestion_id, patient_id, patient_uid, condition, icd10_code, canonical_description, evidence_quote, confidence, validation_status.
An "unverified" result means the code was not found in the reference subset used. Because the reference is a subset of full ICD-10-CM, most unverified codes are real codes simply absent from the reference rather than hallucinated ones. The tool surfaces this uncertainty for human review rather than hiding it.
Stage 4: Ontology
Two object types, with a link and an action:
- Patient: primary key patient_id, title patient_uid, backed by patients_sample_50. Properties: patient_id, patient_uid, age, gender, patient_note. Read-only.
- CodeSuggestion: primary key suggestion_id, title condition, backed by validated_suggestions. Properties: suggestion_id, patient_id, condition, icd10_code, canonical_description, evidence_quote, confidence, validation_status, and a boolean approved defaulting to false. Edits enabled so the action can write back.
- Link: one-to-many, Patient to CodeSuggestions, joined on patient_id.
- Action: "Approve suggestion", parameter code_suggestion, sets approved = true. The write persists in the writeback layer, so rebuilding validated_suggestions does not reset prior approvals.
Stage 5: Workshop application
A Workshop module ("Coding Assistant"):
- Left panel: Object List of all Patients, titled by patient_uid. Selection writes to a selectedPatient variable.
- Right panel, top: Markdown widget bound to the selected patient's patient_note, via an Object-property variable off the active patient.
- Right panel, bottom: Object Table whose object set is the selected patient's linked CodeSuggestions, produced by a "Get linked objects" traversal along the Patient to CodeSuggestions link. Columns: icd10_code, condition, canonical_description, evidence_quote, validation_status, confidence, approved. Conditional formatting shows validation_status green for validated and amber for unverified.
- Approve button: action button wired to the "Approve suggestion" action, with its code_suggestion parameter bound to the currently selected suggestion.
Engineering details and gotchas
- Dotted vs undotted codes: ICD-10 codes appear both as J96.01 and J9601. The join normalizes both sides (uppercase, strip dots) so the validation match does not silently miss.
- Model identifier resolution: selecting Claude Sonnet 4.5 required the exact internal identifier anthropic-claude-4-5-sonnet; several plausible names (claude-sonnet-4-5, claude-3-7-sonnet, claude-5-sonnet) were rejected first.
- Markdown fences in LLM output: Sonnet wraps JSON in code fences. Stripped deterministically in the parse step rather than prompt-wrestled.
- Writeback index provisioning on a branch: a newly created, edits-enabled object type on a branch has its read index ready before its writeback index finishes provisioning, so a headless "approve" call can fail initially. Opening the object type in the UI, or clicking Approve inside the Workshop app in a real user session, bootstraps the writeback index.
Known limitations
- Prototype scale: 50 sampled notes, not volume-tested.
- Uncalibrated confidence: the confidence field is present but not yet meaningful.
- Partial reference: the reference is a subset of full ICD-10-CM, so some "unverified" codes are real but missing rather than wrong.
- No clinical validation: suggestions are traceable and checkable, but code correctness has not been verified by a certified coder. Output is a suggestion for human review, not authoritative coding.
Data and attribution
- Patient notes: the PMC-Patients dataset. Zhao, Z., Jin, Q., Chen, F., Peng, T., and Yu, S. (2022), "A Large-scale Dataset of Patient Summaries and Relations for Benchmarking Retrieval-based Clinical Decision Support Systems," arXiv:2202.13876. Source repository: https://github.com/pmc-patients/pmc-patients. Derived from open-access case reports (CC BY-NC-SA).
- Codes: ICD-10-CM.
- Patient data is not included in the project repository.
Full stack
Palantir Foundry · Palantir AIP · Pipeline Builder · Ontology · Workshop · Claude Sonnet 4.5 · Python · pandas
Results
Code suggestions generated | 500 across 50 patients, roughly 10 per patient | |
Validated against reference | 475 (95%) | |
Unverified | 25 (5%) | |
Extraction richness vs a smaller model | 13 conditions versus 6 on one representative note, with strictly verbatim quotes |
