Files
gogo-telefon/gogo/domain.py

88 lines
2.5 KiB
Python
Raw Normal View History

"""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 = ""