-
Notifications
You must be signed in to change notification settings - Fork 35
⚡ Bolt: [performance improvement] optimize closure status queries using GROUP BY #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a054a83
8fb1b17
62206ab
f7abfcc
2fb714d
6c34338
9f4090b
2c9c70c
9a6582e
f262ad7
c0bb0f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -31,6 +31,7 @@ | |||||
| AUDIO_CLASS_API_URL = "https://router.huggingface.co/models/MIT/ast-finetuned-audioset-10-10-0.4593" | ||||||
|
|
||||||
| # Speech-to-Text Model (Whisper) | ||||||
| FACIAL_EMOTION_API_URL = "https://router.huggingface.co/models/dima806/facial_emotions_image_detection" | ||||||
| WHISPER_API_URL = "https://router.huggingface.co/models/openai/whisper-large-v3-turbo" | ||||||
|
|
||||||
| async def _make_request(client, url, payload): | ||||||
|
|
@@ -456,3 +457,34 @@ async def detect_abandoned_vehicle_clip(image: Union[Image.Image, bytes], client | |||||
| labels = ["abandoned car", "rusted vehicle", "car with flat tires", "wrecked car", "normal parked car"] | ||||||
| targets = ["abandoned car", "rusted vehicle", "car with flat tires", "wrecked car"] | ||||||
| return await _detect_clip_generic(image, labels, targets, client) | ||||||
|
|
||||||
|
|
||||||
| async def detect_facial_emotion(image: Union[Image.Image, bytes], client: httpx.AsyncClient = None): | ||||||
| """ | ||||||
| Detects facial emotions in an image using Hugging Face's dima806/facial_emotions_image_detection model. | ||||||
| """ | ||||||
| img_bytes = _prepare_image_bytes(image) | ||||||
|
|
||||||
| try: | ||||||
| headers_bin = {"Authorization": f"Bearer {token}"} if token else {} | ||||||
| async def do_post(c): | ||||||
| return await c.post(FACIAL_EMOTION_API_URL, headers=headers_bin, content=img_bytes, timeout=30.0) | ||||||
|
|
||||||
| if client: | ||||||
| response = await do_post(client) | ||||||
| else: | ||||||
| async with httpx.AsyncClient() as new_client: | ||||||
| response = await do_post(new_client) | ||||||
|
|
||||||
| if response.status_code == 200: | ||||||
| data = response.json() | ||||||
| if isinstance(data, list) and len(data) > 0: | ||||||
| return {"emotions": data[:3]} # Return top 3 emotions | ||||||
| return {"emotions": []} | ||||||
| else: | ||||||
| logger.error(f"Emotion API Error: {response.status_code} - {response.text}") | ||||||
| return {"error": "Failed to analyze emotions", "details": response.text} | ||||||
|
|
||||||
| except Exception as e: | ||||||
| logger.error(f"Emotion Estimation Error: {e}") | ||||||
| return {"error": str(e)} | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Avoid returning Prompt for AI agents
Suggested change
|
||||||
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import httpx | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi import APIRouter, UploadFile, File, Request, HTTPException | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from fastapi.concurrency import run_in_threadpool | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from PIL import Image | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -6,6 +7,7 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.utils import process_and_detect, validate_uploaded_file, process_uploaded_image | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.schemas import DetectionResponse, UrgencyAnalysisRequest, UrgencyAnalysisResponse | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.cache import ThreadSafeCache | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.pothole_detection import detect_potholes, validate_image_for_processing | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.unified_detection_service import ( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_vandalism as detect_vandalism_unified, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -35,7 +37,9 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_civic_eye_clip, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_graffiti_art_clip, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_traffic_sign_clip, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_abandoned_vehicle_clip | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_abandoned_vehicle_clip, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detect_facial_emotion, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from backend.dependencies import get_http_client | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.dependencies | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -46,35 +50,22 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Cached Functions | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Simple Cache Implementation to avoid async-lru dependency issues on Render | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cache_store = {} | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| CACHE_TTL = 3600 # 1 hour | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MAX_CACHE_SIZE = 500 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Use ThreadSafeCache for better performance and proper TTL/LRU management | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detection_cache = ThreadSafeCache(ttl=3600, max_size=500) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def _get_cached_result(key: str, func, *args, **kwargs): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| current_time = time.time() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check cache | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if key in _cache_store: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result, timestamp = _cache_store[key] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if current_time - timestamp < CACHE_TTL: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| del _cache_store[key] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Prune cache if too large | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(_cache_store) > MAX_CACHE_SIZE: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| keys_to_remove = list(_cache_store.keys())[:int(MAX_CACHE_SIZE * 0.2)] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for k in keys_to_remove: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| del _cache_store[k] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cached_result = detection_cache.get(key) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if cached_result is not None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return cached_result | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Execute function | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if 'client' not in kwargs: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import backend.dependencies | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| kwargs['client'] = backend.dependencies.SHARED_HTTP_CLIENT | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await func(*args, **kwargs) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _cache_store[key] = (result, current_time) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| detection_cache.set(data=result, key=key) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def _cached_detect_severity(image_bytes: bytes): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -465,3 +456,23 @@ async def detect_abandoned_vehicle_endpoint(image: UploadFile = File(...)): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.error(f"Abandoned vehicle detection error: {e}", exc_info=True) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=500, detail="Internal server error") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @router.post("/api/detect-emotion") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async def detect_emotion_endpoint( | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| image: UploadFile = File(...), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| client: httpx.AsyncClient = backend.dependencies.Depends(get_http_client) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Analyze facial emotions in the image using Hugging Face inference. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| img_data = await validate_uploaded_file(image) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
RohanExploit marked this conversation as resolved.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "error" in img_data: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=400, detail=img_data["error"]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| processed_bytes = await run_in_threadpool(process_uploaded_image, img_data["bytes"]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = await detect_facial_emotion(processed_bytes, client) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if "error" in result: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| raise HTTPException(status_code=500, detail=result["error"]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. P1: Do not expose raw upstream error details in 500 responses; return a generic message instead. Prompt for AI agents
Suggested change
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check warningCode scanning / CodeQL Information exposure through an exception Medium Stack trace information Error loading related location Loading
This autofix suggestion was applied.
Show autofix suggestion
Hide autofix suggestion
Copilot AutofixAI about 2 months ago General fix: Never return raw exception messages or stack traces to API clients. Instead, log the detailed error server-side and send a generic, user-safe error description in responses. If additional structured error info is needed, it should be sanitized and not include internal messages or traces. Best fix here: Change Concretely:
No new methods or external libraries are needed; we only adjust logging and returned data.
Suggested changeset
2
backend/routers/detection.py
backend/hf_api_service.py
Outside changed files
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return result | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
RohanExploit marked this conversation as resolved.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import time | ||
| import collections | ||
| import threading | ||
| import sys | ||
| import os | ||
|
|
||
| # Add parent directory to path to import backend.cache | ||
| sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../..'))) | ||
|
|
||
| from backend.cache import ThreadSafeCache | ||
|
|
||
| def benchmark_cache(cache_size, num_ops): | ||
| cache = ThreadSafeCache(ttl=300, max_size=cache_size) | ||
|
|
||
| # Fill cache | ||
| for i in range(cache_size): | ||
| cache.set(data=i, key=f"key{i}") | ||
|
|
||
| start_time = time.time() | ||
| for i in range(num_ops): | ||
| # Update existing keys to keep the cache full and trigger cleanup | ||
| cache.set(data=i, key=f"key{i % cache_size}") | ||
| end_time = time.time() | ||
|
|
||
| return end_time - start_time | ||
|
|
||
| if __name__ == "__main__": | ||
| size = 1000 | ||
| ops = 5000 | ||
| print(f"Benchmarking ThreadSafeCache with size={size}, ops={ops}...") | ||
| duration = benchmark_cache(size, ops) | ||
| print(f"Duration: {duration:.4f} seconds") | ||
| print(f"Ops/sec: {ops / duration:.2f}") |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import sys | ||
| import time | ||
| import os | ||
|
|
||
| from sqlalchemy import create_engine | ||
| from sqlalchemy.orm import sessionmaker | ||
|
|
||
| sys.path.insert(0, os.path.abspath('.')) | ||
|
|
||
| from backend.database import Base, get_db | ||
| from backend.models import Grievance, GrievanceFollower, ClosureConfirmation | ||
| from backend.routers.grievances import get_closure_status | ||
| from backend.closure_service import ClosureService | ||
| from unittest.mock import patch, MagicMock | ||
|
|
||
| # In-memory SQLite for testing | ||
| engine = create_engine('sqlite:///:memory:', connect_args={"check_same_thread": False}) | ||
| TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) | ||
|
|
||
| Base.metadata.create_all(bind=engine) | ||
|
|
||
| def seed_data(db): | ||
| grievance = Grievance( | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| unique_id="G123", | ||
| category="pothole", | ||
| status="open", | ||
| description="test", | ||
| pincode="123456", | ||
| city="city", | ||
| district="district", | ||
| state="state" | ||
| ) | ||
| db.add(grievance) | ||
| db.commit() | ||
| db.refresh(grievance) | ||
|
RohanExploit marked this conversation as resolved.
RohanExploit marked this conversation as resolved.
|
||
|
|
||
| # Add followers | ||
| for i in range(100): | ||
| db.add(GrievanceFollower(grievance_id=grievance.id, user_email=f"user{i}@test.com")) | ||
|
|
||
| # Add confirmations | ||
| for i in range(50): | ||
| db.add(ClosureConfirmation( | ||
| grievance_id=grievance.id, | ||
| user_email=f"cuser{i}@test.com", | ||
| confirmation_type="confirmed" if i % 2 == 0 else "disputed" | ||
| )) | ||
|
|
||
| db.commit() | ||
| return grievance.id | ||
|
|
||
| def run_benchmark(): | ||
| db = TestingSessionLocal() | ||
| gid = seed_data(db) | ||
|
|
||
| start = time.perf_counter() | ||
| for _ in range(100): | ||
| get_closure_status(grievance_id=gid, db=db) | ||
| end = time.perf_counter() | ||
|
|
||
| print(f"Time taken for 100 calls: {(end - start) * 1000:.2f} ms") | ||
| db.close() | ||
|
|
||
| if __name__ == '__main__': | ||
| run_benchmark() | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| import time | ||
| import collections | ||
|
|
||
| def run_bench(): | ||
| N = 1000 | ||
| ops = 10000 | ||
| timestamps = {f"key{i}": time.time() for i in range(N)} | ||
| current_time = time.time() | ||
| ttl = 300 | ||
|
|
||
| start = time.time() | ||
| for _ in range(ops): | ||
| expired_keys = [ | ||
| key for key, timestamp in timestamps.items() | ||
| if current_time - timestamp >= ttl | ||
| ] | ||
| print(f"Current O(N) cleanup time for {ops} ops: {time.time() - start:.4f}s") | ||
|
|
||
| # Optimized version | ||
| timestamps_od = collections.OrderedDict(timestamps) | ||
| start = time.time() | ||
| for _ in range(ops): | ||
| # Simulated optimized cleanup | ||
| # In real code we use next(iter(self._timestamps.items())) | ||
| for key, ts in timestamps_od.items(): | ||
| if current_time - ts >= ttl: | ||
| pass | ||
| else: | ||
| break | ||
| print(f"Optimized O(K) cleanup time (K=0) for {ops} ops: {time.time() - start:.4f}s") | ||
|
|
||
| if __name__ == "__main__": | ||
| run_bench() |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
P1: Do not return raw upstream
response.textto clients; it can leak internal/provider error details.Prompt for AI agents