FairRelay / brain /app /schemas /driver_api.py
MouleeswaranM's picture
Upload folder using huggingface_hub
fcf8749 verified
raw
history blame
7.64 kB
"""
Pydantic schemas for Phase 2 Driver-facing API endpoints.
"""
import datetime
from typing import List, Optional
from uuid import UUID
from pydantic import BaseModel, Field
# ==================== Today's Assignment Schemas ====================
class PackageDetail(BaseModel):
"""Package details for a stop."""
id: UUID
external_id: str
weight_kg: float
fragility_level: int
priority: str
class StopDetail(BaseModel):
"""Details of a delivery stop."""
stop_order: int
address: str
latitude: float
longitude: float
packages: List[PackageDetail]
building_type: Optional[str] = None
floor_number: Optional[int] = None
stairs_likelihood: Optional[float] = None
class RouteSummaryDetail(BaseModel):
"""Summary of route characteristics."""
num_packages: int
total_weight_kg: float
num_stops: int
route_difficulty_score: float
estimated_time_minutes: int
class AssignmentDetail(BaseModel):
"""Full assignment details with stops."""
assignment_id: UUID
route_id: UUID
workload_score: float
fairness_score: float
explanation: str
route_summary: RouteSummaryDetail
stops: List[StopDetail]
class DriverDetail(BaseModel):
"""Driver info in assignment response."""
id: UUID
external_id: Optional[str]
name: str
preferred_language: str
class TodayAssignmentResponse(BaseModel):
"""Response for GET /api/v1/assignments/today."""
assignment_date: datetime.date
driver: DriverDetail
assignment: AssignmentDetail
# ==================== Driver Stats Schemas ====================
class DayStats(BaseModel):
"""Stats for a single day."""
stats_date: datetime.date
workload_score: float
fairness_score: float
reported_stress_level: Optional[float] = None
reported_fairness_rating: Optional[int] = None
class StatsAggregates(BaseModel):
"""Aggregated stats over window."""
avg_workload: float
avg_fairness_score: float
avg_stress_level: Optional[float] = None
class DriverStatsWindowResponse(BaseModel):
"""Response for GET /api/v1/drivers/{id}/stats."""
driver_id: UUID
window_days: int
days: List[DayStats]
aggregates: StatsAggregates
# ==================== Delivery Log Schemas ====================
class DeliveryLogRequest(BaseModel):
"""Request for POST /api/v1/deliveries/log."""
assignment_id: UUID
route_id: UUID
driver_id: UUID
stop_order: int = Field(..., ge=1)
package_id: Optional[UUID] = None
status: str = Field(..., description="DELIVERED, FAILED, or PARTIAL")
issue_type: str = Field(default="NONE", description="Issue type if any")
photo_url: Optional[str] = None
signature_data: Optional[str] = None
notes: Optional[str] = None
model_config = {
"json_schema_extra": {
"example": {
"assignment_id": "550e8400-e29b-41d4-a716-446655440001",
"route_id": "550e8400-e29b-41d4-a716-446655440002",
"driver_id": "550e8400-e29b-41d4-a716-446655440003",
"stop_order": 1,
"package_id": "550e8400-e29b-41d4-a716-446655440004",
"status": "DELIVERED",
"issue_type": "NONE",
"notes": "Left with security"
}
}
}
class DeliveryLogResponse(BaseModel):
"""Response for POST /api/v1/deliveries/log."""
id: UUID
assignment_id: UUID
route_id: UUID
driver_id: UUID
stop_order: int
package_id: Optional[UUID]
status: str
issue_type: str
photo_url: Optional[str]
signature_data: Optional[str]
notes: Optional[str]
timestamp: datetime.datetime
# ==================== Extended Feedback Schemas ====================
class ExtendedFeedbackRequest(BaseModel):
"""Extended feedback request for Phase 2."""
driver_id: UUID
assignment_id: UUID
fairness_rating: int = Field(..., ge=1, le=5)
stress_level: int = Field(..., ge=1, le=10)
tiredness_level: int = Field(..., ge=1, le=5)
hardest_aspect: Optional[str] = None
route_difficulty_self_report: Optional[int] = Field(None, ge=1, le=5)
would_take_similar_route_again: Optional[bool] = None
most_unfair_aspect: Optional[str] = None
comments: Optional[str] = Field(None, max_length=1000)
model_config = {
"json_schema_extra": {
"example": {
"driver_id": "550e8400-e29b-41d4-a716-446655440001",
"assignment_id": "550e8400-e29b-41d4-a716-446655440002",
"fairness_rating": 4,
"stress_level": 5,
"tiredness_level": 3,
"hardest_aspect": "stairs",
"route_difficulty_self_report": 4,
"would_take_similar_route_again": True,
"most_unfair_aspect": "parking",
"comments": "Too many apartments with no lift"
}
}
}
class ExtendedFeedbackResponse(BaseModel):
"""Response for extended feedback submission."""
id: UUID
driver_id: UUID
assignment_id: UUID
fairness_rating: int
stress_level: int
tiredness_level: int
hardest_aspect: Optional[str]
route_difficulty_self_report: Optional[int]
would_take_similar_route_again: Optional[bool]
most_unfair_aspect: Optional[str]
comments: Optional[str]
created_at: datetime.datetime
# ==================== Route Swap Request Schemas ====================
class RouteSwapRequestCreate(BaseModel):
"""Request for POST /api/v1/route_swap_requests."""
from_driver_id: UUID
to_driver_id: Optional[UUID] = None
assignment_id: UUID
reason: str = Field(..., min_length=1)
preferred_date: Optional[datetime.date] = None
model_config = {
"json_schema_extra": {
"example": {
"from_driver_id": "550e8400-e29b-41d4-a716-446655440001",
"to_driver_id": None,
"assignment_id": "550e8400-e29b-41d4-a716-446655440002",
"reason": "Had 3 hard days, want a lighter one tomorrow",
"preferred_date": "2026-02-11"
}
}
}
class RouteSwapRequestResponse(BaseModel):
"""Response for route swap request."""
id: UUID
from_driver_id: UUID
to_driver_id: Optional[UUID]
assignment_id: UUID
reason: str
preferred_date: Optional[datetime.date]
status: str
created_at: datetime.datetime
updated_at: datetime.datetime
# ==================== Stop Issue Schemas ====================
class StopIssueRequest(BaseModel):
"""Request for POST /api/v1/stop_issues."""
assignment_id: UUID
route_id: UUID
driver_id: UUID
stop_order: int = Field(..., ge=1)
issue_type: str = Field(..., description="NAVIGATION, SAFETY, TIME_WINDOW, CUSTOMER_UNAVAILABLE, OTHER")
notes: str = Field(..., min_length=1)
model_config = {
"json_schema_extra": {
"example": {
"assignment_id": "550e8400-e29b-41d4-a716-446655440001",
"route_id": "550e8400-e29b-41d4-a716-446655440002",
"driver_id": "550e8400-e29b-41d4-a716-446655440003",
"stop_order": 5,
"issue_type": "SAFETY",
"notes": "Street too dark, dogs on road"
}
}
}
class StopIssueResponse(BaseModel):
"""Response for stop issue creation."""
id: UUID
assignment_id: UUID
route_id: UUID
driver_id: UUID
stop_order: int
issue_type: str
notes: str
created_at: datetime.datetime