srishtichugh commited on
Commit
934f824
·
2 Parent(s): d05687ca5d93ec

Merge: combine upgrade ui + working pipelines with proper baselines

Browse files
inference.py CHANGED
@@ -65,7 +65,8 @@ Available apps and key operations:
65
  jira: get_issue, create_issue, update_status, set_priority, assign_owner,
66
  add_label, link_zendesk_ticket, close_issue, list_issues
67
  zendesk: get_ticket, acknowledge_ticket, set_urgency, assign_agent,
68
- escalate_to_jira, resolve_ticket, add_note, list_tickets
 
69
  salesforce: get_account, list_accounts, update_deal_stage, flag_churn_risk,
70
  assign_account_owner, log_interaction, get_opportunity
71
  workday: get_employee, list_employees, provision_access, log_sla_event,
@@ -84,6 +85,7 @@ Example actions:
84
  {"app": "jira", "operation": "create_issue", "args": {"title": "Bug fix for ACME-001", "linked_zendesk": "ZD-001"}}
85
  {"app": "salesforce", "operation": "get_account", "args": {"account_id": "ACME-001"}}
86
  {"app": "workday", "operation": "log_sla_event", "args": {"ticket_id": "ZD-001", "sla_met": true}}
 
87
  """
88
 
89
  WORKFLOW_NAMES = {
@@ -198,8 +200,14 @@ def run_workflow(workflow_id: str) -> float:
198
  history.append({"role": "user", "content": obs_text})
199
 
200
  # Trim history to avoid context overflow
 
 
 
201
  if len(history) > 20:
202
  history = history[-20:]
 
 
 
203
 
204
  try:
205
  response = llm_client.chat.completions.create(
 
65
  jira: get_issue, create_issue, update_status, set_priority, assign_owner,
66
  add_label, link_zendesk_ticket, close_issue, list_issues
67
  zendesk: get_ticket, acknowledge_ticket, set_urgency, assign_agent,
68
+ escalate_to_jira, resolve_ticket, add_note, list_tickets,
69
+ create_agent_profile
70
  salesforce: get_account, list_accounts, update_deal_stage, flag_churn_risk,
71
  assign_account_owner, log_interaction, get_opportunity
72
  workday: get_employee, list_employees, provision_access, log_sla_event,
 
85
  {"app": "jira", "operation": "create_issue", "args": {"title": "Bug fix for ACME-001", "linked_zendesk": "ZD-001"}}
86
  {"app": "salesforce", "operation": "get_account", "args": {"account_id": "ACME-001"}}
87
  {"app": "workday", "operation": "log_sla_event", "args": {"ticket_id": "ZD-001", "sla_met": true}}
88
+ {"app": "zendesk", "operation": "create_agent_profile", "args": {"employee_id": "EMP-NEW-001", "email": "jordan.riley@company.com", "name": "Jordan Riley"}}
89
  """
90
 
