""" Call Flow models — IVR navigation trees. Store known IVR structures for phone numbers you call regularly. The Hold Slayer follows the map instead of exploring blind. """ from datetime import datetime from enum import Enum from typing import Optional from pydantic import BaseModel, Field class ActionType(str, Enum): """Actions the Hold Slayer can take at each IVR step.""" DTMF = "dtmf" # Press a button SPEAK = "speak" # Say something (for speech-recognition IVRs) WAIT = "wait" # Wait for prompt LISTEN = "listen" # Listen and let LLM decide HOLD = "hold" # On hold — activate hold detection TRANSFER = "transfer" # Transfer to user's device class CallFlowStep(BaseModel): """A single step in an IVR navigation tree.""" id: str description: str # Human-readable: "Main menu" expect: Optional[str] = None # What we expect to hear (regex or keywords) action: ActionType action_value: Optional[str] = None # DTMF digit(s), speech text, device target timeout: int = 30 # Seconds to wait before retry/fallback next_step: Optional[str] = None # Next step ID on success fallback_step: Optional[str] = None # Step ID if unexpected response notes: Optional[str] = None # "They changed this menu in Jan 2025" class CallFlow(BaseModel): """A complete IVR navigation tree for a phone number.""" id: str name: str # "Chase Bank - Main Line" phone_number: str # "+18005551234" description: str = "" last_verified: Optional[datetime] = None steps: list[CallFlowStep] tags: list[str] = Field(default_factory=list) notes: Optional[str] = None # Stats from previous runs avg_hold_time: Optional[int] = None # seconds success_rate: Optional[float] = None # 0.0 - 1.0 last_used: Optional[datetime] = None times_used: int = 0 def get_step(self, step_id: str) -> Optional[CallFlowStep]: """Look up a step by ID.""" for step in self.steps: if step.id == step_id: return step return None def first_step(self) -> Optional[CallFlowStep]: """Get the first step in the flow.""" return self.steps[0] if self.steps else None def steps_by_id(self) -> dict[str, CallFlowStep]: """Return a dict mapping step ID -> step for fast lookups.""" return {s.id: s for s in self.steps} class CallFlowCreate(BaseModel): """Request model for creating a new call flow.""" name: str phone_number: str description: str = "" steps: list[CallFlowStep] tags: list[str] = Field(default_factory=list) notes: Optional[str] = None class CallFlowUpdate(BaseModel): """Request model for updating an existing call flow.""" name: Optional[str] = None description: Optional[str] = None steps: Optional[list[CallFlowStep]] = None tags: Optional[list[str]] = None notes: Optional[str] = None last_verified: Optional[datetime] = None class CallFlowSummary(BaseModel): """Lightweight summary for list views.""" id: str name: str phone_number: str description: str = "" step_count: int avg_hold_time: Optional[int] = None success_rate: Optional[float] = None last_used: Optional[datetime] = None times_used: int = 0 tags: list[str] = Field(default_factory=list)