"""Shared domain value objects used across providers, agent tools and the proposal engine. These are deliberately plain (pydantic) models decoupled from the ORM so that SchedulingProvider implementations and the agent can be tested without a database. """ from __future__ import annotations import enum from datetime import datetime from pydantic import BaseModel, Field class BookingStatus(enum.StrEnum): pending = "pending" resolved_by_owner = "resolved_by_owner" # owner contacted client directly (primary flow) confirmed = "confirmed" # owner confirmed a slot → Gogo sends confirmation SMS rejected = "rejected" expired = "expired" class CallOutcome(enum.StrEnum): human_answered = "human_answered" request_created = "request_created" info_only = "info_only" message_taken = "message_taken" abandoned = "abandoned" class ProviderType(enum.StrEnum): google_calendar = "google_calendar" partner_api = "partner_api" mock = "mock" # in-memory, for tests and demos class Slot(BaseModel): """One concrete offerable time slot (timezone-aware datetimes).""" start: datetime end: datetime staff_id: str | None = None staff_name: str | None = None def model_post_init(self, __context) -> None: if self.start.tzinfo is None or self.end.tzinfo is None: raise ValueError("Slot datetimes must be timezone-aware") class ServiceInfo(BaseModel): """Service as seen by providers / the agent (decoupled from ORM row).""" id: str # our service UUID as string, or partner service id for catalog sync name: str duration_min: int price_min: float | None = None price_max: float | None = None currency: str = "BAM" home_visit: bool = False agent_note: str | None = None active: bool = True class BookingRequestData(BaseModel): """Payload the proposal engine hands to a provider's deliver_request().""" gogo_request_id: str tenant_id: str created_at: datetime source: str # "voice" | "chat" client_name: str client_phone: str service_id: str | None = None service_name_raw: str = "" requested_slots: list[Slot] = Field(default_factory=list) # 0-3, ordered by preference time_preference_text: str = "" home_visit: bool = False address: str | None = None summary: str = "" transcript_url: str = "" dry_run: bool = False class DeliveryResult(BaseModel): ok: bool partner_request_id: str | None = None detail: str = ""