PorchaPrahari / app.py
sugatobagchi's picture
Update app.py
0bb7e30 verified
Raw
History Blame Contribute Delete
7.61 kB
"""
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()