mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-06-17 23:09:35 +00:00
179 lines
6.7 KiB
Python
179 lines
6.7 KiB
Python
"""
|
|
Booking Agent — books discovery calls via Calendly (preferred) or Google Calendar.
|
|
وكيل الحجز — يحجز مكالمات الاستكشاف عبر Calendly أو Google Calendar.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime, timedelta
|
|
from typing import Any
|
|
from zoneinfo import ZoneInfo
|
|
|
|
from auto_client_acquisition.agents.intake import Lead
|
|
from core.agents.base import BaseAgent
|
|
from core.config.settings import get_settings
|
|
from core.prompts.sales_scripts import get_sales_script
|
|
from core.utils import generate_id
|
|
|
|
|
|
@dataclass
|
|
class BookingResult:
|
|
booking_id: str
|
|
provider: str # calendly | google | manual
|
|
link: str | None
|
|
scheduled_at: datetime | None
|
|
meeting_minutes: int
|
|
invitee_email: str | None
|
|
invitee_phone: str | None
|
|
confirmation_message: str
|
|
success: bool
|
|
reason: str = ""
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return {
|
|
"booking_id": self.booking_id,
|
|
"provider": self.provider,
|
|
"link": self.link,
|
|
"scheduled_at": self.scheduled_at.isoformat() if self.scheduled_at else None,
|
|
"meeting_minutes": self.meeting_minutes,
|
|
"invitee_email": self.invitee_email,
|
|
"invitee_phone": self.invitee_phone,
|
|
"confirmation_message": self.confirmation_message,
|
|
"success": self.success,
|
|
"reason": self.reason,
|
|
}
|
|
|
|
|
|
class BookingAgent(BaseAgent):
|
|
"""
|
|
Attempts to book a meeting using the best available provider.
|
|
Priority: Calendly (scheduling link) → Google Calendar → manual fallback.
|
|
"""
|
|
|
|
name = "booking"
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
self.settings = get_settings()
|
|
self.tz = ZoneInfo(self.settings.app_timezone)
|
|
|
|
async def run(
|
|
self,
|
|
*,
|
|
lead: Lead,
|
|
preferred_time: datetime | None = None,
|
|
meeting_minutes: int = 30,
|
|
**_: Any,
|
|
) -> BookingResult:
|
|
"""Return booking details or manual fallback."""
|
|
booking_id = generate_id("bkg")
|
|
|
|
# 1. Calendly (preferred for self-service scheduling)
|
|
if self.settings.calendly_api_token and self.settings.calendly_user_uri:
|
|
link = self._calendly_scheduling_link()
|
|
confirm = self._confirm_message(lead, "calendly", None, link)
|
|
self.log.info("booking_calendly_link_sent", lead_id=lead.id, link=link)
|
|
return BookingResult(
|
|
booking_id=booking_id,
|
|
provider="calendly",
|
|
link=link,
|
|
scheduled_at=None,
|
|
meeting_minutes=meeting_minutes,
|
|
invitee_email=lead.contact_email,
|
|
invitee_phone=lead.contact_phone,
|
|
confirmation_message=confirm,
|
|
success=True,
|
|
reason="Sent Calendly scheduling link",
|
|
)
|
|
|
|
# 2. Google Calendar direct-create (if credentials present)
|
|
if self.settings.google_calendar_credentials_file:
|
|
scheduled = preferred_time or self._default_slot()
|
|
# NOTE: actual Google API call happens in integrations/calendar.py
|
|
# The integration layer will be invoked via a callable if present.
|
|
confirm = self._confirm_message(
|
|
lead, "google", scheduled, link=None, meeting_minutes=meeting_minutes
|
|
)
|
|
self.log.info("booking_google_scheduled", lead_id=lead.id, when=scheduled.isoformat())
|
|
return BookingResult(
|
|
booking_id=booking_id,
|
|
provider="google",
|
|
link=None,
|
|
scheduled_at=scheduled,
|
|
meeting_minutes=meeting_minutes,
|
|
invitee_email=lead.contact_email,
|
|
invitee_phone=lead.contact_phone,
|
|
confirmation_message=confirm,
|
|
success=True,
|
|
reason="Scheduled via Google Calendar",
|
|
)
|
|
|
|
# 3. Manual fallback — return instructions
|
|
confirm = self._confirm_message(lead, "manual", None, None)
|
|
self.log.warning("booking_manual_fallback", lead_id=lead.id)
|
|
return BookingResult(
|
|
booking_id=booking_id,
|
|
provider="manual",
|
|
link=None,
|
|
scheduled_at=None,
|
|
meeting_minutes=meeting_minutes,
|
|
invitee_email=lead.contact_email,
|
|
invitee_phone=lead.contact_phone,
|
|
confirmation_message=confirm,
|
|
success=False,
|
|
reason="No booking provider configured",
|
|
)
|
|
|
|
# ── Helpers ─────────────────────────────────────────────────
|
|
def _calendly_scheduling_link(self) -> str:
|
|
"""Return the public Calendly link derived from the user URI."""
|
|
user_uri = self.settings.calendly_user_uri or ""
|
|
if user_uri.startswith("http"):
|
|
return user_uri
|
|
return f"https://calendly.com/{user_uri}"
|
|
|
|
def _default_slot(self) -> datetime:
|
|
"""Next business-day 10:00 Riyadh."""
|
|
now = datetime.now(self.tz)
|
|
# Skip Fri/Sat (weekend in Saudi)
|
|
target = now + timedelta(days=1)
|
|
while target.weekday() in (4, 5):
|
|
target += timedelta(days=1)
|
|
return target.replace(hour=10, minute=0, second=0, microsecond=0)
|
|
|
|
def _confirm_message(
|
|
self,
|
|
lead: Lead,
|
|
provider: str,
|
|
scheduled: datetime | None,
|
|
link: str | None,
|
|
meeting_minutes: int = 30,
|
|
) -> str:
|
|
if provider == "manual":
|
|
if lead.locale == "ar":
|
|
return (
|
|
f"شكراً {lead.contact_name or ''}. "
|
|
f"فريقنا سيتواصل معك خلال 24 ساعة لتحديد موعد مناسب."
|
|
)
|
|
return (
|
|
f"Thanks {lead.contact_name or ''}. "
|
|
f"Our team will reach out within 24 hours to schedule."
|
|
)
|
|
if provider == "calendly" and link:
|
|
if lead.locale == "ar":
|
|
return (
|
|
f"مرحباً {lead.contact_name or ''}،\n" f"اختر الموعد المناسب لك من هنا: {link}"
|
|
)
|
|
return f"Hi {lead.contact_name or ''},\n" f"Pick a slot that works for you: {link}"
|
|
if provider == "google" and scheduled:
|
|
return get_sales_script(
|
|
"demo_confirm",
|
|
locale=lead.locale,
|
|
name=lead.contact_name or "",
|
|
date=scheduled.strftime("%Y-%m-%d"),
|
|
time=scheduled.strftime("%H:%M"),
|
|
link="(meeting link will be sent separately)",
|
|
)
|
|
return "Booking pending."
|