Spaces:
Running
Running
muskan singh commited on
Commit ·
a5d93ec
1
Parent(s): ae350b5
working pipelines with proper baselineees
Browse files- baseline_scores.json +4 -4
- inference.py +9 -1
- server/apps/jira.py +249 -1
- server/apps/salesforce.py +25 -24
- server/apps/zendesk.py +279 -0
baseline_scores.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
| 1 |
{
|
| 2 |
"scores": {
|
| 3 |
-
"workflow_A": 0.
|
| 4 |
-
"workflow_B": 0.
|
| 5 |
-
"workflow_C": 0.
|
| 6 |
},
|
| 7 |
-
"average": 0.
|
| 8 |
}
|
|
|
|
| 1 |
{
|
| 2 |
"scores": {
|
| 3 |
+
"workflow_A": 0.75,
|
| 4 |
+
"workflow_B": 0.613,
|
| 5 |
+
"workflow_C": 0.722
|
| 6 |
},
|
| 7 |
+
"average": 0.695
|
| 8 |
}
|
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": "
|
| 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 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 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})"}
|