# ========================================================= # LEGAL GUIDANCE PLATFORM — v5 (Render-ready Flask app) # ========================================================= # ── 1) IMPORTS & SETUP ─────────────────────────────────── import os import json import secrets from datetime import datetime BASE_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_DIR = BASE_DIR KB_PATH = os.path.join(BASE_DIR, "legal_kb.json") with open(KB_PATH, "r", encoding="utf-8") as f: kb = json.load(f) print(f"✅ KB loaded — {len(kb)} scenarios.") # ── 4) FAISS INDEX ────────────────────────────────────── import faiss, numpy as np from sentence_transformers import SentenceTransformer print("⏳ Loading embedding model...") embed_model = SentenceTransformer("all-MiniLM-L6-v2") scenarios = [item["scenario"] for item in kb] embeddings = embed_model.encode(scenarios, convert_to_numpy=True).astype("float32") index = faiss.IndexFlatL2(embeddings.shape[1]) index.add(embeddings) print(f"✅ FAISS index built — {index.ntotal} scenarios.") # ── 5) GROQ ───────────────────────────────────────────── from groq import Groq GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") if not GROQ_API_KEY: print("⚠️ WARNING: GROQ_API_KEY environment variable is not set. " "Set it in your Render service's Environment settings.") groq_client = Groq(api_key=GROQ_API_KEY) print("✅ Groq configured.") # ── 6) CONSTANTS ───────────────────────────────────────── LEGAL_TERMS = { "FIR": "First Information Report — the first formal police complaint document.", "Legal Notice": "A formal written warning sent before taking stronger legal action.", "Jurisdiction": "The court or authority legally allowed to hear your case.", "Affidavit": "A written statement confirmed as true, signed before an authorized person.", "Annexure": "A supporting document attached to a complaint or petition.", "Acknowledgment": "Official proof that your complaint was received.", "Cognizable": "An offence where police can register a case without court permission.", "Escalation": "Taking your complaint to a higher authority if the first one doesn't act." } MODEL = "llama-3.3-70b-versatile" # ── SENSITIVE CASE DETECTION ───────────────────────────── SENSITIVE_KEYWORDS = [ "sexual assault","rape","molestation","pocso","minor","child abuse", "child sexual","trafficking","domestic violence","acid attack", "sexual harassment","stalking","revenge porn","intimate image", "ncrb","ndps minor","juvenile","underage","sexual offence" ] def is_sensitive_case(title, category, problem=""): combined = (title + " " + category + " " + problem).lower() return any(kw in combined for kw in SENSITIVE_KEYWORDS) # ── 7) CORE HELPERS ────────────────────────────────────── def search(query): qv = embed_model.encode([query], convert_to_numpy=True).astype("float32") dists, idxs = index.search(qv, 1) dist = float(dists[0][0]) matched = kb[int(idxs[0][0])] print(f"\n🔍 Match: '{matched['title']}' | dist: {dist:.4f}") return None if dist > 1.25 else matched def groq_chat(prompt, max_tokens=1500, temperature=0.2): r = groq_client.chat.completions.create( model=MODEL, messages=[{"role":"user","content":prompt}], max_tokens=max_tokens, temperature=temperature ) return r.choices[0].message.content.strip() def parse_json(text): text = text.replace("```json","").replace("```","").strip() s = text.find("{"); e = text.rfind("}") + 1 if s != -1 and e > s: text = text[s:e] return json.loads(text) def translate_text(text, language): if language == "English" or not str(text).strip(): return text try: return groq_chat( f"Translate exactly to {language}. Keep legal terms and section numbers unchanged. Return only translation:\n{text}", max_tokens=800, temperature=0.1 ) except: return text def translate_list(items, language): return [translate_text(str(x), language) for x in items] if language != "English" else items def format_questions(raw): """Convert raw question list (strings or dicts) into structured format.""" out = [] for q in raw: if isinstance(q, dict): question = q.get("question","").strip() input_type = q.get("input_type","text") options = q.get("options",[]) if not question: continue if input_type == "select" and isinstance(options, list) and options: out.append({"question":question,"input_type":"select","options":options}) else: out.append({"question":question,"input_type":"text"}) continue if not isinstance(q, str): continue q = q.strip() ql = q.lower() if ql.startswith(("did ","do ","have ","has ","is ","are ","was ","were ","can ","will ")): out.append({"question":q,"input_type":"select","options":["Yes","No","Not Sure"]}) elif any(x in ql for x in ["which upi","which app","which payment"]): out.append({"question":q,"input_type":"select","options":["PhonePe","Google Pay","Paytm","Bank App","Other"]}) elif "other party involved" in ql: out.append({"question":q,"input_type":"select","options":["Person","Company","Govt. Authority","Employer","Not Sure"]}) else: out.append({"question":q,"input_type":"text"}) return out[:8] # ── DYNAMIC QUESTION GENERATION ────────────────────────── def get_initial_questions(user_input, matched_case, language="English"): sensitive = is_sensitive_case(matched_case.get("title",""), matched_case.get("category",""), user_input) try: steps_text = "\n".join(matched_case["steps"]) docs_text = "\n".join(f"- {d}" for d in matched_case["documents"]) laws_text = "\n".join(f"- {l}" for l in matched_case["provisions"]) auth_text = matched_case["authority"] already_known = [] ul = user_input.lower() if any(w in ul for w in ["₹","rs.","rupee","amount","thousand","lakh"]): already_known.append("approximate amount/financial value") if any(w in ul for w in ["yesterday","today","last week","last month","ago","date"]): already_known.append("approximate time of incident") if any(w in ul for w in ["police","fir","complaint","filed"]): already_known.append("whether complaint was already filed") if any(w in ul for w in ["screenshot","proof","evidence","receipt","video","photo"]): already_known.append("some evidence exists") already_str = ", ".join(already_known) if already_known else "nothing specific yet" lang_note = f"\nIMPORTANT: Write ALL questions in {language} language only." if language != "English" else "" # Sensitive case: never ask for name, exact address, identity-revealing info sensitive_note = "" if sensitive: sensitive_note = """ CRITICAL — SENSITIVE CASE RULES: - NEVER ask for the complainant's full name, home address, exact location, or any identity-revealing information. - Do NOT ask "What is your name?" or "Where do you live?" or "What is your exact address?" - You MAY ask for the district or state (not street/house address) only if needed for jurisdiction. - Focus only on facts of the incident, evidence, timeline, and support sought. - Be gentle and trauma-informed in phrasing. Avoid clinical or blunt language. """ prompt = f"""You are a senior Indian lawyer doing a client intake for this EXACT case. {lang_note} {sensitive_note} USER'S PROBLEM (read carefully): "{user_input}" MATCHED LEGAL CASE: Category : {matched_case['category']} Title : {matched_case['title']} REQUIRED DOCUMENTS FOR THIS CASE: {docs_text} LEGAL STEPS THE CLIENT MUST TAKE: {steps_text} APPLICABLE LAWS: {laws_text} AUTHORITY TO APPROACH: {auth_text} ALREADY KNOWN FROM USER'S DESCRIPTION (DO NOT ASK AGAIN): {already_str} YOUR TASK: Generate 6-8 intake questions SPECIFIC to this exact case type and situation. RULES: 1. Every question must relate to a specific document, step, or law listed above 2. Ask about jurisdiction (state/district) unless sensitive case restricts it 3. Ask about urgency/ongoing risk 4. Ask about specific documents listed above 5. NEVER ask something generic 6. NEVER repeat what the user already told you {"7. DO NOT ask for name, home address, or identity details (sensitive case)" if sensitive else ""} OUTPUT as JSON only: {{ "complexity": "simple | medium | complex", "reason": "one sentence why this complexity level", "questions": [ {{"question": "...", "input_type": "text"}}, {{"question": "...", "input_type": "select", "options": ["Yes","No","Not Sure"]}} ] }}""" raw = groq_chat(prompt, max_tokens=1500, temperature=0.2) data = parse_json(raw) if "questions" not in data or not isinstance(data["questions"], list): raise ValueError("Bad response") # Filter out identity questions for sensitive cases if sensitive: identity_phrases = ["your name","full name","home address","house address","street address", "where do you live","residential address","address of","your address"] filtered = [] for q in data["questions"]: q_lower = q.get("question","").lower() if isinstance(q, dict) else str(q).lower() if not any(phrase in q_lower for phrase in identity_phrases): filtered.append(q) data["questions"] = filtered data["questions"] = format_questions(data["questions"]) if language != "English": translated = [] for q in data["questions"]: q2 = dict(q) q2["question"] = translate_text(q2["question"], language) if q2.get("options"): q2["options"] = translate_list(q2["options"], language) translated.append(q2) data["questions"] = translated data["reason"] = translate_text(data.get("reason",""), language) data["sensitive"] = sensitive if sensitive: data["complexity"] = "complex" data["reason"] = "Sensitive case requiring priority handling and careful legal support." print( f"✅ Generated {len(data['questions'])} questions " f"[{data.get('complexity','?')}] | Sensitive: {sensitive}" ) return data except Exception as e: print(f"❌ Question gen error: {e}") fallback_questions = _build_kb_fallback(matched_case, user_input, language, sensitive) return { "complexity": "medium", "reason": "Questions based on case requirements.", "questions": fallback_questions, "sensitive": sensitive } def _build_kb_fallback(matched_case, user_input, language="English", sensitive=False): questions = [] docs = matched_case.get("documents", []) steps = matched_case.get("steps", []) if not sensitive: questions.append({"question": "In which state and district did this incident occur?", "input_type": "text"}) else: questions.append({"question": "In which state (general region) did this incident occur?", "input_type": "text"}) for doc in docs[:2]: questions.append({ "question": f"Do you currently have the '{doc}'?", "input_type": "select", "options": ["Yes, I have it", "No, I need to get it", "Not Sure"] }) if steps: first_step = steps[0].replace("Step 1:","").strip() questions.append({"question": f"Regarding: {first_step} — have you done this yet?", "input_type": "select", "options": ["Yes","No","Partially"]}) questions += [ {"question": "When did this incident occur? Please give the date or approximate time.", "input_type": "text"}, {"question": "Have you already filed any formal complaint or taken any legal action?", "input_type": "select", "options": ["Yes","No","Not Sure"]}, {"question": "Is this problem still ongoing or causing you harm right now?", "input_type": "select", "options": ["Yes, still ongoing","No, it has stopped","Not Sure"]}, {"question": "What is the financial loss or damage amount involved (if any)?", "input_type": "text"} ] if not sensitive: questions.append({"question": "Who is the other party involved (name, designation, or company name if known)?", "input_type": "text"}) final = format_questions(questions[:8]) if language != "English": translated = [] for q in final: q2 = dict(q) q2["question"] = translate_text(q2["question"], language) if q2.get("options"): q2["options"] = translate_list(q2["options"], language) translated.append(q2) return translated return final def evaluate_answers_and_followup(user_input, matched_case, conversation_history, language="English"): try: history_text = "\n\n".join([f"Q: {h['question']}\nA: {h['answer']}" for h in conversation_history]) docs_text = "\n".join(f"- {d}" for d in matched_case["documents"]) prompt = f"""You are a legal intake reviewer. Case: {matched_case['title']} Problem: "{user_input}" Required documents: {docs_text} Interview so far: {history_text} Is there enough information to draft a complaint and suggest authority? If yes → ready: true If a CRITICAL fact is missing → ask max 2 very specific follow-up questions. Prefer ready:true unless truly critical info is missing. Return ONLY JSON: {{"ready": true}} OR {{"ready": false, "followups": [ {{"question":"...", "input_type":"text"}}, {{"question":"...", "input_type":"select","options":["Yes","No","Not Sure"]}} ]}}""" result = parse_json(groq_chat(prompt, max_tokens=400, temperature=0.2)) if not result.get("ready") and "followups" in result: result["followups"] = format_questions(result["followups"][:2]) if language != "English": translated = [] for q in result["followups"]: q2 = dict(q) q2["question"] = translate_text(q2["question"], language) if q2.get("options"): q2["options"] = translate_list(q2["options"], language) translated.append(q2) result["followups"] = translated return result except Exception as e: print(f"Follow-up error: {e}") return {"ready": True} def generate_final_analysis(user_input, matched_case, history, language="English"): try: history_text = "\n".join([f"Q: {h['question']}\nA: {h['answer']}" for h in history]) prompt = f"""You are a practical Indian legal assistant. User issue: "{user_input}" Case: {matched_case['category']} — {matched_case['title']} Interview answers: {history_text} Write a 5-6 sentence plain language summary for a common person. - Explain what kind of legal issue this is - Mention their strongest fact or evidence - Tell them the most important first action TODAY - Reassure them about their legal rights - Warn about any time-sensitive steps - No jargon. No promises of success. Speak directly to them.""" text = groq_chat(prompt, max_tokens=500, temperature=0.5) return translate_text(text, language) except Exception: fallback = ( f"Your issue is a {matched_case['category']} matter — {matched_case['title']}. " f"You should organize all facts, documents, and evidence immediately. " f"Do not delay the first complaint step if the issue is ongoing. " f"Keep copies of everything you submit." ) return translate_text(fallback, language) def get_location_from_history(history): for h in history: q = h["question"].lower() if any(w in q for w in ["state","district","location","where","city"]): return h["answer"].strip() return "" def calculate_evidence_strength(history, problem): text = (problem + " " + " ".join([h["answer"] for h in history])).lower() score = 0 found = [] evidence_map = { "screenshot":2,"receipt":2,"invoice":2,"recording":3,"audio":3, "video":3,"witness":2,"message":2,"chat":2,"email":2, "call log":1,"transaction id":2,"bank statement":2, "document":1,"agreement":2,"proof":1,"cctv":3,"fir":2 } for k, pts in evidence_map.items(): if k in text: score += pts found.append(k) if score >= 8: level = "Strong"; note = "You appear to have multiple useful forms of evidence." elif score >= 4: level = "Moderate"; note = "Some useful evidence exists. Collecting more will strengthen the case." else: level = "Weak"; note = "Very little concrete evidence visible. Collect stronger proof if possible." return {"level":level,"score":score,"note":note,"evidence_found":sorted(list(set(found)))[:8]} def detect_missing_info(matched_case, history, problem): text = (problem + " " + " ".join([h["answer"] for h in history])).lower() q_text = " ".join([h["question"].lower() for h in history]) missing = [] checks = [ ("Full name / complainant identity", ["name"]), ("Incident date or timeline", ["date","when","year"]), ("Location / state / district", ["state","district","location","where"]), ("Other party / accused / respondent", ["other party","officer","seller","respondent","who","name of"]), ("Evidence details", ["proof","evidence","screenshot","receipt","witness","recording"]), ("Prior complaint / FIR status", ["already filed","complaint","fir","police"]), ("Relief sought", ["result","want","relief","seeking","outcome"]), ] for label, kws in checks: if not any(k in q_text or k in text for k in kws): missing.append(label) for h in history: q = h["question"].lower() a = h["answer"].strip().lower() if a in ["","not sure","no idea","unknown","n/a","na"]: if any(w in q for w in ["state","district","location"]) and "Location" not in " ".join(missing): missing.append("Location / state / district") if any(w in q for w in ["date","when"]) and "Incident date" not in " ".join(missing): missing.append("Incident date or timeline") return missing[:5] def infer_high_risk(matched_case, history, problem): text = (problem + " " + " ".join([h["answer"] for h in history])).lower() title = matched_case.get("title","").lower() danger_words = [ "threat","kill","violence","urgent","ongoing","blackmail","suicide", "assault","sexual","abuse","harassment","attack","child","minor", "still happening","right now","emergency","scared","fear" ] if any(w in text for w in danger_words): return True if any(w in title for w in ["domestic violence","sexual","threat","harassment","acid","murder","stalking"]): return True return False def build_urgency(matched_case, history, problem): title = matched_case.get("title", "") category = matched_case.get("category", "") if is_sensitive_case(title, category, problem): return { "high_risk": True, "label": "🔴 Sensitive & High Priority", "timeline": [ "Immediately preserve all evidence.", "Contact police or relevant authority as soon as possible.", "Seek legal and emotional support if required.", "Maintain records of all communications and events." ] } high = infer_high_risk(matched_case, history, problem) if high: return { "high_risk": True, "label": "🔴 High Priority / Urgent", "timeline": [ "Immediately: secure personal safety and preserve all evidence.", "Today: contact the most relevant authority or emergency support.", "Within 24 hours: submit formal complaint and document every action." ] } return { "high_risk": False, "label": "🟡 Normal Priority", "timeline": [ "Today: organize facts, documents, and evidence.", "Within 1-3 days: submit written complaint to the correct authority.", "After submission: keep all copies and track progress regularly." ] } # ── DYNAMIC PORTAL GENERATION (LLM-powered) ────────────── def get_dynamic_portals(title, category, matched_case, problem=""): """Generate portals dynamically using LLM based on exact case classification.""" try: steps_text = "\n".join(matched_case.get("steps",[])[:3]) authority = matched_case.get("authority","") prompt = f"""You are an Indian legal expert. For this EXACT legal case, provide the most relevant official Indian government portals/websites. Case Title: {title} Category: {category} Authority: {authority} Steps summary: {steps_text} Problem context: {problem[:200]} Return ONLY a JSON array of 2-4 portals most relevant to THIS specific case: [ {{"label": "Portal Name", "url": "https://actual-url.gov.in", "description": "one line what to do here"}}, ... ] Rules: - URLs must be real Indian government portals (use .gov.in, .nic.in, .gov domains) - Pick portals WHERE THE USER ACTUALLY FILES/TRACKS their specific complaint - Be specific: prefer eDaakhil over generic consumer ministry, SCORES over generic SEBI, etc. - Include the primary filing portal AND one reference/helpline portal - No fake URLs. If unsure of exact URL, use the known official domain. Return only valid JSON array.""" raw = groq_chat(prompt, max_tokens=400, temperature=0.1) raw = raw.replace("```json","").replace("```","").strip() s = raw.find("["); e = raw.rfind("]") + 1 if s != -1 and e > s: portals = json.loads(raw[s:e]) if isinstance(portals, list) and len(portals) > 0: return portals[:4] except Exception as ex: print(f"Dynamic portal gen error: {ex}") # Minimal fallback — still case-aware return _get_portals_fallback(title, category) def _get_portals_fallback(title, category): tl = title.lower() if any(w in tl for w in ["cyber","upi","fraud","online","hack","phish"]): return [{"label":"National Cyber Crime Portal","url":"https://cybercrime.gov.in","description":"File cyber fraud complaint"}, {"label":"RBI Sachet","url":"https://sachet.rbi.org.in","description":"Report banking fraud"}] if any(w in tl for w in ["consumer","product","service","refund","defect"]): return [{"label":"eDaakhil Consumer Portal","url":"https://edaakhil.nic.in","description":"File consumer complaint"}, {"label":"National Consumer Helpline","url":"https://consumerhelpline.gov.in","description":"Helpline & grievance"}] if any(w in tl for w in ["labour","salary","employment","epf","gratuity"]): return [{"label":"Shram Suvidha Portal","url":"https://shramsuvidha.gov.in","description":"Labour grievance"}, {"label":"EPFO Portal","url":"https://www.epfindia.gov.in","description":"PF complaints"}] if any(w in tl for w in ["rti","information","public"]): return [{"label":"RTI Online Portal","url":"https://rtionline.gov.in","description":"File RTI application"}] if any(w in tl for w in ["sebi","stock","broker","mutual fund"]): return [{"label":"SEBI SCORES Portal","url":"https://scores.sebi.gov.in","description":"File investor complaint"}] if any(w in tl for w in ["rera","builder","flat","construction"]): return [{"label":"RERA Portal","url":"https://rera.gov.in","description":"Builder/property complaint"}] if any(w in tl for w in ["bribery","corruption","vigilance"]): return [{"label":"Central Vigilance Commission","url":"https://www.cvc.gov.in","description":"Corruption complaint"}, {"label":"CBI","url":"https://cbi.gov.in","description":"Serious corruption cases"}] if any(w in tl for w in ["tax","gst","income tax"]): return [{"label":"Income Tax Portal","url":"https://www.incometax.gov.in","description":"Tax grievances"}, {"label":"GST Portal","url":"https://www.gst.gov.in","description":"GST issues"}] if any(w in tl for w in ["domestic violence","women","harassment"]): return [{"label":"National Commission for Women","url":"https://ncw.nic.in","description":"Women's rights complaint"}, {"label":"WCD Ministry","url":"https://wcd.nic.in","description":"Women & child welfare"}] if any(w in tl for w in ["insurance"]): return [{"label":"IRDAI Bima Bharosa","url":"https://bimabharosa.irdai.gov.in","description":"Insurance complaint"}] if any(w in tl for w in ["passport"]): return [{"label":"Passport Seva","url":"https://www.passportindia.gov.in","description":"Passport grievance"}] return [{"label":"India Government Services","url":"https://www.india.gov.in","description":"Central government portal"}, {"label":"CPGRAMS Grievance Portal","url":"https://pgportal.gov.in","description":"Public grievance filing"}] def build_guidance(matched_case, history, problem, location=""): title = matched_case.get("title","") category = matched_case.get("category","Civil") steps = matched_case.get("steps",[]) docs = matched_case.get("documents",[]) laws = matched_case.get("provisions",[]) authority = matched_case.get("authority","Concerned Authority") step_help = [] for i, step in enumerate(steps[:5]): clean = step.replace(f"Step {i+1}:","").replace(f"Step {i+1}.","").strip() doc_hint = docs[i] if i < len(docs) else "relevant identity and case documents" step_help.append({ "title": clean[:80] + ("..." if len(clean) > 80 else ""), "how": clean, "carry": doc_hint, "outcome": f"This moves your case forward under {laws[0] if laws else 'applicable law'}." }) # Dynamic portals via LLM portals = get_dynamic_portals(title, category, matched_case, problem) safety = _get_safety_advice(title, category, history, problem) authority_chain = authority.split("→") escalation = [] for i in range(len(authority_chain)-1): curr = authority_chain[i].strip() nxt = authority_chain[i+1].strip() escalation.append(f"If {curr} does not respond or resolve the issue, escalate to {nxt} with copies of all prior complaints.") if location: authority = f"{authority} (in your area: {location})" urgent_actions = _get_urgent_actions(title, category) return { "portals": portals, "step_help": step_help, "safety_advice": safety, "escalation": escalation, "urgent_actions":urgent_actions, "authority_name":authority, "template_type": f"{category} — {title}" } def _get_safety_advice(title, category, history, problem): tl = title.lower() text = (problem + " " + " ".join([h["answer"] for h in history])).lower() advice = [ "Keep copies of all documents, complaints, and communications.", "Use written/email communication rather than only phone calls for a paper trail.", "Do not share sensitive personal information with unknown parties.", "Track all complaint reference numbers and submission dates." ] if any(w in tl for w in ["cyber","fraud","hack","phish","upi"]): advice = [ "Do not share OTP, PIN, or remote access with anyone claiming to be from a bank.", "Immediately report to your bank and block the affected account if money is missing.", "Preserve all digital evidence — do not delete messages, screenshots, or call logs.", "Report to cybercrime.gov.in as soon as possible — speed improves recovery chances." ] elif any(w in tl for w in ["domestic","violence","abuse","assault","harassment","sexual"]): advice = [ "Prioritize your personal safety above everything else.", "Document injuries with medical reports and photographs.", "Reach out to a trusted person or shelter if you are in immediate danger.", "Women's Helpline: 181 | Emergency: 100 | POCSO Helpline: 1098" ] elif any(w in tl for w in ["bribery","corruption"]): advice = [ "Do not inform the accused that you are planning to file a complaint.", "Keep detailed records of every demand — amount, date, purpose, and the officer's details.", "Never pay voluntarily unless part of a coordinated lawful process under official advice.", "Preserve any messages, call records, or written demands." ] elif any(w in tl for w in ["cheque bounce"]): advice = [ "The demand notice must be sent within 30 days of receiving cheque return memo.", "If no payment after 15 days of notice, file a criminal complaint under Section 138 NI Act.", "Keep the original cheque and bank return memo safely — these are primary evidence.", "Time limits are strict in cheque bounce cases — act promptly." ] elif any(w in tl for w in ["property","land","encroachment"]): advice = [ "Get an official land survey done before any confrontation.", "Do not remove any physical markers or structures without legal process.", "Keep all title documents, mutation records, and tax receipts safely.", "Approach Revenue Court for record corrections in addition to Civil Court." ] return advice def _get_urgent_actions(title, category): tl = title.lower() if any(w in tl for w in ["cyber","fraud","hack","upi"]): return ["Today: contact bank, block account, preserve evidence.", "Within 24 hours: file cyber complaint online."] if any(w in tl for w in ["cheque bounce"]): return ["Within 30 days of cheque return memo: send legal demand notice.", "Act before limitation period expires."] if any(w in tl for w in ["domestic","violence","abuse","sexual"]): return ["Immediately: ensure safety and get medical attention.", "Today: approach police or Protection Officer."] return ["Today: organize facts and evidence.", "Within 1-3 days: submit written complaint."] def generate_complaint(matched_case, history, problem, guidance, language="English"): authority = guidance["authority_name"] subject = f"Complaint regarding {matched_case.get('title','legal issue')}" def get_answer(kws): for h in history: if all(k in h["question"].lower() for k in kws): return h["answer"].strip() return "" name = get_answer(["name"]) or "Complainant" date_inc = get_answer(["date"]) or get_answer(["when"]) or "As per records" location = get_answer(["state","district"]) or get_answer(["location"]) or "As per records" other = get_answer(["other party"]) or get_answer(["who"]) or "As per records" evidence = get_answer(["proof"]) or get_answer(["evidence"]) or "Documents available with complainant" relief = get_answer(["relief"]) or get_answer(["result"]) or get_answer(["want"]) or "Appropriate legal action as per law" complaint = f"""To, The {authority} Subject: {subject} Respected Sir/Madam, I, {name}, respectfully submit this complaint regarding the legal matter described below. 1. Nature of Issue: {matched_case.get('title','')} ({matched_case.get('category','')} matter) 2. Date / Timeline of Incident: {date_inc} 3. Location of Incident: {location} 4. Details of Other Party / Respondent: {other} 5. Facts of the Case: {problem.strip()} 6. Evidence Available: {evidence} 7. Applicable Legal Provisions: {", ".join(matched_case.get("provisions",[])[:3])} 8. Relief / Action Requested: {relief} I request your office to take prompt legal action as per law. I am ready to provide all supporting documents and further information as needed. I affirm that the above facts are true to the best of my knowledge. Yours faithfully, {name} Date: {datetime.now().strftime('%d-%m-%Y')} Note: This is a draft. Please review and add any additional facts before submission. """ return translate_text(complaint, language) def get_editable_fields(history, problem, language="English"): def ga(kws): for h in history: if all(k in h["question"].lower() for k in kws): return h["answer"].strip() return "" fields = [ {"key":"name", "label":"Complainant Name", "value": ga(["name"]) or ""}, {"key":"date_of_incident","label":"Incident Date / Timeline", "value": ga(["date"]) or ga(["when"]) or ""}, {"key":"location", "label":"Location / State / District","value": ga(["state","district"]) or ga(["location"]) or ""}, {"key":"other_party", "label":"Other Party / Respondent", "value": ga(["other party"]) or ga(["who"]) or ""}, {"key":"evidence", "label":"Available Evidence", "value": ga(["proof"]) or ga(["evidence"]) or ""}, {"key":"relief", "label":"Relief Requested", "value": ga(["relief"]) or ga(["result"]) or ""}, ] if language != "English": for f in fields: f["label"] = translate_text(f["label"], language) return fields def evaluate_readiness(uploaded_docs, documents_required): n = len(uploaded_docs) t = len(documents_required) s = int((n/t)*100) if t > 0 else 0 if s >= 100: return {"score":s,"status":"✅ All Documents Uploaded","color":"green","message":"All required documents are marked uploaded. You are ready to proceed."} elif s >= 50: return {"score":s,"status":"⚠️ Partially Ready","color":"orange","message":"Some documents uploaded. Collect the remaining ones before proceeding."} else: return {"score":s,"status":"⛔ Not Ready","color":"red","message":"Please collect and upload the required documents before filing."} def build_finalized_payload(sess): problem = sess["problem"] matched = sess["matched"] history = sess["history"] language = sess.get("language","English") uploaded = sess.get("uploaded_docs",[]) location = get_location_from_history(history) friendly = generate_final_analysis(problem, matched, history, language) guidance = build_guidance(matched, history, problem, location) missing = detect_missing_info(matched, history, problem) evidence = calculate_evidence_strength(history, problem) urgency = build_urgency(matched, history, problem) readiness = evaluate_readiness(uploaded, matched["documents"]) complaint = generate_complaint(matched, history, problem, guidance, language) editable = sess.get("editable_fields") or get_editable_fields(history, problem, language) if language != "English": guidance["authority_name"] = translate_text(guidance["authority_name"], language) guidance["safety_advice"] = translate_list(guidance["safety_advice"], language) guidance["escalation"] = translate_list(guidance["escalation"], language) for step in guidance["step_help"]: step["title"] = translate_text(step["title"], language) step["how"] = translate_text(step["how"], language) step["carry"] = translate_text(step["carry"], language) step["outcome"] = translate_text(step["outcome"], language) missing = translate_list(missing, language) evidence["note"] = translate_text(evidence["note"], language) urgency["label"] = translate_text(urgency["label"], language) urgency["timeline"] = translate_list(urgency["timeline"], language) readiness["status"] = translate_text(readiness["status"], language) readiness["message"]= translate_text(readiness["message"], language) terms = [{"term":k,"explanation":v} for k,v in list(LEGAL_TERMS.items())[:6]] return { "category": matched["category"], "title": matched["title"], "friendly": friendly, "documents": matched["documents"], "provisions": matched["provisions"], "authority": guidance["authority_name"], "readiness": readiness, "safety_advice": guidance["safety_advice"], "step_help": guidance["step_help"], "complaint_text": complaint, "missing_info": missing, "evidence": evidence, "urgency": urgency, "portals": guidance["portals"], "escalation": guidance["escalation"], "legal_terms": terms, "editable_fields": editable, "progress_tracker":[ {"key":"facts_ready", "label":"Facts organized"}, {"key":"docs_ready", "label":"Documents collected"}, {"key":"complaint_drafted", "label":"Complaint draft prepared"}, {"key":"authority_found", "label":"Authority identified"}, {"key":"complaint_filed", "label":"Complaint submitted"}, {"key":"followup", "label":"Follow-up done"}, ], "next_actions": [ {"label":"View Steps", "type":"scroll_steps"}, {"label":"View Documents", "type":"scroll_docs"}, {"label":"Draft Complaint", "type":"open_complaint"}, ] } # ── 8) FLASK APP ───────────────────────────────────────── from flask import Flask, request, jsonify, render_template_string app = Flask(__name__) app.secret_key = os.environ.get("FLASK_SECRET_KEY", secrets.token_hex(16)) sessions = {} # ─── COMPLAINT PAGE HTML ───────────────────────────────── COMPLAINT_HTML = r""" Complaint Draft — Legal Guidance Platform

