"""Appointment handler — Extract event → Calendar + Telegram."""
import logging
from typing import Dict, Any
from shared.llm import LLMClient
from shared.notify import TelegramNotifier
from family.calendar import CalendarClient
from family.email import EmailProcessor
logger = logging.getLogger(name)
class AppointmentHandler:
"""Handle appointment emails: extract event, create calendar, notify."""
def __init__(
self,
llm_client: LLMClient,
calendar_client: CalendarClient,
telegram: TelegramNotifier
):
self.llm = llm_client
self.calendar = calendar_client
self.telegram = telegram
self.email_processor = EmailProcessor(llm_client)
async def process(
self,
subject: str,
body: str,
sender: str,
received_at: str
) -> Dict[str, Any]:
"""Process appointment email.
Returns:
Dict with event_created, notification_sent, parsed_event
"""
result = {
"type": "appointment",
"event_created": False,
"notification_sent": False,
"parsed": {},
"errors": []
}
# Extract event details
parsed = await self.email_processor.extract_event(subject, body)
if not parsed:
result["errors"].append("LLM extraction failed")
# Still notify about the email
await self._notify_fallback(subject, body, sender)
return result
result["parsed"] = parsed
# Skip low confidence
if parsed.get("confidence", 0) < 0.5:
logger.warning(f"Low confidence extraction: {parsed.get('confidence')}")
result["errors"].append(f"Low confidence: {parsed.get('confidence')}")
await self._notify_fallback(subject, body, sender, parsed)
return result
# Create calendar event
calendar_result = await self.calendar.create_event(
summary=parsed["summary"],
start_datetime=parsed["start_datetime"],
end_datetime=parsed["end_datetime"],
description=parsed.get("description", f"From: {sender}\n\n{subject}"),
location=parsed.get("location", "")
)
result["event_created"] = calendar_result.get("created", False)
if not result["event_created"]:
result["errors"].append(f"Calendar failed: {calendar_result.get('error', 'unknown')}")
# Notify
await self._notify(subject, parsed, result["event_created"])
result["notification_sent"] = True
return result
async def _notify(self, subject: str, parsed: Dict[str, Any], created: bool) -> None:
"""Send Telegram notification."""
event_time = parsed.get("start_datetime", "unknown")
msg = f"📅 <b>New Event</b>\n\n"
msg += f"<b>{parsed['summary']}</b>\n"
msg += f"🕐 {event_time}\n"
if parsed.get("location"):
msg += f"📍 {parsed['location']}\n"
msg += f"\n{'✅ Added to family calendar' if created else '⚠️ Calendar failed — check manually'}"
await self.telegram.to_family(msg)
async def _notify_fallback(
self,
subject: str,
body: str,
sender: str,
parsed: Dict[str, Any] = None
) -> None:
"""Notify when extraction fails or confidence is low."""
msg = f"📧 <b>Appointment Email</b>\n\n"
msg += f"<b>{subject}</b>\n"
msg += f"From: {sender}\n\n"
if parsed:
msg += f"⚠️ Low confidence extraction: {parsed.get('confidence', 'unknown')}\n"
else:
msg += f"⚠️ Could not extract event details\n"
msg += f"Please check email and add manually if needed."
await self.telegram.to_family(msg)