""" 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 = """

📜 PorchaPrahari (পরচা প্রহরী)

An AI pre-check for West Bengal land records. Upload your Bengali Porcha or Dalil. 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.

🔍 Native Bengali Multimodal 🤖 Gemma 4 12B (No External API) ⚡ Anti-Fraud
""" 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()