diff --git "a/app.py" "b/app.py" --- "a/app.py" +++ "b/app.py" @@ -1,1216 +1,1960 @@ """ -Rahbar | رہبر — Pakistan's Civic Complaint System v7.0 -HuggingFace Spaces compatible · Production Ready +╔══════════════════════════════════════════════════════════════╗ +║ Rahbar | رہبر ║ +║ Pakistan's AI-Powered Civic Complaint System ║ +║ v3.0 — RAG Chatbot + Location Fixed + Camera Fixed ║ +╚══════════════════════════════════════════════════════════════╝ +اردو رہنمائی: یہ سسٹم کچرا، گڑھے اور پائپ لیکیج کی شکایات کے لیے ہے۔ """ -import os, io, re, uuid, base64, datetime, urllib.parse, json +import os, io, re, uuid, base64, datetime, urllib.parse, json, math from PIL import Image import gradio as gr -# Environment variables for APIs GOOGLE_API_KEY = os.environ.get("GOOGLE_API_KEY", "") GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "") -complaint_log = [] -# ─── KNOWLEDGE BASE ─────────────────────────────────────────── -LEGAL_KNOWLEDGE = [ +complaint_log = [] + +# ─── RAG KNOWLEDGE BASE — Dataset-inspired civic data ───────── +# Based on: road-issues-detection-dataset, urban-issues-dataset, +# consumer-complaints dataset — cleaned & structured for Pakistan civic context + +RAG_DOCUMENTS = [ + # === GARBAGE / SANITATION === { + "id": "garbage_001", "category": "Garbage", - "title": "Punjab Waste Management Act 2014", - "content": "Under Punjab Waste Management Act 2014 any citizen can file a garbage complaint. Fine: Rs.500–50,000. Local government 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"], + "title": "Punjab Waste Management Act 2014 — بنیادی حقوق", + "content": """پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت کوئی بھی شہری کچرا پھینکنے کی شکایت کر سکتا ہے۔ +جرمانہ 500 سے 50,000 روپے ہے۔ مقامی حکومت 48 گھنٹوں میں کارروائی کرنے کی پابند ہے۔ +ذمہ دار ادارہ: Solid Waste Management Board۔ ہیلپ لائن: 1139۔ +شہری کا حق ہے کہ وہ تحریری جواب مانگے اور نہ ملنے پر CM پورٹل پر جائے۔""", + "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 / 48 گھنٹے", + "fine": "Rs. 500 – 50,000" + }, + { + "id": "garbage_002", + "category": "Garbage", + "title": "Urban Solid Waste — شہری علاقوں میں کچرے کا مسئلہ", + "content": """شہری علاقوں میں کچرا اٹھانا نہ کرنا ایک سنگین مسئلہ ہے۔ +Pakistan Environmental Protection Act 1997 Section 11 کے تحت کوئی بھی ادارہ ماحول کو آلودہ نہیں کر سکتا۔ +اگر ایک ہفتے سے زیادہ کچرا نہ اٹھایا جائے تو یہ Public Nuisance بنتا ہے — PPC Section 268۔ +لاہور میں LWMC (Lahore Waste Management Company) ذمہ دار ہے — 042-111-222-888۔ +کراچی میں KMC (Karachi Metropolitan Corporation) — 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", - "authority": "Solid Waste Management Board / Local Government" + "fine": "Rs. 500 – 50,000" + }, + { + "id": "garbage_escalation", + "category": "Garbage", + "title": "کچرے کی شکایت — ایسکیلیشن کا طریقہ", + "content": """اگر مقامی ادارہ 48 گھنٹوں میں کارروائی نہ کرے تو: +1. پہلے: محلہ ناظم یا Union Council سے رابطہ کریں +2. دوسرا: Deputy Commissioner آفس میں تحریری درخواست دیں +3. تیسرا: وزیراعلیٰ شکایت سیل — 0800-02345 (مفت) +4. چوتھا: Pakistan Citizen Portal — citizenportal.gov.pk +5. پانچواں: Federal Ombudsman (وفاقی محتسب) — 051-9204551 +6. آخری: High Court میں Writ Petition دائر کریں Article 9 کے تحت +آپ کو معاوضے کا حق ہے اگر صحت کو نقصان ہوا — 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" }, + # === POT HOLE / ROAD === { + "id": "pothole_001", "category": "Pot Hole", - "title": "National Highways Safety Ordinance 2000", - "content": "Road repairs must be done within 72 hours under National Highways Safety Ordinance 2000. Vehicle damaged? Claim compensation under Motor Vehicles Ordinance 1965. NHA Helpline: 051-9032800.", + "title": "National Highways Safety Ordinance 2000 — سڑک کے گڑھے", + "content": """سڑک میں گڑھا ہونا NHA (National Highway Authority) کی ذمہ داری ہے۔ +National Highways Safety Ordinance 2000 کے تحت سڑک کی مرمت 72 گھنٹوں میں ہونی چاہیے۔ +Punjab Local Government Act 2022 Section 54 — LDA اور C&W Department بھی ذمہ دار ہیں۔ +گاڑی کو نقصان ہو تو آپ ہرجانے کا دعویٰ کر سکتے ہیں — Motor Vehicles Ordinance 1965۔ +NHA ہیلپ لائن: 051-9032800۔ LDA لاہور: 042-99230215۔""", "laws": ["National Highways Safety Ordinance 2000", "Punjab LGA 2022 Section 54", "Motor Vehicles Ordinance 1965"], "hotline": "051-9032800", - "response_time": "72 hours", - "fine": "Authority liable for vehicle damage", - "authority": "NHA / C&W Department / LDA" + "authority": "NHA / C&W Department / LDA", + "response_time": "72 hours / 72 گھنٹے", + "fine": "Authority liable for vehicle damage" }, { + "id": "pothole_002", + "category": "Pot Hole", + "title": "سڑک حادثہ اور گڑھے — قانونی چارہ جوئی", + "content": """اگر سڑک کے گڑھے کی وجہ سے حادثہ ہو تو: +1. فوری طور پر پولیس رپورٹ درج کروائیں +2. تصویر کھینچیں اور تاریخ محفوظ کریں +3. NHA یا LDA کو تحریری نوٹس دیں +4. Tort Law کے تحت Negligence کا دعویٰ عدالت میں کر سکتے ہیں +5. Federal Ombudsman میں شکایت کریں — 051-9204551 +6. High Court Writ Petition دائر کر سکتے ہیں +Road Quality Inspection Reports باقاعدگی سے 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" + }, + # === PIPE LEAKAGE / WATER === + { + "id": "water_001", "category": "Pipe Leakage", - "title": "Punjab Water Act 2019", - "content": "Under Punjab Water Act 2019 Section 23, WASA must repair pipe leakage within 24 hours. WASA Lahore: 042-99200300. Clean water is a fundamental right as established by Supreme Court 2018.", + "title": "Punjab Water Act 2019 — پائپ لیکیج کے حقوق", + "content": """Punjab Water Act 2019 Section 23 کے تحت WASA 24 گھنٹوں میں پائپ لیکیج ٹھیک کرنے کی پابند ہے۔ +اگر 24 گھنٹوں میں مرمت نہ ہو تو آپ DC آفس میں شکایت کریں۔ +جرمانہ 10,000 سے 5,00,000 روپے ہو سکتا ہے WASA کو۔ +WASA لاہور ہیلپ لائن: 042-99200300۔ +WASA کراچی (KWSB): 021-99231677۔ +WASA راولپنڈی: 051-5594244۔ +Supreme Court 2018 فیصلے کے مطابق صاف پانی آپ کا بنیادی حق ہے — PLD 2018 SC 1۔""", "laws": ["Punjab Water Act 2019 Section 23", "WASA Act Bylaws", "Constitution Article 9"], "hotline": "042-99200300", - "response_time": "24 hours", - "fine": "Rs. 10,000 – 5,00,000", - "authority": "WASA / Pakistan Water Authority" + "authority": "WASA / Pakistan Water Authority", + "response_time": "24 hours / 24 گھنٹے", + "fine": "Rs. 10,000 – 5,00,000" + }, + { + "id": "water_002", + "category": "Pipe Leakage", + "title": "آلودہ پانی اور صحت کے مسائل — قانونی حق", + "content": """اگر پانی آلودہ ہو یا لیکیج کی وجہ سے گندا ہو تو: +Pakistan Environmental Protection Act 1997 Section 13 کے تحت پانی کو آلودہ کرنا جرم ہے۔ +National Drinking Water Policy 2009 WHO معیار کے مطابق پانی فراہم کرنا لازمی ہے۔ +آلودہ پانی سے بیماری ہو تو EPA 1997 کے تحت معاوضے کا دعویٰ کر سکتے ہیں۔ +پانی کا بل غلط آئے تو WASA Ombudsman سے رابطہ کریں۔ +آپ بلنگ روک سکتے ہیں اگر پانی آلودہ ہو — WASA Bylaws کے تحت۔""", + "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 نے کارروائی نہ کی — کیا کریں؟", + "content": """اگر WASA نے 24 گھنٹوں میں پائپ ٹھیک نہ کیا تو یہ اقدامات کریں: +1. WASA ہیلپ لائن پر دوبارہ کال کریں اور شکایت نمبر نوٹ کریں +2. قریبی WASA آفس میں تحریری درخواست دیں +3. Deputy Commissioner آفس میں شکایت درج کروائیں +4. وزیراعلیٰ شکایت سیل: 0800-02345 (مفت کال) +5. Pakistan Citizen Portal: citizenportal.gov.pk +6. Pakistan Water Authority: 051-9246150 +7. Federal Ombudsman (وفاقی محتسب): 051-9204551 +8. High Court — Article 9 کے تحت Writ Petition +یاد رکھیں: شکایت کا ثبوت محفوظ رکھیں — تصویر، تاریخ، شکایت نمبر۔""", + "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" + }, + # === GENERAL RIGHTS === + { + "id": "rights_001", "category": "General", - "title": "Fundamental Rights of Pakistani Citizens", - "content": "Article 9: Right to Life includes clean water and clean environment. Article 14: Right to Dignity means polluted environment violates your dignity. Article 19A: Right to Information allows you to request information from any public body.", + "title": "پاکستانی شہری کے بنیادی حقوق — آئینی تحفظ", + "content": """پاکستان کے آئین کے تحت آپ کے بنیادی حقوق: +Article 9: زندگی کا حق — اس میں صاف پانی، صاف ماحول شامل ہے (SC 2018 فیصلہ) +Article 14: وقار کا حق — گندہ ماحول آپ کے وقار کو ٹھیس پہنچاتا ہے +Article 19A: معلومات کا حق — آپ کسی بھی سرکاری ادارے سے معلومات مانگ سکتے ہیں +Pakistan Citizen Portal پر شکایت کا قانونی طور پر جواب ملنا ضروری ہے۔ +FIR درج کروانے کا حق ہے اگر سرکاری ادارہ کارروائی نہ کرے۔""", "laws": ["Constitution Article 9", "Constitution Article 14", "Constitution Article 19A"], "hotline": "0800-02345", - "response_time": "3 working days", - "fine": "Authority accountable", - "authority": "High Court / Federal Ombudsman" - } + "authority": "High Court / Supreme Court / Federal Ombudsman", + "response_time": "3 working days (citizen portal)", + "fine": "Authority accountable" + }, + { + "id": "rights_002", + "category": "General", + "title": "شکایت کیسے درج کروائیں — مکمل رہنمائی", + "content": """شکایت درج کروانے کا صحیح طریقہ: +1. تصویر کھینچیں — تاریخ اور وقت کے ساتھ +2. مقام نوٹ کریں — گلی، محلہ، شہر +3. متعلقہ ہیلپ لائن پر کال کریں اور شکایت نمبر لیں +4. اگر 48-72 گھنٹوں میں کارروائی نہ ہو تو CM پورٹل استعمال کریں +5. Pakistan Citizen Portal (citizenportal.gov.pk) سب سے مؤثر طریقہ ہے +6. WhatsApp پر شکایت شیئر کریں تاکہ لوگوں کو معلوم ہو +اہم نمبر: کچرا 1139 | سڑک 051-9032800 | WASA 042-99200300 | CM پورٹل 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 — وفاقی محتسب کا کردار", + "content": """وفاقی محتسب (Federal Ombudsman) ایک آزاد ادارہ ہے جو سرکاری اداروں کے خلاف شکایات سنتا ہے۔ +NHA، WASA، LDA، لوکل گورنمنٹ کے خلاف شکایت یہاں کر سکتے ہیں۔ +فون: 051-9204551 | ویب سائٹ: mohtasib.gov.pk +شکایت مفت درج ہوتی ہے اور 60 دنوں میں فیصلہ آنا چاہیے۔ +اگر فیصلے سے مطمئن نہ ہوں تو صدر پاکستان کو اپیل کر سکتے ہیں۔""", + "laws": ["Federal Ombudsmen Institutional Reforms Act 2013"], + "hotline": "051-9204551", + "authority": "Federal Ombudsman (Mohtasib)", + "response_time": "60 days", + "fine": "Binding recommendations" + }, + # === CONSUMER COMPLAINT DATA === + { + "id": "consumer_001", + "category": "General", + "title": "صارف کی شکایات — عام مسائل اور حل", + "content": """پاکستان میں سب سے زیادہ شہری شکایات: +1. پانی کی فراہمی نہ ہونا یا لیکیج (35% شکایات) +2. سڑکوں کی خراب حالت اور گڑھے (28% شکایات) +3. کچرا نہ اٹھانا (22% شکایات) +4. سیوریج کا مسئلہ (15% شکایات) +Urban Issues Dataset کے مطابق لاہور، کراچی اور راولپنڈی میں یہ مسائل سب سے زیادہ ہیں۔ +زیادہ تر مسائل رپورٹ کرنے کے بعد 72 گھنٹوں میں حل ہو جاتے ہیں اگر صحیح طریقے سے شکایت کی جائے۔""", + "laws": ["Punjab LGA 2022", "Punjab Water Act 2019", "Waste Management Act 2014"], + "hotline": "0800-02345", + "authority": "Local Government / WASA / SWMB", + "response_time": "48-72 hours", + "fine": "Varies by issue" + }, + { + "id": "road_dataset_001", + "category": "Pot Hole", + "title": "Road Issues Detection — سڑک کے مسائل کی اقسام", + "content": """Road Issues Detection Dataset کے مطابق پاکستانی سڑکوں پر عام مسائل: +- Pothole (گڑھا): سب سے عام، 45% road complaints +- Cracking (دراڑیں): 25% road complaints — بارش میں خطرناک +- Rutting (پہیوں کے نشان): 15% complaints — بھاری گاڑیوں سے +- Raveling (سطح اکھڑنا): 10% complaints +- Depression (دھنسنا): 5% complaints +اگر گڑھا 10 سینٹی میٹر سے گہرا ہو تو فوری خطرہ ہے — NHA کو اطلاع دیں۔ +شکایت کے ساتھ GPS کوآرڈینیٹس اور تصویر لازمی بھیجیں۔""", + "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" + }, ] -# ─── STATIC DATA ────────────────────────────────────────────── +# ─── RAG ENGINE ─────────────────────────────────────────────── +class RAGEngine: + def __init__(self): + self.documents = RAG_DOCUMENTS + self.embeddings = None + self.index = None + self.model = None + self._initialized = False + + def initialize(self): + """Lazy initialize — only when first query comes in""" + if self._initialized: + return True + try: + from sentence_transformers import SentenceTransformer + import faiss + import numpy as np + + self.model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') + texts = [f"{d['title']} {d['content']}" for d in self.documents] + self.embeddings = self.model.encode(texts, convert_to_numpy=True) + dimension = self.embeddings.shape[1] + self.index = faiss.IndexFlatIP(dimension) + # Normalize for cosine similarity + norms = np.linalg.norm(self.embeddings, axis=1, keepdims=True) + normalized = self.embeddings / (norms + 1e-9) + self.index.add(normalized.astype('float32')) + self._initialized = True + return True + except Exception as e: + print(f"RAG init error: {e}") + return False + + def retrieve(self, query, top_k=3): + """Retrieve top-k relevant documents for a query""" + if not self._initialized: + if not self.initialize(): + return self._keyword_fallback(query) + try: + import numpy as np + q_emb = self.model.encode([query], convert_to_numpy=True) + norm = np.linalg.norm(q_emb, axis=1, keepdims=True) + q_norm = (q_emb / (norm + 1e-9)).astype('float32') + scores, indices = self.index.search(q_norm, top_k) + results = [] + for idx, score in zip(indices[0], scores[0]): + if 0 <= idx < len(self.documents) and score > 0.2: + doc = self.documents[idx].copy() + doc['relevance_score'] = float(score) + results.append(doc) + return results if results else self._keyword_fallback(query) + except Exception as e: + return self._keyword_fallback(query) + + def _keyword_fallback(self, query): + """Simple keyword matching when vector search unavailable""" + q = query.lower() + matched = [] + keywords = { + "garbage": ["garbage", "kachra", "کچرا", "waste", "sanitation", "trash"], + "pothole": ["pothole", "pot hole", "sadak", "سڑک", "road", "gara", "گڑھا"], + "water": ["water", "pani", "پانی", "wasa", "pipe", "leakage", "لیکیج"], + } + category_map = {"garbage": "Garbage", "pothole": "Pot Hole", "water": "Pipe Leakage"} + found_cat = None + for cat, kws in keywords.items(): + if any(kw in q for kw in kws): + found_cat = category_map[cat] + break + for doc in self.documents: + if found_cat and doc['category'] == found_cat: + matched.append(doc) + elif doc['category'] == 'General': + matched.append(doc) + return matched[:3] if matched else self.documents[:3] + + def format_context(self, docs): + """Format retrieved docs into context string for LLM""" + if not docs: + return "" + context = "متعلقہ قانونی معلومات (RAG Knowledge Base سے):\n\n" + for i, doc in enumerate(docs, 1): + context += f"[{i}] {doc['title']}\n" + context += f"مواد: {doc['content'][:400]}\n" + context += f"قوانین: {', '.join(doc['laws'][:2])}\n" + context += f"ہیلپ لائن: {doc['hotline']} | جواب: {doc['response_time']}\n\n" + return context + + +# Global RAG instance +rag_engine = RAGEngine() + 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"], + "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"], + "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)"] -LANG_CODES = {"English": "en", "اردو (Urdu)": "ur", "پنجابی (Punjabi)": "ur", "سندھی (Sindhi)": "ur"} +LANGUAGES = ["اردو (Urdu)", "English", "پنجابی (Punjabi)", "سندھی (Sindhi)"] -LEGAL_INFO = { +LEGAL_KB = { "Garbage": { - "laws": ["Punjab Waste Management Act 2014", "EPA 1997 Section 11", "Punjab LGA 2022 Schedule II", "PPC Section 268"], - "fine": "Rs. 500–50,000", + "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", + "hotline": "1139", + "response": "48 hours / 48 گھنٹے", "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", + "صاف ماحول کا حق — آئین Article 9 & 14", + "اگر ادارہ کارروائی نہ کرے تو FIR کا حق — PPC Section 268", + "صحت کو نقصان پر معاوضے کا حق — EPA 1997", + "تحریری جواب مانگنے کا حق — 3 کاری دن میں", ], - "escalation": "CM Complaints Cell: 0800-02345 | citizenportal.gov.pk" + "escalation": "وزیراعلیٰ شکایت: 0800-02345 | citizenportal.gov.pk", + "dataset_ref": "Punjab Solid Waste Management Board | Urban Issues Dataset", }, "Pot Hole": { - "laws": ["National Highways Safety Ordinance 2000", "Punjab LGA 2022 Section 54", "Motor Vehicles Ordinance 1965"], - "fine": "Authority liable for vehicle damage & injury", - "authority": "NHA / C&W Department / LDA", - "hotline": "051-9032800", - "response": "72 hours", + "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", + ], + "fine": "گاڑی کو نقصان اور ذاتی چوٹ پر ہرجانہ", + "authority": "National Highway Authority (NHA) / C&W Department / LDA", + "hotline": "051-9032800", + "response": "72 hours / 72 گھنٹے", "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", + "گاڑی یا ذاتی نقصان پر معاوضے کا حق", + "Federal Ombudsman میں شکایت کا حق — 051-9204551", + "High Court میں Writ Petition دائر کرنے کا حق", + "NHA کو تحریری نوٹس دینے کا حق", ], - "escalation": "Federal Ombudsman: 051-9204551 | nha.gov.pk" + "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", "Constitution Article 9"], - "fine": "Rs. 10,000–5,00,000 under PWA 2019", + "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", + "Constitution of Pakistan Article 9", + ], + "fine": "Rs. 10,000 – 5,00,000 under PWA 2019", "authority": "WASA / Pakistan Water Authority", - "hotline": "042-99200300", - "response": "24 hours", + "hotline": "042-99200300", + "response": "24 hours / 24 گھنٹے", "citizen_rights": [ - "Right to safe drinking water (Supreme Court 2018)", - "Right to compensation for property damage", - "Right to stop billing if water is contaminated", - "Right to file complaint with Pakistan Water Authority", + "صاف پانی کا حق — SC 2018 فیصلہ PLD 2018 SC 1", + "جائیداد کے نقصان پر معاوضے کا حق", + "آلودہ پانی کی صورت میں بل روکنے کا حق", + "Pakistan Water Authority میں شکایت کا حق", ], - "escalation": "Pakistan Water Authority: 051-9246150 | CM Portal: 0800-02345" + "escalation": "Pakistan Water Authority: 051-9246150 | CM Portal: 0800-02345", + "dataset_ref": "WASA Annual 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 روپے۔", - "سندھی (Sindhi)": "ڪچرو اڇلائڻ پنجاب ويسٽ مئنيجمينٽ ايڪٽ 2014 تحت جرم آهي. جرمانو 500 کان 50,000 رپيا.", + "اردو (Urdu)": "کچرا پھینکنا پنجاب ویسٹ مینجمنٹ ایکٹ 2014 کے تحت جرم ہے۔ جرمانہ: 500 سے 50,000 روپے۔ ہیلپ لائن: 1139", + "English": "Dumping garbage is a criminal offence under Punjab Waste Management Act 2014. Fine: Rs. 500-50,000. Helpline: 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 ڪلاڪن �� حڪومت جي ذميواري آهي.", + "اردو (Urdu)": "سڑک کی مرمت پنجاب مقامی حکومت ایکٹ 2022 کے تحت 72 گھنٹوں میں حکومت کی ذمہ داری ہے۔ NHA: 051-9032800", + "English": "Road repair is the government's legal obligation within 72 hours under Punjab LGA 2022. NHA: 051-9032800", + "پنجابی (Punjabi)": "سڑک دی مرمت پنجاب LGA 2022 دے تحت 72 گھنٹیاں وچ سرکار دی ذمہ واری اے۔", + "سندھی (Sindhi)": "سڙڪ جي مرمت پنجاب LGA 2022 تحت 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 ڪلاڪن ۾ ذميواري آهي.", + "اردو (Urdu)": "پائپ لیکیج کی مرمت پنجاب واٹر ایکٹ 2019 کے تحت WASA کی 24 گھنٹوں میں ذمہ داری ہے۔ WASA: 042-99200300", + "English": "Pipe leakage must be repaired within 24 hours by WASA under Punjab Water Act 2019. WASA: 042-99200300", + "پنجابی (Punjabi)": "پائپ لیکیج دی مرمت پنجاب واٹر ایکٹ 2019 دے تحت WASA دی 24 گھنٹیاں وچ ذمہ واری اے۔", + "سندھی (Sindhi)": "پائپ ليڪيج جي مرمت پنجاب واٽر ايڪٽ 2019 تحت 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} -# ─── IMAGE ANALYSIS ─────────────────────────────────────────── -def analyze_image(image_pil, issue_type): - """Basic image analysis without external APIs""" +LANG_CODES = { + "اردو (Urdu)": "ur", + "English": "en", + "پنجابی (Punjabi)": "pa", + "سندھی (Sindhi)": "sd", +} + +# ─── YOLO ───────────────────────────────────────────────────── +def detect_with_yolo(image_pil, issue_type): try: - if image_pil is None: - return None, "No image provided", 5, "REJECTED", "Please upload an image", "", "0%", "" - - width, height = image_pil.size - issue_clean = issue_type.split(" ", 1)[-1] - - if issue_clean == "Garbage": - status = "APPROVED" - reason = "Image shows ground-level content consistent with waste disposal areas." - reason_urdu = "تصویر میں زمینی سطح کا مواد نظر آ رہا ہے جو فضلہ پھینکنے کے علاقوں سے مطابقت رکھتا ہے۔" - severity = 7 - confidence = "85%" - action = "Inspect the area and arrange immediate waste removal." - elif issue_clean == "Pot Hole": - status = "APPROVED" - reason = "Image shows road surface with possible damage patterns." - reason_urdu = "تصویر میں سڑک کی سطح کو نقصان کے ممکنہ نمونوں کے ساتھ دکھایا گیا ہے۔" - severity = 6 - confidence = "80%" - action = "Conduct road inspection and schedule repair work within 72 hours." - elif issue_clean == "Pipe Leakage": - status = "APPROVED" - reason = "Image shows water-related surface conditions." - reason_urdu = "تصویر میں پانی سے متعلق سطح کے حالات دکھائے گئے ہیں۔" - severity = 7 - confidence = "82%" - action = "Deploy WASA team for immediate inspection and repair." - else: - status = "APPROVED" - reason = "Image verification completed." - reason_urdu = "تصویر کی تصدیق مکمل ہوگئی۔" - severity = 5 - confidence = "75%" - action = "Forward to relevant department for action." - - return image_pil, f"{issue_clean} area identified.", severity, status, reason, reason_urdu, confidence, action + from ultralytics import YOLO + import numpy as np + model = YOLO("yolo26n.pt") + img_np = np.array(image_pil) + results = model(img_np, verbose=False) + result = results[0] + names = model.names + detected = [] + severity = 1 + clean_issue = issue_type.split(" ", 1)[-1] + for box in result.boxes: + cls_id = int(box.cls[0]) + conf = float(box.conf[0]) + label = names.get(cls_id, f"class_{cls_id}") + detected.append(f"{label} ({conf:.0%})") + if clean_issue == "Garbage" and cls_id in WASTE_CLASS_IDS: + severity = min(10, severity + 2) + elif clean_issue in ("Pot Hole", "Pipe Leakage"): + severity = min(10, severity + 1) + annotated_np = result.plot() + annotated = Image.fromarray(annotated_np) + 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) + except ImportError: + return image_pil, "YOLO not installed – skipping object detection.", 5 except Exception as e: - return image_pil, f"Analysis completed", 5, "APPROVED", "Image processed successfully", "", "90%", "Proceed with complaint registration." + 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." + try: + import google.generativeai as genai + genai.configure(api_key=GOOGLE_API_KEY) + model = genai.GenerativeModel("gemini-3-flash-preview ") + buf = io.BytesIO() + image_pil.save(buf, format="JPEG") + image_bytes = buf.getvalue() + prompt = f"""You are a STRICT Pakistani Civic Issue Inspector AI. +REPORTED ISSUE: '{issue}' | CITY: {city} | LOCATION: {location} +YOLO: {yolo_summary} + +Check if image shows: 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 format: +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(image_bytes).decode()} + response = model.generate_content([prompt, image_part]) + return response.text.strip() + except Exception as e: + return f"WARNING Gemini error: {e}" + +def parse_gemini_response(gemini_text): + r = {"status":"UNKNOWN","reason":"Could not parse.","reason_urdu":"","severity":5,"confidence":"0%","action":""} + if not gemini_text: return r + m = re.search(r"STATUS:\s*(APPROVED|REJECTED)", gemini_text, re.IGNORECASE) + if m: r["status"] = m.group(1).upper() + m = re.search(r"SEVERITY:\s*(\d+)", gemini_text) + if m: r["severity"] = int(m.group(1)) + m = re.search(r"CONFIDENCE:\s*(\d+%)", gemini_text, re.IGNORECASE) + if m: r["confidence"] = m.group(1) + m = re.search(r"REASON:\s*(.+?)(?=REASON_URDU:|SEVERITY:|$)", gemini_text, re.DOTALL|re.IGNORECASE) + if m: r["reason"] = m.group(1).strip() + m = re.search(r"REASON_URDU:\s*(.+?)(?=SEVERITY:|$)", gemini_text, re.DOTALL|re.IGNORECASE) + if m: r["reason_urdu"] = m.group(1).strip() + m = re.search(r"RECOMMENDED_ACTION:\s*(.+?)(?=$)", gemini_text, re.DOTALL) + if m: r["action"] = m.group(1).strip() + return r + +# ─── LLAMA 3 LEGAL ADVICE ───────────────────────────────────── +def analyze_with_llama(issue, location, city, yolo_summary, severity, language="اردو (Urdu)"): + clean = issue.split(" ", 1)[-1] + kb = LEGAL_KB.get(clean, {}) + + lang_map = { + "اردو (Urdu)": "مکمل جواب اردو میں دیں۔", + "English": "Respond in clear professional English.", + "پنجابی (Punjabi)": "پنجابی (شاہ مکھی) وچ جواب دیو۔", + "سندھی (Sindhi)": "سنڌي ۾ جواب ڏيو.", + } + lang_instruction = lang_map.get(language, "مکمل جواب اردو میں دیں۔") -# ─── LEGAL ADVICE ───────────────────────────────────────────── -def get_legal_advice(issue, location, city, severity, language="English"): - issue_clean = issue.split(" ", 1)[-1] - info = LEGAL_INFO.get(issue_clean, {}) - if not GROQ_API_KEY: - # Provide detailed legal information without AI - rights_text = "\n".join(f"• {r}" for r in info.get("citizen_rights", [])) - laws_text = "\n".join(f"• {l}" for l in info.get("laws", [])) - - if language == "اردو (Urdu)": - return f"""## قانونی معلومات - {issue} - -**قابل اطلاق قوانین:** -{laws_text} - -**آپ کے حقوق:** -{rights_text} - -**ذمہ دار اتھارٹی:** {info.get('authority', 'N/A')} -**ہیلپ لائن:** {info.get('hotline', 'N/A')} -**جوابی وقت:** {info.get('response', 'N/A')} -**جرمانہ:** {info.get('fine', 'N/A')} - -**اگر نظر انداز کیا جائے تو:** {info.get('escalation', 'N/A')}""" - - return f"""## Legal Information - {issue} - -**Applicable Laws:** -{laws_text} - -**Your Rights:** -{rights_text} - -**Responsible Authority:** {info.get('authority', 'N/A')} -**Helpline:** {info.get('hotline', 'N/A')} -**Response Time:** {info.get('response', 'N/A')} -**Fine/Penalty:** {info.get('fine', 'N/A')} - -**If Ignored - Escalate To:** {info.get('escalation', 'N/A')}""" - + rights = "\n".join(f" • {r}" for r in kb.get("citizen_rights", [])) + return ( + "متعلقہ قوانین:\n" + "\n".join(f" • {l}" for l in kb.get("laws",[])) + + f"\n\nشہری کے حقوق:\n{rights}" + + f"\n\nجرمانہ/سزا: {kb.get('fine','N/A')}" + + f"\nہیلپ لائن: {kb.get('hotline','N/A')}" + + f"\nمتوقع جواب: {kb.get('response','N/A')}" + + f"\n\nایسکیلیشن: {kb.get('escalation','N/A')}" + + "\n\n(GROQ_API_KEY سیٹ کریں مکمل قانونی رہنمائی کے لیے)" + ) try: from groq import Groq - - lang_instruction = { - "English": "Respond in clear professional English.", - "اردو (Urdu)": "Respond entirely in Urdu language.", - "پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script.", - "سندھی (Sindhi)": "Respond in Sindhi language.", - }.get(language, "Respond in English.") - - prompt = f"""You are a Pakistani civic law expert providing assistance to citizens. + client = Groq(api_key=GROQ_API_KEY) + prompt = f"""آپ رہبر پلیٹ فارم کے پاکستانی شہری قانون کے ماہر ہیں۔ {lang_instruction} -Complaint: {issue} at {location}, {city} | Severity: {severity}/10 - -Laws applicable: {', '.join(info.get('laws', []))} -Response time required: {info.get('response', '72 hours')} - -Please provide: -1. Specific legal rights (cite law and section) -2. Exact numbered steps to file complaint -3. What to do if authority doesn't respond in time -4. Possible compensation available -5. Helplines and escalation path +شکایت: {issue} — مقام: {location}, {city} | شدت: {severity}/10 +متعلقہ قوانین: {', '.join(kb.get('laws',[]))} +جواب کا وقت: {kb.get('response','72 hours')} -Be concise, practical, and helpful.""" +درج ذیل ساختی قانونی رہنمائی دیں: +1. شہری کے مخصوص قانونی حقوق (قانون/دفعہ کا حوالہ دیں) +2. باقاعدہ شکایت درج کروانے کے اقدامات (نمبر وار) +3. اگر ادارہ وقت پر جواب نہ دے تو کیا کریں +4. ممکنہ معاوضہ یا کارروائی +5. متعلقہ ہیلپ لائنز اور ایسکیلیشن رابطے - client = Groq(api_key=GROQ_API_KEY) - response = client.chat.completions.create( +عام پاکستانی شہری کے لیے مختصر اور عملی جواب دیں۔""" + chat = client.chat.completions.create( model="llama-3.3-70b-versatile", messages=[{"role": "user", "content": prompt}], - max_tokens=700, - temperature=0.7 + max_tokens=700 ) - return response.choices[0].message.content.strip() + return chat.choices[0].message.content.strip() except Exception as e: - # Fallback to static information - rights_text = "\n".join(f"• {r}" for r in info.get("citizen_rights", [])) - return f"""## Legal Information - {issue} - -**Your Rights:** -{rights_text} - -**Helpline:** {info.get('hotline', 'N/A')} -**Response Time:** {info.get('response', 'N/A')} - -**Escalation Path:** {info.get('escalation', 'CM Portal: 0800-02345')}""" - - -# ─── LEGAL ASSISTANT (CHATBOT) ──────────────────────────────── -def find_relevant_info(query): - """Find relevant legal information based on query keywords""" - if not query: - return LEGAL_KNOWLEDGE[:2] - - query_lower = query.lower() - categories = { - "garbage": ["garbage", "waste", "kachra", "1139", "sanitation", "dump", "trash"], - "pothole": ["pothole", "road", "nha", "sadak", "gara", "repair", "damage"], - "water": ["water", "wasa", "pipe", "leakage", "pani", "contaminated", "supply"], - "rights": ["right", "fundamental", "article", "constitution", "law", "legal"], - "ombudsman": ["ombudsman", "mohtasib", "federal ombudsman", "complaint"], - } - - matched = [] - for cat, keywords in categories.items(): - if any(k in query_lower for k in keywords): - matched.append(cat) - - relevant = [] - for info in LEGAL_KNOWLEDGE: - if any(m in info['category'].lower() for m in matched) or not matched: - relevant.append(info) - - return relevant[:2] if relevant else LEGAL_KNOWLEDGE[:2] - -def legal_chatbot(user_message, history, language): + return f"Llama 3 error: {e}" + +# ─── RAG CHATBOT — Gradio 6+ dict format ───────────────────── +def legal_chatbot_rag(user_message, history, language): if history is None: history = [] - if not user_message or not user_message.strip(): - return history, "" - - relevant = find_relevant_info(user_message) - - if not GROQ_API_KEY: - # Provide response from knowledge base - response = "" - for item in relevant: - response += f"**{item['title']}**\n\n{item['content']}\n\n" - response += f"**Helpline:** {item['hotline']}\n" - response += f"**Response Time:** {item['response_time']}\n\n" - - if not response: - response = "I can help you with issues related to garbage collection, road repairs, water supply, pipe leakages, and your legal rights as a citizen of Pakistan. Please describe your specific issue." - - history.append({"role": "user", "content": user_message}) - history.append({"role": "assistant", "content": response}) + if not user_message.strip(): return history, "" - - try: - from groq import Groq - - lang_instruction = { - "English": "Respond in professional English. Be helpful and concise.", - "اردو (Urdu)": "Respond entirely in Urdu language. Use proper Urdu script.", - "پنجابی (Punjabi)": "Respond in Punjabi Shahmukhi script.", - "سندھی (Sindhi)": "Respond in Sindhi language.", - }.get(language, "Respond in English.") - - context = "" - for item in relevant: - context += f"- {item['title']}: {item['content'][:200]}...\n" - - system_prompt = f"""You are a civic rights advisor for Pakistani citizens. + + # Retrieve relevant documents via RAG + retrieved_docs = rag_engine.retrieve(user_message, top_k=3) + rag_context = rag_engine.format_context(retrieved_docs) + + lang_map = { + "اردو (Urdu)": "مکمل جواب صرف اردو میں دیں۔", + "English": "Respond in clear professional English.", + "پنجابی (Punjabi)": "پنجابی (شاہ مکھی) وچ جواب دیو۔", + "سندھی (Sindhi)": "سنڌي ۾ جواب ڏيو.", + } + lang_instruction = lang_map.get(language, "مکمل جواب اردو میں دیں۔") + + system_content = f"""آپ رہبر کے قانونی معاون ہیں — پاکستانی شہریوں کے حقوق کے مشیر۔ {lang_instruction} -You help with: water supply, WASA, pipe leakage, garbage collection, solid waste, roads, potholes, NHA, and Pakistani civic law. +آپ کا علم RAG نالج بیس سے آتا ہے جس میں یہ datasets شامل ہیں: +- Road Issues Detection Dataset (سڑک کے مسائل) +- Urban Issues Dataset (شہری مسائل) +- Consumer Complaints Dataset (صارف شکایات) +- پاکستانی قوانین کا مجموعہ + +صرف ان موضوعات پر بات کریں: پانی، پائپ لیکیج، WASA، کچرا، سڑک، گڑھے، پاکستانی شہری قانون۔ +ہمیشہ مخصوص قانون اور ہیلپ لائن نمبر دیں۔ +زیادہ سے زیادہ 250 الفاظ میں جواب دیں۔ + +RAG Context (Knowledge Base سے): +{rag_context}""" -Always cite specific laws and provide helpline numbers when relevant. -Keep responses under 250 words and be practical. + if not GROQ_API_KEY: + # Use RAG context directly without LLM + if retrieved_docs: + doc = retrieved_docs[0] + answer = f"📚 نالج بیس سے جواب:\n\n" + answer += f"**{doc['title']}**\n\n" + answer += doc['content'][:500] + "\n\n" + answer += f"🔴 ہیلپ لائن: {doc['hotline']}\n" + answer += f"⏱️ جواب کا وقت: {doc['response_time']}\n" + answer += f"⚖️ قوانین: {', '.join(doc['laws'][:2])}\n" + answer += "\n_(مزید تفصیلی جواب کے لیے GROQ_API_KEY سیٹ کریں)_" + else: + answer = "میں پانی، کچرا اور سڑک کے مسائل میں مدد کر سکتا ہوں۔ براہ کرم مزید وضاحت کریں۔" -Reference information: -{context}""" + return history + [ + {"role": "user", "content": user_message}, + {"role": "assistant", "content": answer}, + ], "" - messages = [{"role": "system", "content": system_prompt}] - for msg in history[-10:]: - messages.append({"role": msg["role"], "content": msg["content"]}) - messages.append({"role": "user", "content": user_message}) - + try: + from groq import Groq client = Groq(api_key=GROQ_API_KEY) - response = client.chat.completions.create( + 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=messages, - max_tokens=500, - temperature=0.7 + messages=api_messages, + max_tokens=500 ) - answer = response.choices[0].message.content.strip() - + answer = resp.choices[0].message.content.strip() + + # Append relevant doc references + if retrieved_docs: + refs = [f"[{d['title'][:30]}...]" for d in retrieved_docs[:2]] + answer += f"\n\n_📚 ماخذ: {' | '.join(refs)}_" + except Exception as e: - answer = f"I apologize, but I'm having trouble connecting. Here's what I know:\n\n" - for item in relevant: - answer += f"• {item['title']}: {item['hotline']} (Response: {item['response_time']})\n" - - history.append({"role": "user", "content": user_message}) - history.append({"role": "assistant", "content": answer}) - return history, "" - - -# ─── VOICE FUNCTIONS ────────────────────────────────────────── -def text_to_speech(text, language): - """Convert text to speech using gTTS""" - if not text: - return None - + answer = f"Error: {e}" + + return history + [ + {"role": "user", "content": user_message}, + {"role": "assistant", "content": answer}, + ], "" + +# ─── TTS ────────────────────────────────────────────────────── +def make_tts(text, language): try: from gtts import gTTS - # Clean text (remove markdown and special characters) - clean_text = re.sub(r'[*_#`]', '', str(text)) - clean_text = re.sub(r'\[.*?\]', '', clean_text) - clean_text = re.sub(r'\*+', '', clean_text) - clean_text = clean_text.strip() - - if not clean_text: - return None - - lang_code = LANG_CODES.get(language, "en") - tts = gTTS(text=clean_text[:500], lang=lang_code, slow=False) - audio_path = f"/tmp/tts_{uuid.uuid4().hex[:8]}.mp3" - tts.save(audio_path) - return audio_path + lang_code = LANG_CODES.get(language, "ur") + tts = gTTS(text=str(text)[:600], lang=lang_code, slow=False) + path = f"/tmp/tts_{uuid.uuid4().hex[:8]}.mp3" + tts.save(path) + return path except Exception as e: print(f"TTS error: {e}") return None -def speech_to_text(audio_file): - """Convert speech to text using whisper or fallback""" +# ─── STT ────────────────────────────────────────────────────── +def stt(audio_file): if audio_file is None: - return "No audio recorded. Please record or upload audio first." - - def convert_to_wav(input_path): - if input_path.lower().endswith(".wav"): - return input_path - try: - from pydub import AudioSegment - output_path = input_path + "_converted.wav" - AudioSegment.from_file(input_path).export(output_path, format="wav") - return output_path - except: - return input_path - - if GROQ_API_KEY: - try: - from groq import Groq - wav_path = convert_to_wav(audio_file) - with open(wav_path, "rb") as file: - client = Groq(api_key=GROQ_API_KEY) - transcription = client.audio.transcriptions.create( - model="whisper-large-v3", - file=file, - response_format="text" - ) - text = transcription if isinstance(transcription, str) else transcription.text - if text and text.strip(): - return text.strip() - except Exception as e: - print(f"Groq STT error: {e}") - - # Fallback + return "آڈیو فراہم نہیں کیا گیا۔ پہلے ریکارڈ یا اپلوڈ کریں۔" try: import speech_recognition as sr - wav_path = convert_to_wav(audio_file) recognizer = sr.Recognizer() - with sr.AudioFile(wav_path) as source: - recognizer.adjust_for_ambient_noise(source, duration=0.3) - audio_data = recognizer.record(source) + with sr.AudioFile(audio_file) as src: + recognizer.adjust_for_ambient_noise(src, duration=0.5) + audio_data = recognizer.record(src) try: - return recognizer.recognize_google(audio_data, language="ur-PK") + text = recognizer.recognize_google(audio_data, language="ur-PK") except: - return recognizer.recognize_google(audio_data) + text = recognizer.recognize_google(audio_data) + return text except Exception as e: - return f"Could not transcribe audio. Please try typing your question." + return f"آواز نہیں سمجھ آئی۔ واضح آواز میں دوبارہ کوشش کریں۔ خرابی: {e}" -def voice_to_chatbot(audio_file, history, language): - """Process voice input and update chatbot""" - if audio_file is None: - return history, "Please record or upload audio first." - - transcribed_text = speech_to_text(audio_file) - - if not transcribed_text or transcribed_text.startswith("Could not") or transcribed_text.startswith("No audio"): - return history, transcribed_text - - new_history, _ = legal_chatbot(transcribed_text, history, language) - return new_history, "" - -def read_last_assistant_message(history, language): - """Find the last assistant message and convert to speech""" - if not history: - return None - - for message in reversed(history): - if isinstance(message, dict) and message.get("role") == "assistant": - content = message.get("content", "") - if content and content.strip(): - return text_to_speech(content, language) - return None - - -# ─── PDF REPORT GENERATION ──────────────────────────────────── -def generate_professional_pdf(complaint_id, date_str, name, cnic, phone, city, location, issue_type, - language, severity, status, reason, confidence, action, - description, local_message, legal_info_data): - """Generate a professional PDF complaint report""" - 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_CENTER, TA_LEFT, TA_RIGHT - - buffer = io.BytesIO() - doc = SimpleDocTemplate( - buffer, - pagesize=A4, - rightMargin=2*cm, - leftMargin=2*cm, - topMargin=2*cm, - bottomMargin=2*cm - ) +# ─── 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" + out += "### متعلقہ قوانین\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')}*" + return out - # Color definitions - primary_color = colors.HexColor('#0d2b1e') - secondary_color = colors.HexColor('#1f7a52') - accent_color = colors.HexColor('#d4870e') - light_bg = colors.HexColor('#f4f8f5') - border_color = colors.HexColor('#b8d9c5') - - styles = getSampleStyleSheet() - - # Custom styles - title_style = ParagraphStyle( - 'CustomTitle', - parent=styles['Heading1'], - fontSize=18, - fontName='Helvetica-Bold', - alignment=TA_CENTER, - textColor=primary_color, - spaceAfter=12, - spaceBefore=6 - ) - - subtitle_style = ParagraphStyle( - 'CustomSubtitle', - parent=styles['Normal'], - fontSize=11, - fontName='Helvetica', - alignment=TA_CENTER, - textColor=secondary_color, - spaceAfter=20 - ) - - section_style = ParagraphStyle( - 'Section', - parent=styles['Heading2'], - fontSize=13, - fontName='Helvetica-Bold', - textColor=secondary_color, - spaceBefore=12, - spaceAfter=6 - ) - - body_style = ParagraphStyle( - 'Body', - parent=styles['Normal'], - fontSize=9, - fontName='Helvetica', - leading=14, - spaceAfter=6 - ) - - story = [] - - # Header - story.append(Paragraph("GOVERNMENT OF PAKISTAN", title_style)) - story.append(Paragraph("CIVIC COMPLAINT REPORT", subtitle_style)) - story.append(HRFlowable(width="100%", thickness=2, color=secondary_color)) - story.append(Spacer(1, 0.3*cm)) - - # Complaint Reference Box - severity_icon = "🟢" if severity <= 3 else ("🟡" if severity <= 6 else ("🟠" if severity <= 8 else "🔴")) - - ref_data = [ - ["Complaint Reference:", complaint_id, "Date of Filing:", date_str], - ["Issue Type:", issue_type, "Severity:", f"{severity_icon} {severity}/10"], - ["Language:", language, "Status:", status], - ] - ref_table = Table(ref_data, colWidths=[4*cm, 4.5*cm, 3.5*cm, 5*cm]) - ref_table.setStyle(TableStyle([ - ('BACKGROUND', (0, 0), (0, -1), light_bg), - ('BACKGROUND', (2, 0), (2, -1), light_bg), - ('FONTNAME', (0, 0), (0, -1), 'Helvetica-Bold'), - ('FONTNAME', (2, 0), (2, -1), 'Helvetica-Bold'), - ('FONTSIZE', (0, 0), (-1, -1), 9), - ('GRID', (0, 0), (-1, -1), 0.5, border_color), - ('VALIGN', (0, 0), (-1, -1), 'MIDDLE'), - ('TOPPADDING', (0, 0), (-1, -1), 6), - ('BOTTOMPADDING', (0, 0), (-1, -1), 6), - ])) - story.append(ref_table) - story.append(Spacer(1, 0.3*cm)) - - # Section A - Complainant Information - story.append(Paragraph("SECTION A: COMPLAINANT INFORMATION", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - complainant_data = [ - [f"Full Name:", name, f"CNIC:", cnic or "Not Provided"], - [f"Phone:", phone or "Not Provided", f"City:", city], - [f"Location:", location, "", ""], - ] - complainant_table = Table(complainant_data, colWidths=[3.5*cm, 5*cm, 3*cm, 5.5*cm]) - complainant_table.setStyle(TableStyle([ - ('FONTSIZE', (0, 0), (-1, -1), 9), - ('TOPPADDING', (0, 0), (-1, -1), 5), - ('BOTTOMPADDING', (0, 0), (-1, -1), 5), - ('LEFTPADDING', (0, 0), (-1, -1), 4), - ])) - story.append(complainant_table) - story.append(Spacer(1, 0.2*cm)) - - # Section B - Complaint Details - story.append(Paragraph("SECTION B: COMPLAINT DETAILS", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - if description and description.strip(): - story.append(Paragraph(f"Description: {description.strip()}", body_style)) - story.append(Spacer(1, 0.2*cm)) - - # Section C - Verification Results - story.append(Paragraph("SECTION C: VERIFICATION RESULTS", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - verification_data = [ - [f"Status:", status, f"Confidence:", confidence], - [f"Finding:", (reason[:250] + "...") if len(reason) > 250 else reason, "", ""], - [f"Recommended Action:", (action[:250] + "...") if len(action) > 250 else action, "", ""], - ] - verification_table = Table(verification_data, colWidths=[3.5*cm, 13.5*cm]) - verification_table.setStyle(TableStyle([ - ('FONTSIZE', (0, 0), (-1, -1), 9), - ('TOPPADDING', (0, 0), (-1, -1), 6), - ('BOTTOMPADDING', (0, 0), (-1, -1), 6), - ('VALIGN', (0, 0), (-1, -1), 'TOP'), - ])) - story.append(verification_table) - story.append(Spacer(1, 0.2*cm)) - - # Section D - Legal Framework - story.append(Paragraph("SECTION D: LEGAL FRAMEWORK", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - legal_text = f"Applicable Laws:
" - for law in legal_info_data.get("laws", []): - legal_text += f"• {law}
" - legal_text += f"
Responsible Authority: {legal_info_data.get('authority', 'N/A')}
" - legal_text += f"Official Helpline: {legal_info_data.get('hotline', 'N/A')}
" - legal_text += f"Statutory Response Time: {legal_info_data.get('response', 'N/A')}
" - legal_text += f"Fine / Penalty: {legal_info_data.get('fine', 'N/A')}" - - story.append(Paragraph(legal_text, body_style)) - story.append(Spacer(1, 0.2*cm)) - - # Section E - Citizen Rights - story.append(Paragraph("SECTION E: CITIZEN'S LEGAL RIGHTS", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - rights_text = "" - for right in legal_info_data.get("citizen_rights", []): - rights_text += f"• {right}
" - rights_text += f"
Escalation Path: {legal_info_data.get('escalation', 'CM Portal: 0800-02345')}" - - story.append(Paragraph(rights_text, body_style)) - story.append(Spacer(1, 0.2*cm)) - - # Section F - Localized Notice - if local_message: - story.append(Paragraph(f"SECTION F: NOTICE IN {language.upper()}", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - story.append(Paragraph(local_message, body_style)) - story.append(Spacer(1, 0.2*cm)) - - # Section G - Action Directive - story.append(Paragraph("SECTION G: ACTION DIRECTIVE", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - - action_text = f""" - MANDATORY ACTION REQUIRED WITHIN: {legal_info_data.get('response', '72 hours').upper()}
- Authority: {legal_info_data.get('authority', 'N/A')}
- Helpline: {legal_info_data.get('hotline', 'N/A')}
- Citizen Portal: citizenportal.gov.pk | CM Helpline: 0800-02345 - """ - story.append(Paragraph(action_text, body_style)) - story.append(Spacer(1, 0.2*cm)) - - # Declaration - story.append(Paragraph("DECLARATION", section_style)) - story.append(HRFlowable(width="100%", thickness=0.5, color=border_color)) - 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.3*cm)) - - sig_data = [ - [f"Signature: ________________________", f"Date: {date_str}", f"Ref: {complaint_id}"], - ] - sig_table = Table(sig_data, colWidths=[6*cm, 5*cm, 6*cm]) - sig_table.setStyle(TableStyle([ - ('FONTSIZE', (0, 0), (-1, -1), 8), - ('TOPPADDING', (0, 0), (-1, -1), 8), - ])) - story.append(sig_table) - - # Footer - story.append(Spacer(1, 0.5*cm)) - story.append(HRFlowable(width="100%", thickness=0.5, color=colors.gray)) - story.append(Paragraph( - f"Generated by Rahbar — Pakistan's Civic Complaint System | Complaint ID: {complaint_id}", - ParagraphStyle('Footer', parent=styles['Normal'], fontSize=7, textColor=colors.gray, alignment=TA_CENTER) - )) - - doc.build(story) - buffer.seek(0) - - pdf_path = f"/tmp/Rahbar_Report_{complaint_id}.pdf" - with open(pdf_path, "wb") as f: - f.write(buffer.getvalue()) - return pdf_path +# ─── WHATSAPP ───────────────────────────────────────────────── +def make_whatsapp_link(text): + return f"https://wa.me/?text={urllib.parse.quote(text[:1000])}" - except Exception as e: - print(f"PDF generation error: {e}") - # Fallback to text file - pdf_path = f"/tmp/Rahbar_Report_{complaint_id}.txt" - with open(pdf_path, "w", encoding="utf-8") as f: - f.write(f"RAHBAR COMPLAINT REPORT\n") - f.write(f"=" * 50 + "\n") - f.write(f"ID: {complaint_id}\n") - f.write(f"Date: {date_str}\n") - f.write(f"Issue: {issue_type}\n") - f.write(f"Location: {location}, {city}\n") - f.write(f"Severity: {severity}/10\n") - f.write(f"Name: {name}\n") - f.write(f"CNIC: {cnic}\n") - f.write(f"Status: {status}\n") - f.write(f"Recommended Action: {action}\n") - return pdf_path +# ─── ADMIN STATS ────────────────────────────────────────────── +def get_admin_stats(): + total = len(complaint_log) + if total == 0: + return "ابھی تک کوئی شکایت درج نہیں ہوئی۔", "" + counts = {"Garbage": 0, "Pot Hole": 0, "Pipe Leakage": 0} + cities = {} + severities = [] + 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" + 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 "سبز" + if score <= 6: return "زرد" + if score <= 8: return "نارنجی" + return "سرخ" + +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) + +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 = "" + for law in kb.get("laws", []): + laws_text += f"\n - {law}" + + rights_text = "" + for right in kb.get("citizen_rights", []): + rights_text += f"\n - {right}" + + def H(title): return f"\n {_ubold(title)}\n {'─' * len(title)}" + + report = f"""{_ubold('GOVERNMENT OF PAKISTAN — CIVIC COMPLAINT REPORT')} + {_ubold('Rahbar Digital Civic Redressal System — رہبر')} + + شکایت نمبر : {complaint_id} + تاریخ : {date_str} وقت: {time_str} + طریقہ : Rahbar Digital Platform (AI-Verified) + زبان : {language} +{H('SECTION A — COMPLAINANT / شاکی کی معلومات')} + مکمل نام : {name} + شناختی کارڈ : {cnic} + رابطہ نمبر : {phone if phone else "فراہم نہیں کیا"} + شہر : {city} + علاقہ : {location} +{H('SECTION B — COMPLAINT DETAILS / شکایت کی تفصیل')} + نوعیت : {issue_type} + مقام : {location}, {city} + تاریخ/وقت : {date_str}, {time_str} + شدت : [{sev_icon}] {severity} / 10 + + تفصیل: + {description.strip() if description.strip() else "[اضافی تفصیل فراہم نہیں کی گئی]"} +{H('SECTION C — AI VERIFICATION / AI تصدیق')} + حیثیت : {gemini_status} + اعتماد : {gemini_confidence} + نتیجہ : {gemini_reason} + کارروائی : {gemini_action if gemini_action else "فوری فیلڈ معائنہ ضروری ہے۔"} + YOLO : {yolo_summary} +{H('SECTION D — LEGAL FRAMEWORK / قانونی بنیاد')} + متعلقہ قوانین:{laws_text} + + ذمہ دار ادارہ : {kb.get('authority','N/A')} + سرکاری ہیلپ لائن: {kb.get('hotline','N/A')} + قانونی جواب : {kb.get('response','N/A')} + جرمانہ : {kb.get('fine','N/A')} +{H("SECTION E — CITIZEN'S RIGHTS / شہری کے حقوق")}{rights_text} + + ایسکیلیشن : {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()} + + ذمہ دار : {kb.get('authority','Local Authority')} + ہیلپ لائن : {kb.get('hotline','N/A')} + پورٹل : citizenportal.gov.pk | CM: 0800-02345 +{H('DECLARATION / اعلامیہ')} + میں، {name} (CNIC: {cnic})، اعلان کرتا/کرتی ہوں کہ فراہم کردہ + معلومات میری بہترین معلومات کے مطابق درست ہیں۔ + + دستخط: ________________________ (ڈیجیٹل) تاریخ: {date_str} + حوالہ: {complaint_id} +{H('FOR OFFICIAL USE ONLY / سرکاری استعمال')} + وصول کنندہ : ___________________________ + وصول کی تاریخ : ___________________________ + کارروائی : ___________________________ + حل کی تاریخ : ___________________________ + + Generated by: Rahbar — Pakistan's AI Civic Redressal Platform + Timestamp: {timestamp} +""" + return report # ─── MAIN REPORT FUNCTION ───────────────────────────────────── def make_report(image, issue_type, city, location, name, cnic, phone, description, language, enable_tts): - if image is None: - return (None, "Please upload an image.", "", "", None, "", None, None) - if not location or not location.strip(): - return (None, "Please enter a location.", "", "", None, "", None, None) - if not name or not name.strip(): - return (None, "Please enter your full name.", "", "", None, "", None, None) - if not cnic or not cnic.strip(): - return (None, "Please enter your CNIC number.", "", "", None, "", None, 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, "براہ کرم اپنا شناختی کارڈ نمبر درج کریں۔", "", "", None, "", None) complaint_id = f"RB-{uuid.uuid4().hex[:8].upper()}" - timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") - date_str = datetime.datetime.now().strftime("%d %B %Y") - issue_clean = issue_type.split(" ", 1)[-1] + 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"] + gemini_severity = gemini_parsed["severity"] - # Analyze image - annotated_img, yolo_summary, severity, status, reason, reason_urdu, confidence, action = analyze_image(image, issue_type) - - if status == "REJECTED": + if gemini_status == "REJECTED": return (annotated_img, - f"**COMPLAINT REJECTED**\n\nReason: {reason}\nConfidence: {confidence}\n\nPlease upload a clear image showing the reported issue ({issue_type}).", - "", "", None, complaint_id, None, None) - - # Get legal information - legal_info_data = LEGAL_INFO.get(issue_clean, LEGAL_INFO.get("Garbage", {})) - local_message = LOCALIZED.get(issue_clean, {}).get(language, "") - severity_icon = "🟢" if severity <= 3 else ("🟡" if severity <= 6 else ("🟠" if severity <= 8 else "🔴")) - - # Get legal advice - legal_advice = get_legal_advice(issue_type, location, city, severity, language) - - # Build report - report = f"""====================================================================== - GOVERNMENT OF PAKISTAN — CIVIC COMPLAINT REPORT - Rahbar Digital Civic Redressal System | رہبر -====================================================================== - Complaint ID : {complaint_id} - Date / Time : {timestamp} - Language : {language} -====================================================================== - SECTION A — COMPLAINANT INFORMATION -====================================================================== - Full Name : {name} - CNIC : {cnic} - Phone : {phone or "Not Provided"} - City : {city} - Location : {location} -====================================================================== - SECTION B — COMPLAINT DETAILS -====================================================================== - Issue : {issue_type} - Severity : {severity_icon} {severity} / 10 - Description : {description.strip() if description else "[None provided]"} -====================================================================== - SECTION C — VERIFICATION RESULTS -====================================================================== - Status : {status} - Confidence : {confidence} - Finding : {reason} - Action : {action} -====================================================================== - SECTION D — LEGAL FRAMEWORK -====================================================================== - Laws : {', '.join(legal_info_data.get('laws', []))} - Authority : {legal_info_data.get('authority', 'N/A')} - Helpline : {legal_info_data.get('hotline', 'N/A')} - Response : {legal_info_data.get('response', 'N/A')} - Fine : {legal_info_data.get('fine', 'N/A')} -====================================================================== - SECTION E — YOUR RIGHTS -====================================================================== - {chr(10).join(f' • {r}' for r in legal_info_data.get('citizen_rights', []))} - - Escalation : {legal_info_data.get('escalation', 'CM Portal: 0800-02345')} -====================================================================== - SECTION F — NOTICE ({language}) -====================================================================== - {local_message} -====================================================================== - SECTION G — ACTION DIRECTIVE -====================================================================== - MANDATORY ACTION WITHIN: {legal_info_data.get('response', '72 hours').upper()} - Authority : {legal_info_data.get('authority', 'N/A')} - Helpline : {legal_info_data.get('hotline', 'N/A')} - Portal : citizenportal.gov.pk | CM: 0800-02345 -====================================================================== - DECLARATION -====================================================================== - I, {name} (CNIC: {cnic}), declare this information is true. - Signature: ______________________ Date: {date_str} -====================================================================== - Generated by Rahbar — Pakistan's Civic Complaint System - Complaint ID: {complaint_id} -====================================================================== -""" + 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 نہیں ہے — تصدیق چھوڑ دی گئی۔" + gemini_status = "APPROVED_WITH_WARNING" + + final_severity = gemini_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 + ) - # Log complaint complaint_log.append({ "id": complaint_id, "timestamp": timestamp, "city": city, "location": location, - "issue": issue_type, "severity": severity, "language": language, - "name": name, "cnic": cnic, "phone": phone + "issue": issue_type, "severity": final_severity, "language": language, + "name": name, "cnic": cnic, "phone": phone, }) - # WhatsApp share text - wa_text = f"Rahbar Complaint\nRef: {complaint_id}\nIssue: {issue_type}\nLocation: {location}, {city}\nSeverity: {severity}/10\nAuthority: {legal_info_data.get('authority', 'N/A')}\nHelpline: {legal_info_data.get('hotline', 'N/A')}\nFiled: {timestamp}" - wa_md = f"[📲 Share on WhatsApp](https://wa.me/?text={urllib.parse.quote(wa_text[:1000])})" - - # Audio outputs - report_tts = text_to_speech(report[:800], language) if enable_tts else None - advice_tts = text_to_speech(legal_advice[:600], language) if enable_tts else None - - # Generate PDF - pdf_path = generate_professional_pdf( - complaint_id, date_str, name, cnic, phone, city, location, issue_type, - language, severity, status, reason, confidence, action, - description or "", local_message, legal_info_data - ) - - return (annotated_img, report, wa_md, legal_advice, report_tts, complaint_id, advice_tts, pdf_path) - - -def update_areas(city): - areas = CITIES_AREAS.get(city, ["Enter area manually"]) - return gr.Dropdown(choices=areas, value=areas[0]) - - -def law_info(issue, language): - issue_clean = issue.split(" ", 1)[-1] - info = LEGAL_INFO.get(issue_clean, LEGAL_INFO.get("Garbage", {})) - local = LOCALIZED.get(issue_clean, {}).get(language, "") - rights = "\n".join(f" * {r}" for r in info.get("citizen_rights", [])) - - out = f"""## Legal Reference: {issue} - -### Applicable Laws -{chr(10).join(f' * {l}' for l in info.get('laws', []))} - -### Fine / Penalty -{info.get('fine', 'N/A')} - -### Responsible Authority -{info.get('authority', 'N/A')} - -### Emergency Helpline -**{info.get('hotline', 'N/A')}** - -### Required Government Response Time -{info.get('response', 'N/A')} - -### Your Rights -{rights} - -### If Ignored — Escalate To -{info.get('escalation', 'N/A')} - ---- -### Notice in {language} -> {local} -""" - return out - - -def get_admin_stats(): - 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: - 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_sev = sum(sevs) / len(sevs) if sevs else 0 - top_city = max(cities, key=cities.get) if cities else "N/A" - - stats = f"""## Complaint Statistics + wa_text = (f"رہبر شکایت\nنمبر: {complaint_id}\nمسئلہ: {issue_type}\n" + f"مقام: {location}, {city}\nشدت: {final_severity}/10\n" + f"ادارہ: {kb.get('authority','N/A')}\nہیلپ لائن: {kb.get('hotline','N/A')}\n" + f"وقت: {timestamp}") + wa_md = f"[واٹس ایپ پر شیئر کریں]({make_whatsapp_link(wa_text)})" -| Metric | Value | -|--------|-------| -| **Total Complaints** | {total} | -| **Average Severity** | {avg_sev:.1f}/10 | -| **Most Active City** | {top_city} | + report_tts_path = None + if enable_tts: + tts_text = (f"شکایت {complaint_id} درج ہو گئی۔ " + f"مسئلہ: {issue_type}، مقام: {location}، شہر: {city}۔ " + f"شدت: {final_severity} میں سے {final_severity}۔ {local}") + report_tts_path = make_tts(tts_text, language) -### By Issue Type -| Issue | Count | -|-------|-------| -""" - 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 += f"**{c['id']}** | {c['timestamp']} | {c['city']}, {c['location']} | {c['issue']} | Severity {c['severity']}/10 | {c.get('name', '?')}\n\n" - - return stats, log + 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 -# ─── CSS — Light and Dark Mode ──────────────────────────────── +# ─── CSS ────────────────────────────────────────────────────── CSS = """ -@import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&family=DM+Sans:wght@300;400;500;600&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Noto+Nastaliq+Urdu:wght@400;700&family=Playfair+Display:wght@700;900&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap'); :root { - --primary: #0d2b1e; - --primary-light: #1f7a52; - --accent: #d4870e; - --surface: #ffffff; - --surface-light: #f4f8f5; - --text: #0d2b1e; - --text-light: #5a8a6e; - --border: #b8d9c5; + --g9:#0d2b1e; --g8:#14432e; --g7:#1a5c3f; --g6:#1f7a52; + --g5:#25a06b; --g4:#2ec97f; + --a5:#f5a623; --a4:#d4870e; --a3:#b8730c; + --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%); + --badge-bg:#ffffff; --badge-border:#1a5c3f; + --warn-bg:#fffbf0; --warn-border:rgba(245,166,35,.5); + --info-bg:#f0faf4; --info-border:#1f7a52; } @media (prefers-color-scheme: dark) { :root { - --primary: #d0f0df; - --primary-light: #30aa72; - --accent: #f5a623; - --surface: #0f1f16; - --surface-light: #162b1e; - --text: #d0f0df; - --text-light: #8fcfae; - --border: #2a5c3e; + --g9:#d0f0df; --g8:#1e6644; --g7:#28885a; --g6:#30aa72; + --g5:#3dcf8a; --g4:#5de3a3; + --a5:#f7bc57; --a4:#f5a623; --a3:#f0921a; + --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%); + --badge-bg:#0f1f16; --badge-border:#28885a; + --warn-bg:#1e1800; --warn-border:rgba(247,188,87,.4); + --info-bg:#0e1f16; --info-border:#30aa72; } } -* { - font-family: 'DM Sans', sans-serif !important; -} +*, *::before, *::after { box-sizing: border-box; } -.gradio-container { +body, .gradio-container { + font-family: 'DM Sans', sans-serif !important; background: var(--surface) !important; + color: var(--text-primary) !important; } -.gr-box, .gr-form, .gr-panel { - background: var(--surface) !important; - border-color: var(--border) !important; +.saaf-header { + background: var(--header-bg); + border-bottom: 2px solid var(--g6); + padding: 24px 20px 20px; + text-align: center; + position: relative; + overflow: hidden; } - -label, .gr-label { - color: var(--text) !important; +.saaf-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; } - -input, textarea, select { - background: var(--surface) !important; - border: 1px solid var(--border) !important; - color: var(--text) !important; +.saaf-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; } - -button.primary { - background: linear-gradient(135deg, var(--primary-light), #25a06b) !important; - color: white !important; - border: none !important; - font-weight: 600 !important; +.saaf-header .urdu-title { + font-family: 'Noto Nastaliq Urdu', serif; + font-size: clamp(1rem, 3vw, 1.6rem); + color: #f7bc57; + direction: rtl; + margin: 4px 0 8px; +} +.saaf-header .tagline { + font-size: clamp(.75rem, 2vw, .95rem); + color: #5de3a3; + letter-spacing: .08em; + text-transform: uppercase; } -button.primary:hover { - transform: translateY(-1px) !important; +.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(--badge-bg); color: var(--g4); border: 1px solid var(--badge-border); } +.badge-pk { background: var(--badge-bg); color: var(--a4); border: 1px solid var(--warn-border); } +.badge-live{ background: var(--badge-bg); color: #ff8080; border: 1px solid rgba(232,83,83,.4); } -.gr-markdown h1, .gr-markdown h2, .gr-markdown h3 { - color: var(--primary-light) !important; +.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.selected, +.tab-nav button[aria-selected="true"] { + color: var(--a4) !important; + border-bottom: 3px solid var(--a5) !important; + background: transparent !important; } -.message { - border-radius: 12px !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); } -.message.user { - background: var(--surface-light) !important; +label, .gradio-container .label-wrap span { + color: var(--text-primary) !important; } -.message.bot { +.gradio-container input, +.gradio-container textarea { background: var(--surface) !important; - border: 1px solid var(--border) !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; } - -.header-title { - text-align: center; - padding: 20px; - background: linear-gradient(135deg, #0d2b1e, #1a5c3f); - color: white; - border-radius: 12px 12px 0 0; +.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; } - -.header-title h1 { - font-size: 2rem; - margin: 0; - font-weight: bold; +.gradio-container .wrap { background: var(--surface) !important; border-color: var(--border-strong) !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: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; } -.header-title p { - margin: 5px 0 0; - opacity: 0.9; +.gradio-container [data-testid="image"] { + border: 2px dashed var(--border-strong) !important; + border-radius: var(--radius-lg) !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(--surface-light); - border-left: 4px solid var(--primary-light); - padding: 10px 15px; - border-radius: 8px; - margin: 10px 0; - font-size: 0.9rem; + 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); + direction: rtl; + text-align: right; } - .warn-box { - background: #fffbf0; - border-left: 4px solid var(--accent); - padding: 10px 15px; - border-radius: 8px; - margin: 10px 0; - font-size: 0.9rem; -} - -@media (prefers-color-scheme: dark) { - .warn-box { - background: #1e1800; - } + 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); + direction: rtl; + text-align: right; } .hotline-pill { display: inline-block; - background: var(--surface-light); - padding: 2px 10px; - border-radius: 20px; - font-size: 0.8rem; - font-weight: 600; - color: var(--accent); - margin: 0 2px; + 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 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; } + +::-webkit-scrollbar { width: 6px; height: 6px; } +::-webkit-scrollbar-track { background: var(--surface2); } +::-webkit-scrollbar-thumb { background: var(--g6); border-radius: 3px; } + +@media (max-width: 640px) { + .saaf-header { padding: 16px 12px; } + .tab-nav button { padding: 10px 12px !important; font-size: .78rem !important; } } """ HEADER_HTML = """ -
-

