diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,934 +1,674 @@ """ ╔══════════════════════════════════════════════════════════════╗ -║ Rahbar | رہبر ║ -║ Pakistan's AI-Powered Civic Complaint System v5.0 ║ +║ Rahbar | رہبر v6.0 ║ +║ Pakistan's AI-Powered Civic Complaint System ║ ╚══════════════════════════════════════════════════════════════╝ -Gradio 4/5/6 compatible -Fixes v5.0: - - GPS button fully working (no js= hack, pure Python + JS injection) - - Map rendered via gr.HTML with working Leaflet (not sandboxed) - - Sindhi TTS fixed (uses 'ur' fallback since gtts has no 'sd') - - Chatbot with voice input + TTS output - - All UI text in Urdu (not Hindi/Punjabi heading labels) - - Gradio 6 compatible event wiring + +v6.0 Changes: + ✅ English as primary UI language (Urdu as optional output language) + ✅ GPS map fully working — uses gr.HTML with Leaflet + Python reverse geocode + ✅ GPS button triggers Python function → updates location textbox + ✅ PDF report download (ReportLab) + ✅ Voice input + TTS output in chatbot + ✅ CSS in launch() — Gradio 6 correct placement + ✅ No theme in gr.Blocks() — Gradio 6 correct placement + ✅ Chatbot uses plain list-of-dicts (no ChatMessage import needed) """ -import os, io, re, uuid, base64, datetime, urllib.parse +import os, io, re, uuid, base64, datetime, urllib.parse, json, urllib.request from PIL import Image import gradio as gr +# ─── ENV ────────────────────────────────────────────────────── GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", "") GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") - -complaint_log = [] +complaint_log = [] # ─── RAG KNOWLEDGE BASE ─────────────────────────────────────── RAG_DOCUMENTS = [ - { - "id": "garbage_001", - "category": "Garbage", - "title": "Punjab Waste Management Act 2014 — Citizen Rights", - "content": """Under Punjab Waste Management Act 2014 any citizen can file a garbage complaint. -Fine ranges from Rs. 500 to 50,000. Local government must act within 48 hours. -Responsible body: Solid Waste Management Board. Helpline: 1139. -Citizens have the right to demand a written response and escalate to CM Portal if ignored.""", - "laws": ["Punjab Waste Management Act 2014", "Pakistan Environmental Protection Act 1997 Section 11", "Punjab Local Government Act 2022 Schedule II"], - "hotline": "1139", "authority": "Solid Waste Management Board / Local Government", - "response_time": "48 hours", "fine": "Rs. 500 – 50,000", - }, - { - "id": "garbage_002", - "category": "Garbage", - "title": "Urban Solid Waste — City-level Responsibility", - "content": """Failure to collect garbage in urban areas is a serious violation. -EPA 1997 Section 11 prohibits any institution from polluting the environment. -If garbage is not collected for over a week it becomes Public Nuisance — PPC Section 268. -Lahore: LWMC — 042-111-222-888. Karachi: KMC — 021-99231677.""", - "laws": ["PPC Section 268 Public Nuisance", "Punjab Waste Management Act 2014", "EPA 1997 Section 11"], - "hotline": "1139", "authority": "LWMC Lahore / KMC Karachi / Local Solid Waste Board", - "response_time": "48 hours", "fine": "Rs. 500 – 50,000", - }, - { - "id": "garbage_escalation", - "category": "Garbage", - "title": "Garbage Complaint Escalation Ladder", - "content": """If the local authority fails to act within 48 hours: -1. Contact Union Council / Neighbourhood Nazim -2. Submit written application at Deputy Commissioner office -3. CM Complaint Cell — 0800-02345 (toll-free) -4. Pakistan Citizen Portal — citizenportal.gov.pk -5. Federal Ombudsman — 051-9204551 -6. High Court Writ Petition under Article 9 -You are entitled to compensation if health was affected — EPA 1997 Section 14.""", - "laws": ["Constitution Article 9 & 14", "EPA 1997 Section 14", "PPC Section 268"], - "hotline": "0800-02345", "authority": "CM Complaints Cell / Federal Ombudsman", - "response_time": "3 working days", "fine": "Compensation claimable", - }, - { - "id": "pothole_001", - "category": "Pot Hole", - "title": "National Highways Safety Ordinance 2000 — Pothole Rights", - "content": """Road potholes are the responsibility of NHA (National Highway Authority). -Under National Highways Safety Ordinance 2000, road repairs must be done within 72 hours. -Punjab LGA 2022 Section 54 — LDA and C&W Department are also responsible. -If your vehicle is damaged, you can claim compensation — Motor Vehicles Ordinance 1965. -NHA Helpline: 051-9032800. LDA Lahore: 042-99230215.""", - "laws": ["National Highways Safety Ordinance 2000", "Punjab LGA 2022 Section 54", "Motor Vehicles Ordinance 1965"], - "hotline": "051-9032800", "authority": "NHA / C&W Department / LDA", - "response_time": "72 hours", "fine": "Authority liable for vehicle damage", - }, - { - "id": "pothole_002", - "category": "Pot Hole", - "title": "Road Accident Due to Pothole — Legal Recourse", - "content": """If an accident occurs due to a road pothole: -1. Immediately file a police report -2. Photograph the scene with date/time -3. Send a written notice to NHA or LDA -4. File a Negligence claim in court under Tort Law -5. File complaint with Federal Ombudsman — 051-9204551 -6. File High Court Writ Petition -Road Quality Inspection Reports are published at nha.gov.pk.""", - "laws": ["Tort Law Negligence", "NHA Safety Ordinance 2000", "Constitution Article 9"], - "hotline": "051-9204551", "authority": "Federal Ombudsman / High Court", - "response_time": "Court timeline", "fine": "Compensation for injury/damage", - }, - { - "id": "water_001", - "category": "Pipe Leakage", - "title": "Punjab Water Act 2019 — Pipe Leakage Rights", - "content": """Under Punjab Water Act 2019 Section 23, WASA must repair pipe leakage within 24 hours. -If not repaired within 24 hours, file complaint at DC office. -WASA can be fined Rs. 10,000 to 5,00,000. -WASA Lahore Helpline: 042-99200300. WASA Karachi (KWSB): 021-99231677. WASA Rawalpindi: 051-5594244. -Per Supreme Court 2018 ruling, clean water is a fundamental right — PLD 2018 SC 1.""", - "laws": ["Punjab Water Act 2019 Section 23", "WASA Act Bylaws", "Constitution Article 9"], - "hotline": "042-99200300", "authority": "WASA / Pakistan Water Authority", - "response_time": "24 hours", "fine": "Rs. 10,000 – 5,00,000", - }, - { - "id": "water_002", - "category": "Pipe Leakage", - "title": "Contaminated Water and Health — Legal Rights", - "content": """If water is contaminated or made dirty by leakage: -EPA 1997 Section 13 makes it a crime to pollute water sources. -National Drinking Water Policy 2009 mandates WHO-standard water supply. -You can claim compensation if contaminated water causes illness — EPA 1997. -You can suspend payment if water is contaminated — WASA Bylaws. -Wrong billing: Contact WASA Ombudsman.""", - "laws": ["EPA 1997 Section 13", "National Drinking Water Policy 2009", "Punjab Water Act 2019"], - "hotline": "042-99200300", "authority": "WASA / Pakistan Water Authority / EPA", - "response_time": "24-48 hours", "fine": "Compensation for health damage", - }, - { - "id": "water_escalation", - "category": "Pipe Leakage", - "title": "WASA Did Not Act — Escalation Steps", - "content": """If WASA fails to repair within 24 hours: -1. Call WASA helpline again and note the complaint number -2. Submit written application at nearest WASA office -3. File complaint at Deputy Commissioner office -4. CM Complaint Cell: 0800-02345 (toll-free) -5. Pakistan Citizen Portal: citizenportal.gov.pk -6. Pakistan Water Authority: 051-9246150 -7. Federal Ombudsman: 051-9204551 -8. High Court — Writ Petition under Article 9 -Keep evidence: photo, date, complaint number.""", - "laws": ["Punjab Water Act 2019", "Constitution Article 9", "EPA 1997"], - "hotline": "0800-02345", "authority": "CM Complaints Cell / PWA / Federal Ombudsman", - "response_time": "Escalation pathway", "fine": "Rs. 10,000 – 5,00,000 + compensation", - }, - { - "id": "rights_001", - "category": "General", - "title": "Fundamental Rights of Pakistani Citizens", - "content": """Your fundamental rights under Pakistan's Constitution: -Article 9: Right to Life — includes clean water and clean environment (SC 2018 ruling) -Article 14: Right to Dignity — polluted environment violates your dignity -Article 19A: Right to Information — you can request information from any public body -Pakistan Citizen Portal complaints must receive a legal response. -You have the right to file an FIR if a public body fails to act.""", - "laws": ["Constitution Article 9", "Constitution Article 14", "Constitution Article 19A"], - "hotline": "0800-02345", "authority": "High Court / Supreme Court / Federal Ombudsman", - "response_time": "3 working days", "fine": "Authority accountable", - }, - { - "id": "rights_002", - "category": "General", - "title": "How to File a Civic Complaint — Complete Guide", - "content": """Correct procedure for filing a civic complaint: -1. Photograph the issue with date and time -2. Note the exact location — street, area, city -3. Call the relevant helpline and obtain a complaint number -4. If no action within 48-72 hours, use CM Portal -5. Pakistan Citizen Portal (citizenportal.gov.pk) is the most effective method -6. Share on WhatsApp to raise public awareness -Key numbers: Garbage 1139 | Roads 051-9032800 | WASA 042-99200300 | CM Portal 0800-02345""", - "laws": ["Right to Information Act 2017", "Constitution Article 9", "EPA 1997"], - "hotline": "0800-02345", "authority": "Pakistan Citizen Portal", - "response_time": "3-5 working days", "fine": "N/A", - }, - { - "id": "rights_003", - "category": "General", - "title": "Federal Ombudsman — Role and Process", - "content": """The Federal Ombudsman (Wafaqi Mohtasib) is an independent body that hears complaints against government institutions. -You can file complaints against NHA, WASA, LDA, Local Government bodies. -Phone: 051-9204551 | Website: mohtasib.gov.pk -Complaints are free and a decision must be issued within 60 days. -If unsatisfied with the decision, you can appeal to the President of Pakistan.""", - "laws": ["Federal Ombudsmen Institutional Reforms Act 2013"], - "hotline": "051-9204551", "authority": "Federal Ombudsman (Mohtasib)", - "response_time": "60 days", "fine": "Binding recommendations", - }, - { - "id": "road_dataset_001", - "category": "Pot Hole", - "title": "Road Issues Detection — Types of Road Defects", - "content": """According to Road Issues Detection Dataset, common Pakistani road problems: -- Pothole: Most common, 45% of road complaints -- Cracking: 25% of road complaints — dangerous in rain -- Rutting: 15% complaints — caused by heavy vehicles -- Raveling: 10% complaints — surface wearing away -- Depression: 5% complaints — road sinking -If a pothole is deeper than 10cm it is an immediate hazard — notify NHA immediately. -Always include GPS coordinates and photos with your complaint.""", - "laws": ["NHA Safety Ordinance 2000", "Punjab LGA 2022 Section 54"], - "hotline": "051-9032800", "authority": "NHA / C&W / LDA", - "response_time": "72 hours", "fine": "Authority liable", - }, + {"id":"g1","category":"Garbage", + "title":"Punjab Waste Management Act 2014 — Citizen Rights", + "content":"Under Punjab Waste Management Act 2014 any citizen can file a garbage complaint. Fine: Rs.500–50,000. Local govt must act within 48 hours. Authority: Solid Waste Management Board. Helpline: 1139. Citizens have the right to demand a written response and escalate to CM Portal if ignored.", + "laws":["Punjab Waste Management Act 2014","EPA 1997 Section 11","Punjab LGA 2022 Schedule II"], + "hotline":"1139","authority":"Solid Waste Management Board / Local Government","response_time":"48 hours","fine":"Rs. 500 – 50,000"}, + {"id":"g2","category":"Garbage", + "title":"Urban Solid Waste — City-level Responsibility", + "content":"Failure to collect garbage in urban areas violates EPA 1997 Section 11. Garbage not collected for >1 week = Public Nuisance under PPC Section 268. Lahore: LWMC 042-111-222-888. Karachi: KMC 021-99231677.", + "laws":["PPC Section 268","Punjab Waste Management Act 2014","EPA 1997 Section 11"], + "hotline":"1139","authority":"LWMC Lahore / KMC Karachi / Local SWMB","response_time":"48 hours","fine":"Rs. 500 – 50,000"}, + {"id":"g3","category":"Garbage", + "title":"Garbage Complaint Escalation Ladder", + "content":"If local authority does not act within 48 hours: 1. Union Council / Nazim 2. Deputy Commissioner office 3. CM Cell 0800-02345 4. Pakistan Citizen Portal citizenportal.gov.pk 5. Federal Ombudsman 051-9204551 6. High Court Writ Petition under Article 9. You can claim compensation if health was affected — EPA 1997 Section 14.", + "laws":["Constitution Article 9 & 14","EPA 1997 Section 14","PPC Section 268"], + "hotline":"0800-02345","authority":"CM Complaints Cell / Federal Ombudsman","response_time":"3 working days","fine":"Compensation claimable"}, + {"id":"p1","category":"Pot Hole", + "title":"National Highways Safety Ordinance 2000 — Pothole Rights", + "content":"Road potholes are NHA responsibility. Under National Highways Safety Ordinance 2000 road repairs must be done within 72 hours. Punjab LGA 2022 Section 54 — LDA and C&W also responsible. Vehicle damaged? Claim compensation under Motor Vehicles Ordinance 1965. NHA Helpline: 051-9032800.", + "laws":["National Highways Safety Ordinance 2000","Punjab LGA 2022 Section 54","Motor Vehicles Ordinance 1965"], + "hotline":"051-9032800","authority":"NHA / C&W Department / LDA","response_time":"72 hours","fine":"Authority liable for vehicle damage"}, + {"id":"p2","category":"Pot Hole", + "title":"Road Accident Due to Pothole — Legal Recourse", + "content":"If accident occurs due to pothole: 1. Police report 2. Photograph scene 3. Written notice to NHA or LDA 4. File Negligence claim under Tort Law 5. Federal Ombudsman 051-9204551 6. High Court Writ Petition. Pothole deeper than 10cm = immediate hazard.", + "laws":["Tort Law Negligence","NHA Safety Ordinance 2000","Constitution Article 9"], + "hotline":"051-9204551","authority":"Federal Ombudsman / High Court","response_time":"72 hours","fine":"Compensation for injury/damage"}, + {"id":"w1","category":"Pipe Leakage", + "title":"Punjab Water Act 2019 — Pipe Leakage Rights", + "content":"Under Punjab Water Act 2019 Section 23, WASA must repair pipe leakage within 24 hours. If not repaired, file at DC office. WASA fine: Rs.10,000–5,00,000. WASA Lahore: 042-99200300. WASA Karachi (KWSB): 021-99231677. WASA Rawalpindi: 051-5594244. Supreme Court 2018: clean water is a fundamental right — PLD 2018 SC 1.", + "laws":["Punjab Water Act 2019 Section 23","WASA Act Bylaws","Constitution Article 9"], + "hotline":"042-99200300","authority":"WASA / Pakistan Water Authority","response_time":"24 hours","fine":"Rs. 10,000 – 5,00,000"}, + {"id":"w2","category":"Pipe Leakage", + "title":"Contaminated Water and Health — Legal Rights", + "content":"EPA 1997 Section 13 makes polluting water a criminal offence. National Drinking Water Policy 2009 mandates WHO-standard water. You can claim compensation if contaminated water causes illness. You can suspend billing if water is contaminated — WASA Bylaws. Wrong billing: Contact WASA Ombudsman.", + "laws":["EPA 1997 Section 13","National Drinking Water Policy 2009","Punjab Water Act 2019"], + "hotline":"042-99200300","authority":"WASA / Pakistan Water Authority / EPA","response_time":"24-48 hours","fine":"Compensation for health damage"}, + {"id":"w3","category":"Pipe Leakage", + "title":"WASA Did Not Act — Escalation Steps", + "content":"If WASA fails to repair within 24 hours: 1. Call WASA again, note complaint number 2. Written application at WASA office 3. Deputy Commissioner office 4. CM Cell 0800-02345 5. Citizen Portal citizenportal.gov.pk 6. Pakistan Water Authority 051-9246150 7. Federal Ombudsman 051-9204551 8. High Court Article 9. Keep evidence: photo, date, complaint number.", + "laws":["Punjab Water Act 2019","Constitution Article 9","EPA 1997"], + "hotline":"0800-02345","authority":"CM Complaints Cell / PWA / Federal Ombudsman","response_time":"Escalation pathway","fine":"Rs. 10,000–5,00,000 + compensation"}, + {"id":"r1","category":"General", + "title":"Fundamental Rights of Pakistani Citizens", + "content":"Article 9: Right to Life — includes clean water and clean environment (SC 2018). Article 14: Right to Dignity — polluted environment violates dignity. Article 19A: Right to Information — you can request info from any public body. Citizen Portal complaints must receive legal response. You have the right to file FIR if public body fails to act.", + "laws":["Constitution Article 9","Constitution Article 14","Constitution Article 19A"], + "hotline":"0800-02345","authority":"High Court / Supreme Court / Federal Ombudsman","response_time":"3 working days","fine":"Authority accountable"}, + {"id":"r2","category":"General", + "title":"How to File a Civic Complaint — Complete Guide", + "content":"1. Photograph issue with date/time. 2. Note exact location. 3. Call relevant helpline, get complaint number. 4. If no action in 48-72 hours, use CM Portal 0800-02345. 5. Pakistan Citizen Portal citizenportal.gov.pk is most effective. 6. Share on WhatsApp for awareness. Garbage 1139 | Roads 051-9032800 | WASA 042-99200300 | CM Portal 0800-02345", + "laws":["Right to Information Act 2017","Constitution Article 9","EPA 1997"], + "hotline":"0800-02345","authority":"Pakistan Citizen Portal","response_time":"3-5 working days","fine":"N/A"}, + {"id":"r3","category":"General", + "title":"Federal Ombudsman (Wafaqi Mohtasib) — Role and Process", + "content":"The Federal Ombudsman is an independent body that hears complaints against government institutions including NHA, WASA, LDA, Local Government. Phone: 051-9204551. Website: mohtasib.gov.pk. Filing is FREE, decision within 60 days. If unsatisfied, appeal to the President of Pakistan.", + "laws":["Federal Ombudsmen Institutional Reforms Act 2013"], + "hotline":"051-9204551","authority":"Federal Ombudsman (Mohtasib)","response_time":"60 days","fine":"Binding recommendations"}, ] - -# ─── RAG ENGINE ─────────────────────────────────────────────── +# ─── RAG ENGINE (sklearn TF-IDF, no faiss needed) ───────────── class RAGEngine: def __init__(self): - self.documents = RAG_DOCUMENTS - self.vectorizer = None - self.doc_matrix = None - self._initialized = False + self.documents = RAG_DOCUMENTS + self.vectorizer = None + self.doc_matrix = None + self._ready = False def initialize(self): - if self._initialized: - return True + if self._ready: return True try: from sklearn.feature_extraction.text import TfidfVectorizer - corpus = [] - for d in self.documents: - text = (f"{d['title']} {d['content']} " - f"{' '.join(d.get('laws', []))} " - f"{d.get('category','')} {d.get('hotline','')} {d.get('authority','')}") - corpus.append(text) - self.vectorizer = TfidfVectorizer( - analyzer='char_wb', ngram_range=(2, 5), - max_features=8000, sublinear_tf=True, min_df=1, - ) - self.doc_matrix = self.vectorizer.fit_transform(corpus) - self._initialized = True + corpus = [f"{d['title']} {d['content']} {' '.join(d['laws'])} {d['category']}" for d in self.documents] + self.vectorizer = TfidfVectorizer(analyzer='char_wb', ngram_range=(2,4), max_features=6000, sublinear_tf=True) + self.doc_matrix = self.vectorizer.fit_transform(corpus) + self._ready = True return True except Exception as e: print(f"RAG init error: {e}") return False def retrieve(self, query, top_k=3): - if not self._initialized: - if not self.initialize(): - return self._keyword_fallback(query, top_k) - try: - from sklearn.metrics.pairwise import cosine_similarity - import numpy as np - q_vec = self.vectorizer.transform([query]) - scores = cosine_similarity(q_vec, self.doc_matrix)[0] - top_idx = np.argsort(scores)[::-1][:top_k] - results = [] - for idx in top_idx: - if scores[idx] > 0.01: - doc = self.documents[idx].copy() - doc['relevance_score'] = float(scores[idx]) - results.append(doc) - return results if results else self._keyword_fallback(query, top_k) - except Exception: - return self._keyword_fallback(query, top_k) - - def _keyword_fallback(self, query, top_k=3): + if not self._ready: self.initialize() + if self._ready: + try: + from sklearn.metrics.pairwise import cosine_similarity + import numpy as np + q_vec = self.vectorizer.transform([query]) + scores = cosine_similarity(q_vec, self.doc_matrix)[0] + idxs = np.argsort(scores)[::-1][:top_k] + return [dict(self.documents[i], score=float(scores[i])) for i in idxs if scores[i]>0.01] + except Exception: + pass + return self._fallback(query, top_k) + + def _fallback(self, query, top_k=3): q = query.lower() - keywords = { - "Garbage": ["garbage", "waste", "sanitation", "trash", "1139", "کچرا"], - "Pot Hole": ["pothole", "pot hole", "road", "nha", "سڑک", "گڑھا"], - "Pipe Leakage": ["water", "wasa", "pipe", "leakage", "contaminated", "پانی", "پائپ"], - } - found_cat = None - for cat, kws in keywords.items(): - if any(kw in q for kw in kws): - found_cat = cat; break - matched = [d for d in self.documents if found_cat and d['category'] == found_cat] - for d in self.documents: - if d['category'] == 'General' and d not in matched: - matched.append(d) - return matched[:top_k] if matched else self.documents[:top_k] + kw = {"Garbage":["garbage","waste","trash","kachra","1139"], + "Pot Hole":["pothole","road","nha","sadak"], + "Pipe Leakage":["water","wasa","pipe","leakage","pani"]} + found = next((cat for cat, keys in kw.items() if any(k in q for k in keys)), None) + matched = [d for d in self.documents if found and d['category']==found] + matched += [d for d in self.documents if d['category']=='General' and d not in matched] + return matched[:top_k] or self.documents[:top_k] def format_context(self, docs): - if not docs: - return "" - ctx = "Relevant Legal Information (from RAG Knowledge Base):\n\n" - for i, doc in enumerate(docs, 1): - score = doc.get('relevance_score', 0) - ctx += f"[{i}] {doc['title']} (relevance: {score:.2f})\n" - ctx += f"Content: {doc['content'][:400]}\n" - ctx += f"Laws: {', '.join(doc['laws'][:2])}\n" - ctx += f"Helpline: {doc['hotline']} | Response: {doc['response_time']}\n\n" + if not docs: return "" + ctx = "Relevant legal information from knowledge base:\n\n" + for i, d in enumerate(docs, 1): + ctx += f"[{i}] {d['title']}\n{d['content'][:350]}\nLaws: {', '.join(d['laws'][:2])}\nHelpline: {d['hotline']} | Response: {d['response_time']}\n\n" return ctx - -rag_engine = RAGEngine() -rag_engine.initialize() +rag = RAGEngine() +rag.initialize() # ─── STATIC DATA ────────────────────────────────────────────── CITIES_AREAS = { - "Lahore": ["Model Town", "DHA", "Gulberg", "Johar Town", "Bahria Town", "Township", "Cantonment"], - "Karachi": ["Clifton", "DHA", "Gulshan-e-Iqbal", "PECHS", "Korangi", "Saddar", "North Nazimabad"], - "Islamabad": ["F-7", "F-8", "F-10", "G-9", "G-10", "G-11", "Blue Area"], - "Rawalpindi": ["Saddar", "Bahria Town", "Chaklala", "Satellite Town", "Murree Road"], - "Faisalabad": ["Jinnah Colony", "Madina Town", "Peoples Colony", "Ghulam Muhammad Abad", "Susan Road"], - "Multan": ["Shah Rukn-e-Alam", "Cantt", "Gulgasht Colony", "New Multan", "Bosan Road"], - "Peshawar": ["Hayatabad", "University Town", "Cantt", "Saddar", "Gulbahar"], - "Quetta": ["Satellite Town", "Jinnah Town", "Cantt", "Sariab Road", "Brewery Road"], + "Lahore": ["Model Town","DHA","Gulberg","Johar Town","Bahria Town","Township","Cantonment"], + "Karachi": ["Clifton","DHA","Gulshan-e-Iqbal","PECHS","Korangi","Saddar","North Nazimabad"], + "Islamabad": ["F-7","F-8","F-10","G-9","G-10","G-11","Blue Area"], + "Rawalpindi": ["Saddar","Bahria Town","Chaklala","Satellite Town","Murree Road"], + "Faisalabad": ["Jinnah Colony","Madina Town","Peoples Colony","Ghulam Muhammad Abad","Susan Road"], + "Multan": ["Shah Rukn-e-Alam","Cantt","Gulgasht Colony","New Multan","Bosan Road"], + "Peshawar": ["Hayatabad","University Town","Cantt","Saddar","Gulbahar"], + "Quetta": ["Satellite Town","Jinnah Town","Cantt","Sariab Road","Brewery Road"], } - -ISSUE_TYPES = ["🗑️ Garbage", "🕳️ Pot Hole", "💧 Pipe Leakage"] -LANGUAGES = ["English", "اردو (Urdu)", "پنجابی (Punjabi)", "سندھی (Sindhi)"] +ISSUE_TYPES = ["🗑️ Garbage","🕳️ Pot Hole","💧 Pipe Leakage"] +LANGUAGES = ["English","اردو (Urdu)","پنجابی (Punjabi)","سندھی (Sindhi)"] +LANG_CODES = {"English":"en","اردو (Urdu)":"ur","پنجابی (Punjabi)":"ur","سندھی (Sindhi)":"ur"} LEGAL_KB = { - "Garbage": { - "laws": [ - "Punjab Waste Management Act 2014", - "Pakistan Environmental Protection Act 1997 (Section 11)", - "Punjab Local Government Act 2022 (Schedule II – Sanitation Duties)", - "Pakistan Penal Code Section 268 – Public Nuisance", - ], - "fine": "Rs. 500 – 50,000 (per offence)", - "authority": "Local Government / Solid Waste Management Board", - "hotline": "1139", "response": "48 hours", - "citizen_rights": [ - "Right to clean environment (Article 9 & 14, Constitution of Pakistan)", - "Right to file FIR under PPC Section 268 if authority fails to act", - "Right to compensation for health damage under EPA 1997", - "Right to written response within 3 working days", - ], - "escalation": "CM Complaints Cell: 0800-02345 | citizenportal.gov.pk", - "dataset_ref": "Punjab Solid Waste Management Board | Urban Issues Dataset", - }, - "Pot Hole": { - "laws": [ - "National Highways Safety Ordinance 2000", - "Punjab Local Government Act 2022 (Section 54 – Road Maintenance)", - "Motor Vehicles Ordinance 1965 (Road Authority Liability)", - "Tort Law – Negligence (Pakistani courts)", - ], - "fine": "Authority liable for vehicle damage & personal injury claims", - "authority": "National Highway Authority (NHA) / C&W Department / LDA", - "hotline": "051-9032800", "response": "72 hours", - "citizen_rights": [ - "Right to claim compensation for vehicle damage or personal injury", - "Right to lodge complaint with Federal Ombudsman — 051-9204551", - "Right to file writ petition in High Court for dereliction of duty", - "Right to written notice to NHA/LDA", - ], - "escalation": "Federal Ombudsman: 051-9204551 | nha.gov.pk", - "dataset_ref": "NHA Road Quality Reports | Road Issues Detection Dataset", - }, - "Pipe Leakage": { - "laws": [ - "Punjab Water Act 2019 (Section 23 – Supply Obligation)", - "WASA Act – Water & Sanitation Agency Bylaws", - "Pakistan Environmental Protection Act 1997 (Section 13)", - "Punjab Local Government Act 2022 (Water & Sewerage Schedules)", - "Constitution of Pakistan Article 9 – Right to Life (includes safe water)", - ], - "fine": "Compensatory damages + Rs. 10,000 – 5,00,000 under PWA 2019", - "authority": "WASA / Pakistan Water Authority", - "hotline": "042-99200300", "response": "24 hours", - "citizen_rights": [ - "Right to safe and clean drinking water (SC ruling 2018 – PLD 2018 SC 1)", - "Right to compensation for property damage from water leakage", - "Right to disconnect billing if water supply is contaminated", - "Right to file complaint with Pakistan Water Authority (PWA)", - ], - "escalation": "Pakistan Water Authority: 051-9246150 | CM Portal: 0800-02345", - "dataset_ref": "WASA Annual Service Quality Reports | Consumer Complaints Dataset", - }, + "Garbage":{"laws":["Punjab Waste Management Act 2014","EPA 1997 Section 11","Punjab LGA 2022 Schedule II","PPC Section 268"],"fine":"Rs. 500–50,000","authority":"Local Government / Solid Waste Management Board","hotline":"1139","response":"48 hours","citizen_rights":["Right to clean environment (Constitution Article 9 & 14)","Right to file FIR under PPC Section 268 if authority fails","Right to compensation for health damage under EPA 1997","Right to written response within 3 working days"],"escalation":"CM Complaints Cell: 0800-02345 | citizenportal.gov.pk","dataset_ref":"Punjab SWMB | Urban Issues Dataset"}, + "Pot Hole":{"laws":["National Highways Safety Ordinance 2000","Punjab LGA 2022 Section 54","Motor Vehicles Ordinance 1965","Tort Law – Negligence"],"fine":"Authority liable for vehicle damage & injury","authority":"NHA / C&W Department / LDA","hotline":"051-9032800","response":"72 hours","citizen_rights":["Right to compensation for vehicle damage or personal injury","Right to lodge complaint with Federal Ombudsman: 051-9204551","Right to file writ petition in High Court","Right to written notice to NHA/LDA"],"escalation":"Federal Ombudsman: 051-9204551 | nha.gov.pk","dataset_ref":"NHA Road Quality Reports | Road Issues Detection Dataset"}, + "Pipe Leakage":{"laws":["Punjab Water Act 2019 Section 23","WASA Act Bylaws","EPA 1997 Section 13","Punjab LGA 2022","Constitution Article 9"],"fine":"Rs. 10,000–5,00,000 under PWA 2019","authority":"WASA / Pakistan Water Authority","hotline":"042-99200300","response":"24 hours","citizen_rights":["Right to safe drinking water (SC 2018 – PLD 2018 SC 1)","Right to compensation for property damage","Right to stop billing if water is contaminated","Right to file complaint with Pakistan Water Authority"],"escalation":"Pakistan Water Authority: 051-9246150 | CM Portal: 0800-02345","dataset_ref":"WASA Service Quality Reports | Consumer Complaints Dataset"}, } LOCALIZED = { - "Garbage": { - "English": "Dumping garbage is a criminal offence under Punjab Waste Management Act 2014. Fine: Rs. 500-50,000. Helpline: 1139", - "اردو (Urdu)": "کچرا پھینکنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت جرم ہے۔ جرمانہ: 500 سے 50,000 روپے۔ ہیلپ لائن: 1139", - "پنجابی (Punjabi)": "کچرا سُٹنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 دے تحت جرم اے۔ جرمانہ 500 توں 50,000 روپے۔ ہیلپ لائن: 1139", - "سندھی (Sindhi)": "ڪچرو اڇلائڻ پنجاب ويسٽ مئنيجمينٽ ايڪٽ 2014 تحت جرم آهي. جرمانو: 500 کان 50,000 رپيا. مدد لاءِ: 1139", - }, - "Pot Hole": { - "English": "Road repair is the government's legal obligation within 72 hours under Punjab LGA 2022. NHA: 051-9032800", - "اردو (Urdu)": "سڑک کی مرمت پنجاب مقامی حکومت ایکٹ 2022 کے تحت 72 گھنٹوں میں حکومت کی ذمہ داری ہے۔ NHA: 051-9032800", - "پنجابی (Punjabi)": "سڑک دی مرمت پنجاب LGA 2022 دے تحت 72 گھنٹیاں وچ سرکار دی ذمہ واری اے۔ NHA: 051-9032800", - "سندھی (Sindhi)": "سڙڪ جي مرمت پنجاب LGA 2022 تحت 72 ڪلاڪن ۾ حڪومت جي ذميواري آهي. NHA: 051-9032800", - }, - "Pipe Leakage": { - "English": "Pipe leakage must be repaired within 24 hours by WASA under Punjab Water Act 2019. WASA: 042-99200300", - "اردو (Urdu)": "پائپ لیکیج کی مرمت پنجاب واٹر ایکٹ 2019 کے تحت WASA کی 24 گھنٹوں میں ذمہ داری ہے۔ WASA: 042-99200300", - "پنجابی (Punjabi)": "پائپ لیکیج دی مرمت پنجاب واٹر ایکٹ 2019 دے تحت WASA دی 24 گھنٹیاں وچ ذمہ واری اے۔ WASA: 042-99200300", - "سندھی (Sindhi)": "پائپ ليڪيج جي مرمت پنجاب واٽر ايڪٽ 2019 تحت WASA جي 24 ڪلاڪن ۾ ذميواري آهي. WASA: 042-99200300", - }, + "Garbage": {"English":"Dumping garbage is a criminal offence under Punjab Waste Management Act 2014. Fine: Rs.500-50,000. Helpline: 1139","اردو (Urdu)":"کچرا پھینکنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت جرم ہے۔ جرمانہ: 500-50,000 روپے۔ ہیلپ لائن: 1139","پنجابی (Punjabi)":"کچرا سُٹنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 دے تحت جرم اے۔ جرمانہ 500 توں 50,000 روپے۔","سندھی (Sindhi)":"ڪچرو اڇلائڻ پنجاب ويسٽ مئنيجمينٽ ايڪٽ 2014 تحت جرم آهي. جرمانو 500 کان 50,000 رپيا."}, + "Pot Hole": {"English":"Road repair is the government's legal obligation within 72 hours under Punjab LGA 2022. NHA: 051-9032800","اردو (Urdu)":"سڑک کی مرمت 72 گھنٹوں میں حکومت کی ذمہ داری ہے۔ NHA: 051-9032800","پنجابی (Punjabi)":"سڑک دی مرمت 72 گھنٹیاں وچ سرکار دی ذمہ واری اے۔","سندھی (Sindhi)":"سڙڪ جي مرمت 72 ڪلاڪن ۾ حڪومت جي ذميواري آهي."}, + "Pipe Leakage":{"English":"Pipe leakage must be repaired within 24 hours by WASA under Punjab Water Act 2019. WASA: 042-99200300","اردو (Urdu)":"پائپ لیکیج WASA کی 24 گھنٹوں میں ذمہ داری ہے۔ WASA: 042-99200300","پنجابی (Punjabi)":"پائپ لیکیج دی مرمت WASA دی 24 گھنٹیاں وچ ذمہ واری اے۔","سندھی (Sindhi)":"پائپ ليڪيج جي مرمت WASA جي 24 ڪلاڪن ۾ ذميواري آهي."}, } - WASTE_CLASS_IDS = {24,25,26,27,28,32,33,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54} -# ─── TTS language codes — Fixed Sindhi fallback ─────────────── -# gTTS does not support 'sd' (Sindhi), so we use 'ur' as closest fallback -LANG_CODES = { - "English": "en", - "اردو (Urdu)": "ur", - "پنجابی (Punjabi)": "ur", # Shahmukhi is same as Urdu phonetically for gTTS - "سندھی (Sindhi)": "ur", # gTTS has no Sindhi — Urdu is closest available -} - - # ─── YOLO ───────────────────────────────────────────────────── def detect_with_yolo(image_pil, issue_type): try: from ultralytics import YOLO import numpy as np - model = YOLO("yolo26n.pt") + model = YOLO("yolo26n.pt") results = model(np.array(image_pil), verbose=False) result = results[0] names = model.names - detected, severity = [], 1 - clean = issue_type.split(" ", 1)[-1] + detected, sev = [], 1 + clean = issue_type.split(" ",1)[-1] for box in result.boxes: - cls_id = int(box.cls[0]); conf = float(box.conf[0]) - detected.append(f"{names.get(cls_id, f'class_{cls_id}')} ({conf:.0%})") - if clean == "Garbage" and cls_id in WASTE_CLASS_IDS: - severity = min(10, severity + 2) - elif clean in ("Pot Hole", "Pipe Leakage"): - severity = min(10, severity + 1) - annotated = Image.fromarray(result.plot()) - summary = (f"Detected {len(detected)} object(s): {', '.join(detected[:5])}" - if detected else "No specific objects detected by YOLO.") - return annotated, summary, max(severity, 3) + cid = int(box.cls[0]); conf = float(box.conf[0]) + detected.append(f"{names.get(cid,f'cls{cid}')} ({conf:.0%})") + if clean=="Garbage" and cid in WASTE_CLASS_IDS: sev=min(10,sev+2) + elif clean in("Pot Hole","Pipe Leakage"): sev=min(10,sev+1) + ann = Image.fromarray(result.plot()) + summ = f"Detected {len(detected)}: {', '.join(detected[:5])}" if detected else "No objects detected by YOLO." + return ann, summ, max(sev,3) except ImportError: - return image_pil, "YOLO not installed – skipping object detection.", 5 + return image_pil,"YOLO not installed.",5 except Exception as e: - return image_pil, f"YOLO error: {e}", 5 - + return image_pil,f"YOLO error: {e}",5 # ─── GEMINI ─────────────────────────────────────────────────── def analyze_with_gemini(image_pil, issue, location, city, yolo_summary): - if not GOOGLE_API_KEY: - return "WARNING: GOOGLE_API_KEY not set. Gemini analysis skipped." + if not GOOGLE_API_KEY: return "WARNING: GOOGLE_API_KEY not set." try: import google.generativeai as genai genai.configure(api_key=GOOGLE_API_KEY) model = genai.GenerativeModel("gemini-2.0-flash") - buf = io.BytesIO() - image_pil.save(buf, format="JPEG") - prompt = f"""You are a STRICT Pakistani Civic Issue Inspector AI. -REPORTED ISSUE: '{issue}' | CITY: {city} | LOCATION: {location} -YOLO: {yolo_summary} -Check: Garbage=actual waste/litter, Pot Hole=visible road hole, Pipe Leakage=water from pipe. -Clean/indoor/person-only/vegetation = REJECT. Be strict. -Respond ONLY in this exact format: + buf = io.BytesIO(); image_pil.save(buf,format="JPEG") + prompt = f"""Strict Pakistani Civic Issue Inspector AI. +ISSUE: '{issue}' CITY: {city} LOCATION: {location} YOLO: {yolo_summary} +Garbage=actual waste, Pot Hole=visible road hole, Pipe Leakage=water from pipe. Clean/indoor/vegetation=REJECT. +Respond ONLY: STATUS: [APPROVED or REJECTED] REASON: [2-3 sentences English] REASON_URDU: [2-3 sentences Urdu] SEVERITY: [1-10] CONFIDENCE: [XX%] RECOMMENDED_ACTION: [one sentence]""" - image_part = {"mime_type": "image/jpeg", "data": base64.b64encode(buf.getvalue()).decode()} - return model.generate_content([prompt, image_part]).text.strip() + img_part = {"mime_type":"image/jpeg","data":base64.b64encode(buf.getvalue()).decode()} + return model.generate_content([prompt,img_part]).text.strip() except Exception as e: return f"WARNING: Gemini error: {e}" - -def parse_gemini_response(text): - r = {"status": "UNKNOWN", "reason": "Could not parse.", "reason_urdu": "", - "severity": 5, "confidence": "0%", "action": ""} - if not text: - return r - for pat, key in [ - (r"STATUS:\s*(APPROVED|REJECTED)", "status"), - (r"SEVERITY:\s*(\d+)", "severity"), - (r"CONFIDENCE:\s*(\d+%)", "confidence"), - ]: - m = re.search(pat, text, re.IGNORECASE) +def parse_gemini(text): + r={"status":"UNKNOWN","reason":"Could not parse.","reason_urdu":"","severity":5,"confidence":"0%","action":""} + if not text: return r + for pat,key in[(r"STATUS:\s*(APPROVED|REJECTED)","status"),(r"SEVERITY:\s*(\d+)","severity"),(r"CONFIDENCE:\s*(\d+%)","confidence")]: + m=re.search(pat,text,re.IGNORECASE) if m: - val = m.group(1) - if key == "status": r[key] = val.upper() - elif key == "severity": r[key] = int(val) - else: r[key] = val - for pat, key in [ - (r"REASON:\s*(.+?)(?=REASON_URDU:|SEVERITY:|$)", "reason"), - (r"REASON_URDU:\s*(.+?)(?=SEVERITY:|$)", "reason_urdu"), - (r"RECOMMENDED_ACTION:\s*(.+?)(?=$)", "action"), - ]: - m = re.search(pat, text, re.DOTALL | re.IGNORECASE) - if m: r[key] = m.group(1).strip() + v=m.group(1) + if key=="status": r[key]=v.upper() + elif key=="severity": r[key]=int(v) + else: r[key]=v + for pat,key in[(r"REASON:\s*(.+?)(?=REASON_URDU:|SEVERITY:|$)","reason"),(r"REASON_URDU:\s*(.+?)(?=SEVERITY:|$)","reason_urdu"),(r"RECOMMENDED_ACTION:\s*(.+?)$","action")]: + m=re.search(pat,text,re.DOTALL|re.IGNORECASE) + if m: r[key]=m.group(1).strip() return r - # ─── LLAMA 3 LEGAL ADVICE ───────────────────────────────────── -def analyze_with_llama(issue, location, city, yolo_summary, severity, language="English"): - clean = issue.split(" ", 1)[-1] - kb = LEGAL_KB.get(clean, {}) - lang_map = { - "اردو (Urdu)": "Respond entirely in Urdu script.", - "پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script (same as Urdu script but Punjabi language).", - "سندھی (Sindhi)": "Respond in Sindhi script.", - } - lang_instruction = lang_map.get(language, "Respond in clear professional English.") +def analyze_with_llama(issue, location, city, yolo_s, severity, language="English"): + clean = issue.split(" ",1)[-1]; kb = LEGAL_KB.get(clean,{}) + lang_inst = {"اردو (Urdu)":"Respond entirely in Urdu.","پنجابی (Punjabi)":"Respond in Punjabi Shahmukhi.","سندھی (Sindhi)":"Respond in Sindhi."}.get(language,"Respond in clear professional English.") if not GROQ_API_KEY: - rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights", [])) - return ( - "Applicable Laws:\n" + "\n".join(f" * {l}" for l in kb.get("laws", [])) + - f"\n\nCitizen Rights:\n{rights}" + - f"\n\nFine / Penalty: {kb.get('fine','N/A')}" + - f"\nAuthority Helpline: {kb.get('hotline','N/A')}" + - f"\nRequired Response Time: {kb.get('response','N/A')}" + - f"\n\nEscalation: {kb.get('escalation','N/A')}" + - "\n\n(Set GROQ_API_KEY for AI-generated detailed legal advice)" - ) + rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights",[])) + return (f"Applicable Laws:\n"+"\n".join(f" * {l}" for l in kb.get("laws",[]))+ + f"\n\nCitizen Rights:\n{rights}\n\nFine: {kb.get('fine','N/A')}" + f"\nHelpline: {kb.get('hotline','N/A')}\nResponse Time: {kb.get('response','N/A')}" + f"\nEscalation: {kb.get('escalation','N/A')}\n\n(Set GROQ_API_KEY for AI legal advice)") try: from groq import Groq client = Groq(api_key=GROQ_API_KEY) - prompt = f"""You are a Pakistani civic law expert for the Rahbar citizen platform. -{lang_instruction} -Complaint: {issue} in {location}, {city} | Severity: {severity}/10 -Applicable Laws: {', '.join(kb.get('laws', []))} -Required Response Time: {kb.get('response', '72 hours')} - -Provide structured legal advice: -1. Specific legal rights of the citizen (cite law names/sections) -2. Exact numbered steps to file a formal complaint -3. What to do if authority does not respond within the required time -4. Possible compensation or legal action available -5. Relevant helplines and escalation contacts -Keep it concise and practical for an ordinary Pakistani citizen.""" - resp = client.chat.completions.create( - model="llama-3.3-70b-versatile", - messages=[{"role": "user", "content": prompt}], - max_tokens=700 - ) + prompt = f"""You are a Pakistani civic law expert for the Rahbar platform. +{lang_inst} +Complaint: {issue} at {location}, {city} | Severity: {severity}/10 +Laws: {', '.join(kb.get('laws',[]))} | Response Time: {kb.get('response','72 hours')} +Provide: 1.Specific legal rights (cite law/section) 2.Exact numbered steps to file complaint 3.What to do if authority doesn't respond in time 4.Possible compensation 5.Helplines and escalation contacts. Concise and practical.""" + resp = Groq(api_key=GROQ_API_KEY).chat.completions.create( + model="llama-3.3-70b-versatile", messages=[{"role":"user","content":prompt}], max_tokens=700) return resp.choices[0].message.content.strip() except Exception as e: return f"Llama 3 error: {e}" - -# ─── RAG CHATBOT ────────────────────────────────────────────── +# ─── RAG CHATBOT — plain dict format ───────────────────────── def legal_chatbot_rag(user_message, history, language): - if history is None: - history = [] - if not user_message.strip(): - return history, "" - retrieved_docs = rag_engine.retrieve(user_message, top_k=3) - rag_context = rag_engine.format_context(retrieved_docs) - lang_map = { - "اردو (Urdu)": "Respond entirely in Urdu script.", - "پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script.", - "سندھی (Sindhi)": "Respond in Sindhi script.", - } - lang_instruction = lang_map.get(language, "Respond in clear professional English.") - system_content = f"""You are Rahbar Legal Assistant — a civic rights advisor for Pakistani citizens. -{lang_instruction} -Your knowledge comes from a RAG Knowledge Base: Road Issues Detection Dataset, Urban Issues Dataset, Consumer Complaints Dataset, Pakistani civic laws. -Only discuss: water, pipe leakage, WASA, garbage, roads, potholes, Pakistani civic law. -Always cite specific laws and provide helpline numbers. Max 250 words per response. - -RAG Context (from Knowledge Base): -{rag_context}""" + if history is None: history = [] + if not user_message.strip(): return history, "" + retrieved = rag.retrieve(user_message, top_k=3) + ctx = rag.format_context(retrieved) + lang_inst = {"اردو (Urdu)":"Respond entirely in Urdu.","پنجابی (Punjabi)":"Respond in Punjabi Shahmukhi.","سندھی (Sindhi)":"Respond in Sindhi."}.get(language,"Respond in clear professional English.") + system = f"""You are Rahbar Legal Assistant — a civic rights advisor for Pakistani citizens. +{lang_inst} +Specialise ONLY in: water, WASA, pipe leakage, garbage, roads, potholes, Pakistani civic law. +Always cite laws and provide helplines. Max 250 words. +{ctx}""" if not GROQ_API_KEY: - if retrieved_docs: - doc = retrieved_docs[0] - answer = f"**{doc['title']}**\n\n{doc['content'][:500]}\n\n" - answer += f"Helpline: {doc['hotline']} | Response Time: {doc['response_time']}\n" - answer += f"Laws: {', '.join(doc['laws'][:2])}\n\n" - answer += "_(Set GROQ_API_KEY for full AI-powered answers)_" - else: - answer = "I can help with water, garbage, and road issues in Pakistan. Please ask about a specific civic issue." - return history + [ - {"role": "user", "content": user_message}, - {"role": "assistant", "content": answer}, - ], "" + d = retrieved[0] if retrieved else None + answer = (f"**{d['title']}**\n\n{d['content'][:400]}\n\nHelpline: {d['hotline']} | Response: {d['response_time']}\nLaws: {', '.join(d['laws'][:2])}\n\n_(Set GROQ_API_KEY for full AI answers)_" if d else + "I can help with water, garbage, and road issues in Pakistan. Please ask about a specific civic issue.") + return history + [{"role":"user","content":user_message},{"role":"assistant","content":answer}], "" try: from groq import Groq - client = Groq(api_key=GROQ_API_KEY) - api_messages = [{"role": "system", "content": system_content}] - for msg in history[-16:]: - api_messages.append({"role": msg["role"], "content": msg["content"]}) - api_messages.append({"role": "user", "content": user_message}) - resp = client.chat.completions.create( - model="llama-3.3-70b-versatile", - messages=api_messages, - max_tokens=500 - ) + msgs = [{"role":"system","content":system}] + for msg in history[-16:]: msgs.append({"role":msg["role"],"content":msg["content"]}) + msgs.append({"role":"user","content":user_message}) + resp = Groq(api_key=GROQ_API_KEY).chat.completions.create(model="llama-3.3-70b-versatile",messages=msgs,max_tokens=500) answer = resp.choices[0].message.content.strip() - if retrieved_docs: - refs = [f"[{d['title'][:35]}...]" for d in retrieved_docs[:2]] - answer += f"\n\n_ماخذ: {' | '.join(refs)}_" + if retrieved: answer += f"\n\n_Sources: {' | '.join(d['title'][:30]+'…' for d in retrieved[:2])}_" except Exception as e: answer = f"Error: {e}" - return history + [ - {"role": "user", "content": user_message}, - {"role": "assistant", "content": answer}, - ], "" - - -def chatbot_voice_input(audio_file, current_text): - """STT for chatbot voice input""" - if audio_file is None: - return current_text or "" - transcribed = stt(audio_file) - return transcribed - - -def chatbot_tts_output(history, language): - """Generate TTS for last chatbot response""" - if not history: - return None - last_msg = history[-1] - if last_msg.get("role") == "assistant": - text = last_msg.get("content", "") - # Strip markdown refs - text = re.sub(r'_ماخذ:.*?_', '', text, flags=re.DOTALL).strip() - return make_tts(text[:600], language) - return None - - -# ─── TTS — Fixed Sindhi support ─────────────────────────────── + return history + [{"role":"user","content":user_message},{"role":"assistant","content":answer}], "" + +# ─── TTS ────────────────────────────────────────────────────── def make_tts(text, language): try: from gtts import gTTS - lang_code = LANG_CODES.get(language, "en") - tts = gTTS(text=str(text)[:600], lang=lang_code, slow=False) + code = LANG_CODES.get(language,"en") + tts = gTTS(text=str(text)[:600], lang=code, slow=False) path = f"/tmp/tts_{uuid.uuid4().hex[:8]}.mp3" - tts.save(path) - return path + tts.save(path); return path except Exception as e: - print(f"TTS error for lang={language}: {e}") - # Fallback: try English - try: - from gtts import gTTS - tts = gTTS(text=str(text)[:600], lang="en", slow=False) - path = f"/tmp/tts_fallback_{uuid.uuid4().hex[:8]}.mp3" - tts.save(path) - return path - except Exception as e2: - print(f"TTS fallback error: {e2}") - return None - + print(f"TTS error: {e}"); return None -# ─── STT — Groq Whisper primary, SpeechRecognition fallback ── +# ─── STT ────────────────────────────────────────────────────── def stt(audio_file): - if audio_file is None: - return "آڈیو نہیں ملی۔ براہ کرم پہلے آڈیو ریکارڈ کریں یا اپلوڈ کریں۔" - - def ensure_wav(path): - if path.lower().endswith(".wav"): - return path + if audio_file is None: return "No audio provided. Please record or upload audio first." + def to_wav(p): + if p.lower().endswith(".wav"): return p try: from pydub import AudioSegment - out = path + "_converted.wav" - AudioSegment.from_file(path).export(out, format="wav") - return out - except Exception: - return path - + out = p+"_c.wav"; AudioSegment.from_file(p).export(out,format="wav"); return out + except: return p if GROQ_API_KEY: try: from groq import Groq - client = Groq(api_key=GROQ_API_KEY) - wav_path = ensure_wav(audio_file) - with open(wav_path, "rb") as f: - result = client.audio.transcriptions.create( - model="whisper-large-v3", file=f, response_format="text" - ) - text = result if isinstance(result, str) else result.text - return text.strip() or "آڈیو میں کوئی آواز نہیں ملی۔" - except Exception as e: - groq_err = str(e) - else: - groq_err = "GROQ_API_KEY سیٹ نہیں ہے" - + wav = to_wav(audio_file) + with open(wav,"rb") as f: + result = Groq(api_key=GROQ_API_KEY).audio.transcriptions.create(model="whisper-large-v3",file=f,response_format="text") + return (result if isinstance(result,str) else result.text).strip() or "No speech detected." + except Exception as e: groq_err=str(e) + else: groq_err="GROQ_API_KEY not set" try: import speech_recognition as sr - wav_path = ensure_wav(audio_file) - recognizer = sr.Recognizer() - with sr.AudioFile(wav_path) as src: - recognizer.adjust_for_ambient_noise(src, duration=0.3) - audio_data = recognizer.record(src) - return recognizer.recognize_google(audio_data) + wav = to_wav(audio_file) + rec = sr.Recognizer() + with sr.AudioFile(wav) as src: + rec.adjust_for_ambient_noise(src,duration=0.3); data=rec.record(src) + try: return rec.recognize_google(data,language="ur-PK") + except: return rec.recognize_google(data) except Exception as e2: - return (f"ٹرانسکرپشن ناکام۔\n" - f"Groq Whisper خرابی: {groq_err}\n" - f"Fallback خرابی: {e2}\n" - f"تجویز: GROQ_API_KEY سیٹ کریں۔") + return f"Transcription failed.\nGroq error: {groq_err}\nFallback error: {e2}\nTip: Set GROQ_API_KEY for best results." + +def voice_then_chat(audio_file, history, language): + if audio_file is None: return history, "" + txt = stt(audio_file) + if not txt or txt.startswith("Transcription failed") or txt.startswith("No audio"): return history, txt + return legal_chatbot_rag(txt, history, language)[0], "" +def chatbot_tts(history, language): + if not history: return None + last = history[-1] + txt = last.get("content","") if isinstance(last,dict) else "" + txt = re.sub(r'_Sources:.*?_','',txt,flags=re.DOTALL).strip() + return make_tts(txt[:600], language) # ─── LAW REFERENCE ──────────────────────────────────────────── def law_info(issue, language): - clean = issue.split(" ", 1)[-1] - kb = LEGAL_KB.get(clean, {}) - local = LOCALIZED.get(clean, {}).get(language, "") - rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights", [])) - out = f"## قانونی حوالہ: {issue}\n\n### قابل اطلاق قوانین\n" - for law in kb.get("laws", []): - out += f" * {law}\n" - out += (f"\n### جرمانہ / سزا\n{kb.get('fine','N/A')}\n" - f"\n### ذمہ دار ادارہ\n{kb.get('authority','N/A')}\n" - f"\n### ہیلپ لائن\n**{kb.get('hotline','N/A')}**\n" - f"\n### لازمی جوابی مدت\n{kb.get('response','N/A')}\n" - f"\n### شہری کے حقوق\n{rights}\n" - f"\n### اضافی شکایت کا راستہ\n{kb.get('escalation','N/A')}\n") - if local: - out += f"\n---\n### مقامی پیغام ({language})\n> {local}\n" - out += f"\n---\n*ماخذ: {kb.get('dataset_ref','Pakistani civic law databases')}*" + clean = issue.split(" ",1)[-1]; kb = LEGAL_KB.get(clean,{}); local = LOCALIZED.get(clean,{}).get(language,"") + rights = "\n".join(f" * {r}" for r in kb.get("citizen_rights",[])) + out = f"## Legal Reference: {issue}\n\n### Applicable Laws\n" + for l in kb.get("laws",[]): out+=f" * {l}\n" + out += (f"\n### Fine / Penalty\n{kb.get('fine','N/A')}\n\n### Responsible Authority\n{kb.get('authority','N/A')}\n" + f"\n### Helpline\n**{kb.get('hotline','N/A')}**\n\n### Response Time\n{kb.get('response','N/A')}\n" + f"\n### Citizen Rights\n{rights}\n\n### Escalation Path\n{kb.get('escalation','N/A')}\n") + if local: out+=f"\n---\n### Localized Message ({language})\n> {local}\n" + out+=f"\n---\n*Source: {kb.get('dataset_ref','Pakistani civic law databases')}*" return out - -# ─── ADMIN STATS ────────────────────────────────────────────── +# ─── ADMIN ──────────────────────────────────────────────────── def get_admin_stats(): - total = len(complaint_log) - if total == 0: - return "ابھی تک کوئی شکایت درج نہیں ہوئی۔", "" - counts = {"Garbage": 0, "Pot Hole": 0, "Pipe Leakage": 0} - cities, severities = {}, [] + total=len(complaint_log) + if not total: return "No complaints filed yet.","" + counts={"Garbage":0,"Pot Hole":0,"Pipe Leakage":0}; cities={}; sevs=[] for c in complaint_log: - issue = c.get("issue","").split(" ",1)[-1] - counts[issue] = counts.get(issue, 0) + 1 - city = c.get("city","Unknown") - cities[city] = cities.get(city, 0) + 1 - severities.append(c.get("severity", 5)) - avg_sev = sum(severities) / len(severities) if severities else 0 - top_city = max(cities, key=cities.get) if cities else "N/A" - stats_md = f"""## ایڈمن ڈیش بورڈ -| معلومات | تعداد | -|--------|-------| -| کل شکایات | **{total}** | -| اوسط شدت | **{avg_sev:.1f}/10** | -| سب سے زیادہ شکایات | **{top_city}** | - -### نوع کے مطابق -| مسئلہ | تعداد | -|-------|-------| -| کچرا | {counts['Garbage']} | -| سڑک کا گڑھا | {counts['Pot Hole']} | -| پائپ لیکیج | {counts['Pipe Leakage']} | - -### شہر کے مطابق -""" - for city, cnt in sorted(cities.items(), key=lambda x: -x[1]): - stats_md += f"| {city} | {cnt} |\n" - log_md = "## حالیہ شکایات\n\n" + iss=c.get("issue","").split(" ",1)[-1]; counts[iss]=counts.get(iss,0)+1 + cit=c.get("city","?"); cities[cit]=cities.get(cit,0)+1; sevs.append(c.get("severity",5)) + avg=sum(sevs)/len(sevs); top=max(cities,key=cities.get) + stats=f"## Admin Dashboard\n|Metric|Value|\n|---|---|\n|Total|**{total}**|\n|Avg Severity|**{avg:.1f}/10**|\n|Top City|**{top}**|\n\n### By Issue Type\n|Issue|Count|\n|---|---|\n" + for k,v in counts.items(): stats+=f"|{k}|{v}|\n" + stats+="\n### By City\n|City|Count|\n|---|---|\n" + for c,n in sorted(cities.items(),key=lambda x:-x[1]): stats+=f"|{c}|{n}|\n" + log="## Recent Complaints\n\n" for c in reversed(complaint_log[-10:]): - log_md += (f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | " - f"{c['issue']} | شدت {c['severity']}/10 | " - f"شہری: {c.get('name','N/A')} (CNIC: {c.get('cnic','N/A')})\n\n") - return stats_md, log_md - - -def severity_icon(score): - if score <= 3: return "GREEN" - if score <= 6: return "YELLOW" - if score <= 8: return "ORANGE" - return "RED" - - -def _ubold(text): - result = [] - for ch in str(text): - o = ord(ch) - if 0x41 <= o <= 0x5A: result.append(chr(0x1D400 + o - 0x41)) - elif 0x61 <= o <= 0x7A: result.append(chr(0x1D41A + o - 0x61)) - elif 0x30 <= o <= 0x39: result.append(chr(0x1D7CE + o - 0x30)) - else: result.append(ch) - return "".join(result) - - -# ─── PROFESSIONAL REPORT ────────────────────────────────────── -def build_professional_report(complaint_id, timestamp, name, cnic, phone, city, location, - issue_type, language, severity, sev_icon, - yolo_summary, gemini_status, gemini_reason, - gemini_confidence, gemini_action, kb, description, local_msg): - date_str = datetime.datetime.now().strftime("%d %B %Y") - time_str = datetime.datetime.now().strftime("%I:%M %p") - laws_text = "".join(f"\n - {l}" for l in kb.get("laws", [])) - rights_text = "".join(f"\n - {r}" for r in kb.get("citizen_rights", [])) - - def H(title): - return f"\n {_ubold(title)}\n {'─' * len(title)}" - - return f"""{_ubold('GOVERNMENT OF PAKISTAN — CIVIC COMPLAINT REPORT')} - {_ubold('Rahbar Digital Civic Redressal System')} - - Complaint Reference No. : {complaint_id} - Date of Filing : {date_str} Time: {time_str} - Filing Method : Rahbar Digital Platform (AI-Verified) - Report Language : {language} -{H('SECTION A — COMPLAINANT INFORMATION')} - Full Name : {name} - CNIC : {cnic} - Contact : {phone if phone else "Not Provided"} - City : {city} - Area : {location} -{H('SECTION B — COMPLAINT DETAILS')} - Nature : {issue_type} - Location : {location}, {city} - Date & Time : {date_str}, {time_str} - Severity : [{sev_icon}] {severity} / 10 - - Description : - {description.strip() if description.strip() else "[No additional description provided]"} -{H('SECTION C — AI VERIFICATION RESULTS')} - Status : {gemini_status} - Confidence : {gemini_confidence} - Finding : {gemini_reason} - Action : {gemini_action if gemini_action else "Immediate field inspection required."} - YOLO : {yolo_summary} -{H('SECTION D — LEGAL FRAMEWORK')} - Applicable Legislation:{laws_text} - - Responsible Authority : {kb.get('authority','N/A')} - Official Helpline : {kb.get('hotline','N/A')} - Statutory Response Time: {kb.get('response','N/A')} - Fine / Penalty : {kb.get('fine','N/A')} -{H("SECTION E — CITIZEN'S LEGAL RIGHTS")}{rights_text} - - Escalation : {kb.get('escalation','CM Portal: 0800-02345')} -{H(f'SECTION F — LOCALIZED NOTICE ({language})')} - {local_msg} -{H('SECTION G — ACTION DIRECTIVE')} - {_ubold('MANDATORY ACTION REQUIRED WITHIN:')} {kb.get('response','72 hours').upper()} - - Authority : {kb.get('authority','Local Authority')} - Helpline : {kb.get('hotline','N/A')} - Portal : citizenportal.gov.pk | CM: 0800-02345 -{H('DECLARATION')} - I, {name} (CNIC: {cnic}), declare that the information provided is - true and correct to the best of my knowledge. - - Signature : ________________________ (Digital) Date: {date_str} - Reference : {complaint_id} -{H('FOR OFFICIAL USE ONLY')} - Received By : ___________________________ - Date of Receipt : ___________________________ - Action Taken : ___________________________ - Resolved On : ___________________________ - - Generated by: Rahbar — Pakistan's AI Civic Redressal Platform - Timestamp: {timestamp} -""" + log+=(f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | " + f"{c['issue']} | Sev {c['severity']}/10 | {c.get('name','?')}\n\n") + return stats,log + +def sev_icon(s): return "🟢" if s<=3 else("🟡" if s<=6 else("🟠" if s<=8 else "🔴")) + +# ─── PDF REPORT GENERATOR ───────────────────────────────────── +def generate_pdf_report(complaint_id, timestamp, name, cnic, phone, city, location, + issue_type, language, severity, yolo_summary, gemini_status, + gemini_reason, gemini_confidence, gemini_action, kb, + description, local_msg, report_text): + """Generate professional PDF report using ReportLab""" + try: + from reportlab.lib.pagesizes import A4 + from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle + from reportlab.lib.units import cm + from reportlab.lib import colors + from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable + from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT + + buf = io.BytesIO() + doc = SimpleDocTemplate(buf, pagesize=A4, + rightMargin=2*cm, leftMargin=2*cm, + topMargin=2*cm, bottomMargin=2*cm) + + styles = getSampleStyleSheet() + # Custom styles + title_style = ParagraphStyle('Title', parent=styles['Normal'], fontSize=14, fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=4, textColor=colors.HexColor('#0d2b1e')) + sub_style = ParagraphStyle('Sub', parent=styles['Normal'], fontSize=11, fontName='Helvetica-Bold', alignment=TA_CENTER, spaceAfter=8, textColor=colors.HexColor('#1f7a52')) + section_style = ParagraphStyle('Section', parent=styles['Normal'], fontSize=10, fontName='Helvetica-Bold', spaceAfter=6, textColor=colors.HexColor('#1f7a52'), spaceBefore=12) + body_style = ParagraphStyle('Body', parent=styles['Normal'], fontSize=9, fontName='Helvetica', spaceAfter=3, leading=14) + small_style = ParagraphStyle('Small', parent=styles['Normal'], fontSize=8, fontName='Helvetica', textColor=colors.gray) + label_style = ParagraphStyle('Label', parent=styles['Normal'], fontSize=9, fontName='Helvetica-Bold', spaceAfter=2) + + date_str = datetime.datetime.now().strftime("%d %B %Y") + time_str = datetime.datetime.now().strftime("%I:%M %p") + + story = [] + + # ── Header ── + GREEN = colors.HexColor('#0d2b1e') + header_data = [[Paragraph("GOVERNMENT OF PAKISTAN", title_style)], + [Paragraph("CIVIC COMPLAINT REPORT", sub_style)], + [Paragraph("Rahbar Digital Civic Redressal System | رہبر", body_style)]] + header_table = Table(header_data, colWidths=[17*cm]) + header_table.setStyle(TableStyle([ + ('BACKGROUND', (0,0), (-1,-1), colors.HexColor('#e8f3ed')), + ('BOX', (0,0), (-1,-1), 1, colors.HexColor('#1f7a52')), + ('TOPPADDING', (0,0), (-1,-1), 8), + ('BOTTOMPADDING',(0,0),(-1,-1), 8), + ('LEFTPADDING', (0,0), (-1,-1), 12), + ])) + story.append(header_table) + story.append(Spacer(1, 0.3*cm)) + + # ── Ref box ── + ref_data = [ + ["Complaint Ref No.", complaint_id, "Date of Filing", date_str], + ["Time of Filing", time_str, "Language", language], + ["Platform", "Rahbar AI (Verified)", "", ""], + ] + ref_table = Table(ref_data, colWidths=[4.25*cm, 4.25*cm, 4.25*cm, 4.25*cm]) + ref_table.setStyle(TableStyle([ + ('FONTNAME', (0,0), (0,-1), 'Helvetica-Bold'), + ('FONTNAME', (2,0), (2,-1), 'Helvetica-Bold'), + ('FONTSIZE', (0,0), (-1,-1), 8), + ('BACKGROUND', (0,0), (0,-1), colors.HexColor('#e8f3ed')), + ('BACKGROUND', (2,0), (2,-1), colors.HexColor('#e8f3ed')), + ('GRID', (0,0), (-1,-1), 0.5, colors.HexColor('#b8d9c5')), + ('TOPPADDING', (0,0), (-1,-1), 4), + ('BOTTOMPADDING',(0,0),(-1,-1), 4), + ('LEFTPADDING', (0,0), (-1,-1), 6), + ])) + story.append(ref_table) + story.append(Spacer(1, 0.3*cm)) + + def section(title): + story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor('#1f7a52'))) + story.append(Paragraph(f"◆ {title}", section_style)) + + def row(label, value): + story.append(Paragraph(f"{label}: {value}", body_style)) + + # ── Section A ── + section("SECTION A — COMPLAINANT INFORMATION") + for lbl, val in [("Full Name",name),("CNIC",cnic),("Phone",phone or "Not Provided"),("City",city),("Area / Location",location)]: + row(lbl, val) + + # ── Section B ── + section("SECTION B — COMPLAINT DETAILS") + for lbl, val in [("Issue Type",issue_type),("Location",f"{location}, {city}"),("Date & Time",f"{date_str}, {time_str}"),("Severity",f"{sev_icon(severity)} {severity} / 10")]: + row(lbl, val) + story.append(Paragraph(f"Description: {description.strip() or '[None provided]'}", body_style)) + + # ── Section C ── + section("SECTION C — AI VERIFICATION RESULTS") + for lbl, val in [("AI Status",gemini_status),("Confidence",gemini_confidence),("Finding",gemini_reason),("Recommended Action",gemini_action or "Immediate field inspection required."),("YOLO Detection",yolo_summary)]: + row(lbl, val) + + # ── Section D ── + section("SECTION D — LEGAL FRAMEWORK") + story.append(Paragraph("Applicable Legislation:", label_style)) + for l in kb.get("laws",[]): story.append(Paragraph(f" • {l}", body_style)) + for lbl, val in [("Responsible Authority",kb.get('authority','N/A')),("Official Helpline",kb.get('hotline','N/A')),("Statutory Response Time",kb.get('response','N/A')),("Fine / Penalty",kb.get('fine','N/A'))]: + row(lbl, val) + + # ── Section E ── + section("SECTION E — CITIZEN'S LEGAL RIGHTS") + for r in kb.get("citizen_rights",[]): story.append(Paragraph(f" • {r}", body_style)) + row("Escalation Path", kb.get('escalation','CM Portal: 0800-02345')) + + # ── Section F ── + section(f"SECTION F — LOCALIZED NOTICE ({language})") + story.append(Paragraph(local_msg or "N/A", body_style)) + + # ── Section G ── + section("SECTION G — ACTION DIRECTIVE") + story.append(Paragraph(f"MANDATORY ACTION REQUIRED WITHIN: {kb.get('response','72 hours').upper()}", label_style)) + row("Authority", kb.get('authority','N/A')) + row("Helpline", kb.get('hotline','N/A')) + row("Portal", "citizenportal.gov.pk | CM: 0800-02345") + + # ── Declaration ── + section("DECLARATION") + story.append(Paragraph(f"I, {name} (CNIC: {cnic}), declare that the information provided is true and correct to the best of my knowledge.", body_style)) + story.append(Spacer(1, 0.4*cm)) + sig_data = [["Signature: ________________________ (Digital)", f"Date: {date_str}", f"Ref: {complaint_id}"]] + sig_table = Table(sig_data, colWidths=[6*cm, 5.5*cm, 5.5*cm]) + sig_table.setStyle(TableStyle([('FONTSIZE',(0,0),(-1,-1),8),('TOPPADDING',(0,0),(-1,-1),4)])) + story.append(sig_table) + + # ── Official use ── + section("FOR OFFICIAL USE ONLY") + for lbl in ["Received By","Date of Receipt","Action Taken","Resolved On"]: + story.append(Paragraph(f"{lbl}: _____________________________", body_style)) + + # ── Footer ── + story.append(Spacer(1, 0.4*cm)) + story.append(HRFlowable(width="100%", thickness=0.5, color=colors.gray)) + story.append(Paragraph(f"Generated by: Rahbar — Pakistan's AI Civic Redressal Platform | Timestamp: {timestamp}", small_style)) + + doc.build(story) + buf.seek(0) + path = f"/tmp/rahbar_report_{complaint_id}.pdf" + with open(path, "wb") as f: f.write(buf.read()) + return path + except Exception as e: + print(f"PDF generation error: {e}") + # Fallback: plain text in a temp file + path = f"/tmp/rahbar_report_{complaint_id}.txt" + with open(path,"w",encoding="utf-8") as f: f.write(report_text) + return path +# ─── TEXT REPORT ────────────────────────────────────────────── +def build_text_report(cid, ts, name, cnic, phone, city, loc, issue, lang, sev, si, + yolo_s, g_status, g_reason, g_conf, g_action, kb, desc, local): + d=datetime.datetime.now().strftime("%d %B %Y"); t=datetime.datetime.now().strftime("%I:%M %p") + laws="".join(f"\n - {l}" for l in kb.get("laws",[])) + rights="".join(f"\n - {r}" for r in kb.get("citizen_rights",[])) + return f""" +=========================================================================== + GOVERNMENT OF PAKISTAN — CIVIC COMPLAINT REPORT + Rahbar Digital Civic Redressal System | رہبر +=========================================================================== + Complaint Ref : {cid} + Date / Time : {d} {t} + Platform : Rahbar AI (AI-Verified) + Language : {lang} +=========================================================================== + SECTION A — COMPLAINANT +=========================================================================== + Full Name : {name} + CNIC : {cnic} + Phone : {phone or "Not Provided"} + City : {city} + Location : {loc} +=========================================================================== + SECTION B — COMPLAINT DETAILS +=========================================================================== + Issue : {issue} + Location : {loc}, {city} + Severity : {si} {sev} / 10 + Description: {desc.strip() or "[None provided]"} +=========================================================================== + SECTION C — AI VERIFICATION +=========================================================================== + Status : {g_status} + Confidence : {g_conf} + Finding : {g_reason} + Action : {g_action or "Immediate field inspection required."} + YOLO : {yolo_s} +=========================================================================== + SECTION D — LEGAL FRAMEWORK +=========================================================================== + Laws :{laws} + + Authority : {kb.get('authority','N/A')} + Helpline : {kb.get('hotline','N/A')} + Response : {kb.get('response','N/A')} + Fine : {kb.get('fine','N/A')} +=========================================================================== + SECTION E — CITIZEN'S RIGHTS +=========================================================================== +{rights} + + Escalation : {kb.get('escalation','CM Portal: 0800-02345')} +=========================================================================== + SECTION F — LOCALIZED NOTICE ({lang}) +=========================================================================== + {local} +=========================================================================== + SECTION G — ACTION DIRECTIVE +=========================================================================== + MANDATORY ACTION WITHIN: {kb.get('response','72 hours').upper()} + Authority: {kb.get('authority','?')} | Helpline: {kb.get('hotline','?')} + Portal: citizenportal.gov.pk | CM: 0800-02345 +=========================================================================== + DECLARATION +=========================================================================== + I, {name} (CNIC: {cnic}), declare this information is true and correct. + Signature: ________________________ (Digital) Date: {d} + Reference: {cid} +=========================================================================== + FOR OFFICIAL USE ONLY +=========================================================================== + Received By : _________________________ + Date Received: _________________________ + Action Taken : _________________________ + Resolved On : _________________________ + Generated: Rahbar Pakistan AI Civic Platform | {ts} +=========================================================================== +""" # ─── MAIN REPORT FUNCTION ───────────────────────────────────── def make_report(image, issue_type, city, location, name, cnic, phone, description, language, enable_tts): - if image is None: - return (None, "براہ کرم ایک تصویر اپلوڈ کریں۔", "", "", None, "", None) - if not location.strip(): - return (None, "براہ کرم مقام درج کریں۔", "", "", None, "", None) - if not name.strip(): - return (None, "براہ کرم اپنا پورا نام درج کریں۔", "", "", None, "", None) - if not cnic.strip(): - return (None, "براہ کرم اپنا CNIC نمبر درج کریں۔", "", "", None, "", None) - - complaint_id = f"RB-{uuid.uuid4().hex[:8].upper()}" - timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - clean_issue = issue_type.split(" ", 1)[-1] - - annotated_img, yolo_summary, yolo_severity = detect_with_yolo(image, issue_type) - gemini_raw = analyze_with_gemini(image, issue_type, location, city, yolo_summary) - gemini_parsed = parse_gemini_response(gemini_raw) - gemini_status = gemini_parsed["status"] - gemini_reason = gemini_parsed["reason"] - - if gemini_status == "REJECTED": - return (annotated_img, - f"شکایت مسترد — AI تصدیق\n\nوجہ: {gemini_reason}\n" - f"اعتماد: {gemini_parsed.get('confidence','N/A')}\n\n" - f"براہ کرم مسئلے کی واضح تصویر اپلوڈ کریں ({issue_type})۔\n" - "آپ کی شکایت لاگ نہیں ہوئی۔", - "", "", None, complaint_id, None) - - if gemini_status == "UNKNOWN" and "GOOGLE_API_KEY not set" in gemini_raw: - gemini_reason = "Gemini API key missing — verification skipped." - gemini_status = "APPROVED_WITH_WARNING" - - final_severity = gemini_parsed["severity"] if gemini_status == "APPROVED" else yolo_severity - kb = LEGAL_KB.get(clean_issue, {}) - local = LOCALIZED.get(clean_issue, {}).get(language, "") - sev_icon = severity_icon(final_severity) - llama_advice = analyze_with_llama(issue_type, location, city, yolo_summary, final_severity, language) - - report = build_professional_report( - complaint_id, timestamp, name, cnic, phone, city, location, - issue_type, language, final_severity, sev_icon, - yolo_summary, gemini_status, gemini_reason, - gemini_parsed.get("confidence","N/A"), gemini_parsed.get("action",""), - kb, description, local - ) - - complaint_log.append({ - "id": complaint_id, "timestamp": timestamp, "city": city, "location": location, - "issue": issue_type, "severity": final_severity, "language": language, - "name": name, "cnic": cnic, "phone": phone, - }) + if image is None: return (None,"Please upload an image.","","",None,"",None,None) + if not location.strip(): return (None,"Please enter a location.","","",None,"",None,None) + if not name.strip(): return (None,"Please enter your full name.","","",None,"",None,None) + if not cnic.strip(): return (None,"Please enter your CNIC number.","","",None,"",None,None) + + cid = f"RB-{uuid.uuid4().hex[:8].upper()}" + ts = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + clean = issue_type.split(" ",1)[-1] + + ann, yolo_s, yolo_sev = detect_with_yolo(image, issue_type) + gem_raw = analyze_with_gemini(image, issue_type, location, city, yolo_s) + gem = parse_gemini(gem_raw) - wa_text = (f"رہبر شکایت\nRef: {complaint_id}\nمسئلہ: {issue_type}\n" - f"مقام: {location}, {city}\nشدت: {final_severity}/10\n" - f"ادارہ: {kb.get('authority','N/A')}\n" - f"ہیلپ لائن: {kb.get('hotline','N/A')}\nوقت: {timestamp}") - wa_md = f"[واٹس ایپ پر شیئر کریں](https://wa.me/?text={urllib.parse.quote(wa_text[:1000])})" + if gem["status"]=="REJECTED": + return (ann, f"COMPLAINT REJECTED BY AI\n\nReason: {gem['reason']}\nConfidence: {gem['confidence']}\n\nPlease upload a clear image showing the reported issue ({issue_type}).\nYour complaint has NOT been logged.", + "","",None,cid,None,None) - report_tts_path = None + if gem["status"]=="UNKNOWN" and "not set" in gem_raw: + gem["reason"]="Gemini key missing — accepted via YOLO."; gem["status"]="APPROVED_WITH_WARNING" + + final_sev = gem["severity"] if gem["status"]=="APPROVED" else yolo_sev + kb = LEGAL_KB.get(clean,{}); local = LOCALIZED.get(clean,{}).get(language,""); si = sev_icon(final_sev) + + advice = analyze_with_llama(issue_type, location, city, yolo_s, final_sev, language) + report = build_text_report(cid, ts, name, cnic, phone, city, location, issue_type, language, + final_sev, si, yolo_s, gem["status"], gem["reason"], + gem["confidence"], gem["action"], kb, description, local) + + complaint_log.append({"id":cid,"timestamp":ts,"city":city,"location":location, + "issue":issue_type,"severity":final_sev,"language":language, + "name":name,"cnic":cnic,"phone":phone}) + + wa_text = f"Rahbar Complaint\nRef: {cid}\nIssue: {issue_type}\nLocation: {location}, {city}\nSeverity: {final_sev}/10\nAuthority: {kb.get('authority','N/A')}\nHelpline: {kb.get('hotline','N/A')}\nFiled: {ts}" + wa_md = f"[📲 Share on WhatsApp](https://wa.me/?text={urllib.parse.quote(wa_text[:1000])})" + + report_tts = None if enable_tts: - tts_text = (f"شکایت {complaint_id} درج ہوئی۔ مسئلہ: {issue_type} " - f"مقام: {location}, {city}۔ شدت: {final_severity} میں سے 10۔ {local}") - report_tts_path = make_tts(tts_text, language) + report_tts = make_tts(f"Complaint {cid} filed. Issue: {issue_type} at {location} in {city}. Severity: {final_sev} out of 10. {local}", language) + + advice_tts = make_tts(advice[:600], language) - advice_tts_path = make_tts(llama_advice[:600], language) - return annotated_img, report, wa_md, llama_advice, report_tts_path, complaint_id, advice_tts_path + # Generate PDF + pdf_path = generate_pdf_report(cid, ts, name, cnic, phone, city, location, + issue_type, language, final_sev, yolo_s, + gem["status"], gem["reason"], gem["confidence"], + gem["action"], kb, description, local, report) + return ann, report, wa_md, advice, report_tts, cid, advice_tts, pdf_path def update_areas(city): - areas = CITIES_AREAS.get(city, ["علاقہ درج کریں"]) + areas = CITIES_AREAS.get(city,["Enter area manually"]) return gr.Dropdown(choices=areas, value=areas[0]) - -# ─── GPS reverse geocode (server-side fallback) ─────────────── -def gps_reverse_geocode(lat_str, lng_str): - """Called from GPS JS via gr.Button, fills location textbox""" +# ─── GPS REVERSE GEOCODE (Python / server-side) ─────────────── +def reverse_geocode_py(lat_str, lng_str): + """Python-side reverse geocoding — called by GPS button click""" try: - lat = float(lat_str) - lng = float(lng_str) - except Exception: - return "" + lat = float(lat_str); lng = float(lng_str) + except: return "Invalid coordinates" try: - import urllib.request, json url = f"https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat={lat}&lon={lng}&zoom=17&addressdetails=1" - req = urllib.request.Request(url, headers={"User-Agent": "Rahbar/5.0"}) - with urllib.request.urlopen(req, timeout=5) as resp: + req = urllib.request.Request(url, headers={"User-Agent":"Rahbar/6.0"}) + with urllib.request.urlopen(req, timeout=6) as resp: data = json.loads(resp.read()) - a = data.get("address", {}) - parts = [] - if a.get("road") or a.get("pedestrian"): - parts.append(a.get("road") or a.get("pedestrian")) - if a.get("suburb") or a.get("neighbourhood"): - parts.append(a.get("suburb") or a.get("neighbourhood")) - if a.get("city_district") or a.get("county"): - parts.append(a.get("city_district") or a.get("county")) - if not parts and data.get("display_name"): - parts = data["display_name"].split(",")[:3] + a = data.get("address",{}); parts=[] + if a.get("road") or a.get("pedestrian"): parts.append(a.get("road") or a.get("pedestrian")) + if a.get("suburb") or a.get("neighbourhood"): parts.append(a.get("suburb") or a.get("neighbourhood")) + if a.get("city_district") or a.get("county"): parts.append(a.get("city_district") or a.get("county")) + if not parts and data.get("display_name"): parts=data["display_name"].split(",")[:3] return ", ".join(p.strip() for p in parts if p.strip()) or f"GPS:{lat:.5f},{lng:.5f}" - except Exception: - return f"GPS:{lat:.5f},{lng:.5f}" - + except: return f"GPS:{lat:.5f},{lng:.5f}" # ─── CSS ────────────────────────────────────────────────────── CSS = """ @@ -937,63 +677,51 @@ CSS = """ --g9:#0d2b1e;--g8:#14432e;--g7:#1a5c3f;--g6:#1f7a52;--g5:#25a06b;--g4:#2ec97f; --a5:#f5a623;--a4:#d4870e; --surface:#ffffff;--surface2:#f4f8f5;--surface3:#e8f3ed; - --text-primary:#0d2b1e;--text-secondary:#2d5a3e;--text-muted:#5a8a6e; - --border:#b8d9c5;--border-strong:#1f7a52; - --shadow:0 2px 8px rgba(13,43,30,.10);--radius:12px;--radius-lg:20px; - --header-bg:linear-gradient(135deg,#14432e 0%,#0d2b1e 60%,#0a1f14 100%); - --warn-bg:#fffbf0;--warn-border:rgba(245,166,35,.5); - --info-bg:#f0faf4;--info-border:#1f7a52; + --text:#0d2b1e;--text2:#2d5a3e;--textm:#5a8a6e; + --border:#b8d9c5;--borderf:#1f7a52; + --shadow:0 2px 8px rgba(13,43,30,.10);--r:12px;--rl:20px; } @media(prefers-color-scheme:dark){ :root{ - --g9:#d0f0df;--g8:#1e6644;--g7:#28885a;--g6:#30aa72;--g5:#3dcf8a;--g4:#5de3a3; - --a5:#f7bc57;--a4:#f5a623; + --g4:#5de3a3;--g5:#3dcf8a;--g6:#30aa72;--a4:#f5a623;--a5:#f7bc57; --surface:#0f1f16;--surface2:#162b1e;--surface3:#1c3828; - --text-primary:#d0f0df;--text-secondary:#8fcfae;--text-muted:#5a9a78; - --border:#2a5c3e;--border-strong:#30aa72; - --shadow:0 2px 12px rgba(0,0,0,.4); - --header-bg:linear-gradient(135deg,#0a1f14 0%,#071510 60%,#050f0a 100%); - --warn-bg:#1e1800;--warn-border:rgba(247,188,87,.4); - --info-bg:#0e1f16;--info-border:#30aa72; + --text:#d0f0df;--text2:#8fcfae;--textm:#5a9a78; + --border:#2a5c3e;--borderf:#30aa72;--shadow:0 2px 12px rgba(0,0,0,.4); } } *,*::before,*::after{box-sizing:border-box} -body,.gradio-container{font-family:'DM Sans',sans-serif!important;background:var(--surface)!important;color:var(--text-primary)!important} -.rh-header{background:var(--header-bg);border-bottom:2px solid var(--g6);padding:24px 20px 20px;text-align:center;position:relative;overflow:hidden} +body,.gradio-container{font-family:'DM Sans',sans-serif!important;background:var(--surface)!important;color:var(--text)!important} +.rh-header{background:linear-gradient(135deg,#14432e 0%,#0d2b1e 60%,#0a1f14 100%);border-bottom:2px solid var(--g6);padding:24px 20px 20px;text-align:center;position:relative;overflow:hidden} .rh-header::before{content:'';position:absolute;inset:0;background:radial-gradient(ellipse 60% 60% at 50% 0%,rgba(37,160,107,.18),transparent);pointer-events:none} .rh-header h1{font-family:'Playfair Display',serif!important;font-size:clamp(1.8rem,5vw,3rem)!important;font-weight:900!important;color:#fafdf8!important;margin:0 0 4px!important;line-height:1.1} .rh-header .rh-sub{font-family:'Noto Nastaliq Urdu',serif;font-size:clamp(1rem,3vw,1.5rem);color:#f7bc57;direction:rtl;margin:4px 0 8px} .rh-header .rh-tag{font-size:clamp(.75rem,2vw,.9rem);color:#5de3a3;letter-spacing:.08em;text-transform:uppercase} .badge-strip{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;padding:10px 16px;background:var(--surface2);border-bottom:1px solid var(--border)} .badge{font-size:.72rem;font-weight:600;letter-spacing:.06em;padding:4px 12px;border-radius:20px;text-transform:uppercase} -.badge-ai{background:var(--surface);color:var(--g4);border:1px solid var(--border-strong)} -.badge-pk{background:var(--surface);color:var(--a4);border:1px solid var(--warn-border)} +.badge-ai{background:var(--surface);color:var(--g4);border:1px solid var(--borderf)} +.badge-pk{background:var(--surface);color:var(--a4);border:1px solid rgba(245,166,35,.4)} .badge-live{background:var(--surface);color:#ff8080;border:1px solid rgba(232,83,83,.4)} .tab-nav{background:var(--surface2)!important;border-bottom:2px solid var(--border)!important} -.tab-nav button{font-family:'DM Sans',sans-serif!important;font-weight:500!important;font-size:.85rem!important;color:var(--text-muted)!important;padding:12px 18px!important;border-radius:0!important;transition:all .2s!important} +.tab-nav button{font-family:'DM Sans',sans-serif!important;font-weight:500!important;font-size:.85rem!important;color:var(--textm)!important;padding:12px 18px!important;border-radius:0!important;transition:all .2s!important} .tab-nav button.selected,.tab-nav button[aria-selected="true"]{color:var(--a4)!important;border-bottom:3px solid var(--a5)!important;background:transparent!important} .card-title{font-size:.7rem;font-weight:600;letter-spacing:.1em;text-transform:uppercase;color:var(--g4);margin-bottom:12px;padding-bottom:8px;border-bottom:1px solid var(--border)} -label,.gradio-container .label-wrap span{color:var(--text-primary)!important} -.gradio-container input,.gradio-container textarea{background:var(--surface)!important;border:1px solid var(--border-strong)!important;border-radius:var(--radius)!important;color:var(--text-primary)!important;font-family:'DM Sans',sans-serif!important} +label,.gradio-container .label-wrap span{color:var(--text)!important} +.gradio-container input,.gradio-container textarea{background:var(--surface)!important;border:1px solid var(--borderf)!important;border-radius:var(--r)!important;color:var(--text)!important;font-family:'DM Sans',sans-serif!important} .gradio-container input:focus,.gradio-container textarea:focus{border-color:var(--a5)!important;outline:none!important;box-shadow:0 0 0 3px rgba(245,166,35,.15)!important} -.gradio-container .wrap{background:var(--surface)!important;border-color:var(--border-strong)!important} +.gradio-container .wrap{background:var(--surface)!important;border-color:var(--borderf)!important} .gradio-container .block{background:var(--surface)!important} -.gradio-container button.primary{background:linear-gradient(135deg,var(--g6),var(--g5))!important;color:#fafdf8!important;border:none!important;border-radius:var(--radius)!important;font-weight:600!important;font-size:.9rem!important;padding:12px 24px!important;cursor:pointer!important;box-shadow:var(--shadow)!important;transition:all .2s!important} +.gradio-container button.primary{background:linear-gradient(135deg,var(--g6),var(--g5))!important;color:#fafdf8!important;border:none!important;border-radius:var(--r)!important;font-weight:600!important;font-size:.9rem!important;padding:12px 24px!important;cursor:pointer!important;box-shadow:var(--shadow)!important;transition:all .2s!important} .gradio-container button.primary:hover{background:linear-gradient(135deg,var(--g5),var(--g4))!important;transform:translateY(-1px)!important} -.gradio-container button.secondary{background:var(--surface)!important;border:1px solid var(--border-strong)!important;color:var(--g4)!important} -.gradio-container [data-testid="image"]{border:2px dashed var(--border-strong)!important;border-radius:var(--radius-lg)!important;background:var(--surface2)!important} +.gradio-container button.secondary{background:var(--surface)!important;border:1px solid var(--borderf)!important;color:var(--g4)!important} +.gradio-container [data-testid="image"]{border:2px dashed var(--borderf)!important;border-radius:var(--rl)!important;background:var(--surface2)!important} .gradio-container .prose h2,.gradio-container .prose h3{color:var(--a4)!important} -.gradio-container audio{width:100%!important;border-radius:var(--radius)!important} -.info-box{background:var(--info-bg);border:1px solid var(--info-border);border-left:4px solid var(--g5);border-radius:var(--radius);padding:12px 16px;font-size:.88rem;line-height:1.6;margin-bottom:8px;color:var(--text-secondary)} -.warn-box{background:var(--warn-bg);border:1px solid var(--warn-border);border-left:4px solid var(--a5);border-radius:var(--radius);padding:12px 16px;font-size:.88rem;margin-bottom:8px;color:var(--text-secondary)} -.hotline-pill{display:inline-block;background:var(--surface2);color:var(--a4);border:1px solid var(--warn-border);border-radius:20px;padding:2px 12px;font-size:.8rem;font-weight:600} +.gradio-container audio{width:100%!important;border-radius:var(--r)!important} .gradio-container textarea{font-family:'DM Mono','Courier New',monospace!important;font-size:.82rem!important;line-height:1.7!important} -.gradio-container select,.gradio-container [data-testid="dropdown"]{background:var(--surface)!important;color:var(--text-primary)!important;border-color:var(--border-strong)!important} -.gradio-container .message.user{background:var(--surface3)!important;color:var(--text-primary)!important} -.gradio-container .message.bot{background:var(--surface2)!important;color:var(--text-primary)!important} -#map_container{border-radius:12px;overflow:hidden;border:2px solid var(--border-strong);margin:8px 0} -#rahbar_map{height:260px;width:100%;background:#e8f5e9} -.gps-status{font-size:.82rem;min-height:28px;padding:7px 12px;border-radius:8px;background:var(--surface2);border:1px solid var(--border);color:var(--g5);font-weight:500;margin-bottom:8px} +.info-box{background:var(--surface2);border:1px solid var(--borderf);border-left:4px solid var(--g5);border-radius:var(--r);padding:12px 16px;font-size:.88rem;line-height:1.6;margin-bottom:8px;color:var(--text2)} +.warn-box{background:#fffbf0;border:1px solid rgba(245,166,35,.5);border-left:4px solid var(--a5);border-radius:var(--r);padding:12px 16px;font-size:.88rem;margin-bottom:8px;color:var(--text2)} +.hotline-pill{display:inline-block;background:var(--surface2);color:var(--a4);border:1px solid rgba(245,166,35,.4);border-radius:20px;padding:2px 12px;font-size:.8rem;font-weight:600} +#rahbar-map-box{height:260px;width:100%;border-radius:12px;border:2px solid var(--borderf);background:#e8f5e9;position:relative;overflow:hidden;margin:8px 0} +.gps-status-box{font-size:.82rem;min-height:28px;padding:7px 12px;border-radius:8px;background:var(--surface2);border:1px solid var(--border);color:var(--g5);font-weight:500;margin-bottom:8px} ::-webkit-scrollbar{width:6px;height:6px} ::-webkit-scrollbar-track{background:var(--surface2)} ::-webkit-scrollbar-thumb{background:var(--g6);border-radius:3px} @@ -1002,302 +730,302 @@ label,.gradio-container .label-wrap span{color:var(--text-primary)!important} HEADER_HTML = """
-

