Files
citypulse/backend/app/routes/report.py
2025-09-27 14:19:37 +08:00

168 lines
5.9 KiB
Python

from typing import Optional
from fastapi import APIRouter, UploadFile, File, Form, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session
from pathlib import Path
import logging, uuid
from app.database import get_db
from app.services.ticket_service import TicketService, SeverityLevel
from app.models.ticket_model import User
from app.services.global_ai import get_ai_service
from app.utils import make_image_url, normalize_image_path_for_url
router = APIRouter()
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
UPLOAD_DIR = Path("static") / "uploads"
UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
# ----------------------
# API 1: Analyze image (no DB write)
# ----------------------
@router.post("/analyze")
async def analyze_image(
image: UploadFile = File(...),
request: Request = None
):
logger.debug("Received analyze request")
# Validate file extension and type
allowed_extensions = {'.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'}
allowed_content_types = {
'image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/webp',
'application/octet-stream'
}
file_ext = Path(image.filename).suffix.lower()
if file_ext not in allowed_extensions:
raise HTTPException(status_code=400, detail="Only image files are allowed")
if image.content_type not in allowed_content_types:
raise HTTPException(status_code=400, detail="Invalid file type")
# Save file
filename = f"{uuid.uuid4()}{file_ext}"
file_path_obj = UPLOAD_DIR / filename
try:
content = await image.read()
file_path_obj.write_bytes(content)
logger.debug(f"Saved image for analysis: {file_path_obj}")
except Exception:
logger.exception("Failed to save image for analysis")
raise HTTPException(status_code=500, detail="Failed to save uploaded image")
# Run AI
ai_service = get_ai_service()
try:
category = ai_service.classify_category(str(file_path_obj))
logger.debug(f"Classification result: {category}")
severity = SeverityLevel.NA
annotated_path = None
if category.lower() == "pothole":
severity_str, annotated_path = ai_service.detect_pothole_severity(str(file_path_obj))
severity = {
"High": SeverityLevel.HIGH,
"Medium": SeverityLevel.MEDIUM,
"Low": SeverityLevel.LOW,
"Unknown": SeverityLevel.NA
}.get(severity_str, SeverityLevel.NA)
logger.debug(f"Severity detection: {severity_str}")
except Exception:
logger.exception("AI analysis failed")
category = "Unknown"
severity = SeverityLevel.NA
rel_path = normalize_image_path_for_url(file_path_obj.as_posix())
image_url = make_image_url(rel_path, request)
response = {
"temp_id": str(uuid.uuid4()),
"filename": filename,
"image_path": rel_path,
"image_url": image_url,
"category": category,
"severity": severity.value
}
logger.debug(f"Analyze response: {response}")
return JSONResponse(status_code=200, content=response)
# ----------------------
# API 2: Submit report (with analyzed file + DB write)
# ----------------------
@router.post("/report")
async def report_issue(
user_id: Optional[str] = Form(None),
user_name: Optional[str] = Form(None),
latitude: float = Form(...),
longitude: float = Form(...),
address: Optional[str] = Form(None),
description: str = Form(""),
analyzed_file: str = Form(...), # filename returned from /analyze
category: str = Form(...),
severity: str = Form(...),
db: Session = Depends(get_db),
request: Request = None
):
logger.debug("Received report submission request")
ticket_service = TicketService(db)
# Ensure user
user = None
if user_id:
user = ticket_service.get_user(user_id)
if not user:
guest_email = f"guest-{uuid.uuid4()}@example.local"
guest_name = user_name or f"Guest-{str(uuid.uuid4())[:8]}"
try:
user = ticket_service.create_user(name=guest_name, email=guest_email)
logger.info(f"Created guest user: {user}")
except Exception:
logger.exception("Failed to create guest user")
raise HTTPException(status_code=500, detail="Failed to ensure user")
# Verify analyzed file exists
file_path_obj = UPLOAD_DIR / analyzed_file
if not file_path_obj.exists():
logger.error(f"Analyzed file not found: {analyzed_file}")
raise HTTPException(status_code=400, detail="Analyzed file not found")
# Save ticket
severity_enum = SeverityLevel.__members__.get(severity.upper(), SeverityLevel.NA)
try:
ticket = ticket_service.create_ticket(
user_id=user.id,
image_path=file_path_obj.as_posix(),
category=category,
severity=severity_enum,
latitude=latitude,
longitude=longitude,
description=description,
address=address
)
logger.info(f"Ticket created: {ticket.id} for user {user.id}")
except Exception:
logger.exception("Failed to create ticket")
raise HTTPException(status_code=500, detail="Failed to create ticket")
rel_path = normalize_image_path_for_url(ticket.image_path)
image_url = make_image_url(rel_path, request)
response = {
"ticket_id": ticket.id,
"user_id": user.id,
"user_name": user.name,
"user_email": user.email,
"category": ticket.category,
"severity": ticket.severity.value,
"status": ticket.status.value,
"description": ticket.description,
"image_path": rel_path,
"image_url": image_url,
"address": ticket.address
}
logger.debug(f"Report response: {response}")
return JSONResponse(status_code=201, content=response)