Legal Guidance Platform

Complaint Draft & Export

← Back to Summary
🧾

Smart Complaint Draft

Edit the fields below and regenerate your complaint. Download as PDF when ready.

✍️
Edit Complaint Details
💡Fill in the fields below accurately. Click "Update Draft" to regenerate the complaint with your changes.
📄
Complaint Text

Generating PDF...

""" # ─── MAIN PAGE HTML ────────────────────────────────────── HTML = r""" Legal Guidance Platform
⚖️

Legal Guidance Platform

Personalized Indian legal guidance — step by step

Step 1 of 3 — Describe your problem 0%
📝
Describe Your Legal Problem

Explain what happened in your own words. Include names, dates, amounts, location, and any evidence you have. The more detail you provide, the more specific the guidance.

Analyzing your problem...

📋
Case Summary
🚨 High-Risk / Urgent Alert
⏱️
Urgency & Timeline
    🧠
    Evidence Strength
    Missing Information
    ⚠️
    Safety & Risk Advice
      🏛️
      Suggested Authority
      🌐
      Official Portals
        ⬆️
        Escalation Path
          👣
          Legal Workflow Checklist
          0 of 0 steps completed
            📌
            Case Progress Tracker
            📁
            Document Checklist
            0 of 0 documents uploaded
              Document Readiness
              📜
              Applicable Laws
                📘
                Plain-Language Legal Terms
                  Ready to draft your complaint?

                  Edit your complaint details, regenerate the draft, and download it as a PDF — all in one place.

                  """ # ── 9) ROUTES ──────────────────────────────────────────── @app.route("/") def home(): return render_template_string(HTML) @app.route("/health") def health(): return jsonify({"status": "ok", "kb_size": len(kb)}) @app.route("/complaint_page") def complaint_page(): return render_template_string(COMPLAINT_HTML) @app.route("/generate_pdf", methods=["POST"]) def generate_pdf(): """Generate a PDF from the complaint text using reportlab.""" from io import BytesIO from flask import make_response from reportlab.lib.pagesizes import A4 from reportlab.lib.styles import ParagraphStyle from reportlab.lib.units import cm from reportlab.lib import colors from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, HRFlowable from reportlab.lib.enums import TA_LEFT, TA_CENTER try: data = request.json or {} complaint_text = (data.get("complaint_text") or "").strip() if not complaint_text: return jsonify({"error": "No complaint text provided"}), 400 buf = BytesIO() doc = SimpleDocTemplate( buf, pagesize=A4, leftMargin=2.8*cm, rightMargin=2.5*cm, topMargin=2.5*cm, bottomMargin=2.5*cm ) navy = colors.HexColor("#0B1F45") gold = colors.HexColor("#C9A84C") gray = colors.HexColor("#4B5563") dark = colors.HexColor("#1F2937") light_gray = colors.HexColor("#E5E7EB") title_style = ParagraphStyle("LGTitle", fontName="Helvetica-Bold", fontSize=16, textColor=navy, spaceAfter=4, alignment=TA_CENTER) sub_style = ParagraphStyle("LGSub", fontName="Helvetica", fontSize=10, textColor=gray, spaceAfter=12, alignment=TA_CENTER) body_style = ParagraphStyle("LGBody", fontName="Helvetica", fontSize=11, textColor=dark, spaceAfter=5, leading=17, alignment=TA_LEFT, wordWrap="LTR") note_style = ParagraphStyle("LGNote", fontName="Helvetica-Oblique", fontSize=9, textColor=gray, spaceAfter=0, alignment=TA_LEFT) story = [] story.append(Paragraph("Legal Complaint Draft", title_style)) story.append(Paragraph( "Generated on: " + datetime.now().strftime("%d %B %Y"), sub_style)) story.append(HRFlowable(width="100%", thickness=1.5, color=gold, spaceAfter=10)) story.append(Spacer(1, 4)) for line in complaint_text.split("\n"): stripped = line.strip() if not stripped: story.append(Spacer(1, 4)) else: # Convert to ASCII safely — keeps numbers, punctuation, section refs intact safe = stripped.encode("ascii", "replace").decode("ascii") # Escape XML special characters required by ReportLab Paragraph safe = safe.replace("&", "&").replace("<", "<").replace(">", ">") story.append(Paragraph(safe, body_style)) story.append(Spacer(1, 16)) story.append(HRFlowable(width="100%", thickness=0.5, color=light_gray, spaceAfter=6)) story.append(Paragraph( "This document was generated by the Legal Guidance Platform. " "Please review and verify all details before submission.", note_style )) doc.build(story) pdf_bytes = buf.getvalue() response = make_response(pdf_bytes) response.headers["Content-Type"] = "application/pdf" response.headers["Content-Disposition"] = "attachment; filename=legal_complaint_draft.pdf" response.headers["Content-Length"] = str(len(pdf_bytes)) response.headers["Cache-Control"] = "no-cache, no-store, must-revalidate" return response except Exception as e: import traceback traceback.print_exc() return jsonify({"error": "PDF generation failed: " + str(e)}), 500 @app.route("/start_session", methods=["POST"]) def start_session(): try: data = request.json or {} problem = data.get("problem","")[:2000] session_id = data.get("session_id", secrets.token_hex(8)) language = data.get("language","English") matched = search(problem) if matched is None: return jsonify({"error":"unrecognized"}), 422 result = get_initial_questions(problem, matched, language) sessions[session_id] = { "problem": problem, "matched": matched, "history": [], "complexity": result.get("complexity","medium"), "language": language, "uploaded_docs": [], "editable_fields": [] } return jsonify({ "questions": result.get("questions",[]), "complexity": result.get("complexity","medium"), "reason": result.get("reason",""), "category": matched["category"], "title": matched["title"], "sensitive": result.get("sensitive", False) }) except Exception as e: return jsonify({"error":str(e)}), 500 @app.route("/submit_answers", methods=["POST"]) def submit_answers(): try: data = request.json or {} session_id = data.get("session_id","") answers = data.get("answers",{}) if session_id not in sessions: return jsonify({"error":"Session not found"}), 400 sess = sessions[session_id] problem = sess["problem"] matched = sess["matched"] language = sess.get("language","English") for q, a in answers.items(): sess["history"].append({"question":q,"answer":a}) if len(sess["history"]) >= 8: return jsonify({"ready":True}) eval_result = evaluate_answers_and_followup(problem, matched, sess["history"], language) if eval_result.get("ready", True): return jsonify({"ready":True}) else: return jsonify({ "ready": False, "followups": eval_result.get("followups",[])[:2], "complexity": sess["complexity"], "reason": "Clarifying missing facts", "category": matched["category"], "title": matched["title"] }) except Exception as e: return jsonify({"error":str(e)}), 500 @app.route("/finalize", methods=["POST"]) def finalize(): try: data = request.json or {} session_id = data.get("session_id","") if session_id not in sessions: return jsonify({"error":"Session not found"}), 400 sess = sessions[session_id] payload = build_finalized_payload(sess) sess["finalized_result"] = payload return jsonify(payload) except Exception as e: return jsonify({"error":str(e)}), 500 @app.route("/update_uploaded_docs", methods=["POST"]) def update_uploaded_docs(): try: data = request.json or {} session_id = data.get("session_id","") uploaded = data.get("uploaded_docs",[]) if session_id not in sessions: return jsonify({"error":"Session not found"}), 400 sess = sessions[session_id] sess["uploaded_docs"] = uploaded readiness = evaluate_readiness(uploaded, sess["matched"]["documents"]) lang = sess.get("language","English") if lang != "English": readiness["status"] = translate_text(readiness["status"], lang) readiness["message"] = translate_text(readiness["message"], lang) return jsonify({"success":True,"readiness":readiness}) except Exception as e: return jsonify({"error":str(e)}), 500 @app.route("/update_complaint_fields", methods=["POST"]) def update_complaint_fields(): try: data = request.json or {} session_id = data.get("session_id","") editable_fields = data.get("editable_fields",[]) if session_id not in sessions: return jsonify({"error":"Session not found"}), 400 sess = sessions[session_id] sess["editable_fields"] = editable_fields fm = {item["key"]:item["value"] for item in editable_fields} matched = sess["matched"] authority = matched.get("authority","Concerned Authority") subject = f"Complaint regarding {matched.get('title','legal issue')}" complaint = f"""To, The {authority} Subject: {subject} Respected Sir/Madam, I, {fm.get('name','Complainant')}, respectfully submit this complaint. 1. Nature of Issue: {matched.get('title','')} ({matched.get('category','')}) 2. Date / Timeline: {fm.get('date_of_incident','Not specified')} 3. Location: {fm.get('location','Not specified')} 4. Other Party: {fm.get('other_party','Not specified')} 5. Facts: {sess['problem']} 6. Evidence: {fm.get('evidence','Not specified')} 7. Relief Requested: {fm.get('relief','Appropriate legal action')} Yours faithfully, {fm.get('name','Complainant')} Date: {datetime.now().strftime('%d-%m-%Y')}""" lang = sess.get("language","English") if lang != "English": complaint = translate_text(complaint, lang) return jsonify({"success":True,"complaint_text":complaint}) except Exception as e: return jsonify({"error":str(e)}), 500 @app.route("/save_session", methods=["POST"]) def save_session(): try: data = request.json or {} session_id = data.get("session_id","") if session_id not in sessions: return jsonify({"error":"Session not found"}), 400 sess = sessions[session_id] if "finalized_result" not in sess: try: sess["finalized_result"] = build_finalized_payload(sess) except: pass path = os.path.join(PROJECT_DIR, f"session_{session_id}.json") with open(path,"w",encoding="utf-8") as f: json.dump(sess, f, ensure_ascii=False, indent=2) return jsonify({"success":True,"message":f"Saved. Session ID: {session_id}"}) except Exception as e: return jsonify({"error":str(e)}), 500 # ── 10) LAUNCH (local dev only — Render uses gunicorn via Procfile) ────── if __name__ == "__main__": port = int(os.environ.get("PORT", 5000)) app.run(host="0.0.0.0", port=port, debug=False)