Rahbar

-
رہبر — ہمارا حق، ہماری آواز
+

Rahbar | رہبر

+
ہمارا حق — ہماری آواز
Pakistan's AI-Powered Civic Complaint System
- Gemini Vision + Gemini 2.0 YOLOv8 Llama 3.3 RAG Chatbot - 4 زبانیں + 4 Languages + PDF Export LIVE
""" -# ─── MAP HTML — Leaflet loaded directly (no iframe sandbox) ─── -# This is injected into the Gradio page DOM directly. -# The GPS button Python handler updates lat_state/lng_state, -# and a JS watcher moves the map pin accordingly. +# ─── MAP HTML ───────────────────────────────────────────────── +# Key insight: The GPS button is a PYTHON gr.Button. +# JS only handles the map pin movement and filling location box. +# Actual reverse geocode is done Python-side via the button click. MAP_HTML = """ -
-
- نقشہ تیار ہے۔ GPS بٹن دبائیں یا نقشے پر کلک کریں۔ -
-
-

- نقشے پر کلک کریں یا پن گھسیٹیں — GPS بٹن آپ کا پتہ خود بھرے گا -

+ +
+ Map ready — click "Get My GPS Location" or click on the map to set location.
+
+
+
+

+ Click map or drag pin to change location · GPS button auto-fills address +

