Add new civic grievance detectors and optimize deployment#387
Add new civic grievance detectors and optimize deployment#387RohanExploit wants to merge 4 commits intomainfrom
Conversation
- 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>
|
👋 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 New to Jules? Learn more at jules.google/docs. For security, I will only act on instructions from the user who triggered this task. |
❌ Deploy Preview for fixmybharat failed. Why did it fail? →
|
🙏 Thank you for your contribution, @RohanExploit!PR Details:
Quality Checklist:
Review Process:
Note: The maintainers will monitor code quality and ensure the overall project flow isn't broken. |
📝 WalkthroughWalkthroughAdds 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
No actionable comments were generated in the recent review. 🎉 🧹 Recent nitpick comments
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. Comment |
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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>
| 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] |
| }, 'image/jpeg', 0.8); | ||
| }; | ||
|
|
||
| useEffect(() => { |
There was a problem hiding this comment.
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>
|
|
||
| <Route | ||
| path="/" | ||
| path="/home" |
There was a problem hiding this comment.
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>
| } 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); |
There was a problem hiding this comment.
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>
| @@ -35,7 +35,9 @@ | |||
| detect_civic_eye_clip, | |||
There was a problem hiding this comment.
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>
| 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: |
There was a problem hiding this comment.
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>
There was a problem hiding this comment.
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 — alogging.warningwould 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 loggingcan be moved to the top of the file instead.)frontend/src/components/GenericDetector.jsx (4)
136-156: Stale closure:detectFramecaptured bysetIntervalwon't see updated state.
detectFrameis re-created every render butsetIntervalon line 140 captures only the version from the render whenisDetectingbecametrue. If any state or prop (other thanisDetecting/apiEndpoint) thatdetectFramedepends on changes mid-detection, the interval will use stale values. More importantly, ifdrawDetectionscallssetLastDetectionsbut the closed-over context is stale, you may get subtle bugs.A more robust pattern is to store
detectFramein 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 inforEachto 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: DuplicatesetLastDetectionscall.
drawDetectionsalready callssetLastDetections(detections)on line 71 for the no-box path. Then line 128 unconditionally callssetLastDetections(detections)again. For the box path,drawDetectionsdraws 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 unconditionalsetLastDetectionsout ofdrawDetectionsentirely 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 rawawait image.read()instead ofprocess_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_imagefor 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 viat(). This matches some existing items (e.g.,"Noise","Crowd") but diverges from the majority that uset('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: thevalidViewsarray is getting unwieldy.Consider extracting this to a constant or deriving it from route definitions to keep it in sync and improve readability.
|
|
||
| > 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) |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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).
| 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) |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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.
| // 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. | ||
|
|
There was a problem hiding this comment.
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.
| // 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. |
| # Fallback if scikit-learn is not installed | ||
| return [[issue] for issue in valid_issues] |
There was a problem hiding this comment.
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.
| # 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 |
| 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) |
There was a problem hiding this comment.
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.
| 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()) |
| @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") |
There was a problem hiding this comment.
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.
| 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}`); |
There was a problem hiding this comment.
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').
| navigate(view === 'home' ? '/home' : `/${view}`); | |
| navigate(view === 'home' ? '/' : `/${view}`); |
| @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") |
There was a problem hiding this comment.
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.
| 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) |
There was a problem hiding this comment.
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.
| @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") |
There was a problem hiding this comment.
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.
| <Route | ||
| path="/" | ||
| path="/home" |
There was a problem hiding this comment.
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.
| print("SUCCESS: Routes found in code.") | ||
| sys.exit(0) | ||
| else: | ||
| print("FAILED: Routes not found in code.") | ||
| sys.exit(1) |
There was a problem hiding this comment.
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).
| 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) |
…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>
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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>
| except Exception as e: | |
| except HTTPException: | |
| raise | |
| except Exception as e: |
There was a problem hiding this comment.
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_syncandprocess_uploaded_image_sync.Both functions duplicate file-size checking, MIME validation, PIL open, and resize logic. This already led to an inconsistency (the
HTTPExceptionre-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: Useraise ... fromto preserve exception context.Per Ruff B904, re-raising a new exception inside an
exceptblock 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
| # 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") |
There was a problem hiding this comment.
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.
| # 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.
| # 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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
| 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>
🔍 Quality Reminder |
- 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>
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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>
GenericDetectorcomponent for reusable camera/API logic.hf_api_service.pyto support new detection types via CLIP.backend/routers/detection.py.Home.jsxwith navigation buttons for new features.scikit-learnfrombackend/requirements-render.txtand adding a fallback inspatial_utils.pyfor DBSCAN.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
Dependencies
Written for commit d67beb4. Summary will update on new commits.
Summary by CodeRabbit
New Features
Improvements
Chores