From 1a9822a74231889905245e0ef28a6f43d98b9e2d Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:01:47 +0000 Subject: [PATCH 1/3] perf(escalation): use joinedload to fix N+1 query bottleneck Added `joinedload(Grievance.jurisdiction)` in `_get_grievances_for_evaluation` to eager load the related jurisdiction, preventing N+1 queries during periodic grievance evaluations. --- .jules/bolt.md | 3 +++ backend/escalation_engine.py | 6 ++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index bf784525..4eb18565 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -25,3 +25,6 @@ ## 2025-07-15 - Fast Bounding Box Pre-filter **Learning:** Calculating great circle distance (Haversine) for every issue against a target location is computationally expensive (O(N) with heavy math ops like sin, cos, atan2). In high-traffic aggregations, this can become a bottleneck. **Action:** Use a fast bounding box pre-filter (`get_bounding_box` with a 5% epsilon) to quickly discard issues that are definitely outside the search radius before running the expensive exact haversine distance calculation. +## 2025-05-30 - N+1 Query in Escalation Engine +**Learning:** The `EscalationEngine.evaluate_and_escalate_grievances` fetches grievances that need evaluation, and subsequent loops access `grievance.jurisdiction.level`. Since `jurisdiction` is evaluated lazily, this causes an N+1 query problem during the periodic cron job. +**Action:** Use `joinedload(Grievance.jurisdiction)` in `_get_grievances_for_evaluation` to eager-load the jurisdiction and eliminate the bottleneck. diff --git a/backend/escalation_engine.py b/backend/escalation_engine.py index 67137b9b..f386c6ae 100644 --- a/backend/escalation_engine.py +++ b/backend/escalation_engine.py @@ -5,7 +5,7 @@ import datetime from typing import List, Dict, Any, Optional -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, joinedload from sqlalchemy import and_, or_ from backend.models import Grievance, Jurisdiction, EscalationAudit, GrievanceStatus, JurisdictionLevel, EscalationReason, SeverityLevel from backend.database import SessionLocal @@ -150,7 +150,9 @@ def _get_grievances_for_evaluation(self, db: Session) -> List[Grievance]: now = datetime.datetime.now(datetime.timezone.utc) # Get grievances that are active and past SLA deadline - return db.query(Grievance).filter( + # Bolt Optimization: Use joinedload to resolve N+1 query bottlenecks + # during periodic grievance evaluation when accessing jurisdiction level + return db.query(Grievance).options(joinedload(Grievance.jurisdiction)).filter( and_( Grievance.status.in_([GrievanceStatus.OPEN, GrievanceStatus.IN_PROGRESS, GrievanceStatus.ESCALATED]), Grievance.sla_deadline < now From 9401552d7448cc9cf39bbee5f1962f9f6b5798fb Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:28:07 +0000 Subject: [PATCH 2/3] fix: syntax and test failure fixes --- backend/bot.py | 4 +-- backend/hf_service.py | 11 +++++++ backend/main.py | 70 +++++++++++++++++-------------------------- 3 files changed, 41 insertions(+), 44 deletions(-) diff --git a/backend/bot.py b/backend/bot.py index 062d7b53..dbc6c951 100644 --- a/backend/bot.py +++ b/backend/bot.py @@ -4,9 +4,9 @@ import threading from telegram import Update, ReplyKeyboardMarkup, ReplyKeyboardRemove from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, MessageHandler, filters, ConversationHandler -from backend.database import engine, SessionLocal +from database import engine, SessionLocal -from backend.models import Base, Issue +from models import Base, Issue # Enable logging diff --git a/backend/hf_service.py b/backend/hf_service.py index 379e6007..e9c27337 100644 --- a/backend/hf_service.py +++ b/backend/hf_service.py @@ -88,6 +88,17 @@ async def _make_request(client, image_bytes, labels): raise ExternalAPIException("Hugging Face API", str(e)) from e def _prepare_image_bytes(image: Union[Image.Image, bytes]) -> bytes: + if isinstance(image, bytes): + return image + elif isinstance(image, Image.Image): + buffered = io.BytesIO() + image_format = image.format if image.format else "JPEG" + image.save(buffered, format=image_format) + return buffered.getvalue() + else: + raise ValueError("Input must be a PIL Image or bytes") + +async def detect_vandalism_clip(image: Union[Image.Image, bytes], client: httpx.AsyncClient = None): """ Detects vandalism/graffiti using Zero-Shot Image Classification with CLIP (Async). Includes retry logic with exponential backoff for transient failures. diff --git a/backend/main.py b/backend/main.py index 6697f0ee..64388be5 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,6 +4,7 @@ from sqlalchemy.orm import Session from database import engine, get_db from models import Base, Issue +from schemas import SuccessResponse, HealthResponse, StatsResponse, MLStatusResponse from ai_service import generate_action_plan, chat_with_civic_assistant from maharashtra_locator import find_constituency_by_pincode, find_mla_by_constituency from pydantic import BaseModel @@ -11,6 +12,8 @@ import json import os import io +import sys +from functools import lru_cache # Add the project root to sys.path so we can import 'backend' modules sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -35,7 +38,7 @@ from pothole_detection import detect_potholes from garbage_detection import detect_garbage from vandalism_detection import detect_vandalism -from flood_detection import detect_flooding +from flooding_detection import detect_flooding # Import AI and Logic services from ai_service import analyze_issue_image, chat_with_civic_assistant, analyze_issue_with_ai, generate_action_plan @@ -133,16 +136,6 @@ def read_root(): "version": "1.0.0" } -@app.get("/", response_model=SuccessResponse) -def root(): - return SuccessResponse( - message="VishwaGuru API is running", - data={ - "service": "VishwaGuru API", - "version": "1.0.0" - } - ) - @app.get("/health", response_model=HealthResponse) def health(): return HealthResponse( @@ -234,45 +227,38 @@ async def create_issue( # Offload blocking file I/O to a thread def save_file(): - with open(image_path, "wb") as buffer: + with open(file_location, "wb") as buffer: shutil.copyfileobj(image.file, buffer) await asyncio.to_thread(save_file) - # Offload blocking DB operations to a thread - def save_to_db(): - new_issue = Issue( - description=description, - category=category, - image_path=image_path, - source="web" - ) - db.add(new_issue) - db.commit() - db.refresh(new_issue) - return new_issue - - new_issue = await asyncio.to_thread(save_to_db) - # Generate Action Plan (AI) action_plan = await generate_action_plan(description, category, file_location) - db_issue = Issue( - description=description, - category=category, - image_path=file_location, - source=source, - user_email=user_email - ) - db.add(db_issue) - db.commit() - db.refresh(db_issue) + # Offload blocking DB operations to a thread + def save_to_db(): + new_issue = Issue( + description=description, + category=category, + image_path=file_location, + source=source, + user_email=user_email + ) + db.add(new_issue) + db.commit() + db.refresh(new_issue) + return new_issue + + new_issue = await asyncio.to_thread(save_to_db) - return { - "id": new_issue.id, - "message": "Issue reported successfully", - "action_plan": action_plan - } + return { + "id": new_issue.id, + "message": "Issue reported successfully", + "action_plan": action_plan + } + except Exception as e: + logger.error(f"Error creating issue: {e}") + raise HTTPException(status_code=500, detail="Failed to create issue") @lru_cache(maxsize=1) def _load_responsibility_map(): From 2023ba24f9ed10779e0787c23588030d5538e633 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Sat, 18 Jul 2026 14:44:47 +0000 Subject: [PATCH 3/3] fix: resolve backend/main.py import and syntax errors to fix render deployment Added missing imports (`create_all_ai_services`, `initialize_ai_services`, `logger`, `lru_cache`, schemas) to resolve the Render deployment failure and syntax errors that crashed the app at startup. --- .jules/bolt.md | 3 +++ backend/main.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/.jules/bolt.md b/.jules/bolt.md index 4eb18565..432e6943 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -28,3 +28,6 @@ ## 2025-05-30 - N+1 Query in Escalation Engine **Learning:** The `EscalationEngine.evaluate_and_escalate_grievances` fetches grievances that need evaluation, and subsequent loops access `grievance.jurisdiction.level`. Since `jurisdiction` is evaluated lazily, this causes an N+1 query problem during the periodic cron job. **Action:** Use `joinedload(Grievance.jurisdiction)` in `_get_grievances_for_evaluation` to eager-load the jurisdiction and eliminate the bottleneck. +## 2025-07-18 - Render Deployment Failures and Imports +**Learning:** Deployment failures on Render can occur when refactoring components without updating all corresponding imports in the `main.py` entrypoint. Furthermore, when mocking missing dependencies in `pytest`, `AttributeError` indicates the mock target itself cannot be found in the namespace. +**Action:** When creating new components like `ai_factory.py`, make sure to import them appropriately in `main.py`, and when patching mock targets in `pytest`, ensure the import path is fully qualified and exists within the patched module. diff --git a/backend/main.py b/backend/main.py index 64388be5..97e532cc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -6,6 +6,8 @@ from models import Base, Issue from schemas import SuccessResponse, HealthResponse, StatsResponse, MLStatusResponse from ai_service import generate_action_plan, chat_with_civic_assistant +from ai_factory import create_all_ai_services +from gemini_services import initialize_ai_services from maharashtra_locator import find_constituency_by_pincode, find_mla_by_constituency from pydantic import BaseModel from gemini_summary import generate_mla_summary @@ -13,8 +15,12 @@ import os import io import sys +import logging from functools import lru_cache +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO) + # Add the project root to sys.path so we can import 'backend' modules sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))