-
Notifications
You must be signed in to change notification settings - Fork 38
feat: implement daily civic intelligence refinement engine #897
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
43e0736
44a1b3a
ab1d269
6a6658b
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 |
|---|---|---|
| @@ -1,6 +1,10 @@ | ||
| from hf_service import client | ||
| from PIL import Image | ||
| import io | ||
| import httpx | ||
| from hf_service import query_hf_api | ||
| import asyncio | ||
|
|
||
| _hf_client = httpx.AsyncClient() | ||
|
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. P2: The module-level Prompt for AI agents
Comment on lines
+3
to
+7
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== File map ==\n'
git ls-files backend | sed -n '1,200p'
printf '\n== Relevant symbols in backend/flood_detection.py ==\n'
ast-grep outline backend/flood_detection.py --view expanded || true
printf '\n== Read backend/flood_detection.py ==\n'
wc -l backend/flood_detection.py
cat -n backend/flood_detection.py | sed -n '1,240p'
printf '\n== Find query_hf_api definition and uses ==\n'
rg -n "def query_hf_api|query_hf_api\(" backend . -g '!**/.git/**' || true
printf '\n== Read hf_service module ==\n'
fd -a "hf_service.py" . || true
for f in $(fd -a "hf_service.py" .); do
echo "--- $f"
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
done
printf '\n== Search for AsyncClient lifecycle/close usage ==\n'
rg -n "AsyncClient\(|aclose\(|close\(" backend . -g '!**/.git/**' || true
printf '\n== Search for asyncio.run and thread usage in backend ==\n'
rg -n "asyncio\.run|threading|Thread\(" backend -g '!**/.git/**' || trueRepository: RohanExploit/VishwaGuru Length of output: 23576 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== Uses of detect_flooding ==\n'
rg -n "\bdetect_flooding\b" backend tests -g '!**/.git/**' || true
printf '\n== Uses of flood_detection module ==\n'
rg -n "\bflood_detection\b" backend tests -g '!**/.git/**' || true
printf '\n== Search for any close/aclose on _hf_client or flood client ==\n'
rg -n "_hf_client|flood.*aclose|flood.*close|query_hf_api\(.*_hf_client" backend tests -g '!**/.git/**' || trueRepository: RohanExploit/VishwaGuru Length of output: 1616 🏁 Script executed: #!/bin/bash
set -euo pipefail
wc -l backend/main.py
cat -n backend/main.py | sed -n '430,475p'
printf '\n== flood_detection import context ==\n'
cat -n backend/main.py | sed -n '35,55p'Repository: RohanExploit/VishwaGuru Length of output: 3333 Avoid reusing 🤖 Prompt for AI Agents |
||
|
|
||
| def detect_flooding(image: Image.Image): | ||
| """ | ||
|
|
@@ -15,12 +19,23 @@ def detect_flooding(image: Image.Image): | |
| image.save(img_byte_arr, format=image.format if image.format else 'JPEG') | ||
| img_byte_arr = img_byte_arr.getvalue() | ||
|
|
||
| # Using the same model as vandalism for consistency and caching benefits | ||
| results = client.zero_shot_image_classification( | ||
| image=img_byte_arr, | ||
| labels=labels, | ||
| model="openai/clip-vit-base-patch32" | ||
| ) | ||
| try: | ||
| loop = asyncio.get_running_loop() | ||
| except RuntimeError: | ||
| loop = None | ||
|
|
||
| if loop and loop.is_running(): | ||
| # If we're already in an async context, this synchronous function won't work easily with our async HF client wrapper. | ||
| # But we can try to fall back or spawn a thread if really needed. For now, try running synchronously using asyncio.run if possible. | ||
| import threading | ||
| def _run(): | ||
| return asyncio.run(query_hf_api(img_byte_arr, labels, _hf_client)) | ||
| # Just do a blocking call inside a thread | ||
| import concurrent.futures | ||
| with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool: | ||
| results = pool.submit(_run).result() | ||
| else: | ||
| results = asyncio.run(query_hf_api(img_byte_arr, labels, _hf_client)) | ||
|
Comment on lines
+22
to
+38
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. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n## backend/flood_detection.py (outline)\n'
ast-grep outline backend/flood_detection.py --view expanded || true
printf '\n## relevant lines\n'
nl -ba backend/flood_detection.py | sed -n '1,220p'
printf '\n## search for _hf_client and query_hf_api\n'
rg -n "_hf_client|query_hf_api|ThreadPoolExecutor|asyncio.run|get_running_loop" backend -SRepository: RohanExploit/VishwaGuru Length of output: 368 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
path = Path("backend/flood_detection.py")
text = path.read_text()
lines = text.splitlines()
for i, line in enumerate(lines, 1):
if 1 <= i <= 220:
print(f"{i:4d}: {line}")
PY
printf '\n## references\n'
rg -n "_hf_client|query_hf_api|detect_flooding|ThreadPoolExecutor|asyncio.run|get_running_loop" backend -SRepository: RohanExploit/VishwaGuru Length of output: 6111 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
from pathlib import Path
for path_str in ["backend/hf_service.py", "backend/main.py"]:
path = Path(path_str)
print(f"\n## {path_str}")
text = path.read_text().splitlines()
for i, line in enumerate(text, 1):
if path_str.endswith("hf_service.py") and 1 <= i <= 260:
print(f"{i:4d}: {line}")
elif path_str.endswith("main.py") and 430 <= i <= 490:
print(f"{i:4d}: {line}")
PYRepository: RohanExploit/VishwaGuru Length of output: 10915 🌐 Web query:
💡 Result: You cannot share an httpx.AsyncClient instance across multiple event loops [1][2][3]. While AsyncClient is designed to be task-safe within a single event loop [4][5], it is strictly bound to the event loop in which it was created [1][2]. Attempting to reuse an AsyncClient across different event loops (for instance, by calling asyncio.run multiple times with the same client instance) will typically result in a RuntimeError: Event loop is closed, because the underlying connection pool and transport layer cannot be shared across different loop contexts [1][2]. Key guidelines for usage: 1. One Client Per Loop: If your application uses multiple event loops or runs in environments like AWS Lambda where the event loop may be recreated across invocations, you must ensure that each invocation or loop has its own dedicated AsyncClient instance [2][6]. 2. Task Safety: Within a single event loop, AsyncClient is safe to be shared across multiple concurrent tasks (e.g., using asyncio.gather or TaskGroup) [5][7]. This is the recommended pattern to benefit from connection pooling [8][9]. 3. Synchronous vs Asynchronous: If you have a requirement to share a client across multiple threads in a way that is not bound to a single async event loop, consider using the synchronous httpx.Client, which is thread-safe and designed for such scenarios [4][3][10]. In summary, always associate the lifetime of your AsyncClient with the lifetime of the event loop that creates it [6]. Using the async context manager (async with httpx.AsyncClient as client:) is the best practice to ensure proper resource management and lifetime scoping [6][8]. Citations:
Avoid reusing 🤖 Prompt for AI Agents |
||
|
|
||
| # Filter for flooding related | ||
| flood_labels = ["flooded street", "waterlogging", "submerged car"] | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -88,6 +88,14 @@ 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 | ||||||
| import io | ||||||
| img_byte_arr = io.BytesIO() | ||||||
| image.save(img_byte_arr, format='JPEG') | ||||||
|
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. P2: Saving PIL Image as JPEG without RGB conversion will crash for images with alpha channels (RGBA, P, LA modes). Call Prompt for AI agents
Suggested change
|
||||||
| return img_byte_arr.getvalue() | ||||||
|
Comment on lines
90
to
+96
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. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== backend/hf_service.py excerpt ==\n'
nl -ba backend/hf_service.py | sed -n '1,180p'
printf '\n== usages of _prepare_image_bytes ==\n'
rg -n "_prepare_image_bytes|detect_vandalism_clip|Image\.open|convert\\('RGB'\\)|convert\\(\"RGB\"\\)" backend -SRepository: RohanExploit/VishwaGuru Length of output: 238 🏁 Script executed: #!/bin/bash
set -euo pipefail
nl -ba backend/hf_service.py | sed -n '1,180p'
rg -n "_prepare_image_bytes|detect_vandalism_clip|convert\\('RGB'\\)|convert\\(\"RGB\"\\)" backend -SRepository: RohanExploit/VishwaGuru Length of output: 201 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '\n== backend/hf_service.py excerpt ==\n'
cat -n backend/hf_service.py | sed -n '1,180p'
printf '\n== usages of _prepare_image_bytes / image conversion ==\n'
rg -n "_prepare_image_bytes|detect_vandalism_clip|convert\\('RGB'\\)|convert\\(\"RGB\"\\)" backend -SRepository: RohanExploit/VishwaGuru Length of output: 9741 Convert images to RGB before JPEG encoding. 🤖 Prompt for AI Agents |
||||||
|
|
||||||
| async def detect_vandalism_clip(image: Union[Image.Image, bytes], client: httpx.AsyncClient) -> List[Dict[str, Any]]: | ||||||
|
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. P0: The new Prompt for AI agents
Suggested change
|
||||||
| """ | ||||||
| Detects vandalism/graffiti using Zero-Shot Image Classification with CLIP (Async). | ||||||
| Includes retry logic with exponential backoff for transient failures. | ||||||
|
|
||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,3 +1,4 @@ | ||||||||||||||||||||
| import sys | ||||||||||||||||||||
| from fastapi import FastAPI, UploadFile, File, Form, HTTPException, Query | ||||||||||||||||||||
| from fastapi.middleware.cors import CORSMiddleware | ||||||||||||||||||||
| from fastapi.concurrency import run_in_threadpool | ||||||||||||||||||||
|
|
@@ -11,8 +12,10 @@ | |||||||||||||||||||
| import json | ||||||||||||||||||||
| import os | ||||||||||||||||||||
| import io | ||||||||||||||||||||
| from functools import lru_cache | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Add the project root to sys.path so we can import 'backend' modules | ||||||||||||||||||||
| import sys | ||||||||||||||||||||
| sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| from fastapi import FastAPI, HTTPException, UploadFile, File, Form, BackgroundTasks, Depends, Query | ||||||||||||||||||||
|
|
@@ -21,7 +24,9 @@ | |||||||||||||||||||
| from fastapi.concurrency import run_in_threadpool | ||||||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||||||
| from sqlalchemy.orm import Session | ||||||||||||||||||||
| from schemas import StatsResponse, MLStatusResponse | ||||||||||||||||||||
| from database import SessionLocal, engine, Base | ||||||||||||||||||||
| from ai_factory import create_all_ai_services | ||||||||||||||||||||
| from models import Issue | ||||||||||||||||||||
| from contextlib import asynccontextmanager | ||||||||||||||||||||
| import shutil | ||||||||||||||||||||
|
|
@@ -65,9 +70,9 @@ async def lifespan(app: FastAPI): | |||||||||||||||||||
| try: | ||||||||||||||||||||
| load_maharashtra_pincode_data() | ||||||||||||||||||||
| load_maharashtra_mla_data() | ||||||||||||||||||||
| logger.info("Maharashtra data pre-loaded successfully.") | ||||||||||||||||||||
| print("Maharashtra data pre-loaded successfully.") | ||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||
| logger.error(f"Error pre-loading Maharashtra data: {e}") | ||||||||||||||||||||
| print(f"Error pre-loading Maharashtra data: {e}") | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Run database migrations | ||||||||||||||||||||
| try: | ||||||||||||||||||||
|
|
@@ -133,27 +138,18 @@ 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) | ||||||||||||||||||||
| @app.get("/health") | ||||||||||||||||||||
| def health(): | ||||||||||||||||||||
| return HealthResponse( | ||||||||||||||||||||
| status="healthy", | ||||||||||||||||||||
| timestamp=datetime.now(timezone.utc), | ||||||||||||||||||||
| version="1.0.0", | ||||||||||||||||||||
| services={ | ||||||||||||||||||||
| from datetime import datetime, timezone | ||||||||||||||||||||
| return { | ||||||||||||||||||||
| "status": "healthy", | ||||||||||||||||||||
| "timestamp": datetime.now(timezone.utc).isoformat(), | ||||||||||||||||||||
| "version": "1.0.0", | ||||||||||||||||||||
| "services": { | ||||||||||||||||||||
| "database": "connected", | ||||||||||||||||||||
| "ai_services": "initialized" | ||||||||||||||||||||
| } | ||||||||||||||||||||
| ) | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| @app.get("/api/stats", response_model=StatsResponse) | ||||||||||||||||||||
| def get_stats(db: Session = Depends(get_db)): | ||||||||||||||||||||
|
|
@@ -234,45 +230,39 @@ 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) | ||||||||||||||||||||
|
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. 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
rg -nP '^\s*import\s+asyncio\b|^\s*from\s+asyncio\b' backend/main.pyRepository: RohanExploit/VishwaGuru Length of output: 161 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- imports / top of file ---'
sed -n '1,80p' backend/main.py
echo
echo '--- around first call site ---'
sed -n '220,245p' backend/main.py
echo
echo '--- around second call site ---'
sed -n '248,265p' backend/main.pyRepository: RohanExploit/VishwaGuru Length of output: 4584 Import 🧰 Tools🪛 Ruff (0.15.20)[error] 236-236: Undefined name (F821) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||
|
|
||||||||||||||||||||
| # 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, | ||||||||||||||||||||
|
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. P2: Web clients can now forge Prompt for AI agents
Suggested change
|
||||||||||||||||||||
| 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: | ||||||||||||||||||||
| import traceback | ||||||||||||||||||||
| traceback.print_exc() | ||||||||||||||||||||
| raise HTTPException(status_code=500, detail=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. P2: Failed issue submissions expose raw internal exception text to callers. Return a generic 500 detail and retain traceback logging server-side. Prompt for AI agents
Suggested change
Comment on lines
+262
to
+265
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. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win Avoid leaking raw exception text to clients and preserve the cause chain.
🛡️ Proposed change except Exception as e:
- import traceback
- traceback.print_exc()
- raise HTTPException(status_code=500, detail=str(e))
+ logger.exception("Failed to create issue")
+ raise HTTPException(status_code=500, detail="Internal server error") from e📝 Committable suggestion
Suggested change
🧰 Tools🪛 Ruff (0.15.20)[warning] 262-262: Do not catch blind exception: (BLE001) [warning] 265-265: Within an (B904) 🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||||||||||||||||||||
|
|
||||||||||||||||||||
| @lru_cache(maxsize=1) | ||||||||||||||||||||
| def _load_responsibility_map(): | ||||||||||||||||||||
|
|
||||||||||||||||||||
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: Repeated
/api/detect-floodingrequests can silently return no detections after the first request because_hf_clientis reused across event loops thatasyncio.run()closes. Scope anAsyncClientto each coroutine run (or own it in one long-lived event loop) instead of sharing this instance across those runs.Prompt for AI agents