- """ - # ─── BUILD UI ───────────────────────────────────────────────── def build_ui(): - with gr.Blocks(title="Rahbar | رہبر", css=CSS) as demo: + # gr.Blocks with NO css/theme (Gradio 6: these go in launch()) + with gr.Blocks(title="Rahbar | رہبر") as demo: gr.HTML(HEADER_HTML) with gr.Tabs(): - # ══════════════════════════════════════════════════ - # TAB 1 — شکایت درج کریں - # ══════════════════════════════════════════════════ - with gr.Tab("📝 شکایت درج کریں"): + # ══ TAB 1: Report Issue ═══════════════════════════ + with gr.Tab("📸 Report Issue"): with gr.Row(equal_height=False): - # ── بائیں: inputs ────────────────────────── + # ── Left column ─────────────────────────── with gr.Column(scale=1, min_width=300): - - gr.HTML('
شہری معلومات
') - name_tb = gr.Textbox(label="پورا نام", placeholder="مثلاً: علی رضا", lines=1) - cnic_tb = gr.Textbox(label="شناختی کارڈ نمبر (بغیر ڈیش)", placeholder="1234567890123", lines=1) - phone_tb = gr.Textbox(label="فون نمبر (اختیاری)", placeholder="03xxxxxxxxx", lines=1) - - gr.HTML('
مسئلے کی تصویر
') - gr.HTML("""
مسئلے کی واضح تصویر اپلوڈ کریں۔ -موبائل پر: "ویب کیم" کے ذریعے کیمرہ استعمال کر سکتے ہیں۔
""") - image_input = gr.Image( - type="pil", - label="تصویر اپلوڈ یا کیپچر کریں", - sources=["webcam", "upload"], - height=220, - ) - - gr.HTML('
شکایت کی تفصیل
') - issue_type = gr.Radio(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="مسئلے کی نوعیت") - city_dd = gr.Dropdown(choices=list(CITIES_AREAS.keys()), value="Lahore", label="شہر") - area_dd = gr.Dropdown(choices=CITIES_AREAS["Lahore"], value="Model Town", label="علاقہ") - - gr.HTML('
GPS مقام
') + gr.HTML('
👤 Citizen Details
') + name_tb = gr.Textbox(label="Full Name", placeholder="e.g., Ali Raza", lines=1) + cnic_tb = gr.Textbox(label="CNIC (without dashes)", placeholder="1234567890123", lines=1) + phone_tb = gr.Textbox(label="Phone Number (optional)",placeholder="03xxxxxxxxx", lines=1) + + gr.HTML('
📸 Issue Photo
') + gr.HTML('
Upload a clear photo of the civic issue. On mobile you can use your camera directly.
') + image_input = gr.Image(type="pil", label="Upload / Take Photo", + sources=["webcam","upload"], height=220) + + gr.HTML('
📋 Complaint Details
') + issue_type = gr.Radio(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="Issue Type") + city_dd = gr.Dropdown(choices=list(CITIES_AREAS.keys()), value="Lahore", label="City") + area_dd = gr.Dropdown(choices=CITIES_AREAS["Lahore"], value="Model Town", label="Area") + + gr.HTML('
📍 GPS Location
') gr.HTML("""
-"میرا GPS مقام حاصل کریں" بٹن دبائیں اور مقام کی اجازت دیں۔ -آپ کا پتہ خود بھر جائے گا۔ نقشے پر کلک کر کے بھی مقام تبدیل کر سکتے ہیں۔ +Click "Get My GPS Location" and allow location access when your browser asks.
+Your address will auto-fill. You can also click on the map to drop a pin.
""") - # GPS button — elem_id for JS targeting - gps_btn = gr.Button( - "📍 میرا GPS مقام حاصل کریں", - variant="primary", - elem_id="rahbar_gps_btn", - ) + # GPS button — elem_id so JS can identify it + gps_btn = gr.Button("📍 Get My GPS Location", variant="primary", + elem_id="rahbar-gps-btn") - # Map — loaded directly in page (not iframe) + # Map rendered via HTML (Leaflet, not sandboxed) gr.HTML(MAP_HTML) location_tb = gr.Textbox( - label="گلی / نشانی / GPS پتہ", - placeholder="GPS پتہ یہاں خود بھرے گا — یا خود لکھیں", - lines=1, - elem_id="rahbar_location_tb", + label="Street / Landmark / GPS Address", + placeholder="GPS address auto-fills here — or type street/landmark", + lines=1 ) - desc_tb = gr.Textbox(label="اضافی تفصیل (اختیاری)", placeholder="مسئلے کو بیان کریں...", lines=3) - language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="رپورٹ اور آواز کی زبان") - tts_cb = gr.Checkbox(label="رپورٹ اونچی آواز میں پڑھیں (Text-to-Speech)", value=False) - submit_btn = gr.Button("شکایت جمع کریں", variant="primary", size="lg") + # GPS button Python event — dummy (JS handles GPS, Python updates state) + gps_status_tb = gr.Textbox(visible=False, value="") + gps_btn.click(fn=lambda: "GPS button clicked — check map above", + inputs=[], outputs=[gps_status_tb]) - # ── دائیں: outputs ──────────────────────── - with gr.Column(scale=2, min_width=320): + desc_tb = gr.Textbox(label="Description (optional)", placeholder="Describe the issue in detail...", lines=3) + language_dd = gr.Dropdown(choices=LANGUAGES, value="English", + label="🌐 Report & Voice Language") + tts_cb = gr.Checkbox(label="🔊 Read report aloud (TTS)", value=False) + submit_btn = gr.Button("🚀 Submit Complaint", variant="primary", size="lg") - gr.HTML('
AI تجزیہ کے نتائج
') - annotated_out = gr.Image(label="AI ڈیٹیکشن نتیجہ", height=240) - complaint_id_out = gr.Textbox(label="شکایت نمبر", interactive=False) + # ── Right column ────────────────────────── + with gr.Column(scale=2, min_width=320): + gr.HTML('
📊 AI Analysis Results
') + annotated_out = gr.Image(label="🔍 AI Detection Result", height=240) + complaint_id_out = gr.Textbox(label="🆔 Complaint ID", interactive=False) - gr.HTML('
سرکاری شکایت رپورٹ
') + gr.HTML('
📄 Official Complaint Report
') report_out = gr.Textbox( - label="مکمل شکایت رپورٹ (جمع کرانے کے لیے تیار)", - lines=22, interactive=False, - placeholder="جمع کرانے کے بعد رپورٹ یہاں آئے گی..." + label="Full Report (Ready for Submission)", + lines=20, interactive=False, + placeholder="Report will appear here after submission..." ) - wa_out = gr.Markdown() - report_tts_out = gr.Audio(label="رپورٹ آڈیو (منتخب زبان میں)", autoplay=False) + wa_out = gr.Markdown() + + # PDF Download button + gr.HTML('
⬇️ Download Report
') + pdf_download = gr.File(label="📥 Download PDF Report", visible=True) - gr.HTML('
قانونی مشورہ
') - gr.HTML('
پاکستانی شہری قانون کی بنیاد پر AI کا قانونی رہنمائی۔
') + report_tts_out = gr.Audio(label="🔊 Report Voice (selected language)", autoplay=False) + + gr.HTML('
⚖️ Legal Advice
') + gr.HTML('
AI-generated legal guidance in your selected language, based on Pakistani civic law.
') legal_advice_out = gr.Textbox( - label="قانونی حقوق اور اقدامات", - lines=12, interactive=False, - placeholder="قانونی مشورہ یہاں آئے گا..." + label="⚖️ Legal Rights & Action Steps (selected language)", + lines=10, interactive=False, + placeholder="Legal advice will appear here in your selected language..." ) - advice_tts_out = gr.Audio(label="قانونی مشورہ آڈیو (منتخب زبان میں)", autoplay=False) + advice_tts_out = gr.Audio(label="🔊 Legal Advice Voice (selected language)", autoplay=False) city_dd.change(fn=update_areas, inputs=[city_dd], outputs=[area_dd]) submit_btn.click( @@ -1305,169 +1033,132 @@ def build_ui(): inputs=[image_input, issue_type, city_dd, location_tb, name_tb, cnic_tb, phone_tb, desc_tb, language_dd, tts_cb], outputs=[annotated_out, report_out, wa_out, legal_advice_out, - report_tts_out, complaint_id_out, advice_tts_out], + report_tts_out, complaint_id_out, advice_tts_out, pdf_download] ) - # ══════════════════════════════════════════════════ - # TAB 2 — قانون اور چیٹ بوٹ - # ══════════════════════════════════════════════════ - with gr.Tab("⚖️ قانون اور چیٹ بوٹ"): + # ══ TAB 2: Law Reference + RAG Chatbot ═══════════ + with gr.Tab("⚖️ Law & Chatbot"): - gr.HTML('
پاکستانی شہری قوانین کا ڈیٹا بیس
') + gr.HTML('
📜 Pakistani Civic Laws Database
') with gr.Row(): - law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="مسئلہ منتخب کریں", scale=1) - law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="زبان", scale=1) + law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="Select Issue", scale=1) + law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Language", scale=1) law_out = gr.Markdown() - gr.Button("قانونی تفصیل دکھائیں", variant="primary").click( + gr.Button("📖 Show Law Details", variant="primary").click( fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out] ) gr.HTML("""
-فوری ہیلپ لائنیں:  -کچرا: 1139 |  -سڑکیں/NHA: 051-9032800 |  -WASA لاہور: 042-99200300 |  -CM پورٹل: 0800-02345 |  -وفاقی محتسب: 051-9204551 + Quick Hotlines: + Garbage 1139 + Roads 051-9032800 + WASA 042-99200300 + CM Portal 0800-02345 + Ombudsman 051-9204551
""") - gr.HTML('
🤖 RAG قانونی چیٹ بوٹ
') + gr.HTML('
🤖 RAG Legal Chatbot
') gr.HTML("""
-یہ چیٹ بوٹ RAG (Retrieval-Augmented Generation) استعمال کرتا ہے۔ -پانی، WASA، پائپ لیکیج، کچرا، سڑک کے گڑھے، یا پاکستانی شہری قوانین کے بارے میں پوچھیں۔ -اب آپ آواز سے بھی سوال پوچھ سکتے ہیں اور جواب سن سکتے ہیں۔ + This chatbot uses RAG (Retrieval-Augmented Generation) with Road Issues Detection Dataset, + Urban Issues Dataset, Consumer Complaints Dataset, and Pakistani civic law database.
+ Ask about water, WASA, pipe leakage, garbage, roads, potholes, or your legal rights. + Voice input and TTS output supported below.
""") - chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="جواب کی زبان") - chatbot = gr.Chatbot(label="رہبر قانونی معاون", height=420, value=[]) + chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="🌐 Response Language") + chatbot = gr.Chatbot(label="Rahbar RAG Legal Assistant", height=450, value=[]) - # ── Chat input row ──────────────────────────── + # Text input row with gr.Row(): chat_input = gr.Textbox( - label="آپ کا سوال", - placeholder="مثلاً: WASA نے 3 دن میں پائپ ٹھیک نہیں کیا — میرے کیا حقوق ہیں؟", + label="Your Question", + placeholder="e.g., WASA did not fix the pipe for 3 days — what are my rights?", lines=2, scale=4 ) - chat_send_btn = gr.Button("بھیجیں", variant="primary", scale=1) + chat_send_btn = gr.Button("Send ➤", variant="primary", scale=1) - # ── Voice input for chatbot ─────────────────── - gr.HTML('
🎤 آواز سے سوال پوچھیں
') + # Voice input for chatbot + gr.HTML('
🎤 Voice Input for Chatbot
') + gr.HTML('
Record your question, then click "Send Voice Question".
') with gr.Row(): - chat_audio_in = gr.Audio( - type="filepath", - label="آواز ریکارڈ کریں", - sources=["microphone", "upload"], - scale=3, - ) - chat_voice_btn = gr.Button("🎤 آواز سے سوال بھیجیں", variant="secondary", scale=1) + chat_audio_in = gr.Audio(type="filepath", label="Record Question", + sources=["microphone","upload"], scale=3) + chat_voice_btn = gr.Button("🎤 Send Voice Question", variant="secondary", scale=1) - # ── Chatbot TTS output ──────────────────────── - gr.HTML('
🔊 آواز میں جواب سنیں
') + # TTS output for chatbot + gr.HTML('
🔊 Voice Output
') with gr.Row(): - chat_tts_out = gr.Audio(label="چیٹ بوٹ کا آواز جواب", autoplay=False, scale=3) - chat_tts_btn = gr.Button("🔊 آخری جواب سنیں", variant="secondary", scale=1) + chat_tts_out = gr.Audio(label="Last Answer (Audio)", autoplay=False, scale=3) + chat_tts_btn = gr.Button("🔊 Read Last Answer", variant="secondary", scale=1) - # ── Examples ────────────────────────────────── gr.Examples( examples=[ - ["WASA نے 3 دن میں لیکیج ٹھیک نہیں کی — میرے کیا حقوق ہیں؟"], - ["میرے علاقے کا پانی آلودہ ہے — کہاں شکایت کروں؟"], - ["ایک ہفتے سے کچرا نہیں اٹھا — کون سا قانون لاگو ہوتا ہے؟"], - ["ادارہ شکایت نظرانداز کرے تو کیا کریں؟"], - ["گڑھے سے گاڑی کو نقصان ہوا — کیا معاوضہ مل سکتا ہے؟"], - ["Pakistan Citizen Portal پر شکایت کیسے کریں؟"], + ["WASA did not fix the leakage for 3 days. What are my rights?"], + ["The water in my area is contaminated. Where do I complain?"], + ["Garbage not collected for a week — which law applies?"], + ["How do I escalate if the authority ignores my complaint?"], + ["Pothole damaged my car — can I get compensation?"], + ["How to file a complaint on Pakistan Citizen Portal?"], + ["میرے علاقے میں پانی آلودہ ہے — میں کہاں شکایت کروں؟"], ], - inputs=chat_input, - label="یہ سوال آزمائیں" + inputs=chat_input, label="💡 Try These Questions" ) - # ── Event wiring for chatbot ────────────────── - chat_send_btn.click( - fn=legal_chatbot_rag, + # Event wiring + chat_send_btn.click(fn=legal_chatbot_rag, inputs=[chat_input, chatbot, chat_lang_dd], - outputs=[chatbot, chat_input] - ) - chat_input.submit( - fn=legal_chatbot_rag, + outputs=[chatbot, chat_input]) + chat_input.submit(fn=legal_chatbot_rag, inputs=[chat_input, chatbot, chat_lang_dd], - outputs=[chatbot, chat_input] - ) - - def voice_then_send(audio_file, history, language): - """Transcribe voice, send to chatbot, return updated history""" - if audio_file is None: - return history, "" - transcribed = stt(audio_file) - if not transcribed or transcribed.startswith("آڈیو") or transcribed.startswith("ٹرانسکرپشن"): - return history, transcribed - return legal_chatbot_rag(transcribed, history, language)[0], "" - - chat_voice_btn.click( - fn=voice_then_send, + outputs=[chatbot, chat_input]) + chat_voice_btn.click(fn=voice_then_chat, inputs=[chat_audio_in, chatbot, chat_lang_dd], - outputs=[chatbot, chat_input] - ) - chat_tts_btn.click( - fn=chatbot_tts_output, + outputs=[chatbot, chat_input]) + chat_tts_btn.click(fn=chatbot_tts, inputs=[chatbot, chat_lang_dd], - outputs=[chat_tts_out] - ) + outputs=[chat_tts_out]) - # ══════════════════════════════════════════════════ - # TAB 3 — آواز کا ان پٹ / STT - # ══════════════════════════════════════════════════ - with gr.Tab("🎤 آواز کا ان پٹ"): + # ══ TAB 3: Voice / STT ════════════════════════════ + with gr.Tab("🎤 Voice Input / STT"): - gr.HTML('
تقریر سے متن (Speech-to-Text)
') + gr.HTML('
🎙️ Speech-to-Text Transcription
') gr.HTML("""
-اپنی شکایت یا تفصیل نیچے مائیکروفون سے ریکارڈ کریں۔ -ٹرانسکرپشن Groq Whisper large-v3 (اگر GROQ_API_KEY سیٹ ہو) -یا Google Speech Recognition (متبادل) سے ہوگی۔
-اردو، انگریزی، پنجابی اور سندھی میں بولیں۔ + Record your complaint or description below. Transcription uses Groq Whisper large-v3 + (if GROQ_API_KEY is set) or Google Speech Recognition as fallback.
+ After transcription, copy the text to the Report Issue tab.
""") gr.HTML("""
-تجویز: واضح آواز میں بولیں، شور کم ہو۔ -ٹرانسکرپشن کے بعد متن کاپی کریں اور "شکایت درج کریں" ٹیب میں پیسٹ کریں۔ + ⚠️ Allow microphone access when your browser asks. Works best in Chrome/Edge. + Urdu, English, Punjabi, and Sindhi are supported.
""") - audio_in = gr.Audio( - type="filepath", - label="آڈیو ریکارڈ یا اپلوڈ کریں", - sources=["microphone", "upload"], - ) - stt_btn = gr.Button("آڈیو کو متن میں تبدیل کریں", variant="primary") - stt_out = gr.Textbox( - label="ٹرانسکرپشن (قابل ترمیم — شکایت ٹیب میں کاپی کریں)", - lines=6, interactive=True, - placeholder="ٹرانسکرپٹ شدہ متن یہاں آئے گا..." - ) + audio_in = gr.Audio(type="filepath", label="🎤 Record or Upload Audio", + sources=["microphone","upload"]) + stt_btn = gr.Button("📝 Transcribe Audio", variant="primary") + stt_out = gr.Textbox(label="📄 Transcription (editable — copy to Report tab)", + lines=6, interactive=True, + placeholder="Transcribed text will appear here...") stt_btn.click(fn=stt, inputs=[audio_in], outputs=[stt_out]) - gr.HTML('
متن سے آواز (TTS) ٹیسٹ
') - gr.HTML('
کسی بھی زبان میں آواز آزمائیں۔
') + gr.HTML('
🔊 Text-to-Speech Test
') + gr.HTML('
Test voice output in any supported language.
') with gr.Row(): - tts_text_in = gr.Textbox(label="سننے کے لیے متن درج کریں", placeholder="کچھ لکھیں...", scale=3) - tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="English", label="زبان", scale=1) - tts_test_btn = gr.Button("▶ چلائیں", variant="secondary") - tts_test_out = gr.Audio(label="آڈیو آؤٹ پٹ", autoplay=True) + tts_text_in = gr.Textbox(label="Enter text to hear", placeholder="Type something...", scale=3) + tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="English", label="Language", scale=1) + tts_test_btn = gr.Button("▶️ Play", variant="secondary") + tts_test_out = gr.Audio(label="🔊 Audio Output", autoplay=True) tts_test_btn.click(fn=make_tts, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out]) - # ══════════════════════════════════════════════════ - # TAB 4 — ایڈمن ڈیش بورڈ - # ══════════════════════════════════════════════════ - with gr.Tab("📊 ایڈمن ڈیش بورڈ"): - - gr.HTML('
شکایات کے اعداد و شمار
') - refresh_btn = gr.Button("اعداد و شمار تازہ کریں", variant="primary") + # ══ TAB 4: Admin Dashboard ═══════════════════════ + with gr.Tab("🛡️ Admin Dashboard"): + gr.HTML('
📊 Complaint Statistics
') + refresh_btn = gr.Button("🔄 Refresh Stats", variant="primary") with gr.Row(): stats_out = gr.Markdown() log_out = gr.Markdown() refresh_btn.click(fn=get_admin_stats, outputs=[stats_out, log_out]) - gr.HTML("""
-RAG نالج بیس: -Road Issues Detection Dataset  |  Urban Issues Dataset  |  Consumer Complaints Dataset
-AI ماڈلز: -Gemini 2.0 Flash  |  YOLOv8  |  Llama 3.3 70B  |  Whisper large-v3 (Groq)
-ویکٹر سرچ: TF-IDF Cosine Similarity (char n-gram, کثیر زبان) + RAG Knowledge Base: Road Issues Detection Dataset | Urban Issues Dataset | Consumer Complaints Dataset
+ AI Stack: Gemini 2.0 Flash | YOLOv8 | Llama 3.3 70B | Whisper large-v3 (Groq) | TF-IDF RAG
+ New in v6.0: English UI | PDF Reports | Voice Chatbot | Working GPS Map
""") return demo @@ -1475,17 +1166,18 @@ Gemini 2.0 Flash  |  YOLOv8  |  Llama 3.3 70B  |  # ─── LAUNCH ─────────────────────────────────────────────────── if __name__ == "__main__": - print("Rahbar v5.0 شروع ہو رہا ہے...") - print("RAG Engine:", "تیار" if rag_engine._initialized else "شروع ہو رہا ہے...") + print("Rahbar v6.0 starting...") + print(f"RAG Engine: {'Ready' if rag._ready else 'Initializing...'}") demo = build_ui() demo.launch( server_name="0.0.0.0", server_port=7860, share=True, + # Gradio 6: CSS and theme go HERE in launch(), NOT in gr.Blocks() + css=CSS, theme=gr.themes.Base( primary_hue=gr.themes.colors.green, secondary_hue=gr.themes.colors.yellow, ), - ) - - \ No newline at end of file + ssr_mode=False, # Disable SSR to ensure JS works correctly + ) \ No newline at end of file