TheDeepDas commited on
Commit
ac86e10
·
1 Parent(s): 8430709

Validator complete

Browse files
app/__pycache__/main.cpython-39.pyc CHANGED
Binary files a/app/__pycache__/main.cpython-39.pyc and b/app/__pycache__/main.cpython-39.pyc differ
 
app/__pycache__/schemas.cpython-311.pyc CHANGED
Binary files a/app/__pycache__/schemas.cpython-311.pyc and b/app/__pycache__/schemas.cpython-311.pyc differ
 
app/routers/__pycache__/incidents.cpython-311.pyc CHANGED
Binary files a/app/routers/__pycache__/incidents.cpython-311.pyc and b/app/routers/__pycache__/incidents.cpython-311.pyc differ
 
app/routers/incidents.py CHANGED
@@ -1,12 +1,16 @@
1
  from datetime import datetime
2
  from typing import List
3
 
4
- from fastapi import APIRouter, Depends, File, Form, HTTPException, status, UploadFile
 
5
  from bson import ObjectId
6
 
7
  from ..dependencies import get_current_user
8
  from ..schemas import IncidentResponse, IncidentList
9
- from ..services.incidents import save_incident_document, store_image, get_user_incidents, get_all_incidents
 
 
 
10
  from ..services.nlp import classify_incident, get_model_info
11
  from ..database import is_database_available
12
 
@@ -122,6 +126,54 @@ async def list_incidents(current_user=Depends(get_current_user)):
122
  detail=f"Failed to list incidents: {str(e)}"
123
  )
124
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  @router.post("/test-classify")
126
  async def test_classification(
127
  description: str = Form(...),
 
1
  from datetime import datetime
2
  from typing import List
3
 
4
+ from fastapi import APIRouter, Depends, File, Form, HTTPException, status, UploadFile, Body
5
+ from pydantic import BaseModel
6
  from bson import ObjectId
7
 
8
  from ..dependencies import get_current_user
9
  from ..schemas import IncidentResponse, IncidentList
10
+ from ..services.incidents import (
11
+ save_incident_document, store_image, get_user_incidents,
12
+ get_all_incidents, update_incident_status
13
+ )
14
  from ..services.nlp import classify_incident, get_model_info
15
  from ..database import is_database_available
16
 
 
126
  detail=f"Failed to list incidents: {str(e)}"
127
  )
128
 
129
+ @router.post("/update-status/{incident_id}")
130
+ async def update_status(
131
+ incident_id: str,
132
+ status: str = Body(..., embed=True),
133
+ current_user=Depends(get_current_user),
134
+ ):
135
+ """Update the status of an incident (validated, rejected, investigating)"""
136
+ if not is_database_available():
137
+ raise HTTPException(
138
+ status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
139
+ detail="Database service is currently unavailable. Please try again later."
140
+ )
141
+
142
+ # Check if the user is a validator or higher role
143
+ if current_user.get("role") not in ["validator", "researcher", "policy_maker"]:
144
+ raise HTTPException(
145
+ status_code=status.HTTP_403_FORBIDDEN,
146
+ detail="Only validators can update incident status"
147
+ )
148
+
149
+ # Validate the status value
150
+ if status not in ["validated", "rejected", "investigating"]:
151
+ raise HTTPException(
152
+ status_code=status.HTTP_400_BAD_REQUEST,
153
+ detail="Invalid status. Must be one of: validated, rejected, investigating"
154
+ )
155
+
156
+ try:
157
+ success = await update_incident_status(
158
+ incident_id=incident_id,
159
+ status=status,
160
+ validator_id=current_user["id"]
161
+ )
162
+
163
+ if success:
164
+ return {"message": f"Incident status updated to {status}"}
165
+ else:
166
+ raise HTTPException(
167
+ status_code=status.HTTP_404_NOT_FOUND,
168
+ detail="Incident not found or could not be updated"
169
+ )
170
+ except Exception as e:
171
+ raise HTTPException(
172
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
173
+ detail=f"Failed to update incident status: {str(e)}"
174
+ )
175
+
176
+
177
  @router.post("/test-classify")
