Skip to content

Add new civic grievance detectors and optimize deployment#387

Open
RohanExploit wants to merge 4 commits intomainfrom
features/public-grievance-enhancement-1767509276103771134
Open

Add new civic grievance detectors and optimize deployment#387
RohanExploit wants to merge 4 commits intomainfrom
features/public-grievance-enhancement-1767509276103771134

Conversation

@RohanExploit
Copy link
Owner

@RohanExploit RohanExploit commented Feb 14, 2026

  • Added detectors for Public Facilities, Construction Safety, Traffic Signs, and Abandoned Vehicles.
  • Implemented GenericDetector component for reusable camera/API logic.
  • Updated backend hf_api_service.py to support new detection types via CLIP.
  • Exposed new endpoints in backend/routers/detection.py.
  • Updated Home.jsx with navigation buttons for new features.
  • Optimized deployment by removing scikit-learn from backend/requirements-render.txt and adding a fallback in spatial_utils.py for DBSCAN.
  • Verified frontend navigation with Playwright.

PR created automatically by Jules for task 1767509276103771134 started by @RohanExploit


Summary by cubic

Adds four civic grievance detectors and a reusable camera-based GenericDetector. Improves deploy reliability by moving SPA routing and security headers to public/, simplifying Netlify config, and making detection code resilient to missing dependencies.

  • New Features

    • Backend: CLIP detectors for public facilities and construction safety with POST routes /api/detect-public-facilities and /api/detect-construction-safety.
    • Frontend: New screens for Traffic Sign, Abandoned Vehicle, Public Facilities, and Construction Safety using GenericDetector; Home adds quick actions; main route moved to /home.
  • Dependencies

    • Removed scikit-learn and python-magic; added PIL-based validation and DBSCAN fallback in spatial_utils.
    • Garbage detection handles model init failures and returns empty results; ultralytics remains optional with lazy loading.
    • Netlify: SPA redirect and security headers moved to frontend/public (_redirects, _headers); simplified netlify.toml.

Written for commit d67beb4. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added Public Facilities and Construction Safety detectors plus Traffic Sign and Abandoned Vehicle detector tools, and a live camera detection UI with real-time analysis and visual overlays.
  • Improvements

    • More resilient image validation and automatic resizing; graceful fallbacks when optional image/cluster libraries are unavailable.
  • Chores

    • Removed a legacy end-to-end test and an unused dependency; adjusted hosting/redirects and updated static security header configuration.

- Added detectors for Public Facilities, Construction Safety, Traffic Signs, and Abandoned Vehicles.
- Implemented `GenericDetector` component for reusable camera/API logic.
- Updated backend `hf_api_service.py` to support new detection types via CLIP.
- Exposed new endpoints in `backend/routers/detection.py`.
- Updated `Home.jsx` with navigation buttons for new features.
- Optimized deployment by removing `scikit-learn` from `backend/requirements-render.txt` and adding a fallback in `spatial_utils.py` for DBSCAN.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
Copilot AI review requested due to automatic review settings February 14, 2026 09:55
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@netlify
Copy link

netlify bot commented Feb 14, 2026

Deploy Preview for fixmybharat failed. Why did it fail? →

Name Link
🔨 Latest commit d67beb4
🔍 Latest deploy log https://app.netlify.com/projects/fixmybharat/deploys/69904ebf1a3a750008af73d4

@github-actions
Copy link

🙏 Thank you for your contribution, @RohanExploit!

PR Details:

Quality Checklist:
Please ensure your PR meets the following criteria:

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is commented where necessary
  • Documentation updated (if applicable)
  • No new warnings generated
  • Tests added/updated (if applicable)
  • All tests passing locally
  • No breaking changes to existing functionality

Review Process:

  1. Automated checks will run on your code
  2. A maintainer will review your changes
  3. Address any requested changes promptly
  4. Once approved, your PR will be merged! 🎉

Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken.

@coderabbitai
Copy link

coderabbitai bot commented Feb 14, 2026

📝 Walkthrough

Walkthrough

Adds two CLIP-based detection functions and endpoints for public facilities and construction safety; introduces a reusable live-camera GenericDetector frontend with four detector views and routes; removes scikit-learn from render requirements and adds a DBSCAN fallback; improves image validation and verification tooling.

Changes

Cohort / File(s) Summary
Backend CLIP Detectors & Router
backend/hf_api_service.py, backend/routers/detection.py
Added detect_public_facilities_clip and detect_construction_safety_clip (reuse _detect_clip_generic) and exposed /api/detect-public-facilities and /api/detect-construction-safety endpoints with existing error handling.
Dependency & Clustering Fallback
backend/requirements-render.txt, backend/spatial_utils.py
Removed scikit-learn from render requirements; spatial_utils.py safely imports DBSCAN and falls back to single-item clusters when unavailable.
Image Validation & Utilities
backend/utils.py
Made python-magic optional for MIME checks, added graceful logging/fallback when absent; performs progressive image resizing and replaces BytesIO for downstream processing.
Frontend: Core Detector UI
frontend/src/components/GenericDetector.jsx
New live camera-based component: captures frames every 2s, POSTs JPEG to provided apiEndpoint, renders bounding boxes or zero-shot labels, manages camera lifecycle, errors, and overlays.
Frontend: Detector Views & Routing
frontend/src/App.jsx, frontend/src/views/Home.jsx, frontend/src/TrafficSignDetector.jsx, frontend/src/AbandonedVehicleDetector.jsx, frontend/src/PublicFacilitiesDetector.jsx, frontend/src/ConstructionSafetyDetector.jsx
Added four detector components and lazy imports, updated routes (Home -> /home), navigation, and added menu items for Construction and Public Facility.
Testing & Verification Scripts
verification_script.py, verify_backend_endpoints.py
Removed Playwright E2E verification script; added verify_backend_endpoints.py to assert presence of detection routes via app route inspection or file-content fallback.
Frontend Config / Logs & Headers
frontend/public/_redirects, frontend/public/_headers, frontend_log.txt, netlify.toml
Minor redirects formatting change; added static security headers file; dev-server logs updated (port flag, proxy query params); removed SPA redirect and Netlify headers from netlify.toml.
Model Initialization Robustness
backend/garbage_detection.py
Wrapped model initialization with try/except inside lock; get_model() errors are logged and return None, detect_garbage() guards against initialization failures.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Frontend as GenericDetector (Browser)
    participant Backend as FastAPI Detection Router
    participant HF as HF CLIP Service
    User->>Frontend: Start detection
    loop every 2s
        Frontend->>Frontend: capture frame -> JPEG
        Frontend->>Backend: POST /api/detect-...
        activate Backend
        Backend->>HF: call CLIP detection (_detect_clip_generic)
        activate HF
        HF-->>Backend: detection results (boxes or labels)
        deactivate HF
        Backend-->>Frontend: JSON results
        deactivate Backend
        Frontend->>Frontend: draw boxes or show labels
    end
    User->>Frontend: Stop detection
    Frontend->>Frontend: stop camera & clear interval
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested labels

ECWoC26, ECWoC26-L3

Poem

