Spaces:
Running on Zero
Running on Zero
| """ | |
| PorchaPrahari — West Bengal Land Record Pre-verification (Gradio UI). | |
| Pre-checks Bengali property documents (Porcha, Dalil) against Banglarbhumi | |
| compliance rules before portal submission. OCR + verification run natively | |
| on Gemma 4 12B via transformers (GPU) with no external cloud API. | |
| """ | |
| import gradio as gr | |
| from llm import LLMConfigError, verify, _file_to_images | |
| STAGE_PORCHA = "porcha" | |
| STAGE_DALIL = "dalil" | |
| STAGE_LABELS = { | |
| STAGE_PORCHA: "Record of Right (Porcha)", | |
| STAGE_DALIL: "Deed of Conveyance (Dalil)" | |
| } | |
| STAGE_RADIO_CHOICES = [ | |
| (STAGE_LABELS[STAGE_PORCHA], STAGE_PORCHA), | |
| (STAGE_LABELS[STAGE_DALIL], STAGE_DALIL), | |
| ] | |
| # Kolkata/Bengal specific document rules mock-database | |
| DOC_TYPES = { | |
| "RESIDENTIAL": { | |
| "code": "RESIDENTIAL", | |
| "name": "Bastu (Residential) Property Transfer", | |
| "procedure": "Title Verification", | |
| "rules": [ | |
| {"id": "r1", "check": "Land classification (Shreni) must explicitly mention 'Bastu' (বাস্তু) or 'Homestead'."}, | |
| {"id": "r2", "check": "Total land area must be clearly stated in Satak (শতক) or Kathas (কাঠা)."} | |
| ] | |
| }, | |
| "AGRICULTURAL": { | |
| "code": "AGRICULTURAL", | |
| "name": "Sali (Agricultural) Land Verification", | |
| "procedure": "Agricultural Transfer", | |
| "rules": [ | |
| {"id": "r3", "check": "Land classification must mention 'Sali' (শালি) or 'Agricultural'."}, | |
| {"id": "r4", "check": "Must not contain NOC for commercial building construction."} | |
| ] | |
| } | |
| } | |
| def required_documents(code, stage): | |
| if stage == STAGE_PORCHA: | |
| return [ | |
| {"id": "d1", "label": "LR Porcha", "verify": "Contains Khaitan No. and LR Dag No.", "note": "Must have govt watermark or signature."}, | |
| {"id": "d2", "label": "Owner ID (Aadhaar/EPIC)", "verify": "Name matches the recorded owner on Porcha."} | |
| ] | |
| else: | |
| return [ | |
| {"id": "d3", "label": "Registered Dalil", "verify": "Shows buyer, seller, and Mouza details clearly."}, | |
| {"id": "d4", "label": "Query Receipt", "verify": "Contains the 14-digit e-Nathikaran query number."} | |
| ] | |
| # Synthetic SAMPLES for judges to test with 1-click | |
| SAMPLES = [ | |
| ("🏠 Residential Bastu · Porcha · Bengali", "RESIDENTIAL", STAGE_PORCHA, | |
| ["samples/bastu_porcha_pg1.jpg", "samples/owner_aadhaar.jpg"]), | |
| ("🌾 Agricultural Sali · Sale Deed · EN+BN", "AGRICULTURAL", STAGE_DALIL, | |
| ["samples/sali_dalil_pg1.jpg", "samples/sali_dalil_pg2.jpg"]), | |
| ] | |
| def package_info_md(code): | |
| pkg = DOC_TYPES.get(code) | |
| if not pkg: return "" | |
| return (f"**{pkg['name']}** \nProcess: {pkg.get('procedure', '—')}") | |
| def checklist_md(code, stage): | |
| if not code or not stage: | |
| return "_Select a transaction type and stage to see required documents._" | |
| pkg = DOC_TYPES.get(code) | |
| docs = required_documents(code, stage) | |
| lines = [f"### Required documents — {STAGE_LABELS[stage]}", f"_{pkg['name']}_", ""] | |
| for i, doc in enumerate(docs, 1): | |
| line = f"{i}. **{doc['label']}** — {doc['verify']}" | |
| lines.append(line) | |
| rules = pkg.get("rules", []) | |
| if rules: | |
| lines.append("\n**Legal Compliance checks:**") | |
| 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): | |
| 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 or not stage or not files: | |
| yield "⚠️ Please select type, stage, and upload documents." | |
| return | |
| pkg = DOC_TYPES.get(code) | |
| docs = required_documents(code, stage) | |
| paths = [getattr(f, "name", f) for f in files] | |
| yield f"⏳ **Analyzing {len(paths)} document(s)…** Projecting document image patches to Gemma 4 12B for Bengali evaluation." | |
| try: | |
| result = verify(pkg, STAGE_LABELS[stage], docs, pkg.get("rules", []), paths) | |
| except Exception as e: | |
| yield f"## ❌ Verification failed\n\nError: `{type(e).__name__}: {e}`" | |
| return | |
| yield result | |
| # --- Styling & UI -------------------------------------------------------------- | |
| THEME = gr.themes.Soft(primary_hue=gr.themes.colors.emerald) | |
| CSS = """ | |
| .gradio-container {max-width: 1500px !important;} | |
| #hero { background: linear-gradient(135deg, #059669 0%, #10b981 100%); color: #fff; border-radius: 16px; padding: 28px; } | |
| #hero h1 {margin: 0 0 8px 0; font-size: 31px; font-weight: 800;} | |
| .pill { display:inline-block; background: rgba(255,255,255,.2); border:1px solid rgba(255,255,255,.4); padding: 5px 13px; border-radius: 999px; font-size: 12.5px; margin: 0 8px 6px 0;} | |
| """ | |
| HERO = """ | |
| <div id="hero"> | |
| <h1>📜 PorchaPrahari (পরচা প্রহরী)</h1> | |
| <p>An AI pre-check for West Bengal land records. Upload your Bengali <b>Porcha</b> or <b>Dalil</b>. Gemma 4 reads the native Bengali text from the images, verifies mandatory clauses (Dag, Khatian, Mouza), and highlights discrepancies before you submit to Banglarbhumi or execute a real estate deal.</p> | |
| <div class="pills" style="margin-top: 10px;"> | |
| <span class="pill">🔍 Native Bengali Multimodal</span> | |
| <span class="pill">🤖 Gemma 4 12B (No External API)</span> | |
| <span class="pill">⚡ Anti-Fraud</span> | |
| </div> | |
| </div> | |
| """ | |
| with gr.Blocks(theme=THEME, css=CSS, title="PorchaPrahari") as demo: | |
| gr.HTML(HERO) | |
| with gr.Row(): | |
| sample_buttons = [gr.Button(s[0], size="sm") for s in SAMPLES] | |
| with gr.Row(): | |
| with gr.Column(scale=5): | |
| package_dd = gr.Dropdown(choices=[(v["name"], k) for k,v in DOC_TYPES.items()], label="Transaction Type") | |
| package_info = gr.Markdown() | |
| stage_radio = gr.Radio(choices=STAGE_RADIO_CHOICES, label="Document Stage") | |
| files_in = gr.File(label="Upload Documents (Images/PDFs)", file_count="multiple", file_types=["image", ".pdf"], height=160) | |
| verify_btn = gr.Button("🔎 Verify Bengali Land Records", variant="primary") | |
| with gr.Column(scale=5): | |
| with gr.Tabs(): | |
| with gr.Tab("📋 Required documents & checks"): | |
| checklist_out = gr.Markdown("_Select options to view checklist._") | |
| with gr.Tab("📎 View uploaded documents"): | |
| doc_gallery = gr.Gallery(columns=2, height=450, object_fit="contain") | |
| with gr.Group(elem_id="result-card"): | |
| result_out = gr.Markdown("_Verification report will appear here._") | |
| 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]) | |
| if __name__ == "__main__": | |
| demo.launch() |