From 71712702b6d73eebef6702204cf90f47b6db7467 Mon Sep 17 00:00:00 2001 From: RohanExploit <178623867+RohanExploit@users.noreply.github.com> Date: Tue, 21 Apr 2026 16:28:45 +0000 Subject: [PATCH] =?UTF-8?q?=E2=9A=A1=20Bolt:=20[performance=20improvement]?= =?UTF-8?q?=20Optimize=20grievance=20query=20to=20use=20load=5Fonly?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .jules/bolt.md | 4 ++++ backend/civic_intelligence.py | 5 +++-- backend/tests/test_civic_intelligence.py | 1 + 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index 191736f9..5c4ce886 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -77,3 +77,7 @@ ## 2026-04-20 - Async File I/O in Voice Submission **Learning:** Saving audio recordings (up to 10MB) synchronously in a FastAPI async endpoint blocks the main event loop, significantly increasing tail latency for all concurrent users during high-traffic periods. **Action:** Wrap blocking synchronous File I/O operations like `f.write()` in `run_in_threadpool` to offload them to a separate thread, keeping the event loop responsive for other requests. + +## 2026-05-18 - Mocking SQLAlchemy load_only options +**Learning:** When changing a database query from `db.query(Model)` to `db.query(Model).options(load_only(...))` to optimize data fetching, the return type remains the ORM model (unlike column projection which returns tuples). However, if the tests mock the SQLAlchemy query chain, the mock assertions will fail because `.options()` is added to the chain. +**Action:** When adding `.options(load_only(...))` to a query, always ensure the corresponding mock queries in tests are updated to include `.options.return_value` (e.g., `mock_query.options.return_value.filter.return_value.all.return_value = ...`) to correctly mock the new chain. diff --git a/backend/civic_intelligence.py b/backend/civic_intelligence.py index 4a90640f..c070491c 100644 --- a/backend/civic_intelligence.py +++ b/backend/civic_intelligence.py @@ -3,7 +3,7 @@ import logging from datetime import datetime, timedelta, timezone from typing import List, Dict, Any -from sqlalchemy.orm import Session +from sqlalchemy.orm import Session, load_only from sqlalchemy import func from backend.models import Issue, EscalationAudit, EscalationReason, Grievance @@ -88,7 +88,8 @@ def run_daily_cycle(self): # Optimization: Fetch all related grievances in one query to avoid N+1 grievance_ids = [audit.grievance_id for audit in upgrades] if grievance_ids: - grievances = db.query(Grievance).filter(Grievance.id.in_(grievance_ids)).all() + # Optimized: Use load_only to avoid fetching unnecessary columns while preserving ORM objects + grievances = db.query(Grievance).options(load_only(Grievance.id, Grievance.category)).filter(Grievance.id.in_(grievance_ids)).all() grievance_map = {g.id: g for g in grievances} else: grievance_map = {} diff --git a/backend/tests/test_civic_intelligence.py b/backend/tests/test_civic_intelligence.py index dec96015..f79a37f3 100644 --- a/backend/tests/test_civic_intelligence.py +++ b/backend/tests/test_civic_intelligence.py @@ -190,6 +190,7 @@ def query_side_effect(*args): g1 = Grievance(id=1, category="Fire") g2 = Grievance(id=2, category="Fire") g3 = Grievance(id=3, category="Fire") + mock_query_grievance.options.return_value.filter.return_value.all.return_value = [g1, g2, g3] mock_query_grievance.filter.return_value.all.return_value = [g1, g2, g3] # Setup Trend Analyzer