🐰 A hop, a flash, the camera gleams,
Detecting benches, helmets, signs and beams,
CLIP peeks in and names the scene,
Boxes drawn and labels keen,
Hopping on—more safety dreams! 🥕✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add new civic grievance detectors and optimize deployment' directly and comprehensively summarizes the main changes in the pull request.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch features/public-grievance-enhancement-1767509276103771134

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
backend/garbage_detection.py (2)

37-43: Redundant try/exceptload_model() already swallows all exceptions.

load_model() (line 29) catches Exception and returns None, so the try/except here can never fire. If the intent is defense-in-depth, that's fine, but it adds noise. Also, per Ruff TRY400, prefer logger.exception(...) over logger.error(...) to capture the traceback.

♻️ Suggested simplification
     if _model is None:
         with _model_lock:
-            try:
-                # Double check inside lock to avoid race conditions
-                if _model is None:
-                    _model = load_model()
-            except Exception as e:
-                logger.error(f"Error initializing garbage model: {e}")
-                return None
+            # Double check inside lock to avoid race conditions
+            if _model is None:
+                _model = load_model()
     return _model

56-60: Same redundancy: get_model() never raises.

The comment on line 59 already notes this. Since get_model() returns None on failure, this try/except is dead code. The if not model guard on line 62 is the actual safety net.

♻️ Suggested simplification
-    try:
-        model = get_model()
-    except Exception:
-        # Catch any initialization errors if get_model raises, though it returns None above
-        model = None
-
+    model = get_model()
+
     if not model:
frontend/public/_headers (1)

1-5: Consider adding Strict-Transport-Security and Content-Security-Policy headers.

Strict-Transport-Security (HSTS) is a standard baseline for production deployments to enforce HTTPS. Its absence means browsers won't prevent downgrade attacks on repeat visits.

Also, X-XSS-Protection: 1; mode=block is deprecated — modern browsers have removed or ignore it, and in some older IE versions it can paradoxically introduce XSS vectors. A Content-Security-Policy header is the modern replacement for XSS mitigation.