91
  WORKFLOW_NAMES = {
 
200
  history.append({"role": "user", "content": obs_text})
201
 
202
  # Trim history to avoid context overflow
203
+ # if len(history) > 20:
204
+ # history = history[-20:]
205
+ # Trim history — always keep an even number so roles alternate correctly
206
  if len(history) > 20:
207
  history = history[-20:]
208
+ # Ensure history starts with a user message (Gemma requires strict alternation)
209
+ if history and history[0]["role"] != "user":
210
+ history = history[1:]
211
 
212
  try:
213
  response = llm_client.chat.completions.create(
server/apps/jira.py CHANGED
@@ -191,9 +191,13 @@ class JiraApp(BaseApp):
191
 
192
  assignee = (kwargs.get("assignee") or kwargs.get("owner")
193
  or kwargs.get("assigned_to"))
 
 
 
194
  if not assignee:
 
195
  return {"success": False,
196
- "message": "Provide assignee / owner / assigned_to value"}
197
 
198
  rec["assignee"] = assignee
199
  self._assigned_issues.add(issue_id)
@@ -241,3 +245,247 @@ class JiraApp(BaseApp):
241
  return {"success": True, "data": compact,
242
  "message": f"Found {len(compact)} {status} issues"
243
  + (f" for {customer_id}" if customer_id else "")}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
 
192
  assignee = (kwargs.get("assignee") or kwargs.get("owner")
193
  or kwargs.get("assigned_to"))
194
+ # if not assignee:
195
+ # return {"success": False,
196
+ # "message": "Provide assignee / owner / assigned_to value"}
197
  if not assignee:
198
+ correct_field = self._drift.translate_field("assignee", self.APP_NAME)
199
  return {"success": False,
200
+ "message": f"Missing assignee field. Use '{correct_field}' as the arg key for this episode."}
201
 
202
  rec["assignee"] = assignee
203
  self._assigned_issues.add(issue_id)
 
245
  return {"success": True, "data": compact,
246
  "message": f"Found {len(compact)} {status} issues"
247
  + (f" for {customer_id}" if customer_id else "")}
248
+ # """Jira-like app — engineering ticket management."""
249
+
250
+ # from typing import Dict, List, Optional
251
+ # from server.apps.base_app import BaseApp
252
+ # from server.schema_drift import SchemaDriftEngine
253
+
254
+
255
+ # class JiraApp(BaseApp):
256
+ # APP_NAME = "jira"
257
+
258
+ # OPERATIONS = [
259
+ # "get_issue", "create_issue", "update_status", "set_priority",
260
+ # "assign_owner", "add_label", "link_zendesk_ticket", "close_issue", "list_issues",
261
+ # ]
262
+
263
+ # def __init__(self, drift: SchemaDriftEngine):
264
+ # super().__init__(drift)
265
+ # self._records: Dict[str, Dict] = {}
266
+ # # Workflow completion state tracking
267
+ # self._linked_issues: set = set() # issue_ids linked to a Zendesk ticket
268
+ # self._assigned_issues: set = set() # issue_ids with a non-null assignee
269
+ # self._bugs_checked: bool = False # list_issues was called (Workflow C)
270
+
271
+ # # ------------------------------------------------------------------
272
+ # # BaseApp interface
273
+ # # ------------------------------------------------------------------
274
+
275
+ # def initialize(self, records: List[Dict]) -> None:
276
+ # self._records = {r["issue_id"]: r for r in records}
277
+ # self._linked_issues.clear()
278
+ # self._assigned_issues.clear()
279
+ # self._bugs_checked = False
280
+ # # Seed state from loaded data
281
+ # for issue_id, rec in self._records.items():
282
+ # if rec.get("assignee"):
283
+ # self._assigned_issues.add(issue_id)
284
+ # if rec.get("linked_zendesk"):
285
+ # self._linked_issues.add(issue_id)
286
+
287
+ # def execute(self, operation: str, args: Dict) -> Dict:
288
+ # method = getattr(self, f"_op_{operation}", None)
289
+ # if method is None:
290
+ # return {
291
+ # "success": False,
292
+ # "message": f"Unknown operation '{operation}'. Available: {', '.join(self.OPERATIONS)}",
293
+ # }
294
+ # try:
295
+ # return method(**args)
296
+ # except TypeError as exc:
297
+ # return {"success": False, "message": f"Bad args for '{operation}': {exc}"}
298
+
299
+ # def get_state_view(self, max_rows: int = 5) -> str:
300
+ # open_issues = [r for r in self._records.values()
301
+ # if r.get("status") not in ("closed",)][:max_rows]
302
+ # if not open_issues:
303
+ # return "No open issues."
304
+ # lines = []
305
+ # for rec in open_issues:
306
+ # view = self._to_agent_view(rec)
307
+ # keep = ["issue_id", "title",
308
+ # "priority", "severity", "urgency_level",
309
+ # "assignee", "owner", "assigned_to",
310
+ # "status", "state", "current_state",
311
+ # "customer_id", "linked_zendesk"]
312
+ # compact = {k: v for k, v in view.items() if k in keep and v is not None}
313
+ # lines.append(str(compact))
314
+ # return "\n".join(lines)
315
+
316
+ # def count_open_items(self) -> int:
317
+ # return sum(1 for r in self._records.values() if r.get("status") != "closed")
318
+
319
+ # # ------------------------------------------------------------------
320
+ # # Workflow completion state checks
321
+ # # ------------------------------------------------------------------
322
+
323
+ # def has_linked_issue(self) -> bool:
324
+ # """True once any issue is linked to a Zendesk ticket (Workflow A step A2)."""
325
+ # return len(self._linked_issues) > 0
326
+
327
+ # def issue_assigned(self) -> bool:
328
+ # """True once JIRA-001 (primary bug) has an assignee (Workflow A step A4)."""
329
+ # return bool(self._records.get("JIRA-001", {}).get("assignee"))
330
+
331
+ # def bugs_checked(self) -> bool:
332
+ # """True once list_issues has been called (Workflow C step C3)."""
333
+ # return self._bugs_checked
334
+
335
+ # # ------------------------------------------------------------------
336
+ # # Operations
337
+ # # ------------------------------------------------------------------
338
+
339
+ # def _op_get_issue(self, issue_id: str) -> Dict:
340
+ # rec = self._records.get(issue_id)
341
+ # if not rec:
342
+ # return {"success": False, "message": f"Issue {issue_id} not found. Use list_issues to browse."}
343
+ # return {"success": True, "data": self._to_agent_view(rec),
344
+ # "message": f"Retrieved {issue_id}"}
345
+
346
+ # def _op_create_issue(self, title: str, **kwargs) -> Dict:
347
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
348
+ # if schema_error:
349
+ # return {
350
+ # "success": False,
351
+ # "schema_error": schema_error,
352
+ # "message": (f"Schema error: field '{schema_error}' is not in the current schema. "
353
+ # f"Check schema_hints for the correct field name."),
354
+ # }
355
+
356
+ # issue_id = f"JIRA-{len(self._records) + 1:03d}"
357
+ # # Accept both canonical and drifted names for priority / assignee
358
+ # priority = (kwargs.get("priority") or kwargs.get("severity")
359
+ # or kwargs.get("urgency_level", "p2"))
360
+ # linked = kwargs.get("linked_zendesk") or kwargs.get("zendesk_ticket")
361
+
362
+ # rec = {
363
+ # "issue_id": issue_id,
364
+ # "title": title,
365
+ # "priority": priority,
366
+ # "assignee": kwargs.get("assignee") or kwargs.get("owner") or kwargs.get("assigned_to"),
367
+ # "status": "open",
368
+ # "reporter": kwargs.get("reporter", "agent"),
369
+ # "customer_id": kwargs.get("customer_id"),
370
+ # "linked_zendesk": linked,
371
+ # "labels": [],
372
+ # "created_at": "2026-04-21T09:00:00",
373
+ # }
374
+ # self._records[issue_id] = rec
375
+
376
+ # if linked:
377
+ # self._linked_issues.add(issue_id)
378
+ # if rec["assignee"]:
379
+ # self._assigned_issues.add(issue_id)
380
+
381
+ # return {
382
+ # "success": True,
383
+ # "data": {"issue_id": issue_id},
384
+ # "schema_adapted": schema_adapted,
385
+ # "message": f"Created {issue_id}: '{title}'"
386
+ # + (f" linked to {linked}" if linked else ""),
387
+ # }
388
+
389
+ # def _op_update_status(self, issue_id: str, **kwargs) -> Dict:
390
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
391
+ # if schema_error:
392
+ # return {"success": False, "schema_error": schema_error,
393
+ # "message": f"Schema error: use current field name, not '{schema_error}'"}
394
+
395
+ # rec = self._records.get(issue_id)
396
+ # if not rec:
397
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
398
+
399
+ # new_status = (kwargs.get("status") or kwargs.get("state")
400
+ # or kwargs.get("current_state"))
401
+ # if not new_status:
402
+ # return {"success": False, "message": "Provide status/state/current_state value"}
403
+
404
+ # rec["status"] = new_status
405
+ # return {"success": True, "schema_adapted": schema_adapted,
406
+ # "message": f"{issue_id} status → '{new_status}'"}
407
+
408
+ # def _op_set_priority(self, issue_id: str, **kwargs) -> Dict:
409
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
410
+ # if schema_error:
411
+ # return {"success": False, "schema_error": schema_error,
412
+ # "message": f"Schema error: '{schema_error}' is a stale field name"}
413
+
414
+ # rec = self._records.get(issue_id)
415
+ # if not rec:
416
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
417
+
418
+ # new_priority = (kwargs.get("priority") or kwargs.get("severity")
419
+ # or kwargs.get("urgency_level"))
420
+ # if not new_priority:
421
+ # return {"success": False,
422
+ # "message": "Provide priority / severity / urgency_level value"}
423
+
424
+ # rec["priority"] = new_priority
425
+ # return {"success": True, "schema_adapted": schema_adapted,
426
+ # "message": f"{issue_id} priority → '{new_priority}'"}
427
+
428
+ # def _op_assign_owner(self, issue_id: str, **kwargs) -> Dict:
429
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
430
+ # if schema_error:
431
+ # hint = self._drift.translate_field("assignee", self.APP_NAME)
432
+ # return {"success": False, "schema_error": schema_error,
433
+ # "message": f"Schema error: use '{hint}' instead of '{schema_error}'"}
434
+
435
+ # rec = self._records.get(issue_id)
436
+ # if not rec:
437
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
438
+
439
+ # assignee = (kwargs.get("assignee") or kwargs.get("owner")
440
+ # or kwargs.get("assigned_to"))
441
+ # if not assignee:
442
+ # correct_field = self._drift.translate_field("assignee", self.APP_NAME)
443
+ # return {"success": False,
444
+ # "message": f"Missing assignee field. Use '{correct_field}' as the arg key for this episode."}
445
+
446
+ # rec["assignee"] = assignee
447
+ # self._assigned_issues.add(issue_id)
448
+ # return {"success": True, "schema_adapted": schema_adapted,
449
+ # "message": f"{issue_id} assigned to '{assignee}'"}
450
+
451
+ # def _op_add_label(self, issue_id: str, label: str) -> Dict:
452
+ # rec = self._records.get(issue_id)
453
+ # if not rec:
454
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
455
+ # rec.setdefault("labels", []).append(label)
456
+ # return {"success": True, "message": f"Added label '{label}' to {issue_id}"}
457
+
458
+ # def _op_link_zendesk_ticket(self, issue_id: str, zendesk_ticket_number: str) -> Dict:
459
+ # rec = self._records.get(issue_id)
460
+ # if not rec:
461
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
462
+ # rec["linked_zendesk"] = zendesk_ticket_number
463
+ # self._linked_issues.add(issue_id)
464
+ # return {"success": True,
465
+ # "message": f"Linked {issue_id} ↔ Zendesk {zendesk_ticket_number}"}
466
+
467
+ # def _op_close_issue(self, issue_id: str) -> Dict:
468
+ # rec = self._records.get(issue_id)
469
+ # if not rec:
470
+ # return {"success": False, "message": f"Issue {issue_id} not found"}
471
+ # rec["status"] = "closed"
472
+ # return {"success": True, "message": f"Closed {issue_id}"}
473
+
474
+ # def _op_list_issues(self, status: str = "open", customer_id: Optional[str] = None,
475
+ # limit: int = 10) -> Dict:
476
+ # self._bugs_checked = True
477
+ # matching = [
478
+ # r for r in self._records.values()
479
+ # if (status == "all" or r.get("status") == status)
480
+ # and (customer_id is None or r.get("customer_id") == customer_id)
481
+ # ][:limit]
482
+ # drifted = [self._to_agent_view(r) for r in matching]
483
+ # keep = ["issue_id", "title", "priority", "severity", "urgency_level",
484
+ # "assignee", "owner", "assigned_to",
485
+ # "status", "state", "current_state",
486
+ # "customer_id", "linked_zendesk"]
487
+ # compact = [{k: v for k, v in r.items() if k in keep and v is not None}
488
+ # for r in drifted]
489
+ # return {"success": True, "data": compact,
490
+ # "message": f"Found {len(compact)} {status} issues"
491
+ # + (f" for {customer_id}" if customer_id else "")}
server/apps/salesforce.py CHANGED
@@ -149,30 +149,31 @@ class SalesforceApp(BaseApp):
149
  }
150
 
151
  def _op_assign_account_owner(self, account_id: str, **kwargs) -> Dict:
152
- schema_error, schema_adapted = self._check_schema_drift(kwargs)
153
- if schema_error:
154
- hint = self._drift.translate_field("owner", self.APP_NAME)
155
- return {"success": False, "schema_error": schema_error,
156
- "message": f"Schema error: use '{hint}' not '{schema_error}'"}
157
-
158
- rec = self._records.get(account_id)
159
- if not rec:
160
- return {"success": False, "message": f"Account {account_id} not found"}
161
-
162
- new_owner = (kwargs.get("owner") or kwargs.get("owner_name")
163
- or kwargs.get("account_owner") or kwargs.get("rep_email"))
164
- if not new_owner:
165
- return {"success": False,
166
- "message": "Provide owner / owner_name / account_owner / rep_email"}
167
-
168
- rec["owner"] = new_owner
169
- rec["_team_assigned"] = True
170
- if account_id == "ACME-003":
171
- rec["_intervention_assigned"] = True
172
-
173
- return {"success": True, "schema_adapted": schema_adapted,
174
- "message": f"{account_id} owner → '{new_owner}'"}
175
-
 
176
  def _op_log_interaction(self, account_id: str, note: str = "") -> Dict:
177
  rec = self._records.get(account_id)
178
  if not rec:
 
149
  }