178
  async def test_classification(
179
  description: str = Form(...),
app/schemas.py CHANGED
@@ -47,6 +47,9 @@ class IncidentInDB(BaseModel):
47
  created_at: datetime
48
  reporter_id: Optional[str]
49
  image_path: Optional[str]
 
 
 
50
 
51
 
52
  class IncidentResponse(BaseModel):
 
47
  created_at: datetime
48
  reporter_id: Optional[str]
49
  image_path: Optional[str]
50
+ status: Optional[str] = None
51
+ validated_by: Optional[str] = None
52
+ validated_at: Optional[datetime] = None
53
 
54
 
55
  class IncidentResponse(BaseModel):
app/services/incidents.py CHANGED
@@ -6,12 +6,11 @@ import cloudinary
6
  import cloudinary.uploader
7
  import tempfile
8
  import os
9
-
10
  from ..database import get_collection
11
  from ..config import get_settings
12
 
13
  logger = logging.getLogger(__name__)
14
-
15
  INCIDENTS_COLLECTION = "incidents"
16
 
17
 
@@ -36,6 +35,41 @@ async def get_all_incidents() -> list:
36
  return await cursor.to_list(length=None)
37
 
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  async def store_image(upload_file) -> Optional[str]:
40
  """
41
  Store an uploaded image using Cloudinary only.
 
6
  import cloudinary.uploader
7
  import tempfile
8
  import os
9
+ from datetime import datetime
10
  from ..database import get_collection
11
  from ..config import get_settings
12
 
13
  logger = logging.getLogger(__name__)
 
14
  INCIDENTS_COLLECTION = "incidents"
15
 
16
 
 
35
  return await cursor.to_list(length=None)
36
 
37
 
38
+ async def update_incident_status(incident_id: str, status: str, validator_id: str) -> bool:
39
+ """
40
+ Update the status of an incident
41
+
42
+ Args:
43
+ incident_id: The ID of the incident to update
44
+ status: The new status (validated, rejected, investigating)
45
+ validator_id: The ID of the validator who updated the status
46
+
47
+ Returns:
48
+ True if the update was successful, False otherwise
49
+ """
50
+ try:
51
+ collection = get_collection(INCIDENTS_COLLECTION)
52
+
53
+ # Convert string ID to ObjectId
54
+ from bson import ObjectId
55
+ object_id = ObjectId(incident_id)
56
+
57
+ # Update the incident with the new status and validator information
58
+ result = await collection.update_one(
59
+ {"_id": object_id},
60
+ {"$set": {
61
+ "status": status,
62
+ "validated_by": validator_id,
63
+ "validated_at": datetime.utcnow()
64
+ }}
65
+ )
66
+
67
+ return result.modified_count > 0
68
+ except Exception as e:
69
+ logger.error(f"Failed to update incident status: {e}")
70
+ return False
71
+
72
+
73
  async def store_image(upload_file) -> Optional[str]:
74
  """
75
  Store an uploaded image using Cloudinary only.
generate_test_incidents.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import requests
3
+ import json
4
+ from pathlib import Path
5
+ import random
6
+ import time
7
+ from dotenv import load_dotenv
8
+
9
+ # Load environment variables
10
+ load_dotenv()
11
+
12
+ # API endpoint
13
+ API_BASE_URL = os.getenv("API_BASE_URL", "http://localhost:8000/api")
14
+
15
+ # Test user credentials
16
+ EMAIL = "test@example.com"
17
+ PASSWORD = "password123" # Change to your test user password
18
+ TOKEN = None
19
+
20
+ # Sample incidents data
21
+ SAMPLE_INCIDENTS = [
22
+ {
23
+ "name": "Large plastic debris near coastal area",
24
+ "description": "Found a significant amount of plastic waste including bottles, bags, and packaging materials washing up on the northern section of the beach. Appears to have accumulated over several days.",
25
+ "latitude": 34.0522,
26
+ "longitude": -118.2437,
27
+ "image_filename": None, # No image for this one
28
+ },
29
+ {
30
+ "name": "Oil spill from fishing vessel",
31
+ "description": "Observed a small to medium oil slick extending approximately 100m from a commercial fishing vessel. The oil appears to be engine oil with rainbow sheen visible on the water surface.",
32
+ "latitude": 37.7749,
33
+ "longitude": -122.4194,
34
+ "image_filename": "test_files/cloudinary_test_7574.jpg",
35
+ },
36
+ {
37
+ "name": "Chemical discharge near industrial area",
38
+ "description": "Unusual discoloration and strong chemical odor in the water near the industrial park outflow. Several dead fish observed floating nearby. Possible unauthorized chemical discharge.",
39
+ "latitude": 40.7128,
40
+ "longitude": -74.0060,
41
+ "image_filename": None,
42
+ },
43
+ {
44
+ "name": "Fishing nets and gear debris",
45
+ "description": "Large tangled mass of abandoned fishing nets, lines, and traps washed up on the shoreline. Poses danger to marine life and should be removed immediately.",
46
+ "latitude": 32.7157,
47
+ "longitude": -117.1611,
48
+ "image_filename": None,
49
+ },
50
+ {
51
+ "name": "Microplastic concentration",
52
+ "description": "High concentration of microplastic particles observed in the sand along the shoreline. The plastic particles are small, multicolored, and appear to be widespread across the beach.",
53
+ "latitude": 21.3069,
54
+ "longitude": -157.8583,
55
+ "image_filename": None,
56
+ },
57
+ ]
58
+
59
+ def login():
60
+ """Login and get authentication token"""
61
+ try:
62
+ response = requests.post(
63
+ f"{API_BASE_URL}/auth/login",
64
+ json={"email": EMAIL, "password": PASSWORD},
65
+ )
66
+ response.raise_for_status()
67
+ return response.json()["access_token"]
68
+ except requests.RequestException as e:
69
+ print(f"Login failed: {str(e)}")
70
+ if hasattr(response, 'text'):
71
+ print(f"Response: {response.text}")
72
+ return None
73
+
74
+ def create_incident(incident_data, token):
75
+ """Create a test incident"""
76
+ try:
77
+ # Create form data
78
+ form_data = {
79
+ "name": incident_data["name"],
80
+ "description": incident_data["description"],
81
+ "latitude": str(incident_data["latitude"]),
82
+ "longitude": str(incident_data["longitude"]),
83
+ }
84
+
85
+ files = {}
86
+
87
+ # Add image if available
88
+ if incident_data["image_filename"] and os.path.exists(incident_data["image_filename"]):
89
+ files = {
90
+ "image": (
91
+ os.path.basename(incident_data["image_filename"]),
92
+ open(incident_data["image_filename"], "rb"),
93
+ "image/jpeg"
94
+ )
95
+ }
96
+
97
+ # Send request
98
+ response = requests.post(
99
+ f"{API_BASE_URL}/incidents/classify",
100
+ data=form_data,
101
+ files=files,
102
+ headers={"Authorization": f"Bearer {token}"}
103
+ )
104
+ response.raise_for_status()
105
+
106
+ # Close the file if it was opened
107
+ if "image" in files:
108
+ files["image"][1].close()
109
+
110
+ return response.json()
111
+ except requests.RequestException as e:
112
+ print(f"Failed to create incident: {str(e)}")
113
+ if hasattr(response, 'text'):
114
+ print(f"Response: {response.text}")
115
+ return None
116
+
117
+ def generate_test_incidents():
118
+ """Generate several test incidents"""
119
+ # Login to get token
120
+ token = login()
121
+ if not token:
122
+ print("Authentication failed, cannot create test incidents")
123
+ return False
124
+
125
+ print(f"Successfully logged in as {EMAIL}")
126
+
127
+ # Create each sample incident
128
+ for i, incident in enumerate(SAMPLE_INCIDENTS, 1):
129
+ print(f"Creating incident {i}/{len(SAMPLE_INCIDENTS)}: {incident['name']}")
130
+ result = create_incident(incident, token)
131
+
132
+ if result:
133
+ print(f"✅ Created incident: {result.get('incident_id')} - {result.get('incident_class')} (Severity: {result.get('severity')})")
134
+ else:
135
+ print("❌ Failed to create incident")
136
+
137
+ # Add a delay to prevent rate limiting
138
+ time.sleep(1)
139
+
140
+ print("\nAll test incidents created successfully!")
141
+ return True
142
+
143
+ if __name__ == "__main__":
144
+ print("=== Marine Guard Test Incident Generator ===")
145
+ generate_test_incidents()