Suggested additions
 /*
   X-Frame-Options: DENY
   X-Content-Type-Options: nosniff
-  X-XSS-Protection: 1; mode=block
+  X-XSS-Protection: 0
   Referrer-Policy: strict-origin-when-cross-origin
+  Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
+  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' https://*.onrender.com
+  Permissions-Policy: camera=(self), microphone=(), geolocation=()

Adjust the CSP connect-src to match your actual backend origin, and tune Permissions-Policy based on which browser APIs the detectors need (e.g., camera for the live-camera GenericDetector component).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

6 issues found across 14 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/spatial_utils.py">

<violation number="1" location="backend/spatial_utils.py:133">
P1: Silent fallback disables spatial deduplication with no warning. When `scikit-learn` is absent (which is the expected state per the removal from `requirements-render.txt`), clustering degrades to treating every issue as its own cluster—effectively turning off deduplication. A `logging.warning()` (or at minimum a one-time log) should be emitted so operators are aware the feature is degraded.</violation>
</file>

<file name="frontend/src/components/GenericDetector.jsx">

<violation number="1" location="frontend/src/components/GenericDetector.jsx:71">
P2: `setLastDetections(detections)` is called redundantly. `drawDetections` calls it in its else branch (line 71), and then `detectFrame` always calls it again on line 128. Remove the `setLastDetections` call from inside `drawDetections` since the caller always sets it, or remove the one in the caller. This avoids unnecessary re-renders and confusing dual ownership of state updates.</violation>

<violation number="2" location="frontend/src/components/GenericDetector.jsx:136">
P1: Race condition: in-flight fetch requests are not cancelled when detection stops or the component unmounts. A pending response can call `setLastDetections` after cleanup has already cleared the state (or after unmount). Use an `AbortController` created in the effect and abort it in cleanup to cancel pending requests.</violation>
</file>

<file name="backend/routers/detection.py">

<violation number="1" location="backend/routers/detection.py:445">
P2: Same issue as the public-facilities endpoint: raw `await image.read()` skips all validation and optimization provided by `process_uploaded_image`. This endpoint should also use `_, image_bytes = await process_uploaded_image(image)` to enforce file size limits, MIME type checks, and image resizing.</violation>
</file>

<file name="frontend/src/App.jsx">

<violation number="1" location="frontend/src/App.jsx:203">
P1: Changing the Home route from `path="/"` to `path="/home"` breaks all `onBack={() => navigate('/')}` callbacks across the app. Since `location.pathname === '/'` renders the Landing page (line 155), every detector's back button now navigates to the Landing page instead of the Home dashboard. All `navigate('/')` calls in `onBack` handlers (including the 4 new ones below) should be updated to `navigate('/home')`.</violation>
</file>

<file name="verify_backend_endpoints.py">

<violation number="1" location="verify_backend_endpoints.py:42">
P2: Fallback route check only validates 2 of 4 expected routes. If the `ImportError` path is taken, `/api/detect-traffic-sign` and `/api/detect-abandoned-vehicle` are never verified, allowing a false SUCCESS.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.


if DBSCAN is None:
# Fallback if scikit-learn is not installed
return [[issue] for issue in valid_issues]
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P1: Silent fallback disables spatial deduplication with no warning. When scikit-learn is absent (which is the expected state per the removal from requirements-render.txt), clustering degrades to treating every issue as its own cluster—effectively turning off deduplication. A logging.warning() (or at minimum a one-time log) should be emitted so operators are aware the feature is degraded.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/spatial_utils.py, line 133:

<comment>Silent fallback disables spatial deduplication with no warning. When `scikit-learn` is absent (which is the expected state per the removal from `requirements-render.txt`), clustering degrades to treating every issue as its own cluster—effectively turning off deduplication. A `logging.warning()` (or at minimum a one-time log) should be emitted so operators are aware the feature is degraded.</comment>

<file context>
@@ -125,6 +128,10 @@ def cluster_issues_dbscan(issues: List[Issue], eps_meters: float = 30.0) -> List
 
+    if DBSCAN is None:
+        # Fallback if scikit-learn is not installed
+        return [[issue] for issue in valid_issues]
+
     # Perform DBSCAN clustering
</file context>
Suggested change
return [[issue] for issue in valid_issues]
import logging
logging.getLogger(__name__).warning(
"scikit-learn is not installed; DBSCAN clustering is disabled. "
"Each issue will be treated as its own cluster."
)
return [[issue] for issue in valid_issues]
Fix with Cubic

}, 'image/jpeg', 0.8);
};

useEffect(() => {
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P1: Race condition: in-flight fetch requests are not cancelled when detection stops or the component unmounts. A pending response can call setLastDetections after cleanup has already cleared the state (or after unmount). Use an AbortController created in the effect and abort it in cleanup to cancel pending requests.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/GenericDetector.jsx, line 136:

<comment>Race condition: in-flight fetch requests are not cancelled when detection stops or the component unmounts. A pending response can call `setLastDetections` after cleanup has already cleared the state (or after unmount). Use an `AbortController` created in the effect and abort it in cleanup to cancel pending requests.</comment>

<file context>
@@ -0,0 +1,227 @@
+        }, 'image/jpeg', 0.8);
+    };
+
+    useEffect(() => {
+        let interval;
+        if (isDetecting) {
</file context>
Fix with Cubic


<Route
path="/"
path="/home"
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P1: Changing the Home route from path="/" to path="/home" breaks all onBack={() => navigate('/')} callbacks across the app. Since location.pathname === '/' renders the Landing page (line 155), every detector's back button now navigates to the Landing page instead of the Home dashboard. All navigate('/') calls in onBack handlers (including the 4 new ones below) should be updated to navigate('/home').

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/App.jsx, line 203:

<comment>Changing the Home route from `path="/"` to `path="/home"` breaks all `onBack={() => navigate('/')}` callbacks across the app. Since `location.pathname === '/'` renders the Landing page (line 155), every detector's back button now navigates to the Landing page instead of the Home dashboard. All `navigate('/')` calls in `onBack` handlers (including the 4 new ones below) should be updated to `navigate('/home')`.</comment>

<file context>
@@ -196,7 +200,7 @@ function AppContent() {
 
             <Route
-              path="/"
+              path="/home"
               element={
                 <Home
</file context>
Fix with Cubic

} else {
// If no boxes (e.g., Zero-Shot Classification), display top result as overlay
// We use state to render this in React instead of Canvas for better styling
setLastDetections(detections);
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: setLastDetections(detections) is called redundantly. drawDetections calls it in its else branch (line 71), and then detectFrame always calls it again on line 128. Remove the setLastDetections call from inside drawDetections since the caller always sets it, or remove the one in the caller. This avoids unnecessary re-renders and confusing dual ownership of state updates.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/src/components/GenericDetector.jsx, line 71:

<comment>`setLastDetections(detections)` is called redundantly. `drawDetections` calls it in its else branch (line 71), and then `detectFrame` always calls it again on line 128. Remove the `setLastDetections` call from inside `drawDetections` since the caller always sets it, or remove the one in the caller. This avoids unnecessary re-renders and confusing dual ownership of state updates.</comment>

<file context>
@@ -0,0 +1,227 @@
+        } else {
+            // If no boxes (e.g., Zero-Shot Classification), display top result as overlay
+            // We use state to render this in React instead of Canvas for better styling
+            setLastDetections(detections);
+        }
+    };
</file context>
Fix with Cubic

@@ -35,7 +35,9 @@
detect_civic_eye_clip,
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: Same issue as the public-facilities endpoint: raw await image.read() skips all validation and optimization provided by process_uploaded_image. This endpoint should also use _, image_bytes = await process_uploaded_image(image) to enforce file size limits, MIME type checks, and image resizing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/routers/detection.py, line 445:

<comment>Same issue as the public-facilities endpoint: raw `await image.read()` skips all validation and optimization provided by `process_uploaded_image`. This endpoint should also use `_, image_bytes = await process_uploaded_image(image)` to enforce file size limits, MIME type checks, and image resizing.</comment>

<file context>
@@ -436,3 +438,35 @@ async def detect_abandoned_vehicle_endpoint(request: Request, image: UploadFile
+@router.post("/api/detect-public-facilities")
+async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
+    try:
+        image_bytes = await image.read()
+    except Exception as e:
+        logger.error(f"Invalid image file: {e}", exc_info=True)
</file context>
Fix with Cubic

print("Checking file content directly as fallback...")
with open("backend/routers/detection.py", "r") as f:
content = f.read()
if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: Fallback route check only validates 2 of 4 expected routes. If the ImportError path is taken, /api/detect-traffic-sign and /api/detect-abandoned-vehicle are never verified, allowing a false SUCCESS.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At verify_backend_endpoints.py, line 42:

<comment>Fallback route check only validates 2 of 4 expected routes. If the `ImportError` path is taken, `/api/detect-traffic-sign` and `/api/detect-abandoned-vehicle` are never verified, allowing a false SUCCESS.</comment>

<file context>
@@ -0,0 +1,50 @@
+    print("Checking file content directly as fallback...")
+    with open("backend/routers/detection.py", "r") as f:
+        content = f.read()
+        if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
+             print("SUCCESS: Routes found in code.")
+             sys.exit(0)
</file context>
Fix with Cubic

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@frontend_log.txt`:
- Around line 1-100: Remove the transient Vite dev log file frontend_log.txt
from the repository and prevent it from being committed again by adding
frontend_log.txt to .gitignore; then remove the tracked copy from git history
(unstage/remove the tracked file) and commit the updated .gitignore so only the
clean ignore rule remains. Ensure you reference the filename frontend_log.txt
when updating .gitignore and confirm the removal by committing the change.

In `@frontend/src/App.jsx`:
- Line 203: Update the onBack callbacks that currently call navigate('/') so
they navigate to the Home route path '/home' instead; specifically change the
four new routes' onBack handlers (and the pre-existing onBack handlers between
lines ~260–297 and ~306–309) from navigate('/') to navigate('/home'), or better
yet extract a shared function like goHome() that calls navigate('/home') and use
that helper in all onBack callbacks (identify handlers by the onBack prop in
App.jsx).

In `@verify_backend_endpoints.py`:
- Around line 36-47: The except ImportError fallback only checks two routes;
update the fallback block in the except ImportError handler that opens
"backend/routers/detection.py" to verify all four expected route
strings—'/api/detect-public-facilities', '/api/detect-construction-safety',
'/api/detect-traffic-sign', and '/api/detect-abandoned-vehicle'—and only print
"SUCCESS: Routes found in code." and exit(0) if all four are present, otherwise
print "FAILED: Routes not found in code." and exit(1).
🧹 Nitpick comments (8)
backend/spatial_utils.py (1)

131-133: Move the fallback guard before the numpy computation and log a warning.

The guard at line 131 runs after constructing the numpy array and computing eps_degrees (lines 122-129), both of which are unused in the fallback path. Moving the check earlier avoids wasted work. Also, silently returning un-clustered results can mask a deployment misconfiguration — a logging.warning would help operators catch it.

♻️ Proposed fix
+    if DBSCAN is None:
+        import logging
+        logging.warning("scikit-learn not installed; skipping DBSCAN clustering, returning individual issues")
+        return [[issue] for issue in valid_issues]
+
     # Convert to numpy array for DBSCAN
     coordinates = np.array([
         [issue.latitude, issue.longitude] for issue in valid_issues
     ])
 
     # Convert eps from meters to degrees (approximate)
     # 1 degree latitude ≈ 111,000 meters
     # 1 degree longitude ≈ 111,000 * cos(latitude) meters
     eps_degrees = eps_meters / 111000  # Rough approximation
 
-    if DBSCAN is None:
-        # Fallback if scikit-learn is not installed
-        return [[issue] for issue in valid_issues]
-
     # Perform DBSCAN clustering

(The import logging can be moved to the top of the file instead.)

frontend/src/components/GenericDetector.jsx (4)

136-156: Stale closure: detectFrame captured by setInterval won't see updated state.

detectFrame is re-created every render but setInterval on line 140 captures only the version from the render when isDetecting became true. If any state or prop (other than isDetecting / apiEndpoint) that detectFrame depends on changes mid-detection, the interval will use stale values. More importantly, if drawDetections calls setLastDetections but the closed-over context is stale, you may get subtle bugs.

A more robust pattern is to store detectFrame in a ref and call through it:

♻️ Suggested approach
+    const detectFrameRef = useRef(null);
+    detectFrameRef.current = detectFrame;
+
     useEffect(() => {
         let interval;
         if (isDetecting) {
             startCamera();
-            interval = setInterval(detectFrame, 2000);
+            interval = setInterval(() => detectFrameRef.current?.(), 2000);
         } else {

92-106: Remove verbose developer thinking notes.

Lines 92–106 read like stream-of-consciousness design notes rather than useful code comments. They add cognitive load for future readers without conveying actionable information.


31-37: Use block body in forEach to avoid implicit return (Biome lint).

Biome flags track => track.stop() because the arrow shorthand implicitly returns the result. Use a block body to be explicit:

🔧 Proposed fix
-            tracks.forEach(track => track.stop());
+            tracks.forEach(track => { track.stop(); });

126-128: Duplicate setLastDetections call.

drawDetections already calls setLastDetections(detections) on line 71 for the no-box path. Then line 128 unconditionally calls setLastDetections(detections) again. For the box path, drawDetections draws on canvas and does not set state, so line 128 is needed there. But for the no-box path, it's a redundant state update. Consider moving the unconditional setLastDetections out of drawDetections entirely and keeping only the one on line 128.

backend/routers/detection.py (1)

442-472: New endpoints skip image validation unlike most other endpoints.

These endpoints (and the two they were copied from: detect_traffic_sign_endpoint, detect_abandoned_vehicle_endpoint) use raw await image.read() instead of process_uploaded_image(), which other endpoints use for validation and optimization. This means no content-type checking, size limits, or image format validation is applied.

Consider using process_uploaded_image for consistency:

♻️ Suggested change for detect_public_facilities_endpoint
 `@router.post`("/api/detect-public-facilities")
 async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
-    try:
-        image_bytes = await image.read()
-    except Exception as e:
-        logger.error(f"Invalid image file: {e}", exc_info=True)
-        raise HTTPException(status_code=400, detail="Invalid image file")
+    _, image_bytes = await process_uploaded_image(image)
 
     try:
         client = get_http_client(request)

Same applies to detect_construction_safety_endpoint.

frontend/src/views/Home.jsx (1)

104-104: New category items look good, but hardcoded labels skip i18n.

The new items use hardcoded English strings ("Construction", "Public Facility") instead of translation keys via t(). This matches some existing items (e.g., "Noise", "Crowd") but diverges from the majority that use t('home.issues.*'). Consider adding translation keys for consistency when i18n support is extended.

Also applies to: 111-111

frontend/src/App.jsx (1)

70-72: Maintainability: the validViews array is getting unwieldy.

Consider extracting this to a constant or deriving it from route definitions to keep it in sync and improve readability.

Comment on lines 1 to 100

> frontend@0.0.0 dev
> vite
> vite --port 5173


VITE v7.3.1 ready in 493 ms
VITE v7.3.1 ready in 271 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose
9:55:20 AM [vite] http proxy error: /api/issues/recent
9:45:55 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:55:20 AM [vite] http proxy error: /api/issues/recent
9:45:55 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:55:22 AM [vite] (client) ✨ new dependencies optimized: workbox-precaching, workbox-routing, workbox-strategies, workbox-expiration
9:55:22 AM [vite] (client) ✨ optimized dependencies changed. reloading
9:55:23 AM [vite] http proxy error: /api/issues/recent
9:45:57 AM [vite] (client) ✨ new dependencies optimized: workbox-precaching, workbox-routing, workbox-strategies, workbox-expiration
9:45:57 AM [vite] (client) ✨ optimized dependencies changed. reloading
9:45:58 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:55:23 AM [vite] http proxy error: /api/issues/recent
9:45:58 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:46:43 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:46:43 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:47:18 AM [vite] (client) hmr update /src/App.jsx, /src/index.css
9:47:24 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:47:24 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:48:20 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:48:20 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:49:16 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:49:16 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:50:27 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:50:27 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:51:34 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:51:34 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:51:35 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:51:35 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:53:10 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:53:10 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:53:12 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
9:53:12 AM [vite] http proxy error: /api/issues/recent?limit=10&offset=0
AggregateError [ECONNREFUSED]:
at internalConnectMultiple (node:net:1134:18)
at afterConnectMultiple (node:net:1715:7)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Development log file should not be committed to the repository.

This file is a Vite dev server log containing proxy errors and timestamps. It's a transient development artifact and adds noise to the repo. Add it to .gitignore and remove it from version control.

🤖 Prompt for AI Agents
In `@frontend_log.txt` around lines 1 - 100, Remove the transient Vite dev log
file frontend_log.txt from the repository and prevent it from being committed
again by adding frontend_log.txt to .gitignore; then remove the tracked copy
from git history (unstage/remove the tracked file) and commit the updated
.gitignore so only the clean ignore rule remains. Ensure you reference the
filename frontend_log.txt when updating .gitignore and confirm the removal by
committing the change.


<Route
path="/"
path="/home"
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

onBack navigates to / (Landing) instead of /home (Home) — users land on the wrong page.

The Home route was moved from "/" to "/home", but all onBack callbacks (both new and pre-existing on lines 260–297) still call navigate('/'), which now renders the Landing page. Clicking "Back to Home" from any detector will take the user to the Landing page instead of the Home dashboard.

At minimum, update the four new routes you're adding. Ideally, fix the pre-existing ones in the same pass or extract a shared goHome helper.

Proposed fix for the new routes
-            <Route path="/traffic-sign" element={<TrafficSignDetector onBack={() => navigate('/')} />} />
-            <Route path="/abandoned-vehicle" element={<AbandonedVehicleDetector onBack={() => navigate('/')} />} />
-            <Route path="/public-facilities" element={<PublicFacilitiesDetector onBack={() => navigate('/')} />} />
-            <Route path="/construction-safety" element={<ConstructionSafetyDetector onBack={() => navigate('/')} />} />
+            <Route path="/traffic-sign" element={<TrafficSignDetector onBack={() => navigate('/home')} />} />
+            <Route path="/abandoned-vehicle" element={<AbandonedVehicleDetector onBack={() => navigate('/home')} />} />
+            <Route path="/public-facilities" element={<PublicFacilitiesDetector onBack={() => navigate('/home')} />} />
+            <Route path="/construction-safety" element={<ConstructionSafetyDetector onBack={() => navigate('/home')} />} />

Also applies to: 306-309

🤖 Prompt for AI Agents
In `@frontend/src/App.jsx` at line 203, Update the onBack callbacks that currently
call navigate('/') so they navigate to the Home route path '/home' instead;
specifically change the four new routes' onBack handlers (and the pre-existing
onBack handlers between lines ~260–297 and ~306–309) from navigate('/') to
navigate('/home'), or better yet extract a shared function like goHome() that
calls navigate('/home') and use that helper in all onBack callbacks (identify
handlers by the onBack prop in App.jsx).

Comment on lines +36 to +47
except ImportError as e:
print(f"ImportError: {e}")
# Fallback: Check if file exists and contains the strings
print("Checking file content directly as fallback...")
with open("backend/routers/detection.py", "r") as f:
content = f.read()
if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fallback only verifies 2 of 4 expected routes.

The primary check (lines 17–22) validates all four routes, but the fallback (line 42) only checks for /api/detect-public-facilities and /api/detect-construction-safety, silently skipping traffic-sign and abandoned-vehicle.

🔧 Proposed fix
-        if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
+        fallback_routes = [
+            "/api/detect-public-facilities",
+            "/api/detect-construction-safety",
+            "/api/detect-traffic-sign",
+            "/api/detect-abandoned-vehicle",
+        ]
+        missing = [r for r in fallback_routes if r not in content]
+        if not missing:
🤖 Prompt for AI Agents
In `@verify_backend_endpoints.py` around lines 36 - 47, The except ImportError
fallback only checks two routes; update the fallback block in the except
ImportError handler that opens "backend/routers/detection.py" to verify all four
expected route strings—'/api/detect-public-facilities',
'/api/detect-construction-safety', '/api/detect-traffic-sign', and
'/api/detect-abandoned-vehicle'—and only print "SUCCESS: Routes found in code."
and exit(0) if all four are present, otherwise print "FAILED: Routes not found
in code." and exit(1).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds four new civic grievance detection features (Public Facilities, Construction Safety, Traffic Signs, and Abandoned Vehicles) using a reusable GenericDetector component, along with deployment optimization by removing scikit-learn from production requirements.

Changes:

  • Added four new CLIP-based detection endpoints and frontend detectors for civic infrastructure monitoring
  • Implemented GenericDetector component to consolidate camera and API logic across detectors
  • Optimized deployment by removing scikit-learn dependency with fallback logic for DBSCAN clustering

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
backend/hf_api_service.py Added detect_public_facilities_clip and detect_construction_safety_clip functions using CLIP zero-shot classification
backend/routers/detection.py Added /api/detect-public-facilities and /api/detect-construction-safety endpoints
backend/spatial_utils.py Added try/except import for sklearn.cluster.DBSCAN with fallback returning single-item clusters
backend/requirements-render.txt Removed scikit-learn dependency
frontend/src/components/GenericDetector.jsx New reusable component for camera-based detection with live video feed and API integration
frontend/src/PublicFacilitiesDetector.jsx New detector wrapper for public facilities damage detection
frontend/src/ConstructionSafetyDetector.jsx New detector wrapper for construction safety violations
frontend/src/TrafficSignDetector.jsx New detector wrapper for damaged traffic signs
frontend/src/AbandonedVehicleDetector.jsx New detector wrapper for abandoned vehicles
frontend/src/App.jsx Added routes for new detectors and changed home route from "/" to "/home"
frontend/src/views/Home.jsx Added navigation buttons for new detectors in appropriate categories
verify_backend_endpoints.py Added verification script to check new endpoint registration
verification_script.py Removed old Playwright verification script
frontend_log.txt Updated with development logs showing explicit port configuration

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +92 to +107
// 1. Draw clean video frame (optional if we want to process it, but usually we just need the blob)
// We don't draw the video on canvas because the video element is visible behind it.
// We draw detections ON TOP of the video.
// But we DO need to draw the frame to a canvas to get the blob.
// Let's use a separate offscreen canvas or just draw temporarily?
// Actually, PotholeDetector draws the video to the canvas, then draws detections.
// But the video element is also visible?
// In PotholeDetector:
// <video ... style={{ opacity: isDetecting ? 1 : 0.5 }} />
// <canvas ... className="absolute top-0 left-0" />
// The canvas is transparent overlay.
// But detectFrame does: context.drawImage(video, 0, 0...)
// This overwrites the transparent canvas with the video frame!
// So the user sees the canvas (video + boxes) on top of the video element.
// This is fine.

Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

This section contains excessive commented-out explanatory text (lines 92-106) that reads like development notes rather than useful code comments. These verbose implementation details about canvas rendering and PotholeDetector comparisons should either be removed or condensed into a concise comment explaining the drawing strategy. This improves code maintainability and readability.

Suggested change
// 1. Draw clean video frame (optional if we want to process it, but usually we just need the blob)
// We don't draw the video on canvas because the video element is visible behind it.
// We draw detections ON TOP of the video.
// But we DO need to draw the frame to a canvas to get the blob.
// Let's use a separate offscreen canvas or just draw temporarily?
// Actually, PotholeDetector draws the video to the canvas, then draws detections.
// But the video element is also visible?
// In PotholeDetector:
// <video ... style={{ opacity: isDetecting ? 1 : 0.5 }} />
// <canvas ... className="absolute top-0 left-0" />
// The canvas is transparent overlay.
// But detectFrame does: context.drawImage(video, 0, 0...)
// This overwrites the transparent canvas with the video frame!
// So the user sees the canvas (video + boxes) on top of the video element.
// This is fine.
// Draw the current video frame onto the canvas so we can both capture it as an image and overlay detections.

Copilot uses AI. Check for mistakes.
Comment on lines +132 to +133
# Fallback if scikit-learn is not installed
return [[issue] for issue in valid_issues]
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The fallback returns each issue as a single-item cluster when scikit-learn is not available. However, this changes the clustering behavior significantly - instead of grouping nearby issues together, every issue becomes its own cluster. This could cause unexpected behavior in code that depends on DBSCAN clustering to group spatially proximate issues. Consider either: (1) documenting this behavioral change clearly in the docstring, or (2) implementing a simple distance-based clustering fallback that preserves the intended grouping behavior.

Suggested change
# Fallback if scikit-learn is not installed
return [[issue] for issue in valid_issues]
# Fallback if scikit-learn is not installed: simple distance-based clustering
clusters: List[List[Issue]] = []
for issue in valid_issues:
placed = False
for cluster in clusters:
# Check distance to any member of the cluster (single-linkage)
for member in cluster:
if haversine_distance(
issue.latitude,
issue.longitude,
member.latitude,
member.longitude,
) <= eps_meters:
cluster.append(issue)
placed = True
break
if placed:
break
if not placed:
clusters.append([issue])
return clusters

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +50
try:
from backend.main import app

routes = [route.path for route in app.routes]

expected_routes = [
"/api/detect-public-facilities",
"/api/detect-construction-safety",
"/api/detect-traffic-sign",
"/api/detect-abandoned-vehicle"
]

missing = []
for route in expected_routes:
if route not in routes:
missing.append(route)

if missing:
print(f"FAILED: Missing routes: {missing}")
sys.exit(1)
else:
print("SUCCESS: All new routes found.")
sys.exit(0)

except ImportError as e:
print(f"ImportError: {e}")
# Fallback: Check if file exists and contains the strings
print("Checking file content directly as fallback...")
with open("backend/routers/detection.py", "r") as f:
content = f.read()
if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

This verification script should be removed or moved to a test directory as it appears to be a temporary development/verification script rather than production code. Scripts like this that are used for one-time verification during development should not be committed to the repository root unless they serve an ongoing testing or CI/CD purpose.

Suggested change
try:
from backend.main import app
routes = [route.path for route in app.routes]
expected_routes = [
"/api/detect-public-facilities",
"/api/detect-construction-safety",
"/api/detect-traffic-sign",
"/api/detect-abandoned-vehicle"
]
missing = []
for route in expected_routes:
if route not in routes:
missing.append(route)
if missing:
print(f"FAILED: Missing routes: {missing}")
sys.exit(1)
else:
print("SUCCESS: All new routes found.")
sys.exit(0)
except ImportError as e:
print(f"ImportError: {e}")
# Fallback: Check if file exists and contains the strings
print("Checking file content directly as fallback...")
with open("backend/routers/detection.py", "r") as f:
content = f.read()
if "/api/detect-public-facilities" in content and "/api/detect-construction-safety" in content:
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
def verify_backend_endpoints() -> int:
"""
Verify that expected backend detection routes are registered on the app.
Returns
-------
int
0 if all expected routes are found, 1 otherwise.
"""
try:
from backend.main import app
routes = [route.path for route in app.routes]
expected_routes = [
"/api/detect-public-facilities",
"/api/detect-construction-safety",
"/api/detect-traffic-sign",
"/api/detect-abandoned-vehicle",
]
missing = []
for route in expected_routes:
if route not in routes:
missing.append(route)
if missing:
print(f"FAILED: Missing routes: {missing}")
return 1
else:
print("SUCCESS: All new routes found.")
return 0
except ImportError as e:
print(f"ImportError: {e}")
# Fallback: Check if file exists and contains the strings
print("Checking file content directly as fallback...")
try:
with open("backend/routers/detection.py", "r") as f:
content = f.read()
except OSError as file_error:
print(f"FAILED: Could not read fallback file: {file_error}")
return 1
if (
"/api/detect-public-facilities" in content
and "/api/detect-construction-safety" in content
):
print("SUCCESS: Routes found in code.")
return 0
else:
print("FAILED: Routes not found in code.")
return 1
except Exception as e:
print(f"Error: {e}")
return 1
if __name__ == "__main__":
raise SystemExit(verify_backend_endpoints())

Copilot uses AI. Check for mistakes.
Comment on lines +458 to +472
@router.post("/api/detect-construction-safety")
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_construction_safety_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Construction safety detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The new construction safety endpoint is not using process_uploaded_image for image validation, size checking, resizing, and EXIF stripping, unlike other detection endpoints in this file (e.g., detect_waste_endpoint, detect_civic_eye_endpoint, detect_graffiti_endpoint at lines 376-406). This creates an inconsistency in how images are processed and could lead to security vulnerabilities (unvalidated EXIF data), performance issues (large unoptimized images), and inconsistent behavior. All detection endpoints should use process_uploaded_image as established by the codebase convention.

Copilot uses AI. Check for mistakes.
const validViews = ['home', 'map', 'report', 'action', 'mh-rep', 'pothole', 'garbage', 'vandalism', 'flood', 'infrastructure', 'parking', 'streetlight', 'fire', 'animal', 'blocked', 'tree', 'pest', 'smart-scan', 'grievance-analysis', 'noise', 'safety-check', 'my-reports', 'login', 'signup', 'traffic-sign', 'abandoned-vehicle', 'public-facilities', 'construction-safety'];
if (validViews.includes(view)) {
navigate(view === 'home' ? '/' : `/${view}`);
navigate(view === 'home' ? '/home' : `/${view}`);
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The navigateToView function maps 'home' to '/home' route, but this creates inconsistency with the previous behavior where 'home' navigated to '/'. Throughout the codebase, many onBack handlers use navigate('/') which now will show the NotFound page instead of returning to Home. Either the root path should remain as "/" for the Home component, or all navigate('/') calls need to be updated to navigate('/home').

Suggested change
navigate(view === 'home' ? '/home' : `/${view}`);
navigate(view === 'home' ? '/' : `/${view}`);

Copilot uses AI. Check for mistakes.
Comment on lines +442 to +472
@router.post("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_public_facilities_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Public facilities detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")

@router.post("/api/detect-construction-safety")
async def detect_construction_safety_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_construction_safety_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Construction safety detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The new detection endpoints are missing response caching that is present in similar endpoints (e.g., detect_waste_endpoint, detect_civic_eye_endpoint, detect_graffiti_endpoint at lines 376-406 use @alru_cache decorated helper functions). Without caching, repeated identical image uploads will make redundant API calls to Hugging Face services, increasing latency and costs. Consider adding cached wrapper functions similar to _cached_detect_graffiti (line 72) for the new detection functions.

Copilot uses AI. Check for mistakes.
Comment on lines +460 to +474
async def detect_public_facilities_clip(image: Union[Image.Image, bytes], client: httpx.AsyncClient = None):
"""
Detects damaged public facilities like benches, fountains, or playground equipment.
"""
labels = ["broken bench", "damaged park bench", "broken water fountain", "damaged playground equipment", "broken swing", "graffiti on facility", "good condition bench", "clean park"]
targets = ["broken bench", "damaged park bench", "broken water fountain", "damaged playground equipment", "broken swing", "graffiti on facility"]
return await _detect_clip_generic(image, labels, targets, client)

async def detect_construction_safety_clip(image: Union[Image.Image, bytes], client: httpx.AsyncClient = None):
"""
Detects construction safety violations.
"""
labels = ["unsafe scaffolding", "construction worker without helmet", "construction debris blocking path", "unsecured construction site", "safe construction site", "worker with helmet"]
targets = ["unsafe scaffolding", "construction worker without helmet", "construction debris blocking path", "unsecured construction site"]
return await _detect_clip_generic(image, labels, targets, client)
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The new detection functions have minimal documentation. The docstrings only describe what they detect but don't mention important details like: the confidence threshold used (0.4), the return format (list of detection objects with label, confidence, and empty box), or that they use CLIP for zero-shot classification. Consider expanding the docstrings to match the detail level of other complex functions in the codebase, particularly for public API functions that may be called from multiple places.

Copilot uses AI. Check for mistakes.
Comment on lines +442 to +456
@router.post("/api/detect-public-facilities")
async def detect_public_facilities_endpoint(request: Request, image: UploadFile = File(...)):
try:
image_bytes = await image.read()
except Exception as e:
logger.error(f"Invalid image file: {e}", exc_info=True)
raise HTTPException(status_code=400, detail="Invalid image file")

try:
client = get_http_client(request)
detections = await detect_public_facilities_clip(image_bytes, client=client)
return {"detections": detections}
except Exception as e:
logger.error(f"Public facilities detection error: {e}", exc_info=True)
raise HTTPException(status_code=500, detail="Internal server error")
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The new detection endpoints are not using process_uploaded_image for image validation, size checking, resizing, and EXIF stripping, unlike other detection endpoints in this file (e.g., detect_waste_endpoint, detect_civic_eye_endpoint, detect_graffiti_endpoint at lines 376-406). This creates an inconsistency in how images are processed and could lead to security vulnerabilities (unvalidated EXIF data), performance issues (large unoptimized images), and inconsistent behavior. All detection endpoints should use process_uploaded_image as established by the codebase convention.

Copilot uses AI. Check for mistakes.
Comment on lines 202 to +203
<Route
path="/"
path="/home"
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

The root path "/" route has been changed to "/home", but there's no redirect from "/" to "/home". This means users visiting the root URL will see the NotFound page (line 315) instead of the Home page. This breaks the expected user experience. Consider either: (1) keeping path="/" for the Home route, or (2) adding a redirect route from "/" to "/home" before the wildcard route.

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +47
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)
Copy link

Copilot AI Feb 14, 2026

Choose a reason for hiding this comment

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

Inconsistent indentation: lines 43 and 46 use more spaces than the surrounding code. This should use consistent 4-space indentation to match the rest of the file (see lines 30-34, 40-47).

Suggested change
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)
print("SUCCESS: Routes found in code.")
sys.exit(0)
else:
print("FAILED: Routes not found in code.")
sys.exit(1)

Copilot uses AI. Check for mistakes.
…ify conflicts

- Backend: Removed `python-magic` from `requirements-render.txt` as it requires system libraries not available on Render.
- Backend: Updated `backend/utils.py` to handle `ImportError` for `magic` gracefully, preventing startup crashes.
- Frontend: Removed `frontend/public/_redirects` to resolve potential conflicts with `netlify.toml` routing rules.
- Confirmed `backend/pothole_detection.py` handles missing `ultralytics` gracefully (lazy loading with exception handling).

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/utils.py">

<violation number="1" location="backend/utils.py:91">
P1: Security bug: `HTTPException` from MIME type validation is silently swallowed by the broad `except Exception` handler, allowing files with non-image MIME types (e.g., executables) to bypass validation. `HTTPException` inherits from `Exception`, so it gets caught and only logged as a warning.

Note that `process_uploaded_image_sync` handles this correctly with a separate `except HTTPException: raise` clause — the same pattern should be applied here.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
except Exception as e:
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P1: Security bug: HTTPException from MIME type validation is silently swallowed by the broad except Exception handler, allowing files with non-image MIME types (e.g., executables) to bypass validation. HTTPException inherits from Exception, so it gets caught and only logged as a warning.

Note that process_uploaded_image_sync handles this correctly with a separate except HTTPException: raise clause — the same pattern should be applied here.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/utils.py, line 91:

<comment>Security bug: `HTTPException` from MIME type validation is silently swallowed by the broad `except Exception` handler, allowing files with non-image MIME types (e.g., executables) to bypass validation. `HTTPException` inherits from `Exception`, so it gets caught and only logged as a warning.

Note that `process_uploaded_image_sync` handles this correctly with a separate `except HTTPException: raise` clause — the same pattern should be applied here.</comment>

<file context>
@@ -71,66 +74,63 @@ def _validate_uploaded_file_sync(file: UploadFile) -> Optional[Image.Image]:
+                    status_code=400,
+                    detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
+                )
+        except Exception as e:
+            logger.warning(f"Magic validation failed: {e}")
+            # Fallback to extension check or PIL check
</file context>
Suggested change
except Exception as e:
except HTTPException:
raise
except Exception as e:
Fix with Cubic

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@backend/utils.py`:
- Line 115: The save call using img.save(output, format=img.format or 'JPEG',
quality=85) can fail for RGBA images because JPEG doesn't support alpha; update
the save logic in the same function to detect alpha modes (e.g., img.mode in
('RGBA','LA') or images with transparency) and either convert the image to RGB
(img = img.convert('RGB')) before saving when falling back to 'JPEG', or choose
a lossless format like 'PNG' when alpha is present; apply this change around the
img.save call so it mirrors the safe handling already implemented in
process_uploaded_image_sync.
- Around line 113-125: The code sets file.size from output.tell() after seeking
back to 0, causing size to always be 0; instead measure the buffer length before
rewinding (or use output.getbuffer().nbytes) and then reset the pointer.
Specifically, after img.save(...) capture the byte length (e.g., size =
output.getbuffer().nbytes or by doing output.seek(0, io.SEEK_END); size =
output.tell()), then output.seek(0) and assign file.file = output and file.size
= size so file.size reflects the actual resized image length (references:
variables/members output, img.save, file.file, file.size).
- Around line 77-95: The broad except around the python-magic MIME check
swallows HTTPException raised for invalid MIME types; update the except handling
in the magic block so that if an HTTPException is raised (the one constructed
using HTTPException with detail=f"Invalid file type..."), it is re-raised
immediately, and only non-HTTPException errors are caught and logged (i.e., add
an explicit except HTTPException: raise before the generic except Exception as
e: logger.warning(...) fallback). Reference: the magic MIME check
(magic.from_buffer / detected_mime / ALLOWED_MIME_TYPES) and the HTTPException
being raised there; mirror the pattern used in process_uploaded_image_sync where
HTTPException is re-raised prior to catching generic exceptions.
🧹 Nitpick comments (2)
backend/utils.py (2)

61-134: Significant code duplication between _validate_uploaded_file_sync and process_uploaded_image_sync.

Both functions duplicate file-size checking, MIME validation, PIL open, and resize logic. This already led to an inconsistency (the HTTPException re-raise is present in one but missing in the other). Consider extracting the shared validation/resize steps into a common helper to keep behavior consistent and reduce maintenance burden.

Also applies to: 143-210


205-210: Use raise ... from to preserve exception context.

Per Ruff B904, re-raising a new exception inside an except block without chaining loses the original traceback context, making debugging harder.

Proposed fix
     except Exception as pil_error:
         logger.error(f"PIL processing failed: {pil_error}")
         raise HTTPException(
             status_code=400,
             detail="Invalid image file."
-        )
+        ) from pil_error

Comment on lines +77 to +95
# Check MIME type from content using python-magic if available
if magic:
try:
# Read first 1024 bytes for MIME detection
file_content = file.file.read(1024)
file.file.seek(0) # Reset file pointer

detected_mime = magic.from_buffer(file_content, mime=True)

if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
except Exception as e:
logger.warning(f"Magic validation failed: {e}")
# Fallback to extension check or PIL check
else:
logger.warning("python-magic not available, skipping MIME type check")
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Bug: except Exception swallows the HTTPException raised for invalid MIME types.

On line 91, the broad except Exception catches the HTTPException raised on lines 87–90, logs it as a warning, and falls through — silently accepting files with disallowed MIME types. Compare with process_uploaded_image_sync (lines 171–172), which correctly re-raises HTTPException before catching generic exceptions.

Proposed fix
             if detected_mime not in ALLOWED_MIME_TYPES:
                 raise HTTPException(
                     status_code=400,
                     detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
                 )
+        except HTTPException:
+            raise
         except Exception as e:
             logger.warning(f"Magic validation failed: {e}")
             # Fallback to extension check or PIL check
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Check MIME type from content using python-magic if available
if magic:
try:
# Read first 1024 bytes for MIME detection
file_content = file.file.read(1024)
file.file.seek(0) # Reset file pointer
detected_mime = magic.from_buffer(file_content, mime=True)
if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
except Exception as e:
logger.warning(f"Magic validation failed: {e}")
# Fallback to extension check or PIL check
else:
logger.warning("python-magic not available, skipping MIME type check")
# Check MIME type from content using python-magic if available
if magic:
try:
# Read first 1024 bytes for MIME detection
file_content = file.file.read(1024)
file.file.seek(0) # Reset file pointer
detected_mime = magic.from_buffer(file_content, mime=True)
if detected_mime not in ALLOWED_MIME_TYPES:
raise HTTPException(
status_code=400,
detail=f"Invalid file type. Only image files are allowed. Detected: {detected_mime}"
)
except HTTPException:
raise
except Exception as e:
logger.warning(f"Magic validation failed: {e}")
# Fallback to extension check or PIL check
else:
logger.warning("python-magic not available, skipping MIME type check")
🧰 Tools
🪛 Ruff (0.15.0)

[warning] 87-90: Abstract raise to an inner function

(TRY301)


[warning] 91-91: Do not catch blind exception: Exception

(BLE001)

🤖 Prompt for AI Agents
In `@backend/utils.py` around lines 77 - 95, The broad except around the
python-magic MIME check swallows HTTPException raised for invalid MIME types;
update the except handling in the magic block so that if an HTTPException is
raised (the one constructed using HTTPException with detail=f"Invalid file
type..."), it is re-raised immediately, and only non-HTTPException errors are
caught and logged (i.e., add an explicit except HTTPException: raise before the
generic except Exception as e: logger.warning(...) fallback). Reference: the
magic MIME check (magic.from_buffer / detected_mime / ALLOWED_MIME_TYPES) and
the HTTPException being raised there; mirror the pattern used in
process_uploaded_image_sync where HTTPException is re-raised prior to catching
generic exceptions.

Comment on lines +113 to +125
# Save resized image back to file
output = io.BytesIO()
img.save(output, format=img.format or 'JPEG', quality=85)
output.seek(0)

return img
# Replace file content
file.file = output
file.size = output.tell()
output.seek(0)

except Exception as pil_error:
logger.error(f"PIL validation failed for {file.filename}: {pil_error}")
raise HTTPException(
status_code=400,
detail="Invalid image file. The file appears to be corrupted or not a valid image."
)
# Return the image object (resized or original)
# Ensure file pointer is at start for any subsequent reads from file.file
file.file.seek(0)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

file.size is always set to 0 after resizing.

On line 116, output.seek(0) moves the pointer to the start. Then on line 120, output.tell() returns 0. The subsequent output.seek(0) on line 121 is redundant. This means file.size will incorrectly report 0 for all resized images, which could break downstream logic that relies on the file size.

Proposed fix
             # Save resized image back to file
             output = io.BytesIO()
             img.save(output, format=img.format or 'JPEG', quality=85)
-            output.seek(0)
 
             # Replace file content
+            file.size = output.tell()
+            output.seek(0)
             file.file = output
-            file.size = output.tell()
-            output.seek(0)
🤖 Prompt for AI Agents
In `@backend/utils.py` around lines 113 - 125, The code sets file.size from
output.tell() after seeking back to 0, causing size to always be 0; instead
measure the buffer length before rewinding (or use output.getbuffer().nbytes)
and then reset the pointer. Specifically, after img.save(...) capture the byte
length (e.g., size = output.getbuffer().nbytes or by doing output.seek(0,
io.SEEK_END); size = output.tell()), then output.seek(0) and assign file.file =
output and file.size = size so file.size reflects the actual resized image
length (references: variables/members output, img.save, file.file, file.size).

file.file.seek(0)
# Save resized image back to file
output = io.BytesIO()
img.save(output, format=img.format or 'JPEG', quality=85)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

RGBA images saved as JPEG will raise an error.

If the original image is RGBA (e.g., a PNG with transparency) and img.format is None (which can happen after certain PIL operations), the fallback 'JPEG' will cause img.save to fail because JPEG does not support an alpha channel. The process_uploaded_image_sync function (lines 195–198) correctly handles this case.

Proposed fix
-            img.save(output, format=img.format or 'JPEG', quality=85)
+            fmt = img.format or ('PNG' if img.mode == 'RGBA' else 'JPEG')
+            img.save(output, format=fmt, quality=85)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
img.save(output, format=img.format or 'JPEG', quality=85)
fmt = img.format or ('PNG' if img.mode == 'RGBA' else 'JPEG')
img.save(output, format=fmt, quality=85)
🤖 Prompt for AI Agents
In `@backend/utils.py` at line 115, The save call using img.save(output,
format=img.format or 'JPEG', quality=85) can fail for RGBA images because JPEG
doesn't support alpha; update the save logic in the same function to detect
alpha modes (e.g., img.mode in ('RGBA','LA') or images with transparency) and
either convert the image to RGB (img = img.convert('RGB')) before saving when
falling back to 'JPEG', or choose a lossless format like 'PNG' when alpha is
present; apply this change around the img.save call so it mirrors the safe
handling already implemented in process_uploaded_image_sync.

- Updated `backend/garbage_detection.py` to gracefully handle `ImportError` when `ultralytics` is missing, similar to `pothole_detection.py`.
- Verified `cv2` is not directly imported in the backend.
- Confirmed `backend/requirements-render.txt` is minimal to fit within slug size limits, while code handles optional dependencies.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
@github-actions
Copy link

🔍 Quality Reminder

Thanks for the updates! Please ensure:
- Your changes don't break existing functionality
- All tests still pass
- Code quality standards are maintained

*The maintainers will verify that the overall project flow remains intact.*

- Created `frontend/public/_redirects` to fix SPA routing on Netlify.
- Created `frontend/public/_headers` for security headers.
- Simplified `netlify.toml` to point to `frontend/dist`.
- Updated `backend/garbage_detection.py` and confirmed `backend/spatial_utils.py` handle missing optional dependencies (like `ultralytics`, `sklearn`) gracefully for Render deployment.

Co-authored-by: RohanExploit <178623867+RohanExploit@users.noreply.github.com>
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="frontend/public/_headers">

<violation number="1" location="frontend/public/_headers:4">
P2: `X-XSS-Protection: 1; mode=block` is deprecated and can **introduce** XSS vulnerabilities in older browsers that still have the buggy XSS auditor (per MDN and OWASP). The current OWASP recommendation is to either explicitly disable it with `0` or omit it entirely and rely on a `Content-Security-Policy` header instead.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

/*
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
X-XSS-Protection: 1; mode=block
Copy link

@cubic-dev-ai cubic-dev-ai bot Feb 14, 2026

Choose a reason for hiding this comment

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

P2: X-XSS-Protection: 1; mode=block is deprecated and can introduce XSS vulnerabilities in older browsers that still have the buggy XSS auditor (per MDN and OWASP). The current OWASP recommendation is to either explicitly disable it with 0 or omit it entirely and rely on a Content-Security-Policy header instead.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At frontend/public/_headers, line 4:

<comment>`X-XSS-Protection: 1; mode=block` is deprecated and can **introduce** XSS vulnerabilities in older browsers that still have the buggy XSS auditor (per MDN and OWASP). The current OWASP recommendation is to either explicitly disable it with `0` or omit it entirely and rely on a `Content-Security-Policy` header instead.</comment>

<file context>
@@ -0,0 +1,5 @@
+/*
+  X-Frame-Options: DENY
+  X-Content-Type-Options: nosniff
+  X-XSS-Protection: 1; mode=block
+  Referrer-Policy: strict-origin-when-cross-origin
</file context>
Fix with Cubic

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments