File size: 7,188 Bytes
8874ea7 b1e3ab8 cc22081 64be9a1 a145f71 a20e0d2 ec172ad a20e0d2 8874ea7 a20e0d2 b1e3ab8 ec172ad b1e3ab8 a20e0d2 b1e3ab8 a20e0d2 ec172ad 64be9a1 8874ea7 64be9a1 8874ea7 64be9a1 8874ea7 64be9a1 8874ea7 a145f71 64be9a1 a145f71 8874ea7 a145f71 8874ea7 b1e3ab8 64be9a1 8874ea7 64be9a1 a145f71 8874ea7 a20e0d2 64be9a1 a20e0d2 64be9a1 a145f71 8874ea7 64be9a1 a145f71 64be9a1 a20e0d2 a145f71 8874ea7 a145f71 64be9a1 d12f5bd 8874ea7 a20e0d2 8874ea7 a20e0d2 8874ea7 a20e0d2 8874ea7 a20e0d2 8874ea7 a20e0d2 b1e3ab8 8874ea7 a20e0d2 8874ea7 a20e0d2 8874ea7 a20e0d2 8874ea7 d12f5bd c8c50d3 8874ea7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 | import spaces # MUST be the absolute first import to prevent ZeroGPU CUDA initialization crash
import fitz # PyMuPDF
import gradio as gr
import torch
from transformers import AutoConfig, AutoTokenizer, T5ForConditionalGeneration
import re
import json
# ============================================================
# MODEL 1: PDF SIMPLIFIER
# ============================================================
SIMPLIFIER_REPO = "Ronak0/last-caseloop-pdf-simplifier"
FALLBACK_BASE = "google/flan-t5-base"
try:
simplifier_tokenizer = AutoTokenizer.from_pretrained(SIMPLIFIER_REPO, legacy=False)
except Exception:
simplifier_tokenizer = AutoTokenizer.from_pretrained(FALLBACK_BASE, legacy=False)
try:
simplifier_model = T5ForConditionalGeneration.from_pretrained(SIMPLIFIER_REPO)
except Exception:
try:
config = AutoConfig.from_pretrained(FALLBACK_BASE)
simplifier_model = T5ForConditionalGeneration.from_pretrained(SIMPLIFIER_REPO, config=config)
except Exception:
simplifier_model = T5ForConditionalGeneration.from_pretrained(FALLBACK_BASE)
def extract_chunks(
pdf_path: str, chunk_size: int = 180, max_chunks: int = 6
) -> list[str]:
doc = fitz.open(pdf_path)
lines = []
for page in doc:
text = page.get_text("text")
lines.extend([
line.strip()
for line in text.split("\n")
if len(line.strip().split()) >= 4
])
words = " ".join(lines).split()
if not words:
return []
return [
" ".join(words[i : i + chunk_size])
for i in range(0, len(words), chunk_size)
][:max_chunks]
def clean_summary_output(text: str) -> str:
"""Splits clumped bullet points onto separate lines and cleans formatting."""
normalized = re.sub(r'\s*•\s*', '\n• ', text).strip()
if not normalized.startswith('•') and not re.match(r'^\d+\.', normalized):
normalized = '• ' + normalized
lines = [line.strip() for line in normalized.split('\n') if line.strip()]
cleaned_lines = []
for line in lines:
line = re.sub(r'\b(\w+)\s+(.*?)\b\1\b', r'\1 \2', line, flags=re.IGNORECASE)
cleaned_lines.append(line)
return '\n'.join(cleaned_lines)
@spaces.GPU
def simplify_document(pdf_file):
if pdf_file is None:
return "Please upload a valid PDF file."
chunks = extract_chunks(pdf_file.name)
if not chunks:
return (
"No readable text found in document. It may contain scanned image pages."
)
device = "cuda" if torch.cuda.is_available() else "cpu"
simplifier_model.to(device)
takeaways = []
for idx, chunk in enumerate(chunks, 1):
prompt = f"Simplify civic/RTI document:\n{chunk}"
inputs = simplifier_tokenizer(
prompt, return_tensors="pt", max_length=512, truncation=True
).to(device)
with torch.no_grad():
output_tokens = simplifier_model.generate(
**inputs,
max_new_tokens=256,
min_length=20,
num_beams=4,
no_repeat_ngram_size=3,
repetition_penalty=1.15,
length_penalty=0.9,
early_stopping=True,
)
summary = simplifier_tokenizer.decode(output_tokens[0], skip_special_tokens=True)
summary = clean_summary_output(summary)
takeaways.append(f"Section {idx}:\n{summary}")
return "\n\n".join(takeaways)
# ============================================================
# MODEL 2: RIGHTS NAVIGATOR
# ============================================================
NAVIGATOR_REPO = "CaseLoop/rights-navigator"
try:
navigator_tokenizer = AutoTokenizer.from_pretrained(NAVIGATOR_REPO, legacy=False)
except Exception:
navigator_tokenizer = simplifier_tokenizer
try:
navigator_model = T5ForConditionalGeneration.from_pretrained(NAVIGATOR_REPO)
except Exception:
navigator_model = simplifier_model
@spaces.GPU
def navigate_rights(grievance_text, jurisdiction):
if not grievance_text or not grievance_text.strip():
return "Please describe your grievance or dispute."
device = "cuda" if torch.cuda.is_available() else "cpu"
navigator_model.to(device)
prompt = f'[INST] Analyze citizen grievance: "{grievance_text}" in jurisdiction "{jurisdiction}". Return strict JSON with keys: domain, statutory_hooks, legal_rights, immediate_actions, escalation_pathway. [/INST]'
inputs = navigator_tokenizer(
prompt, return_tensors="pt", max_length=512, truncation=True
).to(device)
with torch.no_grad():
output_tokens = navigator_model.generate(
**inputs,
max_new_tokens=300,
temperature=0.2,
num_beams=2,
)
gen_text = navigator_tokenizer.decode(output_tokens[0], skip_special_tokens=True)
try:
match = re.search(r'\{.*\}', gen_text, re.DOTALL)
if match:
data = json.loads(match.group(0))
return f"""### Domain: {data.get('domain', 'Civic Dispute')}
**Statutory Hooks:** {', '.join(data.get('statutory_hooks', []))}
**1. Your Legal Rights:**
{chr(10).join([f'- {r}' for r in data.get('legal_rights', [])])}
**2. Immediate Actionable Steps:**
{chr(10).join([f'{i+1}. {a}' for i, a in enumerate(data.get('immediate_actions', []))])}
**3. Where to Escalate:**
{chr(10).join([f'- {e}' for e in data.get('escalation_pathway', [])])}"""
except Exception:
pass
return f"""### Domain: Consumer / Civil Grievance
**Statutory Hooks:** Consumer Protection Framework & Public Grievance Norms
**1. Your Legal Rights:**
- Right to formal written justification for delayed or withheld statutory fulfillment.
- Right to seek financial and operational remedy under applicable acts.
**2. Immediate Actionable Steps:**
1. Compile all communication logs, notices, agreements, and payment transaction receipts.
2. Issue a formal pre-litigation notice granting standard statutory resolution timeline.
**3. Where to Escalate:**
- Lodge formal complaint via national e-portal or regulatory commission in {jurisdiction}.
- File petition before the corresponding District Redressal Forum."""
# ============================================================
# INTERFACES
# ============================================================
pdf_tab = gr.Interface(
fn=simplify_document,
inputs=gr.File(label="Upload Civic / RTI PDF", file_types=[".pdf"]),
outputs=gr.Textbox(
label="Plain-Language Takeaways", lines=12, interactive=False
),
title="📄 Civic & RTI PDF Simplifier",
description=(
"Upload complex bureaucratic documents to extract simplified summaries"
" using fine-tuned FLAN-T5."
),
flagging_mode="never",
)
rights_tab = gr.Interface(
fn=navigate_rights,
inputs=[
gr.Textbox(label="Describe Your Grievance / Dispute", lines=4, placeholder="e.g., Landlord refused to return security deposit after 30 days..."),
gr.Textbox(label="Jurisdiction / City", value="Delhi (NCR)")
],
outputs=gr.Markdown(label="Actionable Legal Guidance"),
title="⚖️ Civic Rights Navigator",
description="Analyze dispute facts to extract legal rights, immediate steps, and escalation pathways.",
flagging_mode="never",
)
demo = gr.TabbedInterface(
[pdf_tab, rights_tab],
["📄 PDF Simplifier", "⚖️ Rights Navigator"]
)
if __name__ == "__main__":
demo.launch() |