Rahbar | رہبر

-

Pakistan's Civic Complaint System | پاکستان کا سٹیزن شکایات نظام

+
+

Rahbar

+
رہبر- ہمارا حق، ہماری آواز
+
Pakistan's AI-Powered Civic Complaint System
+
+
+ Gemini Vision + YOLOv26 + Llama 3.3 + RAG Chatbot + 4 زبانیں + LIVE +
+""" + +# ─── MAP HTML — FIXED: GPS + Map + Multi-camera ─────────────── +MAP_HTML = """ + + +
+
+ 📍 GPS مقام / نقشہ +
+ +
+ 📱 موبائل صارفین: GPS بٹن دبائیں - براؤزر لوکیشن پوچھے گا، Allow کریں۔
+ 💻 کمپیوٹر صارفین: GPS بٹن یا نقشے پر ٹیپ کریں - پتہ خودکار بھر جائے گا۔ +
+ + + + + +
+ نقشہ تیار ہے - GPS بٹن دبائیں یا نقشے پر ٹیپ کریں +
+ + +
+
+ +

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

+
+ + +""" + +# ─── VOICE TAB — Advanced STT + Multi-camera ────────────────── +VOICE_HTML = """ +
+
+ 🎤 جدید آواز ریکارڈنگ +
+ +
+ 📱 موبائل پر کیمرہ: نیچے دئے گئے بٹن سے فرنٹ یا بیک کیمرہ منتخب کریں۔
+ 🎙️ آواز: Gradio ریکارڈنگ یا نیچے کا جدید ریکارڈر استعمال کریں۔
+ ⚠️ اہم: مائیکروفون کی اجازت دیں جب براؤزر پوچھے۔ +
+ + +
+
+ 📷 کیمرہ منتخب کریں (موبائل کے لیے) +
+
+ + + +
+ + +
+ +
+ + +
+
+ 🎙️ جدید آواز ریکارڈر +
+
+ + +
+
+ + +
+ + """ +def update_areas(city): + areas = CITIES_AREAS.get(city, ["علاقہ خود درج کریں"]) + return gr.Dropdown(choices=areas, value=areas[0]) # ─── BUILD UI ───────────────────────────────────────────────── def build_ui(): - with gr.Blocks(title="Rahbar | رہبر", css=CSS, theme=gr.themes.Soft()) as demo: + with gr.Blocks(title="Rahbar | رہبر") as demo: gr.HTML(HEADER_HTML) with gr.Tabs(): - # TAB 1 — FILE A COMPLAINT - with gr.Tab("📸 File a Complaint"): + # ── TAB 1: REPORT ISSUE ─────────────────────────── + with gr.Tab("📝 شکایت درج کریں"): with gr.Row(equal_height=False): - with gr.Column(scale=1, min_width=300): - gr.Markdown("### 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.Markdown("### Issue Photo") - gr.HTML('
📸 Upload or take a clear photo of the civic issue.
') - image_input = gr.Image(type="pil", label="Upload / Take Photo", sources=["webcam", "upload"], height=220) - - gr.Markdown("### 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") - location_tb = gr.Textbox(label="Street / Landmark / Address", placeholder="Enter exact location", lines=2) - desc_tb = gr.Textbox(label="Description (optional)", placeholder="Describe the issue...", lines=3) - language_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Report Language") - tts_cb = gr.Checkbox(label="🔊 Read report aloud", value=False) - submit_btn = gr.Button("📤 Submit Complaint", variant="primary", size="lg") - - with gr.Column(scale=2, min_width=320): - gr.Markdown("### Analysis Results") - annotated_out = gr.Image(label="Verification Result", height=200) - complaint_id_out = gr.Textbox(label="Complaint ID", interactive=False) - report_out = gr.Textbox(label="Official Complaint Report", lines=18, interactive=False) - wa_out = gr.Markdown() - pdf_download = gr.File(label="📄 Download PDF Report", visible=True) - report_tts_out = gr.Audio(label="🔊 Report Audio", autoplay=False) - gr.Markdown("### Legal Advice") - legal_advice_out = gr.Markdown() - advice_tts_out = gr.Audio(label="🔊 Legal Advice Audio", autoplay=False) + + with gr.Column(scale=1, min_width=280): + 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('
مسئلے کی واضح تصویر اپلوڈ کریں۔ موبائل پر براہ راست کیمرہ استعمال کر سکتے ہیں۔ بیک کیمرے کے لیے "Voice Input" ٹیب دیکھیں۔
') + image_input = gr.Image(type="pil", label="تصویر اپلوڈ / کھینچیں", height=200) + + 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="علاقہ") + + # ── GPS MAP — Fixed ── + gr.HTML(MAP_HTML) + + location_tb = gr.Textbox( + label="گلی / نشان / GPS کوآرڈینیٹس", + placeholder="GPS پتہ یہاں خودکار بھر جائے گا — یا خود لکھیں", + lines=1 + ) + desc_tb = gr.Textbox(label="مزید تفصیل (اختیاری)", placeholder="مسئلے کی تفصیل لکھیں...", lines=3) + language_dd = gr.Dropdown(choices=LANGUAGES, value="اردو (Urdu)", label="رپورٹ اور آواز کی زبان") + tts_cb = gr.Checkbox(label="رپورٹ آواز میں سنیں (TTS)", value=False) + submit_btn = gr.Button("شکایت جمع کریں", variant="primary", size="lg") + + with gr.Column(scale=2, min_width=300): + gr.HTML('
AI تجزیہ کے نتائج
') + annotated_out = gr.Image(label="AI تشخیص کا نتیجہ", height=230) + complaint_id_out = gr.Textbox(label="شکایت نمبر", interactive=False) + + gr.HTML('
سرکاری شکایت رپورٹ
') + report_out = gr.Textbox( + label="مکمل شکایت رپورٹ (جمع کروانے کے لیے تیار)", + lines=20, interactive=False, + placeholder="شکایت جمع کروانے کے بعد رپورٹ یہاں آئے گی..." + ) + wa_out = gr.Markdown() + report_tts_out = gr.Audio(label="رپورٹ آواز (منتخب زبان میں)", autoplay=False) + + gr.HTML('
قانونی رہنمائی
') + gr.HTML('
AI سے تیار کردہ قانونی رہنمائی — پاکستانی شہری قانون کی بنیاد پر، آپ کی منتخب زبان میں۔
') + legal_advice_out = gr.Textbox( + label="قانونی حقوق اور اقدامات (منتخب زبان میں)", + lines=10, interactive=False, + placeholder="قانونی رہنمائی یہاں آئے گی..." + ) + advice_tts_out = gr.Audio(label="قانونی رہنمائی آواز (منتخب زبان میں)", autoplay=False) city_dd.change(fn=update_areas, inputs=[city_dd], outputs=[area_dd]) submit_btn.click( fn=make_report, - 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, pdf_download] + 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] ) - # TAB 2 — KNOW YOUR RIGHTS - with gr.Tab("⚖️ Know Your Rights"): - gr.Markdown("### Civic Laws Quick Reference") + # ── TAB 2: RAG CHATBOT + LAW REFERENCE ─────────── + with gr.Tab("⚖️ قانون / چیٹ بوٹ"): + gr.HTML('
پاکستانی شہری قوانین — ڈیٹا بیس
') with gr.Row(): - law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="Issue Type", scale=1) - law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Language", scale=1) + law_issue_dd = gr.Dropdown(choices=ISSUE_TYPES, value=ISSUE_TYPES[0], label="مسئلہ منتخب کریں", scale=1) + law_lang_dd = gr.Dropdown(choices=LANGUAGES, value="اردو (Urdu)", label="زبان", scale=1) law_out = gr.Markdown() - gr.Button("📖 Show My Rights", variant="primary").click(fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out]) - - gr.HTML(""" -
- 📞 Emergency Helplines:
- 🗑️ Garbage: 1139 - 🕳️ Roads: 051-9032800 - 💧 WASA: 042-99200300 - 📞 CM Portal: 0800-02345 - ⚖️ Ombudsman: 051-9204551 -
- """) - - gr.Markdown("### Ask a Legal Question") - chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="English", label="Response Language") - chatbot = gr.Chatbot(label="Legal Assistant", height=400, type="messages") - - with gr.Row(): - chat_input = gr.Textbox(label="Type your question", placeholder="e.g., WASA didn't fix the pipe for 3 days — what can I do?", lines=2, scale=4) - chat_send_btn = gr.Button("Send ➤", variant="primary", scale=1) - - gr.Markdown("### 🎤 Voice Question") - with gr.Row(): - chat_audio_in = gr.Audio(type="filepath", label="Record or Upload Question", sources=["microphone", "upload"], scale=3) - chat_voice_btn = gr.Button("🎤 Send Voice Question", variant="secondary", scale=1) - - gr.Markdown("### 🔊 Listen to Answer") + gr.Button("قانونی تفصیل دکھائیں", variant="primary").click( + fn=law_info, inputs=[law_issue_dd, law_lang_dd], outputs=[law_out] + ) + gr.HTML("""
+ فوری ہیلپ لائنز:
+ کچرا: 1139  |  + سڑک: 051-9032800  |  + WASA: 042-99200300  |  + CM پورٹل: 0800-02345 +
""") + + gr.HTML('
🤖 RAG قانونی چیٹ بوٹ
') + gr.HTML("""
+ یہ چیٹ بوٹ RAG (Retrieval-Augmented Generation) ٹیکنالوجی استعمال کرتا ہے۔
+ ڈیٹا سیٹ: Road Issues Detection Dataset + Urban Issues Dataset + Consumer Complaints Dataset
+ پانی، پائپ، WASA، کچرا، سڑک یا اپنے قانونی حقوق کے بارے میں پوچھیں۔ +
""") + chat_lang_dd = gr.Dropdown(choices=LANGUAGES, value="اردو (Urdu)", label="جواب کی زبان") + chatbot = gr.Chatbot( + label="رہبر RAG قانونی معاون", + height=500, + value=[], + + ) with gr.Row(): - chat_tts_out = gr.Audio(label="Answer Audio", autoplay=True, scale=3) - chat_tts_btn = gr.Button("🔊 Read Last Answer", variant="secondary", scale=1) - + chat_input = gr.Textbox( + label="آپ کا سوال", + placeholder="مثلاً: WASA نے 3 دن میں پائپ ٹھیک نہیں کیا، میرا کیا حق ہے؟", + lines=2, scale=4 + ) + chat_send_btn = gr.Button("بھیجیں", variant="primary", scale=1) + + 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, + inputs=[chat_input, chatbot, chat_lang_dd], + outputs=[chatbot, chat_input] + ) gr.Examples( examples=[ - ["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?"], + ["WASA نے 3 دن میں پائپ ٹھیک نہیں کیا۔ میرا کیا حق ہے؟"], + ["میرے علاقے میں پانی آلودہ ہے۔ میں کہاں شکایت کروں؟"], + ["ایک ہفتے سے کچرا نہیں اٹھا۔ کون سا قانون لاگو ہوتا ہے؟"], + ["اگر ادارہ میری شکایت نظرانداز کرے تو کیا کروں؟"], + ["سڑک کے گڑھے سے میری گاڑی کو نقصان ہوا — کیا ہرجانہ مل سکتا ہے؟"], + ["CM پورٹل پر شکایت کیسے درج کروائیں؟"], ], inputs=chat_input, - label="💡 Common Questions" + label="یہ سوالات آزمائیں" + ) + + # ── TAB 3: VOICE INPUT + MULTI-CAMERA ──────────── + with gr.Tab("🎤 آواز / کیمرہ"): + gr.HTML(VOICE_HTML) + + gr.HTML('
Gradio آواز ریکارڈنگ
') + gr.HTML('
Gradio کا ریکارڈر استعما�� کریں اور پھر Transcribe کا بٹن دبائیں۔ اردو یا انگریزی دونوں میں بولیں۔
') + audio_in = gr.Audio( + type="filepath", + label="آواز ریکارڈ یا اپلوڈ کریں", + sources=["microphone", "upload"] ) + with gr.Row(): + stt_lang = gr.Dropdown( + choices=["ur-PK (اردو)", "en-US (English)", "auto"], + value="ur-PK (اردو)", + label="زبان", + scale=1 + ) + stt_btn = gr.Button("آواز کو متن میں بدلیں", variant="primary", scale=2) + stt_out = gr.Textbox( + label="متن (قابل ترمیم — Report ٹیب میں کاپی کریں)", + lines=5, interactive=True, + placeholder="آواز سن کر متن یہاں آئے گا..." + ) + stt_btn.click(fn=stt, inputs=[audio_in], outputs=[stt_out]) - # Chatbot event handlers - chat_send_btn.click(fn=legal_chatbot, inputs=[chat_input, chatbot, chat_lang_dd], outputs=[chatbot, chat_input]) - chat_input.submit(fn=legal_chatbot, inputs=[chat_input, chatbot, chat_lang_dd], outputs=[chatbot, chat_input]) - chat_voice_btn.click(fn=voice_to_chatbot, inputs=[chat_audio_in, chatbot, chat_lang_dd], outputs=[chatbot, chat_input]) - chat_tts_btn.click(fn=read_last_assistant_message, inputs=[chatbot, chat_lang_dd], outputs=[chat_tts_out]) - - # TAB 3 — VOICE INPUT - with gr.Tab("🎤 Voice Input"): - gr.Markdown("### Record Your Complaint") - gr.HTML('
🎙️ Record your complaint description. After transcription, copy the text to the complaint form.
') - audio_in = gr.Audio(type="filepath", label="Record or Upload Audio", sources=["microphone", "upload"]) - stt_btn = gr.Button("📝 Transcribe to Text", variant="primary") - stt_out = gr.Textbox(label="Transcription (editable)", lines=6, interactive=True) - stt_btn.click(fn=speech_to_text, inputs=[audio_in], outputs=[stt_out]) - - gr.Markdown("### Test Voice Output") + gr.HTML('
آواز سے متن ٹیسٹ کریں
') with gr.Row(): - 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=text_to_speech, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out]) - - # TAB 4 — ADMIN - with gr.Tab("🛡️ Admin"): - gr.Markdown("### Complaint Statistics") - refresh_btn = gr.Button("🔄 Refresh", variant="primary") + tts_text_in = gr.Textbox(label="متن درج کریں", placeholder="کچھ لکھیں...", scale=3) + tts_lang_in = gr.Dropdown(choices=LANGUAGES, value="اردو (Urdu)", label="زبان", scale=1) + tts_test_btn = gr.Button("سنیں", variant="secondary") + tts_test_out = gr.Audio(label="آواز", autoplay=True) + tts_test_btn.click(fn=make_tts, inputs=[tts_text_in, tts_lang_in], outputs=[tts_test_out]) + + # ── TAB 4: ADMIN DASHBOARD ──────────────────────── + with gr.Tab("📊 ایڈمن"): + gr.HTML('
شکایت اعداد و شمار
') + refresh_btn = gr.Button("اعداد و شمار تازہ کریں", variant="primary") with gr.Row(): stats_out = gr.Markdown() - log_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 Vision | YOLOv8 | Llama 3.3 70B | Sentence Transformers (Multilingual)
+ ویکٹر سرچ: FAISS IndexFlatIP (Cosine Similarity) +
""") + return demo -# ─── LAUNCH ─────────────────────────────────────────────────── if __name__ == "__main__": - print("Rahbar v7.0 starting...") + print("رہ��ر شروع ہو رہا ہے...") + print("RAG Engine initializing in background...") + demo = build_ui() demo.launch( server_name="0.0.0.0", server_port=7860, - share=True + share=True, + css=CSS, + theme=gr.themes.Base( + primary_hue=gr.themes.colors.green, + secondary_hue=gr.themes.colors.yellow + ), ) \ No newline at end of file