| """Queue and wait-time models.""" |
|
|
| from dataclasses import dataclass, field |
| from datetime import datetime |
| from typing import Optional |
| from enum import Enum |
| import uuid |
|
|
|
|
| class QueueCategory(Enum): |
| """Category of service queue.""" |
|
|
| FOOD = "food" |
| MERCHANDISE = "merch" |
| RESTROOM = "restroom" |
| ENTRY = "entry" |
| EXIT = "exit" |
| TICKET = "ticket" |
|
|
| @property |
| def icon(self): |
| return { |
| "food": "π", |
| "merch": "ποΈ", |
| "restroom": "π»", |
| "entry": "πͺ", |
| "exit": "πΆ", |
| "ticket": "π«", |
| }[self.value] |
|
|
| @property |
| def label(self): |
| return { |
| "food": "Food & Beverages", |
| "merch": "Merchandise", |
| "restroom": "Restrooms", |
| "entry": "Entry Gates", |
| "exit": "Exit Gates", |
| "ticket": "Ticket Counter", |
| }[self.value] |
|
|
|
|
| @dataclass |
| class QueueStation: |
| """A single service station (e.g., one food stall).""" |
|
|
| id: str |
| name: str |
| category: str |
| zone_id: str |
| current_length: int = 0 |
| avg_service_time_sec: float = 45.0 |
| is_open: bool = True |
| lat: float = 0.0 |
| lng: float = 0.0 |
|
|
| @property |
| def estimated_wait_minutes(self) -> float: |
| if not self.is_open or self.current_length == 0: |
| return 0.0 |
| return round((self.current_length * self.avg_service_time_sec) / 60, 1) |
|
|
| @property |
| def wait_level(self) -> str: |
| wait = self.estimated_wait_minutes |
| if wait < 5: |
| return "short" |
| elif wait < 15: |
| return "moderate" |
| elif wait < 30: |
| return "long" |
| else: |
| return "very_long" |
|
|
| @property |
| def wait_color(self) -> str: |
| return { |
| "short": "#22c55e", |
| "moderate": "#f59e0b", |
| "long": "#ef4444", |
| "very_long": "#18181b", |
| }[self.wait_level] |
|
|
| def to_dict(self) -> dict: |
| return { |
| "id": self.id, |
| "name": self.name, |
| "category": self.category, |
| "category_icon": QueueCategory(self.category).icon, |
| "category_label": QueueCategory(self.category).label, |
| "zone_id": self.zone_id, |
| "current_length": self.current_length, |
| "estimated_wait_minutes": self.estimated_wait_minutes, |
| "wait_level": self.wait_level, |
| "wait_color": self.wait_color, |
| "is_open": self.is_open, |
| "lat": self.lat, |
| "lng": self.lng, |
| } |
|
|
|
|
| @dataclass |
| class VirtualQueueTicket: |
| """A virtual queue reservation for an attendee.""" |
|
|
| id: str = field(default_factory=lambda: str(uuid.uuid4())[:8].upper()) |
| user_id: str = "" |
| station_id: str = "" |
| station_name: str = "" |
| category: str = "" |
| position: int = 0 |
| estimated_ready_time: Optional[datetime] = None |
| status: str = "waiting" |
| created_at: datetime = field(default_factory=datetime.utcnow) |
|
|
| def to_dict(self) -> dict: |
| return { |
| "id": self.id, |
| "user_id": self.user_id, |
| "station_id": self.station_id, |
| "station_name": self.station_name, |
| "category": self.category, |
| "category_icon": QueueCategory(self.category).icon if self.category else "", |
| "position": self.position, |
| "estimated_ready_time": ( |
| self.estimated_ready_time.isoformat() |
| if self.estimated_ready_time |
| else None |
| ), |
| "status": self.status, |
| "created_at": self.created_at.isoformat(), |
| } |
|
|