claim-ready / app.py
vinaybabu's picture
Deploy ClaimReady: on-device Gemma 3 via llama.cpp
e3f8f8d verified
Raw
History Blame Contribute Delete
11.9 kB
"""
ClaimReady — pre-submission claim checker (Gradio UI).
Pre-checks hospital insurance claim / pre-auth document sets against the Standard
Treatment Guidelines before they're uploaded to the government portal. OCR +
content verification run inside the Space on Gemma 3 12B via transformers (GPU),
with no cloud inference API.
"""
import gradio as gr
from llm import LLMConfigError, verify, _file_to_images
from packages import (
STAGE_PREAUTH,
STAGE_CLAIM,
STAGE_LABELS,
dropdown_choices,
get_package,
required_documents,
)
STAGE_RADIO_CHOICES = [
(STAGE_LABELS[STAGE_PREAUTH], STAGE_PREAUTH),
(STAGE_LABELS[STAGE_CLAIM], STAGE_CLAIM),
]
# Synthetic (fictional-patient) sample claims — safe to publish, let judges try
# the app in one click. NO real patient data.
# (label, package_code, stage, files) — synthetic, fictional-patient sample claims.
SAMPLES = [
("🤒 Enteric Fever · Pre-Auth · EN+हिन्दी", "MG006A", STAGE_PREAUTH,
["samples/enteric_clinical_notes.png", "samples/enteric_cbc.png"]),
("🤒 Enteric Fever · Claim · EN+हिन्दी", "MG006A", STAGE_CLAIM,
["samples/enteric_indoor_case.png", "samples/enteric_post_cbc.png",
"samples/enteric_discharge.png"]),
("🩸 Severe Anemia · Pre-Auth · EN+తెలుగు", "MG064A", STAGE_PREAUTH,
["samples/anemia_clinical_notes.png", "samples/anemia_cbc_hb.png"]),
("🩸 Severe Anemia · Claim · EN+తెలుగు", "MG064A", STAGE_CLAIM,
["samples/anemia_indoor_case.png", "samples/anemia_post_cbc.png",
"samples/anemia_discharge.png"]),
]
def package_info_md(code):
pkg = get_package(code)
if not pkg:
return ""
return (
f"**{pkg['code']}{pkg['name']}** \n"
f"Procedure: {pkg.get('procedure', '—')} \n"
f"Specialty: {pkg.get('specialty', '—')} \n"
f"Package price: {pkg.get('price', '—')}"
)
def checklist_md(code, stage):
if not code or not stage:
return "_Select a package and stage to see the required documents._"
pkg = get_package(code)
docs = required_documents(code, stage)
if not docs:
return "_No documents configured for this selection._"
lines = [
f"### Required documents — {STAGE_LABELS[stage]}",
f"_{pkg['name']} ({pkg['code']})_",
"",
]
for i, doc in enumerate(docs, 1):
line = f"{i}. **{doc['label']}** — {doc['verify']}"
if doc.get("note"):
line += f" \n _Note: {doc['note']}_"
lines.append(line)
rules = pkg.get("rules", [])
if rules:
lines.append("")
lines.append("**Compliance checks (content):**")
for r in rules:
lines.append(f"- {r['check']}")
return "\n".join(lines)
def on_package_change(code, stage):
return package_info_md(code), checklist_md(code, stage)
def on_stage_change(code, stage):
return checklist_md(code, stage)
def show_documents(files):
"""Render the uploaded/sample files (PDF pages too) into gallery images."""
images = []
for f in files or []:
path = getattr(f, "name", f)
try:
images.extend(_file_to_images(path))
except Exception:
pass
return images
def run_verification(code, stage, files):
if not code:
yield "⚠️ Please select a package code first."
return
if not stage:
yield "⚠️ Please select a stage (Pre-Auth or Claim)."
return
if not files:
yield "⚠️ Please upload at least one document to verify."
return
pkg = get_package(code)
docs = required_documents(code, stage)
paths = [getattr(f, "name", f) for f in files]
yield (
f"⏳ **Analyzing {len(paths)} document(s)…** "
"Reading each page and checking it against the guidelines — "
"the assistive review will appear here shortly."
)
try:
result = verify(pkg, STAGE_LABELS[stage], docs, pkg.get("rules", []), paths)
except LLMConfigError as e:
yield f"## ⚠️ Configuration error\n\n{e}"
return
except Exception as e:
yield (
"## ❌ Verification failed\n\n"
f"The model did not complete: `{type(e).__name__}: {e}`"
)
return
yield result
# --- Styling ------------------------------------------------------------------
THEME = gr.themes.Soft(
primary_hue=gr.themes.colors.teal,
secondary_hue=gr.themes.colors.blue,
neutral_hue=gr.themes.colors.slate,
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
).set(
body_background_fill="#eef2f6",
block_background_fill="#ffffff",
block_border_width="1px",
block_shadow="0 1px 3px rgba(16,24,40,.06)",
block_radius="14px",
)
CSS = """
.gradio-container {max-width: 1500px !important; width: 96% !important; margin: auto !important;}
#hero {
background: linear-gradient(135deg, #2563eb 0%, #06b6d4 100%);
color: #fff; border-radius: 16px; padding: 28px 32px; margin-bottom: 6px;
box-shadow: 0 10px 26px rgba(37,99,235,.28);
}
#hero h1 {margin: 0 0 8px 0; font-size: 31px; font-weight: 800;
text-shadow: 0 1px 2px rgba(0,0,0,.18);}
#hero p {margin: 0; color:#f0f9ff; opacity: 1; font-size: 15.5px; line-height: 1.55;}
#hero p b {color:#ffffff;}
#hero .pills {margin-top: 16px;}
#hero .pill {
display:inline-block; background: rgba(255,255,255,.22); border:1px solid rgba(255,255,255,.45);
color:#fff; padding: 5px 13px; border-radius: 999px; font-size: 12.5px;
margin: 0 8px 6px 0; font-weight: 500;
}
#run-btn {font-weight: 700; font-size: 16px;}
#result-card .prose {font-size: 15px;}
.step-label {font-weight: 700; color:#0f4c81; margin-bottom: 2px;}
footer {display: none !important;}
#foot {text-align:center; color:#64748b; font-size:12.5px; margin-top:10px;}
/* keep text + list markers off the edge so the first glyph never clips */
.prose, .md, .prose *, .md * {overflow: visible !important;}
.prose {padding: 2px 6px !important;}
.prose ul, .prose ol {padding-left: 1.6em !important; margin-left: 0 !important;}
.prose li {line-height: 1.6; margin: 4px 0;}
.prose p, .prose h1, .prose h2, .prose h3, .prose h4 {line-height: 1.55;}
#problem-sidebar {padding: 6px 18px 16px 22px !important;}
#result-card {padding: 2px 18px !important;}
"""
HERO = """
<div id="hero">
<h1>🏥 ClaimReady</h1>
<p>An <b>assistive pre-check</b> for hospital insurance <b>claim</b> and
<b>pre-authorization</b> document sets. Before you upload to the government portal,
ClaimReady reads each document and checks it against the Standard Treatment
Guidelines, <b>highlighting</b> missing papers and possible compliance gaps so your
team can fix them early and reduce rejections.</p>
<div class="pills">
<span class="pill">🩺 Decision-support</span>
<span class="pill">🔒 No cloud API — runs in the Space</span>
<span class="pill">🤖 Gemma 3 12B (≤32B open model)</span>
<span class="pill">⚡ GPU-accelerated</span>
</div>
</div>
"""
PROBLEM = """
Health-insurance claims — for example under India's **Ayushman Bharat / PMJAY** scheme — require a
specific set of **supporting documents** that must satisfy the applicable **clinical / treatment
guidelines** for each procedure and stage (pre-authorization / claim).
**The problem**
- Every claim must include **all mandatory documents** and meet defined **content conditions** for the procedure and stage.
- A missing document — or a value that doesn't meet a condition — can lead to **claim rejection**, delays and rework.
**What ClaimReady offers**
- 📄 Reads every uploaded document with **on-device OCR** — images *and* PDFs.
- ✅ Verifies the set against the **required-document checklist** for the selected package and stage.
- 🔎 Evaluates **content rules** (thresholds, conditions) against the values it actually reads.
- 🌐 Handles **mixed-language** documents (e.g. English + Hindi / Telugu).
- ⚠️ Surfaces **missing documents and unmet conditions early**, with supporting evidence — as an **assistive** pre-check.
"""
with gr.Blocks(theme=THEME, css=CSS, title="ClaimReady — Claim Submission Check") as demo:
with gr.Sidebar(label="ℹ️ The problem", open=False, position="left", width=400,
elem_id="problem-sidebar"):
gr.Markdown("### The problem ClaimReady solves")
gr.Markdown(PROBLEM)
gr.HTML(HERO)
gr.Markdown("**▶ New here? Load a sample claim** &nbsp;·&nbsp; "
"_synthetic data, no real patients · mixed English + हिन्दी / తెలుగు_")
with gr.Row():
sample_buttons = [gr.Button(s[0], size="sm") for s in SAMPLES]
with gr.Row(equal_height=False):
with gr.Column(scale=5):
gr.Markdown("#### 1 · Select the package", elem_classes="step-label")
package_dd = gr.Dropdown(
choices=dropdown_choices(),
label="Package code",
info="The PMJAY Health Benefit Package being claimed",
)
package_info = gr.Markdown()
gr.Markdown("#### 2 · Select the stage", elem_classes="step-label")
stage_radio = gr.Radio(
choices=STAGE_RADIO_CHOICES,
label="Stage",
info="Pre-auth = before treatment · Claim = after treatment",
)
gr.Markdown("#### 3 · Upload the documents", elem_classes="step-label")
files_in = gr.File(
label="Claim documents (images or PDF)",
file_count="multiple",
file_types=["image", ".pdf"],
height=160,
)
verify_btn = gr.Button("🔎 Run Assistive Check", variant="primary", elem_id="run-btn")
with gr.Column(scale=5):
with gr.Tabs():
with gr.Tab("📋 Required documents & checks"):
checklist_out = gr.Markdown(
"_Select a package and stage to see the required documents._"
)
with gr.Tab("📎 View uploaded documents"):
doc_gallery = gr.Gallery(
label="Click any page to enlarge",
columns=2,
height=560,
object_fit="contain",
)
gr.Markdown("#### 🔎 Assistive review", elem_classes="step-label")
with gr.Group(elem_id="result-card"):
result_out = gr.Markdown(
"_Run a check (or click a sample claim above) to see the assistive review here._"
)
gr.HTML(
"<div id='foot'>ClaimReady · runs entirely on an open ≤32B model inside this "
"Space, no cloud inference API · built for the HuggingFace Build Small hackathon</div>"
)
package_dd.change(
on_package_change, inputs=[package_dd, stage_radio],
outputs=[package_info, checklist_out],
)
stage_radio.change(
on_stage_change, inputs=[package_dd, stage_radio], outputs=[checklist_out],
)
files_in.change(show_documents, inputs=[files_in], outputs=[doc_gallery])
verify_btn.click(
run_verification, inputs=[package_dd, stage_radio, files_in], outputs=[result_out],
)
def _load_sample(code, stage, files):
return (
code, stage, files,
package_info_md(code), checklist_md(code, stage), show_documents(files),
)
for _btn, _s in zip(sample_buttons, SAMPLES):
_btn.click(
(lambda c=_s[1], st=_s[2], fl=_s[3]: _load_sample(c, st, fl)),
outputs=[package_dd, stage_radio, files_in, package_info, checklist_out, doc_gallery],
)
demo.queue()
if __name__ == "__main__":
demo.launch()