150
 
151
  def _op_assign_account_owner(self, account_id: str, **kwargs) -> Dict:
152
+ schema_error, schema_adapted = self._check_schema_drift(kwargs)
153
+ if schema_error:
154
+ hint = self._drift.translate_field("owner", self.APP_NAME)
155
+ return {"success": False, "schema_error": schema_error,
156
+ "message": f"Schema error: use '{hint}' not '{schema_error}'"}
157
+
158
+ rec = self._records.get(account_id)
159
+ if not rec:
160
+ return {"success": False, "message": f"Account {account_id} not found"}
161
+
162
+ new_owner = (kwargs.get("owner") or kwargs.get("owner_name")
163
+ or kwargs.get("account_owner") or kwargs.get("rep_email"))
164
+ if not new_owner:
165
+ correct_field = self._drift.translate_field("owner", self.APP_NAME)
166
+ return {"success": False,
167
+ "message": f"Missing owner field. Use '{correct_field}' as the arg key for this episode."}
168
+
169
+ rec["owner"] = new_owner
170
+ rec["_team_assigned"] = True
171
+ if account_id == "ACME-003":
172
+ rec["_intervention_assigned"] = True
173
+
174
+ return {"success": True, "schema_adapted": schema_adapted,
175
+ "message": f"{account_id} owner → '{new_owner}'"}
176
+
177
  def _op_log_interaction(self, account_id: str, note: str = "") -> Dict:
