"""Deterministic task catalog for the support triage environment.""" from __future__ import annotations from pydantic import BaseModel, ConfigDict, Field from support_queue_env.models import Difficulty, Disposition, Priority, QueueName class TicketSpec(BaseModel): model_config = ConfigDict(extra="forbid") ticket_id: str subject: str body: str customer_tier: str product_area: str sla_hours: int recent_events: list[str] = Field(default_factory=list) expected_priority: Priority expected_queue: QueueName expected_disposition: Disposition acceptable_queues: list[QueueName] = Field(default_factory=list) acceptable_dispositions: list[Disposition] = Field(default_factory=list) summary_keywords: list[str] = Field(default_factory=list) response_keywords: list[str] = Field(default_factory=list) disallowed_keywords: list[str] = Field(default_factory=list) class TaskSpec(BaseModel): model_config = ConfigDict(extra="forbid") task_id: str title: str difficulty: Difficulty description: str instructions: str tickets: list[TicketSpec] TASKS: list[TaskSpec] = [ TaskSpec( task_id="easy_inbox_cleanup", title="Inbox Cleanup", difficulty="easy", description="Two straightforward tickets covering access and billing triage.", instructions=( "You are a SaaS support triage agent. For each ticket, choose priority, routing queue, " "and the next best disposition. Write a short internal summary plus the first reply you " "would send to the customer." ), tickets=[ TicketSpec( ticket_id="E-101", subject="Locked out after password reset", body=( "I reset my password this morning and now the admin portal says my account is locked. " "We need to finish payroll before noon." ), customer_tier="starter", product_area="auth", sla_hours=24, recent_events=["Password reset completed 2 hours ago", "No prior incidents on status page"], expected_priority="P3", expected_queue="technical", expected_disposition="respond", summary_keywords=["account", "locked", "password"], response_keywords=["verify", "unlock", "reset"], disallowed_keywords=["refund", "breach"], ), TicketSpec( ticket_id="E-102", subject="We were charged twice for March", body=( "Our card shows two identical charges from your company for invoice INV-4481. " "Please confirm whether one of them will be refunded." ), customer_tier="growth", product_area="billing", sla_hours=8, recent_events=["Invoice INV-4481 marked paid yesterday"], expected_priority="P2", expected_queue="billing", expected_disposition="respond", summary_keywords=["duplicate", "charge", "invoice"], response_keywords=["refund", "investigate", "billing"], disallowed_keywords=["ignore", "security incident"], ), ], ), TaskSpec( task_id="medium_sla_defense", title="SLA Defense", difficulty="medium", description="Three tickets that mix urgent escalation with an ambiguity check.", instructions=( "Prioritize by customer impact and risk. Security events and broad service degradation should " "be escalated immediately. If the customer has not given enough evidence to act safely, ask for " "the minimum details needed to proceed." ), tickets=[ TicketSpec( ticket_id="M-201", subject="Suspicious email asking admins to re-enter credentials", body=( "Several admins received an email that looks like your login page and asks us to " "re-authenticate. One teammate clicked it but says they closed the tab before typing anything." ), customer_tier="enterprise", product_area="security", sla_hours=1, recent_events=["Customer SSO is enabled", "No status page incident posted"], expected_priority="P1", expected_queue="security", expected_disposition="escalate", summary_keywords=["phishing", "credentials", "admins"], response_keywords=["security", "escalated", "do not click"], disallowed_keywords=["send password", "share secrets"], ), TicketSpec( ticket_id="M-202", subject="Webhook deliveries are failing after yesterday's rollout", body=( "Every webhook call since 06:15 UTC has returned HTTP 500. This is blocking our downstream " "fulfillment pipeline. Can you investigate urgently?" ), customer_tier="growth", product_area="integrations", sla_hours=4, recent_events=["Customer is on API version 2025-11", "Platform release went out last night"], expected_priority="P2", expected_queue="technical", expected_disposition="escalate", acceptable_queues=["success"], summary_keywords=["webhook", "500", "rollout"], response_keywords=["engineering", "logs", "investigate"], disallowed_keywords=["duplicate charge", "unsubscribe"], ), TicketSpec( ticket_id="M-203", subject="Maybe double charged? Not fully sure", body=( "My finance teammate thinks we were double billed, but I can only find one invoice in the portal. " "Could you explain what happened and what details you need from me?" ), customer_tier="growth", product_area="billing", sla_hours=12, recent_events=["One paid invoice visible in portal", "No payment failures recorded"], expected_priority="P3", expected_queue="billing", expected_disposition="request_info", acceptable_dispositions=["respond"], summary_keywords=["billing", "unclear", "invoice"], response_keywords=["invoice", "last four", "amount"], disallowed_keywords=["breach", "status page"], ), ], ), TaskSpec( task_id="hard_exec_escalations", title="Executive Escalations", difficulty="hard", description="Four high-stakes tickets that require precise triage under pressure.", instructions=( "You are covering an executive escalation queue during a busy incident window. Optimize for " "business continuity, account safety, and clean handoffs. Use P1 only for severe production or " "security impact. Ask for more detail only when it materially changes the next safe action." ), tickets=[ TicketSpec( ticket_id="H-301", subject="All agents see 502 during login", body=( "Our entire support floor is blocked from logging in. Every browser gets a 502 after " "submitting the sign-in form. The public status page still says operational." ), customer_tier="enterprise", product_area="auth", sla_hours=1, recent_events=["50+ seats on account", "Issue started 18 minutes ago"], expected_priority="P1", expected_queue="technical", expected_disposition="escalate", summary_keywords=["login", "502", "all agents"], response_keywords=["incident", "engineering", "urgent"], disallowed_keywords=["refund only", "close ticket"], ), TicketSpec( ticket_id="H-302", subject="Unknown OAuth app connected after employee departure", body=( "An OAuth app named 'SyncFast' appeared in our workspace this morning from an IP we don't recognize. " "The only recent account change is that one contractor left yesterday." ), customer_tier="enterprise", product_area="security", sla_hours=1, recent_events=["Customer has audit logs enabled", "Former contractor account was deactivated yesterday"], expected_priority="P1", expected_queue="security", expected_disposition="escalate", summary_keywords=["oauth", "unknown", "contractor"], response_keywords=["security", "revoke", "escalated"], disallowed_keywords=["share api key", "ignore"], ), TicketSpec( ticket_id="H-303", subject="Renewal quote lost our committed discount", body=( "Our renewal quote is missing the 18% discount your sales team committed in writing. " "Our CFO will freeze procurement tomorrow if this isn't corrected." ), customer_tier="enterprise", product_area="commercial", sla_hours=6, recent_events=["Renewal date in 2 days", "Account owner is on PTO"], expected_priority="P2", expected_queue="success", expected_disposition="escalate", acceptable_queues=["billing"], summary_keywords=["renewal", "discount", "cfo"], response_keywords=["account manager", "quote", "escalated"], disallowed_keywords=["security breach", "reset password"], ), TicketSpec( ticket_id="H-304", subject="Need cancellation plus data export", body=( "We're planning to cancel next month for budget reasons, but first I need a data export for " "our records. Please tell me exactly what you need from me to start." ), customer_tier="starter", product_area="retention", sla_hours=24, recent_events=["No open invoices", "Account is owner-managed"], expected_priority="P3", expected_queue="success", expected_disposition="request_info", acceptable_queues=["billing"], summary_keywords=["cancel", "data export", "verification"], response_keywords=["verify", "export", "owner"], disallowed_keywords=["breach", "status page"], ), ], ), ] TASK_INDEX = {task.task_id: task for task in TASKS}