"""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"📅 New Event\n\n" msg += f"{parsed['summary']}\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"📧 Appointment Email\n\n" msg += f"{subject}\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)