88 lines
2.5 KiB
Python
88 lines
2.5 KiB
Python
|
|
"""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 = ""
|