Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@
## 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.
## 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.
4 changes: 2 additions & 2 deletions backend/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions backend/escalation_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions backend/hf_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
76 changes: 34 additions & 42 deletions backend/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,22 @@
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 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
import json
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__))))
Expand All @@ -35,7 +44,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

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: /api/detect-flooding now returns an un-awaited coroutine instead of detections, so FastAPI cannot serialize a successful flooding result. Await detect_flooding(img) from the async endpoint rather than invoking it in the synchronous threadpool callback.

Prompt for AI agents
Check if this issue is valid β€” if so, understand the root cause and fix it. At backend/main.py, line 41:

<comment>`/api/detect-flooding` now returns an un-awaited coroutine instead of detections, so FastAPI cannot serialize a successful flooding result. Await `detect_flooding(img)` from the async endpoint rather than invoking it in the synchronous threadpool callback.</comment>

<file context>
@@ -35,7 +38,7 @@
 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
</file context>


# Import AI and Logic services
from ai_service import analyze_issue_image, chat_with_civic_assistant, analyze_issue_with_ai, generate_action_plan
Expand Down Expand Up @@ -133,16 +142,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(
Expand Down Expand Up @@ -234,45 +233,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

return {
"id": new_issue.id,
"message": "Issue reported successfully",
"action_plan": action_plan
}
new_issue = await asyncio.to_thread(save_to_db)

return {
"id": new_issue.id,
"message": "Issue reported successfully",
"action_plan": action_plan
}
except Exception as e:
logger.error(f"Error creating issue: {e}")
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
raise HTTPException(status_code=500, detail="Failed to create issue")

@lru_cache(maxsize=1)
def _load_responsibility_map():
Expand Down
Loading