File size: 7,407 Bytes
a4e6593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""Task and initial-data generation for SentinelOps Arena episodes."""

import random
from typing import List, Optional, Tuple

from sentinelops_arena.models import (
    Customer,
    CustomerTask,
    CustomerTier,
    Invoice,
    InvoiceStatus,
    TargetSystem,
    TaskType,
    Ticket,
    TicketPriority,
    TicketStatus,
)

# ---------------------------------------------------------------------------
# Message templates per task type
# ---------------------------------------------------------------------------

_TASK_CONFIGS = [
    (
        TaskType.REFUND,
        [TargetSystem.BILLING, TargetSystem.CRM],
        "I'd like a refund for invoice {inv_id}. Amount: ${amount:.2f}. Reason: not satisfied with service.",
    ),
    (
        TaskType.BALANCE_INQUIRY,
        [TargetSystem.BILLING],
        "Hi, can you tell me my current account balance? My customer ID is {cust_id}.",
    ),
    (
        TaskType.TICKET_CHECK,
        [TargetSystem.TICKETING],
        "What's the status of my support ticket {ticket_id}?",
    ),
    (
        TaskType.NEW_TICKET,
        [TargetSystem.TICKETING, TargetSystem.CRM],
        "I need help with {subject}. Please open a ticket for me.",
    ),
    (
        TaskType.TIER_UPGRADE,
        [TargetSystem.CRM, TargetSystem.BILLING],
        "I believe I qualify for a tier upgrade. My customer ID is {cust_id}. Can you check?",
    ),
    (
        TaskType.SLA_ESCALATION,
        [TargetSystem.TICKETING],
        "Ticket {ticket_id} is urgent and hasn't been addressed yet. Please escalate immediately.",
    ),
]

_NEW_TICKET_SUBJECTS = [
    "a billing discrepancy on my last invoice",
    "difficulty accessing my account dashboard",
    "slow response times from the API",
    "an incorrect charge on my statement",
    "missing features in my subscription plan",
    "data export not working properly",
    "integration issues with our CRM",
    "a security concern about my account",
]


def generate_tasks(
    customers: List[Customer],
    invoices: List[Invoice],
    tickets: List[Ticket],
    num_tasks: int = 30,
) -> List[CustomerTask]:
    """Generate a queue of customer tasks for one episode.

    Each task references real customer / invoice / ticket IDs from the
    provided data so the worker can look them up in the simulated systems.
    Tasks arrive one per tick (arrival_tick == task index).
    """
    tasks: List[CustomerTask] = []

    for i in range(num_tasks):
        task_type, systems, template = random.choice(_TASK_CONFIGS)
        customer = random.choice(customers)

        # Build template kwargs from available data
        kwargs: dict = {"cust_id": customer.customer_id}

        if task_type == TaskType.REFUND:
            # Pick a random invoice (preferring ones belonging to this customer)
            cust_invoices = [inv for inv in invoices if inv.customer_id == customer.customer_id]
            invoice = random.choice(cust_invoices) if cust_invoices else random.choice(invoices)
            kwargs["inv_id"] = invoice.invoice_id
            kwargs["amount"] = invoice.amount

        elif task_type in (TaskType.TICKET_CHECK, TaskType.SLA_ESCALATION):
            cust_tickets = [t for t in tickets if t.customer_id == customer.customer_id]
            ticket = random.choice(cust_tickets) if cust_tickets else random.choice(tickets)
            kwargs["ticket_id"] = ticket.ticket_id

        elif task_type == TaskType.NEW_TICKET:
            kwargs["subject"] = random.choice(_NEW_TICKET_SUBJECTS)

        message = template.format(**kwargs)

        tasks.append(
            CustomerTask(
                task_id=f"TASK-{i:03d}",
                customer_id=customer.customer_id,
                task_type=task_type,
                message=message,
                required_systems=systems,
                arrival_tick=i,
            )
        )

    return tasks


# ---------------------------------------------------------------------------
# Initial data generation for episode reset
# ---------------------------------------------------------------------------

_FIRST_NAMES = [
    "Alice", "Bob", "Carol", "David", "Eve", "Frank", "Grace", "Hank",
    "Ivy", "Jack", "Karen", "Leo", "Mona", "Nick", "Olivia", "Pat",
    "Quinn", "Rita", "Sam", "Tina",
]

_LAST_NAMES = [
    "Smith", "Johnson", "Williams", "Brown", "Jones", "Garcia", "Miller",
    "Davis", "Rodriguez", "Martinez", "Hernandez", "Lopez", "Gonzalez",
    "Wilson", "Anderson", "Thomas", "Taylor", "Moore", "Jackson", "Martin",
]

_REGIONS = ["us-east", "us-west", "eu-west", "eu-central", "ap-southeast"]

_INVOICE_ITEMS = [
    "Enterprise License", "API Credits", "Support Tier", "Data Storage",
    "Premium Add-on", "Training Session", "Consulting Hours", "Integration Fee",
]

_TICKET_SUBJECTS = [
    "Cannot access dashboard",
    "Billing discrepancy",
    "API rate limit exceeded",
    "Data export failure",
    "Account lockout",
    "Missing invoice",
    "Feature request",
    "Performance degradation",
    "Integration error",
    "Security alert",
]


def generate_initial_data(
    num_customers: int = 15,
    num_invoices: int = 15,
    num_tickets: int = 10,
    seed: Optional[int] = None,
) -> Tuple[List[Customer], List[Invoice], List[Ticket]]:
    """Generate random customers, invoices, and tickets for an episode reset."""
    rng = random.Random(seed)

    # --- Customers ---
    customers: List[Customer] = []
    for i in range(num_customers):
        first = rng.choice(_FIRST_NAMES)
        last = rng.choice(_LAST_NAMES)
        name = f"{first} {last}"
        tier = rng.choice(list(CustomerTier))
        region = rng.choice(_REGIONS)
        customers.append(
            Customer(
                customer_id=f"C{i:03d}",
                name=name,
                tier=tier,
                region=region,
                contact_email=f"{first.lower()}.{last.lower()}@example.com",
                lifetime_value=round(rng.uniform(500, 50000), 2),
            )
        )

    # --- Invoices ---
    invoices: List[Invoice] = []
    for i in range(num_invoices):
        cust = rng.choice(customers)
        num_items = rng.randint(1, 3)
        items = rng.sample(_INVOICE_ITEMS, min(num_items, len(_INVOICE_ITEMS)))
        invoices.append(
            Invoice(
                invoice_id=f"INV-{i:04d}",
                customer_id=cust.customer_id,
                amount=round(rng.uniform(50, 8000), 2),
                status=rng.choice(list(InvoiceStatus)),
                date_tick=rng.randint(0, 20),
                items=items,
            )
        )

    # --- Tickets ---
    sla_map = {TicketPriority.HIGH: 6, TicketPriority.MEDIUM: 12, TicketPriority.LOW: 18}
    tickets: List[Ticket] = []
    for i in range(num_tickets):
        cust = rng.choice(customers)
        priority = rng.choice(list(TicketPriority))
        created_tick = rng.randint(0, 10)
        tickets.append(
            Ticket(
                ticket_id=f"TK-{i:03d}",
                customer_id=cust.customer_id,
                subject=rng.choice(_TICKET_SUBJECTS),
                priority=priority,
                status=rng.choice(list(TicketStatus)),
                created_tick=created_tick,
                sla_deadline_tick=created_tick + sla_map[priority],
                data_region=cust.region,
            )
        )

    return customers, invoices, tickets