"""Task registry for the SupportDesk environment.""" from __future__ import annotations from dataclasses import dataclass from typing import Literal from models import KnowledgeSnippet, SupportTicket ALL_QUEUES = [ "billing_ops", "trust_and_safety", "platform_engineering", "compliance_ops", "general_support", ] ALL_PRIORITIES = ["low", "normal", "high", "urgent"] ALL_STATUSES = ["new", "waiting_on_customer", "resolved", "escalated"] ALL_ISSUE_TYPES = [ "duplicate_charge", "account_compromise", "production_incident", "regulated_exception", "general_question", ] @dataclass(frozen=True) class SupportTaskSpec: """Immutable definition of a single support triage task.""" task_id: str difficulty: Literal["easy", "medium", "hard"] title: str objective: str ticket: SupportTicket knowledge_base: tuple[KnowledgeSnippet, ...] gold_queue: str gold_priority: str gold_issue_type: str gold_status: str gold_resolution_code: str required_requested_fields: tuple[str, ...] required_reply_markers: tuple[tuple[str, ...], ...] required_note_markers: tuple[tuple[str, ...], ...] forbidden_reply_markers: tuple[str, ...] = () risk_flags: tuple[str, ...] = () follow_up_outcome: Literal["none", "partial", "complete", "incorrect"] = "none" follow_up_message: str = "" follow_up_provided_fields: tuple[str, ...] = () follow_up_wrong_fields: tuple[str, ...] = () sla_step_cost: int = 15 over_escalation_queues: tuple[str, ...] = () under_escalation_deadline_step: int | None = None max_steps: int = 6 TASKS: dict[str, SupportTaskSpec] = { "billing_refund_easy": SupportTaskSpec( task_id="billing_refund_easy", difficulty="easy", title="Duplicate charge refund triage", objective=( "Triage a duplicate-charge billing ticket, send the correct customer response, " "and close the case only if no further customer information is required." ), ticket=SupportTicket( customer_name="Riya Shah", customer_tier="pro", company="PixelNorth Studio", subject="Charged twice after I canceled", body=( "I canceled our Pro annual workspace yesterday, but my card was charged again " "this morning and I still see the old invoice. We only had one workspace, " "so this looks like a duplicate charge. Please fix it quickly." ), region="ap-south-1", affected_users=12, sla_minutes_remaining=240, business_impact="Finance ops are blocked from closing the monthly books until the duplicate invoice is fixed.", secondary_concerns=["The customer also wants confirmation that the canceled workspace will stay deactivated."], attachments=["invoice_7741.pdf"], ), knowledge_base=( KnowledgeSnippet( article_id="KB-101", title="Duplicate charges and same-day cancellations", content=( "If a customer reports a duplicate charge and the subscription is already " "canceled, route the ticket to billing_ops with high priority. Billing can " "approve the refund immediately without requesting extra information when an " "invoice is attached." ), ), KnowledgeSnippet( article_id="KB-102", title="Refund communication checklist", content=( "Customer replies for approved duplicate-charge refunds must confirm that a " "refund is being processed, mention the duplicate charge, and set the " "expectation that funds typically appear within 5-7 business days." ), ), KnowledgeSnippet( article_id="KB-103", title="When to close a billing case", content=( "Close the case as resolved only after the refund path is clear and no more " "customer details are needed." ), ), ), gold_queue="billing_ops", gold_priority="high", gold_issue_type="duplicate_charge", gold_status="resolved", gold_resolution_code="refund_approved", required_requested_fields=(), required_reply_markers=( ("refund", "refunded", "reimburse"), ("duplicate charge", "charged twice", "double charge"), ("5-7 business days", "5 to 7 business days", "within 7 business days"), ), required_note_markers=( ("duplicate charge", "double charge"), ("refund", "refund approved"), ), forbidden_reply_markers=("chargeback", "security team"), risk_flags=("finance_close_risk", "avoid_unnecessary_back_and_forth"), over_escalation_queues=("trust_and_safety", "platform_engineering", "compliance_ops"), sla_step_cost=10, max_steps=6, ), "account_takeover_medium": SupportTaskSpec( task_id="account_takeover_medium", difficulty="medium", title="Suspicious login recovery triage", objective=( "Handle a potential account-compromise case, request the missing verification " "details, communicate safe next steps, and keep the case open until the customer replies. " "The agent must protect account safety without promising an unsafe immediate unlock." ), ticket=SupportTicket( customer_name="Marcus Lee", customer_tier="pro", company="Northline Analytics", subject="Locked out after strange login alert", body=( "Our workspace admin got a login alert from a country none of us have visited, " "and now I can't get back into the account. Please unlock it ASAP. The billing " "email is still ours, but I'm worried someone got in." ), region="us-east-1", affected_users=34, sla_minutes_remaining=90, business_impact="The admin is locked out of the analytics workspace ahead of the Monday executive review.", secondary_concerns=["The customer wants the account unlocked immediately, but the verification flow cannot be skipped."], attachments=[], ), knowledge_base=( KnowledgeSnippet( article_id="SEC-201", title="Account compromise routing", content=( "Potential account-takeover reports route to trust_and_safety with urgent " "priority. Do not resolve the case immediately." ), ), KnowledgeSnippet( article_id="SEC-202", title="Verification details before unlock", content=( "Before access can be restored, ask the customer for the workspace_id, the " "last successful login time, and the billing email on file. Keep the status " "waiting_on_customer until the details arrive." ), ), KnowledgeSnippet( article_id="SEC-203", title="Customer response checklist", content=( "Security replies should tell the customer to reset their password, scan " "their device for malware, and explain that the trust team is reviewing the case." ), ), ), gold_queue="trust_and_safety", gold_priority="urgent", gold_issue_type="account_compromise", gold_status="waiting_on_customer", gold_resolution_code="verification_needed", required_requested_fields=("workspace_id", "last_successful_login", "billing_email"), required_reply_markers=( ("reset your password", "change your password"), ("scan", "malware", "device check"), ("trust team", "security team", "trust and safety"), ), required_note_markers=( ("suspicious login", "strange login"), ("locked out", "can't get back", "cannot get back"), ), risk_flags=("unsafe_unlock_request", "identity_verification_required"), follow_up_outcome="partial", follow_up_message=( "Customer follow-up: workspace_id=ws_9021 and billing email confirmed, " "but they could not provide the last successful login time yet." ), follow_up_provided_fields=("workspace_id", "billing_email"), sla_step_cost=18, under_escalation_deadline_step=2, max_steps=7, ), "api_incident_hard": SupportTaskSpec( task_id="api_incident_hard", difficulty="hard", title="Production API incident escalation", objective=( "Triage a high-pressure enterprise incident, ask for the right diagnostics, notify " "the customer that engineering is engaged, and escalate instead of resolving. " "The agent must prioritize the outage over a tempting secondary compliance question." ), ticket=SupportTicket( customer_name="Asha Verma", customer_tier="enterprise", company="Kairo Health", subject="EU rollout blocked by intermittent 500s", body=( "We're launching our EU workspace tonight. Since enabling EU data residency we " "see intermittent HTTP 500 responses from /v1/exports in production. Our " "compliance lead is also asking whether this affects the audit trail, but the " "main issue is the outage. We need help immediately." ), region="eu-west-1", affected_users=1800, sla_minutes_remaining=25, business_impact="A production launch and a customer-facing compliance review are both at risk tonight if the outage persists.", secondary_concerns=["The compliance lead is asking whether audit trails are affected, but the live outage is the primary incident."], attachments=["error_screenshot.png"], ), knowledge_base=( KnowledgeSnippet( article_id="INC-301", title="Production availability incidents", content=( "Any active production 5xx incident for a paying customer routes to " "platform_engineering with urgent priority and should be escalated, not resolved." ), ), KnowledgeSnippet( article_id="INC-302", title="Minimum diagnostics for API incidents", content=( "Before engineering can investigate, request concrete examples including " "request_ids, UTC timestamps, and the affected region." ), ), KnowledgeSnippet( article_id="INC-303", title="Customer communication during an incident", content=( "The reply should acknowledge an incident, say the on-call engineering team " "is engaged, and ask for the diagnostics needed to speed investigation." ), ), KnowledgeSnippet( article_id="INC-304", title="Primary issue triage rule", content=( "When a production outage appears alongside a secondary compliance or audit " "question, resolve the live outage first and avoid treating the secondary " "question as the primary queue-driving issue." ), ), ), gold_queue="platform_engineering", gold_priority="urgent", gold_issue_type="production_incident", gold_status="escalated", gold_resolution_code="incident_opened", required_requested_fields=("request_ids", "timestamp_utc", "region"), required_reply_markers=( ("incident", "outage", "investigating"), ("on-call", "engineering team", "engineering is engaged"), ("request id", "request_ids"), ("utc", "timestamp"), ), required_note_markers=( ("eu data residency", "eu rollout"), ("500", "http 500"), ("launch tonight", "tonight"), ), risk_flags=("sev1_launch_risk", "secondary_issue_distraction", "engineering_escalation_required"), follow_up_outcome="complete", follow_up_message=( "Customer follow-up: request_ids=req_991, req_998; UTC timestamps=2026-04-01T19:20Z, " "2026-04-01T19:27Z; region=eu-west-1." ), follow_up_provided_fields=("request_ids", "timestamp_utc", "region"), sla_step_cost=25, under_escalation_deadline_step=2, max_steps=8, ), "regulated_export_exception_hard": SupportTaskSpec( task_id="regulated_export_exception_hard", difficulty="hard", title="Regulated export exception triage", objective=( "Handle a regulated export-access request where the customer wants a manual bypass. " "The agent must avoid escalating to engineering just because an export error is mentioned, " "route to compliance operations, request the right legal details, and keep the case open " "until the required approvals are supplied." ), ticket=SupportTicket( customer_name="Dr. Elena Kovacs", customer_tier="enterprise", company="NorthSea Clinics", subject="Need urgent export unlock before audit review", body=( "Our audit team needs tonight's export package, but after enabling EU residency the " "export workflow now shows a policy block. Can you temporarily disable the restriction " "or give us a manual bypass just for this audit? We are under pressure and can share " "screenshots if needed." ), region="eu-central-1", affected_users=240, sla_minutes_remaining=55, business_impact="A regulated healthcare audit review may slip tonight if the export path remains blocked.", secondary_concerns=[ "The customer suggests an engineering workaround, but legal approval is the real gating factor." ], attachments=["policy_block.png"], ), knowledge_base=( KnowledgeSnippet( article_id="CMP-401", title="Export policy exceptions", content=( "Requests to bypass region or export restrictions route to compliance_ops. " "Do not promise a manual engineering workaround before approvals are verified." ), ), KnowledgeSnippet( article_id="CMP-402", title="Minimum legal details for export review", content=( "Ask for the tenant_region, dpa_amendment_id, and legal_contact_email before " "compliance can review an export exception." ), ), KnowledgeSnippet( article_id="CMP-403", title="Customer response rules for regulated exceptions", content=( "Replies must explain that no temporary bypass can be granted yet, mention the " "compliance review, and request the required legal approval details." ), ), ), gold_queue="compliance_ops", gold_priority="high", gold_issue_type="regulated_exception", gold_status="waiting_on_customer", gold_resolution_code="legal_approval_required", required_requested_fields=("tenant_region", "dpa_amendment_id", "legal_contact_email"), required_reply_markers=( ("no temporary bypass", "cannot provide a bypass", "can’t provide a bypass"), ("compliance review", "compliance team"), ("tenant_region", "tenant region"), ("dpa_amendment_id", "dpa amendment", "amendment id"), ), required_note_markers=( ("audit", "audit review"), ("eu residency", "policy block"), ("manual bypass", "workaround"), ), forbidden_reply_markers=("engineering workaround", "disable the restriction", "temporary unlock approved"), risk_flags=("regulated_data_risk", "unsafe_shortcut_pressure", "over_escalation_risk"), follow_up_outcome="incorrect", follow_up_message=( "Customer follow-up: sent a screenshot and export job ID, but did not include the DPA " "amendment ID or legal contact." ), follow_up_wrong_fields=("screenshot", "job_id"), sla_step_cost=16, over_escalation_queues=("platform_engineering",), max_steps=8, ), } def get_task(task_id: str) -> SupportTaskSpec: """Return a task definition or raise a helpful error.""" try: return TASKS[task_id] except KeyError as exc: # pragma: no cover - defensive valid = ", ".join(sorted(TASKS)) raise ValueError(f"Unknown task_id '{task_id}'. Valid task ids: {valid}") from exc def list_task_ids() -> list[str]: """List tasks in a stable evaluation order.""" return list(TASKS)