File size: 11,896 Bytes
2305b9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03d30a6
 
 
 
 
2305b9f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a5d93ec
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
03d30a6
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
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
"""Zendesk-like app — customer support ticket management."""

from typing import Dict, List, Optional
from server.apps.base_app import BaseApp
from server.schema_drift import SchemaDriftEngine


class ZendeskApp(BaseApp):
    APP_NAME = "zendesk"

    OPERATIONS = [
        "get_ticket", "acknowledge_ticket", "set_urgency", "assign_agent",
        "escalate_to_jira", "resolve_ticket", "add_note", "list_tickets",
    ]

    def __init__(self, drift: SchemaDriftEngine):
        super().__init__(drift)
        self._records: Dict[str, Dict] = {}

    # ------------------------------------------------------------------
    # BaseApp interface
    # ------------------------------------------------------------------

    def initialize(self, records: List[Dict]) -> None:
        self._records = {r["ticket_number"]: r for r in records}

    def execute(self, operation: str, args: Dict) -> Dict:
        method = getattr(self, f"_op_{operation}", None)
        if method is None:
            return {
                "success": False,
                "message": f"Unknown operation '{operation}'. Available: {', '.join(self.OPERATIONS)}",
            }
        try:
            return method(**args)
        except TypeError as exc:
            return {"success": False, "message": f"Bad args for '{operation}': {exc}"}

    def get_state_view(self, max_rows: int = 5) -> str:
        open_tickets = [r for r in self._records.values()
                        if r.get("state") not in ("resolved", "closed")][:max_rows]
        if not open_tickets:
            return "No open tickets."
        lines = []
        for rec in open_tickets:
            view = self._to_agent_view(rec)
            keep = ["ticket_number", "title",
                    "urgency", "priority", "impact_level",
                    "agent_email", "handler", "assigned_agent",
                    "state", "ticket_state", "resolution_status",
                    "customer_id"]
            compact = {k: v for k, v in view.items() if k in keep and v is not None}
            lines.append(str(compact))
        return "\n".join(lines)

    def count_open_items(self) -> int:
        return sum(1 for r in self._records.values()
                   if r.get("state") not in ("resolved", "closed"))

    # ------------------------------------------------------------------
    # Workflow completion state checks
    # ------------------------------------------------------------------

    def ticket_acknowledged(self) -> bool:
        """True once the primary p1/p0 ticket has been acknowledged (Workflow A step A1)."""
        return any(
            r.get("_is_primary_ticket") and r.get("_acknowledged")
            for r in self._records.values()
        )

    def support_queried(self, account_id: str) -> bool:
        """True once tickets for account_id were listed (Workflow C step C2)."""
        return account_id in self._records.get("ZD-001", {}).get("_queried_accounts", []) or \
               any(account_id in r.get("_queried_accounts", []) for r in self._records.values())

    def profile_created(self) -> bool:
        """True once a new agent profile was created (Workflow B step B4)."""
        return any(r.get("_profile_created") for r in self._records.values())

    # ------------------------------------------------------------------
    # Operations
    # ------------------------------------------------------------------

    def _op_get_ticket(self, ticket_number: str, customer_id: Optional[str] = None) -> Dict:
        # If customer_id provided, look up all tickets for that customer
        if customer_id:
            matching = [r for r in self._records.values()
                        if r.get("customer_id") == customer_id]
            # Mark as queried for Workflow C
            for r in matching:
                r.setdefault("_queried_accounts", [])
                if customer_id not in r["_queried_accounts"]:
                    r["_queried_accounts"].append(customer_id)
            if not matching:
                return {"success": True, "data": [],
                        "message": f"No tickets found for customer {customer_id}"}
            return {
                "success": True,
                "data": [self._to_agent_view(r) for r in matching[:5]],
                "message": f"Found {len(matching)} tickets for {customer_id}",
            }

        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False,
                    "message": f"Ticket {ticket_number} not found. Use list_tickets to browse."}
        rec.setdefault("_queried_accounts", [])
        cid = rec.get("customer_id")
        if cid and cid not in rec["_queried_accounts"]:
            rec["_queried_accounts"].append(cid)

        return {"success": True, "data": self._to_agent_view(rec),
                "ticket": rec,
                "message": f"Retrieved {ticket_number}"}

    def _op_acknowledge_ticket(self, ticket_number: str) -> Dict:
        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False, "message": f"Ticket {ticket_number} not found"}
        rec["_acknowledged"] = True
        if rec.get("state") == "new":
            rec["state"] = "open"
        return {"success": True, "ticket": rec,
                "message": f"Acknowledged {ticket_number} — status → open"}

    def _op_set_urgency(self, ticket_number: str, **kwargs) -> Dict:
        schema_error, schema_adapted = self._check_schema_drift(kwargs)
        if schema_error:
            hint = self._drift.translate_field("urgency", self.APP_NAME)
            return {"success": False, "schema_error": schema_error,
                    "message": f"Schema error: use '{hint}' not '{schema_error}'"}

        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False, "message": f"Ticket {ticket_number} not found"}

        new_urgency = (kwargs.get("urgency") or kwargs.get("priority")
                       or kwargs.get("impact_level"))
        if not new_urgency:
            return {"success": False,
                    "message": "Provide urgency / priority / impact_level value"}

        rec["urgency"] = new_urgency
        return {"success": True, "schema_adapted": schema_adapted,
                "message": f"{ticket_number} urgency → '{new_urgency}'"}

    def _op_assign_agent(self, ticket_number: str, **kwargs) -> Dict:
        schema_error, schema_adapted = self._check_schema_drift(kwargs)
        if schema_error:
            hint = self._drift.translate_field("agent_email", self.APP_NAME)
            return {"success": False, "schema_error": schema_error,
                    "message": f"Schema error: use '{hint}' not '{schema_error}'"}

        rec = self._records.get(ticket_number)
        # For Workflow B profile creation: allow creating a new agent entry
        if not rec:
            # Create a minimal profile record for the new agent
            email = (kwargs.get("agent_email") or kwargs.get("handler")
                     or kwargs.get("assigned_agent"))
            if not email:
                return {"success": False, "message": f"Ticket {ticket_number} not found"}
            # Create a synthetic profile ticket
            profile_rec = {
                "ticket_number":    ticket_number,
                "title":            "Agent profile",
                "urgency":          "p3",
                "agent_email":      email,
                "state":            "closed",
                "customer_id":      None,
                "_acknowledged":    False,
                "_queried_accounts": [],
                "_profile_created": True,
            }
            self._records[ticket_number] = profile_rec
            return {"success": True, "schema_adapted": schema_adapted,
                    "message": f"Created Zendesk profile for agent '{email}'"}

        email = (kwargs.get("agent_email") or kwargs.get("handler")
                 or kwargs.get("assigned_agent"))
        if not email:
            return {"success": False,
                    "message": "Provide agent_email / handler / assigned_agent value"}

        rec["agent_email"] = email
        rec["_profile_created"] = True
        return {"success": True, "schema_adapted": schema_adapted,
                "message": f"{ticket_number} assigned to '{email}'"}

    def _op_escalate_to_jira(self, ticket_number: str,
                              jira_issue_id: Optional[str] = None) -> Dict:
        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False, "message": f"Ticket {ticket_number} not found"}
        rec["state"] = "pending"
        rec["escalated_to_jira"] = jira_issue_id or "pending"
        return {"success": True,
                "message": f"{ticket_number} escalated to Jira"
                           + (f" ({jira_issue_id})" if jira_issue_id else "")}

    def _op_resolve_ticket(self, ticket_number: str) -> Dict:
        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False, "message": f"Ticket {ticket_number} not found"}
        rec["state"] = "resolved"
        return {"success": True, "message": f"{ticket_number} resolved"}

    def _op_add_note(self, ticket_number: str, note: str) -> Dict:
        rec = self._records.get(ticket_number)
        if not rec:
            return {"success": False, "message": f"Ticket {ticket_number} not found"}
        rec.setdefault("notes", []).append(note)
        return {"success": True, "message": f"Note added to {ticket_number}"}

    def _op_list_tickets(self, state: str = "open", customer_id: Optional[str] = None,
                         limit: int = 10) -> Dict:
        matching = [
            r for r in self._records.values()
            if (state == "all" or r.get("state") == state)
            and (customer_id is None or r.get("customer_id") == customer_id)
        ][:limit]
        # Mark accounts as queried
        if customer_id:
            for r in matching:
                r.setdefault("_queried_accounts", [])
                if customer_id not in r["_queried_accounts"]:
                    r["_queried_accounts"].append(customer_id)

        drifted = [self._to_agent_view(r) for r in matching]
        keep = ["ticket_number", "title",
                "urgency", "priority", "impact_level",
                "agent_email", "handler", "assigned_agent",
                "state", "ticket_state", "resolution_status",
                "customer_id"]
        compact = [{k: v for k, v in r.items() if k in keep and v is not None}
                   for r in drifted]
        return {
            "success": True,
            "data": compact,
            "message": f"Found {len(compact)} {state} tickets"
                       + (f" for {customer_id}" if customer_id else ""),
        }

    def _op_create_agent_profile(self, employee_id: str, email: str,
                                 name: Optional[str] = None, **kwargs) -> Dict:
        """Create a Zendesk support agent profile (Workflow B step B4)."""
        if employee_id in self._records:
            return {"success": False,
                    "message": f"Profile for {employee_id} already exists"}
        profile = {
            "ticket_number":     employee_id,
            "title":             f"Agent profile — {name or employee_id}",
            "urgency":           "p3",
            "agent_email":       email,
            "state":             "closed",
            "customer_id":       None,
            "_acknowledged":     False,
            "_queried_accounts": [],
            "_profile_created":  True,
        }
        self._records[employee_id] = profile
        return {"success": True,
                "message": f"Created Zendesk agent profile for {name or employee_id} ({email})"}