| """ |
| Synthetic customer support ticket dataset. |
| Provides realistic, diverse tickets across multiple categories. |
| """ |
| from __future__ import annotations |
|
|
| from typing import Any, Dict, List |
| from datetime import datetime, timezone, timedelta |
| import random |
|
|
| |
|
|
| TICKET_TEMPLATES: List[Dict[str, Any]] = [ |
|
|
| |
| { |
| "id": "T001", |
| "subject": "Double charged for my last order", |
| "body": ( |
| "Hi, I noticed that I was charged twice for order #ORD-88234. " |
| "My bank statement shows two identical charges of $89.99 on November 14th. " |
| "I only placed one order. Please refund the duplicate charge ASAP. " |
| "This is very frustrating." |
| ), |
| "true_category": "billing", |
| "true_priority": "high", |
| "true_tags": ["duplicate-charge", "refund", "urgent"], |
| "customer": { |
| "customer_id": "C1001", |
| "name": "Sarah Johnson", |
| "email": "sarah.j@example.com", |
| "account_tier": "standard", |
| "total_orders": 12, |
| "previous_tickets": 1, |
| "sentiment": "angry", |
| }, |
| "ideal_resolution": "refunded", |
| "sla_hours": 4, |
| "escalation_needed": False, |
| "complexity": "easy", |
| }, |
| { |
| "id": "T002", |
| "subject": "Subscription renewal charge I didn't authorize", |
| "body": ( |
| "I cancelled my Premium subscription 3 weeks ago but you just charged me $199 again. " |
| "I have a confirmation email of the cancellation (ref: CAN-20341). " |
| "I demand a full refund and proof that my subscription is actually cancelled. " |
| "If this is not resolved I will dispute the charge with my bank." |
| ), |
| "true_category": "billing", |
| "true_priority": "urgent", |
| "true_tags": ["unauthorized-charge", "subscription", "refund", "chargeback-risk"], |
| "customer": { |
| "customer_id": "C1002", |
| "name": "Marcus Chen", |
| "email": "marcus.chen@example.com", |
| "account_tier": "premium", |
| "total_orders": 45, |
| "previous_tickets": 3, |
| "sentiment": "angry", |
| }, |
| "ideal_resolution": "refunded", |
| "sla_hours": 2, |
| "escalation_needed": True, |
| "escalation_team": "billing_specialist", |
| "complexity": "medium", |
| }, |
|
|
| |
| { |
| "id": "T003", |
| "subject": "App crashes when I try to upload photos", |
| "body": ( |
| "Every time I try to upload photos from my iPhone 14 Pro, the app crashes immediately. " |
| "I'm on iOS 17.2 and app version 4.3.1. I've tried restarting, reinstalling, and clearing cache. " |
| "Nothing works. This has been happening for 3 days. " |
| "I need to upload photos for my business listings urgently." |
| ), |
| "true_category": "technical", |
| "true_priority": "high", |
| "true_tags": ["ios", "crash", "upload", "bug"], |
| "customer": { |
| "customer_id": "C1003", |
| "name": "Priya Patel", |
| "email": "priya.patel@mybusiness.com", |
| "account_tier": "premium", |
| "total_orders": 8, |
| "previous_tickets": 2, |
| "sentiment": "negative", |
| }, |
| "ideal_resolution": "workaround", |
| "sla_hours": 8, |
| "escalation_needed": True, |
| "escalation_team": "engineering", |
| "complexity": "medium", |
| }, |
| { |
| "id": "T004", |
| "subject": "Can't log in - password reset not working", |
| "body": ( |
| "I forgot my password and tried to reset it. I get the reset email but when I click the link " |
| "it says 'link expired' even when I click it immediately. " |
| "I've tried 5 times in the last hour. Please help." |
| ), |
| "true_category": "technical", |
| "true_priority": "medium", |
| "true_tags": ["login", "password-reset", "authentication"], |
| "customer": { |
| "customer_id": "C1004", |
| "name": "Tom Williams", |
| "email": "tomw@personalmail.com", |
| "account_tier": "standard", |
| "total_orders": 3, |
| "previous_tickets": 0, |
| "sentiment": "neutral", |
| }, |
| "ideal_resolution": "fixed", |
| "sla_hours": 8, |
| "escalation_needed": False, |
| "complexity": "easy", |
| }, |
|
|
| |
| { |
| "id": "T005", |
| "subject": "Package marked delivered but not received", |
| "body": ( |
| "My tracking number TRK-4892751 shows 'delivered' since yesterday at 2pm but I never received my package. " |
| "I was home all day. My neighbor hasn't seen it either. " |
| "The order value is $340. I'm very worried it was stolen. " |
| "What can you do about this?" |
| ), |
| "true_category": "shipping", |
| "true_priority": "high", |
| "true_tags": ["missing-package", "delivered-not-received", "high-value"], |
| "customer": { |
| "customer_id": "C1005", |
| "name": "Emily Rodriguez", |
| "email": "emily.r@emaildomain.com", |
| "account_tier": "standard", |
| "total_orders": 7, |
| "previous_tickets": 0, |
| "sentiment": "negative", |
| }, |
| "ideal_resolution": "refunded", |
| "sla_hours": 24, |
| "escalation_needed": False, |
| "complexity": "medium", |
| }, |
| { |
| "id": "T006", |
| "subject": "Order arriving 3 weeks late - wedding gift", |
| "body": ( |
| "I ordered a gift for my sister's wedding on Oct 15 with guaranteed 5-day delivery. " |
| "Today is Nov 5 and the item still hasn't arrived (order #ORD-77291). " |
| "The wedding was last Saturday! This gift was supposed to be special. " |
| "I need either overnight shipping or a full refund. This is completely unacceptable." |
| ), |
| "true_category": "shipping", |
| "true_priority": "urgent", |
| "true_tags": ["late-delivery", "refund", "compensation"], |
| "customer": { |
| "customer_id": "C1006", |
| "name": "James Park", |
| "email": "james.park@outlook.com", |
| "account_tier": "standard", |
| "total_orders": 4, |
| "previous_tickets": 1, |
| "sentiment": "angry", |
| }, |
| "ideal_resolution": "refunded", |
| "sla_hours": 4, |
| "escalation_needed": False, |
| "complexity": "easy", |
| }, |
|
|
| |
| { |
| "id": "T007", |
| "subject": "Defective product - need return instructions", |
| "body": ( |
| "I received the Bluetooth speaker (SKU: BT-X200) but the right speaker channel doesn't work. " |
| "It's clearly a manufacturing defect. I've had it for 8 days. " |
| "Your policy says 30-day returns. How do I return this and get a replacement or refund?" |
| ), |
| "true_category": "returns", |
| "true_priority": "medium", |
| "true_tags": ["defective", "return", "replacement"], |
| "customer": { |
| "customer_id": "C1007", |
| "name": "Aisha Thompson", |
| "email": "aisha.t@gmail.com", |
| "account_tier": "standard", |
| "total_orders": 15, |
| "previous_tickets": 2, |
| "sentiment": "neutral", |
| }, |
| "ideal_resolution": "fixed", |
| "sla_hours": 24, |
| "escalation_needed": False, |
| "complexity": "easy", |
| }, |
|
|
| |
| { |
| "id": "T008", |
| "subject": "Need to update payment method but can't access settings", |
| "body": ( |
| "I'm trying to update my credit card information but the payment settings page " |
| "just shows a loading spinner forever. I've tried Chrome, Firefox, and Safari. " |
| "I have a subscription renewing in 2 days and I need to update this urgently." |
| ), |
| "true_category": "account", |
| "true_priority": "high", |
| "true_tags": ["account-settings", "payment-method", "bug"], |
| "customer": { |
| "customer_id": "C1008", |
| "name": "David Kim", |
| "email": "david.kim@workmail.com", |
| "account_tier": "premium", |
| "total_orders": 32, |
| "previous_tickets": 1, |
| "sentiment": "negative", |
| }, |
| "ideal_resolution": "fixed", |
| "sla_hours": 4, |
| "escalation_needed": True, |
| "escalation_team": "engineering", |
| "complexity": "medium", |
| }, |
|
|
| |
| { |
| "id": "T009", |
| "subject": "Extremely poor customer service experience", |
| "body": ( |
| "I've been a loyal customer for 4 years and have spent thousands of dollars. " |
| "Last week I contacted support 3 times about a simple issue and got three different answers. " |
| "The agents were dismissive and one actually hung up on me. " |
| "I'm seriously considering cancelling my enterprise subscription ($4,800/year). " |
| "I need a senior manager to call me back today." |
| ), |
| "true_category": "complaint", |
| "true_priority": "urgent", |
| "true_tags": ["churn-risk", "enterprise", "escalation-needed", "vip"], |
| "customer": { |
| "customer_id": "C1009", |
| "name": "Catherine Walsh", |
| "email": "c.walsh@techcorp.io", |
| "account_tier": "enterprise", |
| "total_orders": 148, |
| "previous_tickets": 8, |
| "sentiment": "angry", |
| }, |
| "ideal_resolution": "explained", |
| "sla_hours": 1, |
| "escalation_needed": True, |
| "escalation_team": "tier2", |
| "complexity": "hard", |
| }, |
| { |
| "id": "T010", |
| "subject": "General question about international shipping", |
| "body": ( |
| "Hi, I'd like to order some items to ship to my family in Canada. " |
| "Do you ship internationally? What are the typical delivery times and any customs fees I should expect? " |
| "Thanks!" |
| ), |
| "true_category": "general", |
| "true_priority": "low", |
| "true_tags": ["international-shipping", "info-request"], |
| "customer": { |
| "customer_id": "C1010", |
| "name": "Robert Nguyen", |
| "email": "rnguyen@personal.net", |
| "account_tier": "standard", |
| "total_orders": 2, |
| "previous_tickets": 0, |
| "sentiment": "positive", |
| }, |
| "ideal_resolution": "explained", |
| "sla_hours": 48, |
| "escalation_needed": False, |
| "complexity": "easy", |
| }, |
| ] |
|
|
| |
|
|
| CUSTOMER_FOLLOW_UPS: Dict[str, List[str]] = { |
| "T001": [ |
| "Any update on my refund? It's been over an hour.", |
| "I just checked my bank and still both charges. Is this being processed?", |
| "Can you confirm the refund amount and when I'll receive it?", |
| ], |
| "T002": [ |
| "I still haven't heard back. My bank is asking if I want to dispute this.", |
| "Can you send me written confirmation that the subscription is cancelled?", |
| "How long does the refund take to process?", |
| ], |
| "T003": [ |
| "Is there any workaround? I really need to get these photos uploaded today.", |
| "Are other iOS 17 users having this issue?", |
| "When do you expect a fix?", |
| ], |
| "T004": [ |
| "I just tried again and same issue. The link expires instantly.", |
| "Can you reset my password manually for me?", |
| ], |
| "T005": [ |
| "I checked with my other neighbors too, nobody has it.", |
| "Can you file a claim with the carrier on my behalf?", |
| "I need this resolved today, it was an important item.", |
| ], |
| "T006": [ |
| "The wedding has already passed. A refund is the only acceptable solution now.", |
| "Will I also be compensated for the inconvenience?", |
| ], |
| "T007": [ |
| "Do I need to pay for return shipping?", |
| "Would you prefer I send it back or would you just send a replacement?", |
| ], |
| "T008": [ |
| "My subscription renews tomorrow! I really need this fixed today.", |
| "Can someone manually update my payment info over the phone?", |
| ], |
| "T009": [ |
| "I'm still waiting for a callback. This is getting worse.", |
| "I'd like to speak with someone in leadership, not front-line support.", |
| ], |
| "T010": [ |
| "Thanks! One more question - do you have any items that are restricted for Canada?", |
| ], |
| } |
|
|
| |
|
|
| KNOWLEDGE_BASE: Dict[str, str] = { |
| "billing_refund": ( |
| "Refunds are processed within 3-5 business days to the original payment method. " |
| "For duplicate charges, we issue the refund immediately and it appears within 1-3 days." |
| ), |
| "shipping_lost": ( |
| "For packages marked delivered but not received: (1) Check with neighbors, (2) " |
| "We file a carrier claim within 24h, (3) If unresolved in 5 days, we reship or refund." |
| ), |
| "password_reset": ( |
| "Password reset links expire after 15 minutes. If expired, request a new one. " |
| "Ensure you're clicking the MOST RECENT email. Check spam folder." |
| ), |
| "return_policy": ( |
| "30-day return window for all items. Defective items: free return shipping label provided. " |
| "Refund or replacement at customer's choice." |
| ), |
| "international_shipping": ( |
| "We ship to Canada, UK, EU, Australia. Delivery: 7-14 business days. " |
| "Customs fees vary by country and item type; customer responsibility." |
| ), |
| "subscription_cancellation": ( |
| "Cancellations take effect immediately. Refunds for unused periods granted within 7 days " |
| "of cancellation, prorated." |
| ), |
| } |
|
|
|
|
| def get_ticket_by_id(ticket_id: str) -> Dict[str, Any]: |
| """Retrieve a ticket template by its ID.""" |
| for t in TICKET_TEMPLATES: |
| if t["id"] == ticket_id: |
| return t |
| raise ValueError(f"Ticket {ticket_id} not found") |
|
|
|
|
| def get_random_ticket(complexity: str | None = None) -> Dict[str, Any]: |
| """Get a random ticket, optionally filtered by complexity.""" |
| pool = TICKET_TEMPLATES |
| if complexity: |
| pool = [t for t in pool if t.get("complexity") == complexity] |
| return random.choice(pool) |
|
|
|
|
| def get_all_ticket_ids() -> List[str]: |
| return [t["id"] for t in TICKET_TEMPLATES] |
|
|
|
|
| def compute_sla_deadline(sla_hours: int, reference_time: datetime | None = None) -> str: |
| """Compute SLA deadline string from hours.""" |
| if reference_time is None: |
| reference_time = datetime.now(timezone.utc) |
| deadline = reference_time + timedelta(hours=sla_hours) |
| return deadline.isoformat() |
|
|
|
|
| def time_to_sla_human(deadline_str: str, now: datetime | None = None) -> str: |
| """Return human-readable time until SLA deadline.""" |
| if now is None: |
| now = datetime.now(timezone.utc) |
| try: |
| deadline = datetime.fromisoformat(deadline_str) |
| if deadline.tzinfo is None: |
| deadline = deadline.replace(tzinfo=timezone.utc) |
| delta = deadline - now |
| if delta.total_seconds() < 0: |
| return "SLA BREACHED" |
| hours = int(delta.total_seconds() // 3600) |
| minutes = int((delta.total_seconds() % 3600) // 60) |
| if hours > 0: |
| return f"{hours}h {minutes}m remaining" |
| return f"{minutes}m remaining" |
| except Exception: |
| return "Unknown" |
|
|