Spaces:
Running on Zero
Running on Zero
| """ | |
| 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** · " | |
| "_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() | |