178
  rec = self._records.get(account_id)
179
  if not rec:
server/apps/zendesk.py CHANGED
@@ -236,3 +236,282 @@ class ZendeskApp(BaseApp):
236
  "message": f"Found {len(compact)} {state} tickets"
237
  + (f" for {customer_id}" if customer_id else ""),
238
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
236
  "message": f"Found {len(compact)} {state} tickets"
237
  + (f" for {customer_id}" if customer_id else ""),
238
  }
239
+
240
+ def _op_create_agent_profile(self, employee_id: str, email: str,
241
+ name: Optional[str] = None, **kwargs) -> Dict:
242
+ """Create a Zendesk support agent profile (Workflow B step B4)."""
243
+ if employee_id in self._records:
244
+ return {"success": False,
245
+ "message": f"Profile for {employee_id} already exists"}
246
+ profile = {
247
+ "ticket_number": employee_id,
248
+ "title": f"Agent profile — {name or employee_id}",
249
+ "urgency": "p3",
250
+ "agent_email": email,
251
+ "state": "closed",
252
+ "customer_id": None,
253
+ "_acknowledged": False,
254
+ "_queried_accounts": [],
255
+ "_profile_created": True,
256
+ }
257
+ self._records[employee_id] = profile
258
+ return {"success": True,
259
+ "message": f"Created Zendesk agent profile for {name or employee_id} ({email})"}
260
+ # """Zendesk-like app — customer support ticket management."""
261
+
262
+ # from typing import Dict, List, Optional
263
+ # from server.apps.base_app import BaseApp
264
+ # from server.schema_drift import SchemaDriftEngine
265
+
266
+
267
+ # class ZendeskApp(BaseApp):
268
+ # APP_NAME = "zendesk"
269
+
270
+ # OPERATIONS = [
271
+ # "get_ticket", "acknowledge_ticket", "set_urgency", "assign_agent",
272
+ # "escalate_to_jira", "resolve_ticket", "add_note", "list_tickets","create_agent_profile",
273
+ # ]
274
+
275
+ # def __init__(self, drift: SchemaDriftEngine):
276
+ # super().__init__(drift)
277
+ # self._records: Dict[str, Dict] = {}
278
+
279
+ # # ------------------------------------------------------------------
280
+ # # BaseApp interface
281
+ # # ------------------------------------------------------------------
282
+
283
+ # def initialize(self, records: List[Dict]) -> None:
284
+ # self._records = {r["ticket_number"]: r for r in records}
285
+
286
+ # def execute(self, operation: str, args: Dict) -> Dict:
287
+ # method = getattr(self, f"_op_{operation}", None)
288
+ # if method is None:
289
+ # return {
290
+ # "success": False,
291
+ # "message": f"Unknown operation '{operation}'. Available: {', '.join(self.OPERATIONS)}",
292
+ # }
293
+ # try:
294
+ # return method(**args)
295
+ # except TypeError as exc:
296
+ # return {"success": False, "message": f"Bad args for '{operation}': {exc}"}
297
+
298
+ # def get_state_view(self, max_rows: int = 5) -> str:
299
+ # open_tickets = [r for r in self._records.values()
300
+ # if r.get("state") not in ("resolved", "closed")][:max_rows]
301
+ # if not open_tickets:
302
+ # return "No open tickets."
303
+ # lines = []
304
+ # for rec in open_tickets:
305
+ # view = self._to_agent_view(rec)
306
+ # keep = ["ticket_number", "title",
307
+ # "urgency", "priority", "impact_level",
308
+ # "agent_email", "handler", "assigned_agent",
309
+ # "state", "ticket_state", "resolution_status",
310
+ # "customer_id"]
311
+ # compact = {k: v for k, v in view.items() if k in keep and v is not None}
312
+ # lines.append(str(compact))
313
+ # return "\n".join(lines)
314
+
315
+ # def count_open_items(self) -> int:
316
+ # return sum(1 for r in self._records.values()
317
+ # if r.get("state") not in ("resolved", "closed"))
318
+
319
+ # # ------------------------------------------------------------------
320
+ # # Workflow completion state checks
321
+ # # ------------------------------------------------------------------
322
+
323
+ # def ticket_acknowledged(self) -> bool:
324
+ # """True once ZD-001 has been acknowledged (Workflow A step A1)."""
325
+ # return bool(self._records.get("ZD-001", {}).get("_acknowledged"))
326
+
327
+ # def support_queried(self, account_id: str) -> bool:
328
+ # """True once tickets for account_id were listed (Workflow C step C2)."""
329
+ # return account_id in self._records.get("ZD-001", {}).get("_queried_accounts", []) or \
330
+ # any(account_id in r.get("_queried_accounts", []) for r in self._records.values())
331
+
332
+ # def profile_created(self) -> bool:
333
+ # """True once a new agent profile was created (Workflow B step B4)."""
334
+ # return any(r.get("_profile_created") for r in self._records.values())
335
+
336
+ # # ------------------------------------------------------------------
337
+ # # Operations
338
+ # # ------------------------------------------------------------------
339
+
340
+ # def _op_get_ticket(self, ticket_number: str, customer_id: Optional[str] = None) -> Dict:
341
+ # # If customer_id provided, look up all tickets for that customer
342
+ # if customer_id:
343
+ # matching = [r for r in self._records.values()
344
+ # if r.get("customer_id") == customer_id]
345
+ # # Mark as queried for Workflow C
346
+ # for r in matching:
347
+ # r.setdefault("_queried_accounts", [])
348
+ # if customer_id not in r["_queried_accounts"]:
349
+ # r["_queried_accounts"].append(customer_id)
350
+ # if not matching:
351
+ # return {"success": True, "data": [],
352
+ # "message": f"No tickets found for customer {customer_id}"}
353
+ # return {
354
+ # "success": True,
355
+ # "data": [self._to_agent_view(r) for r in matching[:5]],
356
+ # "message": f"Found {len(matching)} tickets for {customer_id}",
357
+ # }
358
+
359
+ # rec = self._records.get(ticket_number)
360
+ # if not rec:
361
+ # return {"success": False,
362
+ # "message": f"Ticket {ticket_number} not found. Use list_tickets to browse."}
363
+ # rec.setdefault("_queried_accounts", [])
364
+ # cid = rec.get("customer_id")
365
+ # if cid and cid not in rec["_queried_accounts"]:
366
+ # rec["_queried_accounts"].append(cid)
367
+
368
+ # return {"success": True, "data": self._to_agent_view(rec),
369
+ # "ticket": rec,
370
+ # "message": f"Retrieved {ticket_number}"}
371
+
372
+ # def _op_acknowledge_ticket(self, ticket_number: str) -> Dict:
373
+ # rec = self._records.get(ticket_number)
374
+ # if not rec:
375
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
376
+ # rec["_acknowledged"] = True
377
+ # if rec.get("state") == "new":
378
+ # rec["state"] = "open"
379
+ # return {"success": True, "ticket": rec,
380
+ # "message": f"Acknowledged {ticket_number} — status → open"}
381
+
382
+ # def _op_set_urgency(self, ticket_number: str, **kwargs) -> Dict:
383
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
384
+ # if schema_error:
385
+ # hint = self._drift.translate_field("urgency", self.APP_NAME)
386
+ # return {"success": False, "schema_error": schema_error,
387
+ # "message": f"Schema error: use '{hint}' not '{schema_error}'"}
388
+
389
+ # rec = self._records.get(ticket_number)
390
+ # if not rec:
391
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
392
+
393
+ # new_urgency = (kwargs.get("urgency") or kwargs.get("priority")
394
+ # or kwargs.get("impact_level"))
395
+ # if not new_urgency:
396
+ # return {"success": False,
397
+ # "message": "Provide urgency / priority / impact_level value"}
398
+
399
+ # rec["urgency"] = new_urgency
400
+ # return {"success": True, "schema_adapted": schema_adapted,
401
+ # "message": f"{ticket_number} urgency → '{new_urgency}'"}
402
+
403
+ # def _op_assign_agent(self, ticket_number: str, **kwargs) -> Dict:
404
+ # schema_error, schema_adapted = self._check_schema_drift(kwargs)
405
+ # if schema_error:
406
+ # hint = self._drift.translate_field("agent_email", self.APP_NAME)
407
+ # return {"success": False, "schema_error": schema_error,
408
+ # "message": f"Schema error: use '{hint}' not '{schema_error}'"}
409
+
410
+ # rec = self._records.get(ticket_number)
411
+ # # For Workflow B profile creation: allow creating a new agent entry
412
+ # if not rec:
413
+ # # Create a minimal profile record for the new agent
414
+ # email = (kwargs.get("agent_email") or kwargs.get("handler")
415
+ # or kwargs.get("assigned_agent"))
416
+ # if not email:
417
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
418
+ # # Create a synthetic profile ticket
419
+ # profile_rec = {
420
+ # "ticket_number": ticket_number,
421
+ # "title": "Agent profile",
422
+ # "urgency": "p3",
423
+ # "agent_email": email,
424
+ # "state": "closed",
425
+ # "customer_id": None,
426
+ # "_acknowledged": False,
427
+ # "_queried_accounts": [],
428
+ # "_profile_created": True,
429
+ # }
430
+ # self._records[ticket_number] = profile_rec
431
+ # return {"success": True, "schema_adapted": schema_adapted,
432
+ # "message": f"Created Zendesk profile for agent '{email}'"}
433
+
434
+ # email = (kwargs.get("agent_email") or kwargs.get("handler")
435
+ # or kwargs.get("assigned_agent"))
436
+ # if not email:
437
+ # return {"success": False,
438
+ # "message": "Provide agent_email / handler / assigned_agent value"}
439
+
440
+ # rec["agent_email"] = email
441
+ # rec["_profile_created"] = True
442
+ # return {"success": True, "schema_adapted": schema_adapted,
443
+ # "message": f"{ticket_number} assigned to '{email}'"}
444
+
445
+ # def _op_escalate_to_jira(self, ticket_number: str,
446
+ # jira_issue_id: Optional[str] = None) -> Dict:
447
+ # rec = self._records.get(ticket_number)
448
+ # if not rec:
449
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
450
+ # rec["state"] = "pending"
451
+ # rec["escalated_to_jira"] = jira_issue_id or "pending"
452
+ # return {"success": True,
453
+ # "message": f"{ticket_number} escalated to Jira"
454
+ # + (f" ({jira_issue_id})" if jira_issue_id else "")}
455
+
456
+ # def _op_resolve_ticket(self, ticket_number: str) -> Dict:
457
+ # rec = self._records.get(ticket_number)
458
+ # if not rec:
459
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
460
+ # rec["state"] = "resolved"
461
+ # return {"success": True, "message": f"{ticket_number} resolved"}
462
+
463
+ # def _op_add_note(self, ticket_number: str, note: str) -> Dict:
464
+ # rec = self._records.get(ticket_number)
465
+ # if not rec:
466
+ # return {"success": False, "message": f"Ticket {ticket_number} not found"}
467
+ # rec.setdefault("notes", []).append(note)
468
+ # return {"success": True, "message": f"Note added to {ticket_number}"}
469
+
470
+ # def _op_list_tickets(self, state: str = "open", customer_id: Optional[str] = None,
471
+ # limit: int = 10) -> Dict:
472
+ # matching = [
473
+ # r for r in self._records.values()
474
+ # if (state == "all" or r.get("state") == state)
475
+ # and (customer_id is None or r.get("customer_id") == customer_id)
476
+ # ][:limit]
477
+ # # Mark accounts as queried
478
+ # if customer_id:
479
+ # for r in matching:
480
+ # r.setdefault("_queried_accounts", [])
481
+ # if customer_id not in r["_queried_accounts"]:
482
+ # r["_queried_accounts"].append(customer_id)
483
+
484
+ # drifted = [self._to_agent_view(r) for r in matching]
485
+ # keep = ["ticket_number", "title",
486
+ # "urgency", "priority", "impact_level",
487
+ # "agent_email", "handler", "assigned_agent",
488
+ # "state", "ticket_state", "resolution_status",
489
+ # "customer_id"]
490
+ # compact = [{k: v for k, v in r.items() if k in keep and v is not None}
491
+ # for r in drifted]
492
+ # return {
493
+ # "success": True,
494
+ # "data": compact,
495
+ # "message": f"Found {len(compact)} {state} tickets"
496
+ # + (f" for {customer_id}" if customer_id else ""),
497
+ # }
498
+ # def _op_create_agent_profile(self, employee_id: str, email: str,
499
+ # name: Optional[str] = None, **kwargs) -> Dict:
500
+ # """Create a Zendesk support agent profile (Workflow B step B4)."""
501
+ # if employee_id in self._records:
502
+ # return {"success": False,
503
+ # "message": f"Profile for {employee_id} already exists"}
504
+ # profile = {
505
+ # "ticket_number": employee_id,
506
+ # "title": f"Agent profile — {name or employee_id}",
507
+ # "urgency": "p3",
508
+ # "agent_email": email,
509
+ # "state": "closed",
510
+ # "customer_id": None,
511
+ # "_acknowledged": False,
512
+ # "_queried_accounts": [],
513
+ # "_profile_created": True,
514
+ # }
515
+ # self._records[employee_id] = profile
516
+ # return {"success": True,
517
+ # "message": f"Created Zendesk agent profile for {name or employee_id} ({email})"}