NeighbourAid / app /models /alert.py
Parth Kansal
commit
49e9f9d
from datetime import datetime
from enum import Enum
from typing import List, Optional
from pydantic import BaseModel, Field, field_validator
class AlertCategory(str, Enum):
medical = "medical"
flood = "flood"
fire = "fire"
missing = "missing"
power = "power"
other = "other"
class UrgencyLevel(str, Enum):
CRITICAL = "CRITICAL"
HIGH = "HIGH"
MEDIUM = "MEDIUM"
LOW = "LOW"
class AlertStatus(str, Enum):
open = "open"
accepted = "accepted"
resolved = "resolved"
class VolunteerSkill(str, Enum):
medical = "medical"
cpr = "cpr"
swim = "swim"
driver = "driver"
electrician = "electrician"
translator = "translator"
elderly_care = "elderly_care"
child_care = "child_care"
class GeoPoint(BaseModel):
type: str = Field(default="Point")
coordinates: List[float] = Field(min_length=2, max_length=2)
@field_validator("coordinates")
@classmethod
def validate_lng_lat(cls, v: list[float]) -> list[float]:
lng, lat = v
if not (-180 <= lng <= 180):
raise ValueError("longitude must be between -180 and 180")
if not (-90 <= lat <= 90):
raise ValueError("latitude must be between -90 and 90")
return v
# Photos are stored inline as data URLs. We intentionally cap byte size so
# one user can't blow up the document quota. The frontend compresses +
# resizes before upload, so 300 KB per photo × 3 photos is plenty.
_MAX_PHOTO_BYTES = 300_000
_MAX_PHOTOS = 3
def _validate_photos(v: list[str]) -> list[str]:
if len(v) > _MAX_PHOTOS:
raise ValueError(f"max {_MAX_PHOTOS} photos per alert")
for i, p in enumerate(v):
if not isinstance(p, str):
raise ValueError(f"photo {i} must be a string")
if not p.startswith("data:image/"):
raise ValueError(f"photo {i} must be a data:image/... URL")
if len(p) > _MAX_PHOTO_BYTES:
raise ValueError(
f"photo {i} too large ({len(p)} bytes) — max {_MAX_PHOTO_BYTES}"
)
return v
class AlertCreate(BaseModel):
category: AlertCategory
description: str = Field(min_length=10, max_length=2000)
location: GeoPoint
photos: List[str] = Field(default_factory=list)
@field_validator("description")
@classmethod
def strip_description(cls, v: str) -> str:
stripped = v.strip()
if len(stripped) < 10:
raise ValueError("description must be at least 10 non-space characters")
return stripped
@field_validator("photos")
@classmethod
def validate_photos(cls, v: list[str]) -> list[str]:
return _validate_photos(v)
class ETAUpdate(BaseModel):
"""Volunteer-provided estimated time of arrival, in minutes."""
eta_minutes: int = Field(ge=0, le=240)
class AlertOut(BaseModel):
id: str
reporter_id: str
category: AlertCategory
description: str
urgency: UrgencyLevel
urgency_reason: str
location: GeoPoint
status: AlertStatus
accepted_by: Optional[str]
photos: List[str] = Field(default_factory=list)
photo_count: int = 0
eta_minutes: Optional[int] = None
eta_set_at: Optional[datetime] = None
flags: int = 0
created_at: datetime
resolved_at: Optional[datetime]