UrbanLens / Backend /core /schemas.py
0xarchit's picture
Initial Hugging Face Space - Backend deployment
42d88ae
from datetime import datetime
from enum import IntEnum, StrEnum
from typing import Optional
from uuid import UUID, uuid4
from pydantic import BaseModel, Field, field_validator
class IssueState(StrEnum):
REPORTED = "reported"
PENDING_CONFIRMATION = "pending_confirmation"
VALIDATED = "validated"
ASSIGNED = "assigned"
IN_PROGRESS = "in_progress"
PENDING_VERIFICATION = "pending_verification"
RESOLVED = "resolved"
VERIFIED = "verified"
CLOSED = "closed"
ESCALATED = "escalated"
REJECTED = "rejected"
class PriorityLevel(IntEnum):
CRITICAL = 1
HIGH = 2
MEDIUM = 3
LOW = 4
class IssueCategory(StrEnum):
DAMAGED_ROAD = "Damaged Road Issues"
POTHOLE = "Pothole Issues"
ILLEGAL_PARKING = "Illegal Parking Issues"
BROKEN_SIGN = "Broken Road Sign Issues"
FALLEN_TREE = "Fallen Trees"
GARBAGE = "Littering/Garbage on Public Places"
VANDALISM = "Vandalism Issues"
DEAD_ANIMAL = "Dead Animal Pollution"
DAMAGED_CONCRETE = "Damaged Concrete Structures"
DAMAGED_ELECTRIC = "Damaged Electric Wires and Poles"
CLASS_ID_TO_CATEGORY = {
0: IssueCategory.DAMAGED_ROAD,
1: IssueCategory.POTHOLE,
2: IssueCategory.ILLEGAL_PARKING,
3: IssueCategory.BROKEN_SIGN,
4: IssueCategory.FALLEN_TREE,
5: IssueCategory.GARBAGE,
6: IssueCategory.VANDALISM,
7: IssueCategory.DEAD_ANIMAL,
8: IssueCategory.DAMAGED_CONCRETE,
9: IssueCategory.DAMAGED_ELECTRIC,
}
class Coordinates(BaseModel):
latitude: float = Field(..., ge=-90, le=90)
longitude: float = Field(..., ge=-180, le=180)
accuracy_meters: Optional[float] = Field(None, ge=0)
class DeviceMetadata(BaseModel):
platform: str = Field(..., max_length=50)
device_model: Optional[str] = Field(None, max_length=100)
os_version: Optional[str] = Field(None, max_length=50)
app_version: Optional[str] = Field(None, max_length=20)
class IssuePacket(BaseModel):
description: Optional[str] = Field(None, max_length=2000)
coordinates: Coordinates
device_metadata: DeviceMetadata
timestamp: datetime = Field(default_factory=datetime.utcnow)
@field_validator("description")
@classmethod
def clean_description(cls, v: Optional[str]) -> Optional[str]:
if v:
return v.strip()
return v
class DetectionBox(BaseModel):
class_id: int
class_name: str
confidence: float = Field(..., ge=0, le=1)
bbox: tuple[float, float, float, float]
class ClassificationResult(BaseModel):
issue_id: UUID
detections: list[DetectionBox]
primary_category: Optional[IssueCategory] = None
primary_confidence: float = 0.0
annotated_urls: list[str] = []
inference_time_ms: float
model_version: str = "1.0"
def model_post_init(self, __context) -> None:
if self.detections and not self.primary_category:
best = max(self.detections, key=lambda d: d.confidence)
self.primary_category = CLASS_ID_TO_CATEGORY.get(best.class_id)
self.primary_confidence = best.confidence
class IssueCreate(BaseModel):
description: Optional[str] = Field(None, max_length=2000)
latitude: float = Field(..., ge=-90, le=90)
longitude: float = Field(..., ge=-180, le=180)
accuracy_meters: Optional[float] = Field(None, ge=0)
platform: str = Field(..., max_length=50)
device_model: Optional[str] = Field(None, max_length=100)
@field_validator("description")
@classmethod
def clean_description(cls, v: Optional[str]) -> Optional[str]:
if v is None:
return None
cleaned = v.strip()
return cleaned or None
class AgentOutput(BaseModel):
agent: str
decision: str
reasoning: Optional[str] = None
duration_ms: Optional[float] = None
class IssueResponse(BaseModel):
id: UUID
description: Optional[str]
latitude: float
longitude: float
state: IssueState
priority: Optional[PriorityLevel]
priority_reason: Optional[str] = None
category: Optional[str]
confidence: Optional[float]
detections_count: Optional[int] = None
image_urls: list[str]
annotated_urls: list[str] = []
proof_image_url: Optional[str] = None
validation_source: Optional[str] = None
is_duplicate: bool = False
parent_issue_id: Optional[UUID] = None
nearby_count: Optional[int] = None
city: Optional[str] = None
locality: Optional[str] = None
full_address: Optional[str] = None
geo_status: Optional[str] = None
department: Optional[str] = None
assigned_member: Optional[str] = None
sla_hours: Optional[int] = None
sla_deadline: Optional[datetime] = None
agent_flow: list[AgentOutput] = []
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
class IssueListResponse(BaseModel):
items: list[IssueResponse]
total: int
page: int
page_size: int