untrap-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/dist/auth/api-keys.d.ts +53 -0
  2. package/dist/auth/api-keys.d.ts.map +1 -0
  3. package/dist/auth/api-keys.js +115 -0
  4. package/dist/auth/api-keys.js.map +1 -0
  5. package/dist/auth/rate-limiter.d.ts +6 -0
  6. package/dist/auth/rate-limiter.d.ts.map +1 -0
  7. package/dist/auth/rate-limiter.js +77 -0
  8. package/dist/auth/rate-limiter.js.map +1 -0
  9. package/dist/config.d.ts +42 -0
  10. package/dist/config.d.ts.map +1 -0
  11. package/dist/config.js +62 -0
  12. package/dist/config.js.map +1 -0
  13. package/dist/db/__tests__/assert-msp-scoped.test.d.ts +2 -0
  14. package/dist/db/__tests__/assert-msp-scoped.test.d.ts.map +1 -0
  15. package/dist/db/__tests__/assert-msp-scoped.test.js +93 -0
  16. package/dist/db/__tests__/assert-msp-scoped.test.js.map +1 -0
  17. package/dist/db/azure-openai.d.ts +24 -0
  18. package/dist/db/azure-openai.d.ts.map +1 -0
  19. package/dist/db/azure-openai.js +45 -0
  20. package/dist/db/azure-openai.js.map +1 -0
  21. package/dist/db/gateway-client.d.ts +13 -0
  22. package/dist/db/gateway-client.d.ts.map +1 -0
  23. package/dist/db/gateway-client.js +210 -0
  24. package/dist/db/gateway-client.js.map +1 -0
  25. package/dist/index.d.ts +3 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +46 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/middleware/logging.d.ts +16 -0
  30. package/dist/middleware/logging.d.ts.map +1 -0
  31. package/dist/middleware/logging.js +52 -0
  32. package/dist/middleware/logging.js.map +1 -0
  33. package/dist/server.d.ts +3 -0
  34. package/dist/server.d.ts.map +1 -0
  35. package/dist/server.js +466 -0
  36. package/dist/server.js.map +1 -0
  37. package/dist/tools/ai/explain-budget.d.ts +10 -0
  38. package/dist/tools/ai/explain-budget.d.ts.map +1 -0
  39. package/dist/tools/ai/explain-budget.js +61 -0
  40. package/dist/tools/ai/explain-budget.js.map +1 -0
  41. package/dist/tools/ai/explain-experience.d.ts +6 -0
  42. package/dist/tools/ai/explain-experience.d.ts.map +1 -0
  43. package/dist/tools/ai/explain-experience.js +62 -0
  44. package/dist/tools/ai/explain-experience.js.map +1 -0
  45. package/dist/tools/ai/explain-sla.d.ts +9 -0
  46. package/dist/tools/ai/explain-sla.d.ts.map +1 -0
  47. package/dist/tools/ai/explain-sla.js +57 -0
  48. package/dist/tools/ai/explain-sla.js.map +1 -0
  49. package/dist/tools/ai/kb-search.d.ts +17 -0
  50. package/dist/tools/ai/kb-search.d.ts.map +1 -0
  51. package/dist/tools/ai/kb-search.js +28 -0
  52. package/dist/tools/ai/kb-search.js.map +1 -0
  53. package/dist/tools/ai/root-causes.d.ts +21 -0
  54. package/dist/tools/ai/root-causes.d.ts.map +1 -0
  55. package/dist/tools/ai/root-causes.js +54 -0
  56. package/dist/tools/ai/root-causes.js.map +1 -0
  57. package/dist/tools/ai/similar-tickets.d.ts +21 -0
  58. package/dist/tools/ai/similar-tickets.d.ts.map +1 -0
  59. package/dist/tools/ai/similar-tickets.js +47 -0
  60. package/dist/tools/ai/similar-tickets.js.map +1 -0
  61. package/dist/tools/ai/suggest-improvement.d.ts +6 -0
  62. package/dist/tools/ai/suggest-improvement.d.ts.map +1 -0
  63. package/dist/tools/ai/suggest-improvement.js +75 -0
  64. package/dist/tools/ai/suggest-improvement.js.map +1 -0
  65. package/dist/tools/analytics/backlog.d.ts +15 -0
  66. package/dist/tools/analytics/backlog.d.ts.map +1 -0
  67. package/dist/tools/analytics/backlog.js +41 -0
  68. package/dist/tools/analytics/backlog.js.map +1 -0
  69. package/dist/tools/analytics/billing.d.ts +15 -0
  70. package/dist/tools/analytics/billing.d.ts.map +1 -0
  71. package/dist/tools/analytics/billing.js +24 -0
  72. package/dist/tools/analytics/billing.js.map +1 -0
  73. package/dist/tools/analytics/client-experience.d.ts +32 -0
  74. package/dist/tools/analytics/client-experience.d.ts.map +1 -0
  75. package/dist/tools/analytics/client-experience.js +34 -0
  76. package/dist/tools/analytics/client-experience.js.map +1 -0
  77. package/dist/tools/analytics/client-health.d.ts +16 -0
  78. package/dist/tools/analytics/client-health.d.ts.map +1 -0
  79. package/dist/tools/analytics/client-health.js +21 -0
  80. package/dist/tools/analytics/client-health.js.map +1 -0
  81. package/dist/tools/analytics/disengaged.d.ts +5 -0
  82. package/dist/tools/analytics/disengaged.d.ts.map +1 -0
  83. package/dist/tools/analytics/disengaged.js +63 -0
  84. package/dist/tools/analytics/disengaged.js.map +1 -0
  85. package/dist/tools/analytics/patterns.d.ts +9 -0
  86. package/dist/tools/analytics/patterns.d.ts.map +1 -0
  87. package/dist/tools/analytics/patterns.js +11 -0
  88. package/dist/tools/analytics/patterns.js.map +1 -0
  89. package/dist/tools/analytics/qc-violations.d.ts +21 -0
  90. package/dist/tools/analytics/qc-violations.d.ts.map +1 -0
  91. package/dist/tools/analytics/qc-violations.js +55 -0
  92. package/dist/tools/analytics/qc-violations.js.map +1 -0
  93. package/dist/tools/analytics/sla-breach-tickets.d.ts +21 -0
  94. package/dist/tools/analytics/sla-breach-tickets.d.ts.map +1 -0
  95. package/dist/tools/analytics/sla-breach-tickets.js +72 -0
  96. package/dist/tools/analytics/sla-breach-tickets.js.map +1 -0
  97. package/dist/tools/analytics/sla-breaches.d.ts +17 -0
  98. package/dist/tools/analytics/sla-breaches.d.ts.map +1 -0
  99. package/dist/tools/analytics/sla-breaches.js +60 -0
  100. package/dist/tools/analytics/sla-breaches.js.map +1 -0
  101. package/dist/tools/analytics/technician-perf.d.ts +10 -0
  102. package/dist/tools/analytics/technician-perf.d.ts.map +1 -0
  103. package/dist/tools/analytics/technician-perf.js +14 -0
  104. package/dist/tools/analytics/technician-perf.js.map +1 -0
  105. package/dist/tools/cfo/agreement-analysis.d.ts +12 -0
  106. package/dist/tools/cfo/agreement-analysis.d.ts.map +1 -0
  107. package/dist/tools/cfo/agreement-analysis.js +59 -0
  108. package/dist/tools/cfo/agreement-analysis.js.map +1 -0
  109. package/dist/tools/cfo/client-profitability.d.ts +25 -0
  110. package/dist/tools/cfo/client-profitability.d.ts.map +1 -0
  111. package/dist/tools/cfo/client-profitability.js +73 -0
  112. package/dist/tools/cfo/client-profitability.js.map +1 -0
  113. package/dist/tools/cfo/financial-overview.d.ts +34 -0
  114. package/dist/tools/cfo/financial-overview.d.ts.map +1 -0
  115. package/dist/tools/cfo/financial-overview.js +67 -0
  116. package/dist/tools/cfo/financial-overview.js.map +1 -0
  117. package/dist/tools/cfo/labor-cost-trend.d.ts +14 -0
  118. package/dist/tools/cfo/labor-cost-trend.d.ts.map +1 -0
  119. package/dist/tools/cfo/labor-cost-trend.js +65 -0
  120. package/dist/tools/cfo/labor-cost-trend.js.map +1 -0
  121. package/dist/tools/cfo/revenue-leakage.d.ts +22 -0
  122. package/dist/tools/cfo/revenue-leakage.d.ts.map +1 -0
  123. package/dist/tools/cfo/revenue-leakage.js +67 -0
  124. package/dist/tools/cfo/revenue-leakage.js.map +1 -0
  125. package/dist/tools/cfo/service-cost-analysis.d.ts +15 -0
  126. package/dist/tools/cfo/service-cost-analysis.d.ts.map +1 -0
  127. package/dist/tools/cfo/service-cost-analysis.js +66 -0
  128. package/dist/tools/cfo/service-cost-analysis.js.map +1 -0
  129. package/dist/tools/cfo/technician-utilization.d.ts +26 -0
  130. package/dist/tools/cfo/technician-utilization.d.ts.map +1 -0
  131. package/dist/tools/cfo/technician-utilization.js +67 -0
  132. package/dist/tools/cfo/technician-utilization.js.map +1 -0
  133. package/dist/tools/composite/client-360.d.ts +28 -0
  134. package/dist/tools/composite/client-360.d.ts.map +1 -0
  135. package/dist/tools/composite/client-360.js +67 -0
  136. package/dist/tools/composite/client-360.js.map +1 -0
  137. package/dist/tools/composite/compare-periods.d.ts +31 -0
  138. package/dist/tools/composite/compare-periods.d.ts.map +1 -0
  139. package/dist/tools/composite/compare-periods.js +94 -0
  140. package/dist/tools/composite/compare-periods.js.map +1 -0
  141. package/dist/tools/composite/draft-email.d.ts +7 -0
  142. package/dist/tools/composite/draft-email.d.ts.map +1 -0
  143. package/dist/tools/composite/draft-email.js +54 -0
  144. package/dist/tools/composite/draft-email.js.map +1 -0
  145. package/dist/tools/composite/morning-briefing.d.ts +15 -0
  146. package/dist/tools/composite/morning-briefing.d.ts.map +1 -0
  147. package/dist/tools/composite/morning-briefing.js +119 -0
  148. package/dist/tools/composite/morning-briefing.js.map +1 -0
  149. package/dist/tools/composite/technician-360.d.ts +22 -0
  150. package/dist/tools/composite/technician-360.d.ts.map +1 -0
  151. package/dist/tools/composite/technician-360.js +55 -0
  152. package/dist/tools/composite/technician-360.js.map +1 -0
  153. package/dist/tools/composite/ticket-deep-dive.d.ts +24 -0
  154. package/dist/tools/composite/ticket-deep-dive.d.ts.map +1 -0
  155. package/dist/tools/composite/ticket-deep-dive.js +49 -0
  156. package/dist/tools/composite/ticket-deep-dive.js.map +1 -0
  157. package/dist/tools/config/msp-context.d.ts +26 -0
  158. package/dist/tools/config/msp-context.d.ts.map +1 -0
  159. package/dist/tools/config/msp-context.js +41 -0
  160. package/dist/tools/config/msp-context.js.map +1 -0
  161. package/dist/tools/qbr/client-detail.d.ts +26 -0
  162. package/dist/tools/qbr/client-detail.d.ts.map +1 -0
  163. package/dist/tools/qbr/client-detail.js +72 -0
  164. package/dist/tools/qbr/client-detail.js.map +1 -0
  165. package/dist/tools/qbr/client-health.d.ts +11 -0
  166. package/dist/tools/qbr/client-health.d.ts.map +1 -0
  167. package/dist/tools/qbr/client-health.js +48 -0
  168. package/dist/tools/qbr/client-health.js.map +1 -0
  169. package/dist/tools/qbr/executive-summary.d.ts +8 -0
  170. package/dist/tools/qbr/executive-summary.d.ts.map +1 -0
  171. package/dist/tools/qbr/executive-summary.js +83 -0
  172. package/dist/tools/qbr/executive-summary.js.map +1 -0
  173. package/dist/tools/qbr/financial-summary.d.ts +19 -0
  174. package/dist/tools/qbr/financial-summary.d.ts.map +1 -0
  175. package/dist/tools/qbr/financial-summary.js +64 -0
  176. package/dist/tools/qbr/financial-summary.js.map +1 -0
  177. package/dist/tools/qbr/recommendations.d.ts +7 -0
  178. package/dist/tools/qbr/recommendations.d.ts.map +1 -0
  179. package/dist/tools/qbr/recommendations.js +80 -0
  180. package/dist/tools/qbr/recommendations.js.map +1 -0
  181. package/dist/tools/qbr/sla-performance.d.ts +12 -0
  182. package/dist/tools/qbr/sla-performance.d.ts.map +1 -0
  183. package/dist/tools/qbr/sla-performance.js +68 -0
  184. package/dist/tools/qbr/sla-performance.js.map +1 -0
  185. package/dist/tools/qbr/technician-scorecard.d.ts +12 -0
  186. package/dist/tools/qbr/technician-scorecard.d.ts.map +1 -0
  187. package/dist/tools/qbr/technician-scorecard.js +69 -0
  188. package/dist/tools/qbr/technician-scorecard.js.map +1 -0
  189. package/dist/tools/qbr/ticket-trends.d.ts +14 -0
  190. package/dist/tools/qbr/ticket-trends.d.ts.map +1 -0
  191. package/dist/tools/qbr/ticket-trends.js +57 -0
  192. package/dist/tools/qbr/ticket-trends.js.map +1 -0
  193. package/dist/tools/qbr/top-issues.d.ts +12 -0
  194. package/dist/tools/qbr/top-issues.d.ts.map +1 -0
  195. package/dist/tools/qbr/top-issues.js +39 -0
  196. package/dist/tools/qbr/top-issues.js.map +1 -0
  197. package/dist/tools/query/data-query.d.ts +24 -0
  198. package/dist/tools/query/data-query.d.ts.map +1 -0
  199. package/dist/tools/query/data-query.js +160 -0
  200. package/dist/tools/query/data-query.js.map +1 -0
  201. package/dist/types/database.d.ts +137 -0
  202. package/dist/types/database.d.ts.map +1 -0
  203. package/dist/types/database.js +3 -0
  204. package/dist/types/database.js.map +1 -0
  205. package/package.json +41 -0
@@ -0,0 +1,62 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ import { chatCompletion } from "../../db/azure-openai.js";
3
+ export async function explainExperienceScore(mspId, params) {
4
+ const [metrics, sentiment, slaBreaches] = await Promise.all([
5
+ queryRLS(`SELECT company_name, ticket_count, avg_response_minutes, avg_resolution_minutes,
6
+ avg_sentiment_score, health_score, total_cost, sla_breach_count
7
+ FROM analytics.client_experience_chat_mv
8
+ WHERE msp_id = $1 AND LOWER(company_name) = LOWER($2)
9
+ AND period_start >= CURRENT_DATE - INTERVAL '90 days'
10
+ ORDER BY period_start DESC
11
+ LIMIT 1`, [mspId, params.clientName], mspId),
12
+ queryRLS(`SELECT AVG(sentiment_score)::numeric as avg_sentiment,
13
+ COUNT(*) FILTER (WHERE sentiment_score < -0.3)::int as negative_notes,
14
+ COUNT(*)::int as total_notes
15
+ FROM ticket_notes_sentiment tns
16
+ JOIN ticket_lifecycle tl ON tl.msp_id = tns.msp_id AND tl.ticket_id = tns.ticket_id
17
+ WHERE tns.msp_id = $1 AND LOWER(tl.company_name) = LOWER($2)
18
+ AND tl.responded_date >= CURRENT_DATE - INTERVAL '90 days'`, [mspId, params.clientName], mspId),
19
+ queryRLS(`SELECT COUNT(*)::int as breach_count,
20
+ AVG(resolution_minutes)::numeric as avg_breach_resolution_min
21
+ FROM ticket_lifecycle
22
+ WHERE msp_id = $1 AND LOWER(company_name) = LOWER($2)
23
+ AND sla_status = 'breached'
24
+ AND responded_date >= CURRENT_DATE - INTERVAL '90 days'`, [mspId, params.clientName], mspId),
25
+ ]);
26
+ if (metrics.length === 0) {
27
+ return { found: false, message: `No data found for client "${params.clientName}"` };
28
+ }
29
+ const m = metrics[0];
30
+ const s = sentiment[0] ?? {};
31
+ const b = slaBreaches[0] ?? {};
32
+ const result = await chatCompletion({
33
+ messages: [
34
+ {
35
+ role: "system",
36
+ content: `You are an MSP client experience analyst. Break down what's driving a client's experience score. Return JSON with "score" (0-100), "breakdown" (object with sentiment, documentation, resolution_time, response_speed, consistency each 0-100), and "explanation" (2-3 paragraph narrative). Only return valid JSON.`,
37
+ },
38
+ {
39
+ role: "user",
40
+ content: `Client: ${params.clientName}
41
+ Health score: ${m.health_score}
42
+ Tickets (90d): ${m.ticket_count}
43
+ Avg response: ${m.avg_response_minutes} min
44
+ Avg resolution: ${m.avg_resolution_minutes} min
45
+ Avg sentiment: ${s.avg_sentiment} (${s.negative_notes} negative out of ${s.total_notes} notes)
46
+ SLA breaches: ${b.breach_count} (avg ${b.avg_breach_resolution_min} min to resolve)
47
+ Total cost: $${m.total_cost}
48
+
49
+ What's driving this client's experience score? Break it down.`,
50
+ },
51
+ ],
52
+ maxTokens: 600,
53
+ temperature: 0.6,
54
+ });
55
+ try {
56
+ return { found: true, ...JSON.parse(result.content) };
57
+ }
58
+ catch {
59
+ return { found: true, score: m.health_score, explanation: result.content };
60
+ }
61
+ }
62
+ //# sourceMappingURL=explain-experience.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-experience.js","sourceRoot":"","sources":["../../../src/tools/ai/explain-experience.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAM1D,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,KAAa,EACb,MAAwB;IAExB,MAAM,CAAC,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC1D,QAAQ,CACN;;;;;;eAMS,EACT,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,EAC1B,KAAK,CACN;QACD,QAAQ,CACN;;;;;;oEAM8D,EAC9D,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,EAC1B,KAAK,CACN;QACD,QAAQ,CACN;;;;;iEAK2D,EAC3D,CAAC,KAAK,EAAE,MAAM,CAAC,UAAU,CAAC,EAC1B,KAAK,CACN;KACF,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,6BAA6B,MAAM,CAAC,UAAU,GAAG,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACrB,MAAM,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAE/B,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,sTAAsT;aAChU;YACD;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,WAAW,MAAM,CAAC,UAAU;gBAC7B,CAAC,CAAC,YAAY;iBACb,CAAC,CAAC,YAAY;gBACf,CAAC,CAAC,oBAAoB;kBACpB,CAAC,CAAC,sBAAsB;iBACzB,CAAC,CAAC,aAAa,KAAK,CAAC,CAAC,cAAc,oBAAoB,CAAC,CAAC,WAAW;gBACtE,CAAC,CAAC,YAAY,SAAS,CAAC,CAAC,yBAAyB;eACnD,CAAC,CAAC,UAAU;;8DAEmC;aACvD;SACF;QACD,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,YAAY,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;IAC7E,CAAC;AACH,CAAC"}
@@ -0,0 +1,9 @@
1
+ interface ExplainSlaParams {
2
+ ticketId: number;
3
+ }
4
+ export declare function explainSlaVariance(mspId: string, params: ExplainSlaParams): Promise<{
5
+ explanation: any;
6
+ factors: any;
7
+ }>;
8
+ export {};
9
+ //# sourceMappingURL=explain-sla.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-sla.d.ts","sourceRoot":"","sources":["../../../src/tools/ai/explain-sla.ts"],"names":[],"mappings":"AAGA,UAAU,gBAAgB;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,gBAAgB;;;GAiEzB"}
@@ -0,0 +1,57 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ import { chatCompletion } from "../../db/azure-openai.js";
3
+ export async function explainSlaVariance(mspId, params) {
4
+ const [ticket, timeEntries] = await Promise.all([
5
+ queryRLS(`SELECT ticket_id, summary, priority_name, owner_name, company_name,
6
+ board_name, status, response_minutes, resolution_minutes,
7
+ sla_status, entered_date::text, responded_date::text, closed_date::text
8
+ FROM ticket_lifecycle
9
+ WHERE msp_id = $1 AND ticket_id = $2`, [mspId, params.ticketId], mspId),
10
+ queryRLS(`SELECT member_name, actual_hours, date_worked::text, notes_text
11
+ FROM time_entries
12
+ WHERE msp_id = $1 AND ticket_id = $2
13
+ ORDER BY date_worked ASC`, [mspId, params.ticketId], mspId),
14
+ ]);
15
+ if (ticket.length === 0) {
16
+ return { explanation: "Ticket not found.", factors: [] };
17
+ }
18
+ const t = ticket[0];
19
+ const timeLog = timeEntries
20
+ .map((te) => `${te.date_worked}: ${te.member_name} (${te.actual_hours}h) - ${te.notes_text?.substring(0, 100) || "no notes"}`)
21
+ .join("\n");
22
+ const result = await chatCompletion({
23
+ messages: [
24
+ {
25
+ role: "system",
26
+ content: `You are an MSP SLA analyst. Explain why a ticket missed its SLA target. Return a JSON object with "explanation" (2-3 sentences) and "factors" (array of strings naming specific delay causes). Only return valid JSON.`,
27
+ },
28
+ {
29
+ role: "user",
30
+ content: `Ticket #${t.ticket_id}: "${t.summary}"
31
+ Priority: ${t.priority_name}, Status: ${t.status}, SLA: ${t.sla_status}
32
+ Client: ${t.company_name}, Board: ${t.board_name}
33
+ Entered: ${t.entered_date}, Responded: ${t.responded_date}, Closed: ${t.closed_date}
34
+ Response time: ${t.response_minutes} min, Resolution time: ${t.resolution_minutes} min
35
+ Technician: ${t.owner_name}
36
+
37
+ Time log:
38
+ ${timeLog || "No time entries"}
39
+
40
+ Why did this ticket miss SLA?`,
41
+ },
42
+ ],
43
+ maxTokens: 400,
44
+ temperature: 0.5,
45
+ });
46
+ try {
47
+ const parsed = JSON.parse(result.content);
48
+ return {
49
+ explanation: parsed.explanation || result.content,
50
+ factors: parsed.factors || [],
51
+ };
52
+ }
53
+ catch {
54
+ return { explanation: result.content, factors: [] };
55
+ }
56
+ }
57
+ //# sourceMappingURL=explain-sla.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"explain-sla.js","sourceRoot":"","sources":["../../../src/tools/ai/explain-sla.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAM1D,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,MAAwB;IAExB,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC9C,QAAQ,CACN;;;;4CAIsC,EACtC,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EACxB,KAAK,CACN;QACD,QAAQ,CACN;;;gCAG0B,EAC1B,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EACxB,KAAK,CACN;KACF,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,EAAE,WAAW,EAAE,mBAAmB,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IAC3D,CAAC;IAED,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,OAAO,GAAG,WAAW;SACxB,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,EAAE,CAAC,WAAW,KAAK,EAAE,CAAC,WAAW,KAAK,EAAE,CAAC,YAAY,QAAS,EAAE,CAAC,UAAqB,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,IAAI,UAAU,EAAE,CAAC;SACzI,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,wNAAwN;aAClO;YACD;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,WAAW,CAAC,CAAC,SAAS,MAAM,CAAC,CAAC,OAAO;YAC1C,CAAC,CAAC,aAAa,aAAa,CAAC,CAAC,MAAM,UAAU,CAAC,CAAC,UAAU;UAC5D,CAAC,CAAC,YAAY,YAAY,CAAC,CAAC,UAAU;WACrC,CAAC,CAAC,YAAY,gBAAgB,CAAC,CAAC,cAAc,aAAa,CAAC,CAAC,WAAW;iBAClE,CAAC,CAAC,gBAAgB,0BAA0B,CAAC,CAAC,kBAAkB;cACnE,CAAC,CAAC,UAAU;;;EAGxB,OAAO,IAAI,iBAAiB;;8BAEA;aACvB;SACF;QACD,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO;YACL,WAAW,EAAE,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,OAAO;YACjD,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;SAC9B,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;IACtD,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ interface KBSearchParams {
2
+ query: string;
3
+ }
4
+ export declare function searchKnowledgeBase(mspId: string, params: KBSearchParams): Promise<{
5
+ query: string;
6
+ results_count: number;
7
+ articles: {
8
+ id: unknown;
9
+ title: unknown;
10
+ content: string;
11
+ keywords: unknown;
12
+ source_tickets: unknown;
13
+ updated_at: unknown;
14
+ }[];
15
+ }>;
16
+ export {};
17
+ //# sourceMappingURL=kb-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kb-search.d.ts","sourceRoot":"","sources":["../../../src/tools/ai/kb-search.ts"],"names":[],"mappings":"AAEA,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,cAAc;;;;;;;;;;;GA+BvB"}
@@ -0,0 +1,28 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ export async function searchKnowledgeBase(mspId, params) {
3
+ // Search by keyword match across title, content, and keywords
4
+ const articles = await queryRLS(`SELECT id, title, content_md, keywords, source_ticket_ids,
5
+ created_at::text, updated_at::text
6
+ FROM kb_articles
7
+ WHERE msp_id = $1
8
+ AND (
9
+ LOWER(title) LIKE '%' || LOWER($2) || '%'
10
+ OR LOWER(content_md) LIKE '%' || LOWER($2) || '%'
11
+ OR LOWER(keywords::text) LIKE '%' || LOWER($2) || '%'
12
+ )
13
+ ORDER BY updated_at DESC
14
+ LIMIT 10`, [mspId, params.query], mspId);
15
+ return {
16
+ query: params.query,
17
+ results_count: articles.length,
18
+ articles: articles.map((a) => ({
19
+ id: a.id,
20
+ title: a.title,
21
+ content: a.content_md?.substring(0, 500),
22
+ keywords: a.keywords,
23
+ source_tickets: a.source_ticket_ids,
24
+ updated_at: a.updated_at,
25
+ })),
26
+ };
27
+ }
28
+ //# sourceMappingURL=kb-search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"kb-search.js","sourceRoot":"","sources":["../../../src/tools/ai/kb-search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAMtD,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,KAAa,EACb,MAAsB;IAEtB,8DAA8D;IAC9D,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAC7B;;;;;;;;;;cAUU,EACV,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,EACrB,KAAK,CACN,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,MAAM,CAAC,KAAK;QACnB,aAAa,EAAE,QAAQ,CAAC,MAAM;QAC9B,QAAQ,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC7B,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAG,CAAC,CAAC,UAAqB,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC;YACpD,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,cAAc,EAAE,CAAC,CAAC,iBAAiB;YACnC,UAAU,EAAE,CAAC,CAAC,UAAU;SACzB,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ interface RootCauseParams {
2
+ fromDate?: string;
3
+ toDate?: string;
4
+ category?: string;
5
+ }
6
+ export declare function analyzeRootCauses(mspId: string, params: RootCauseParams): Promise<{
7
+ breakdown: never[];
8
+ ai_analysis: string;
9
+ period?: undefined;
10
+ } | {
11
+ breakdown: {
12
+ percentage: number;
13
+ }[];
14
+ period: {
15
+ from: string;
16
+ to: string;
17
+ };
18
+ ai_analysis: string;
19
+ }>;
20
+ export {};
21
+ //# sourceMappingURL=root-causes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-causes.d.ts","sourceRoot":"","sources":["../../../src/tools/ai/root-causes.ts"],"names":[],"mappings":"AAGA,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe;;;;;;;;;;;;;GA8DxB"}
@@ -0,0 +1,54 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ import { chatCompletion } from "../../db/azure-openai.js";
3
+ export async function analyzeRootCauses(mspId, params) {
4
+ const fromDate = params.fromDate ?? new Date(Date.now() - 90 * 86400000).toISOString().split("T")[0];
5
+ const toDate = params.toDate ?? new Date().toISOString().split("T")[0];
6
+ let categoryFilter = "";
7
+ const queryParams = [mspId];
8
+ if (params.category) {
9
+ categoryFilter = ` AND LOWER(rc.root_cause_category) = LOWER($2)`;
10
+ queryParams.push(params.category);
11
+ }
12
+ const rawBreakdown = await queryRLS(`SELECT rc.root_cause_category as category,
13
+ COUNT(*)::int as count
14
+ FROM root_cause_ticket_classifier rc
15
+ WHERE rc.msp_id = $1
16
+ AND rc.root_cause_category IS NOT NULL
17
+ ${categoryFilter}
18
+ GROUP BY rc.root_cause_category
19
+ ORDER BY count DESC
20
+ LIMIT 15`, queryParams, mspId);
21
+ const total = rawBreakdown.reduce((sum, r) => sum + Number(r.count), 0);
22
+ const breakdown = rawBreakdown.map((r) => ({
23
+ ...r,
24
+ percentage: total > 0 ? Math.round((Number(r.count) / total) * 1000) / 10 : 0,
25
+ }));
26
+ if (breakdown.length === 0) {
27
+ return { breakdown: [], ai_analysis: "No root cause data available for this period." };
28
+ }
29
+ // Get AI analysis of the top categories
30
+ const topCategories = breakdown
31
+ .slice(0, 5)
32
+ .map((b) => `${b.category}: ${b.count} tickets (${b.percentage}%)`)
33
+ .join("\n");
34
+ const result = await chatCompletion({
35
+ messages: [
36
+ {
37
+ role: "system",
38
+ content: `You are an MSP operations consultant. Analyze the root cause breakdown and provide actionable recommendations. For each top category: explain what's likely causing it, the business impact, and one specific action to reduce it. Keep it under 200 words total.`,
39
+ },
40
+ {
41
+ role: "user",
42
+ content: `Root cause breakdown (${fromDate} to ${toDate}):\n${topCategories}\n\nAnalyze these patterns and recommend actions.`,
43
+ },
44
+ ],
45
+ maxTokens: 400,
46
+ temperature: 0.6,
47
+ });
48
+ return {
49
+ breakdown,
50
+ period: { from: fromDate, to: toDate },
51
+ ai_analysis: result.content,
52
+ };
53
+ }
54
+ //# sourceMappingURL=root-causes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"root-causes.js","sourceRoot":"","sources":["../../../src/tools/ai/root-causes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAQ1D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,KAAa,EACb,MAAuB;IAEvB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,IAAI,cAAc,GAAG,EAAE,CAAC;IACxB,MAAM,WAAW,GAAc,CAAC,KAAK,CAAC,CAAC;IAEvC,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,cAAc,GAAG,gDAAgD,CAAC;QAClE,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,QAAQ,CACjC;;;;;SAKK,cAAc;;;cAGT,EACV,WAAW,EACX,KAAK,CACN,CAAC;IACF,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC;IACxE,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,GAAG,CAAC;QACJ,UAAU,EAAE,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;KAC9E,CAAC,CAAC,CAAC;IAEJ,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC3B,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,WAAW,EAAE,+CAA+C,EAAE,CAAC;IACzF,CAAC;IAED,wCAAwC;IACxC,MAAM,aAAa,GAAG,SAAS;SAC5B,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;SACX,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAI,CAA6B,CAAC,QAAQ,KAAM,CAA6B,CAAC,KAAK,aAAa,CAAC,CAAC,UAAU,IAAI,CAAC;SAC5H,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,mQAAmQ;aAC7Q;YACD;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,yBAAyB,QAAQ,OAAO,MAAM,OAAO,aAAa,mDAAmD;aAC/H;SACF;QACD,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,OAAO;QACL,SAAS;QACT,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE;QACtC,WAAW,EAAE,MAAM,CAAC,OAAO;KAC5B,CAAC;AACJ,CAAC"}
@@ -0,0 +1,21 @@
1
+ interface SimilarParams {
2
+ ticketId: number;
3
+ limit?: number;
4
+ }
5
+ export declare function findSimilarTickets(mspId: string, params: SimilarParams): Promise<{
6
+ found: boolean;
7
+ message: string;
8
+ source_ticket?: undefined;
9
+ source_summary?: undefined;
10
+ similar?: undefined;
11
+ } | {
12
+ found: boolean;
13
+ source_ticket: number;
14
+ source_summary: string;
15
+ similar: {
16
+ similarity_score: number;
17
+ }[];
18
+ message?: undefined;
19
+ }>;
20
+ export {};
21
+ //# sourceMappingURL=similar-tickets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar-tickets.d.ts","sourceRoot":"","sources":["../../../src/tools/ai/similar-tickets.ts"],"names":[],"mappings":"AAEA,UAAU,aAAa;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa;;;;;;;;;;;;;;GAyDtB"}
@@ -0,0 +1,47 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ export async function findSimilarTickets(mspId, params) {
3
+ const limit = params.limit ?? 5;
4
+ // Get the source ticket's embedding
5
+ const source = await queryRLS(`SELECT problem_embedding, ai_summary
6
+ FROM ticket_embeddings
7
+ WHERE msp_id = $1 AND ticket_id = $2`, [mspId, params.ticketId], mspId);
8
+ if (source.length === 0 || !source[0].problem_embedding) {
9
+ return {
10
+ found: false,
11
+ message: `No embedding found for ticket #${params.ticketId}. The ticket may not have been processed by the AI pipeline yet.`,
12
+ };
13
+ }
14
+ // Find similar tickets using cosine similarity on problem_embedding
15
+ const similar = await queryRLS(`SELECT te.ticket_id,
16
+ 1 - (te.problem_embedding <=> (
17
+ SELECT problem_embedding FROM ticket_embeddings
18
+ WHERE msp_id = $1 AND ticket_id = $2
19
+ )) as similarity_score,
20
+ te.ai_summary as problem_summary,
21
+ te.root_cause as root_cause,
22
+ te.ticket_classification as classification,
23
+ tl.company_name,
24
+ tl.owner_name,
25
+ tl.status,
26
+ tl.responded_date::text
27
+ FROM ticket_embeddings te
28
+ JOIN ticket_lifecycle tl ON tl.msp_id = te.msp_id AND tl.ticket_id = te.ticket_id
29
+ WHERE te.msp_id = $1
30
+ AND te.ticket_id != $2
31
+ AND te.problem_embedding IS NOT NULL
32
+ ORDER BY te.problem_embedding <=> (
33
+ SELECT problem_embedding FROM ticket_embeddings
34
+ WHERE msp_id = $1 AND ticket_id = $2
35
+ )
36
+ LIMIT $3`, [mspId, params.ticketId, limit], mspId);
37
+ return {
38
+ found: true,
39
+ source_ticket: params.ticketId,
40
+ source_summary: source[0].ai_summary,
41
+ similar: similar.map((s) => ({
42
+ ...s,
43
+ similarity_score: Math.round(Number(s.similarity_score) * 1000) / 1000,
44
+ })),
45
+ };
46
+ }
47
+ //# sourceMappingURL=similar-tickets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"similar-tickets.js","sourceRoot":"","sources":["../../../src/tools/ai/similar-tickets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAOtD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAAa,EACb,MAAqB;IAErB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC;IAEhC,oCAAoC;IACpC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAC3B;;0CAEsC,EACtC,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,CAAC,EACxB,KAAK,CACN,CAAC;IAEF,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,iBAAiB,EAAE,CAAC;QACxD,OAAO;YACL,KAAK,EAAE,KAAK;YACZ,OAAO,EAAE,kCAAkC,MAAM,CAAC,QAAQ,kEAAkE;SAC7H,CAAC;IACJ,CAAC;IAED,oEAAoE;IACpE,MAAM,OAAO,GAAG,MAAM,QAAQ,CAC5B;;;;;;;;;;;;;;;;;;;;;cAqBU,EACV,CAAC,KAAK,EAAE,MAAM,CAAC,QAAQ,EAAE,KAAK,CAAC,EAC/B,KAAK,CACN,CAAC;IAEF,OAAO;QACL,KAAK,EAAE,IAAI;QACX,aAAa,EAAE,MAAM,CAAC,QAAQ;QAC9B,cAAc,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,UAAU;QACpC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,GAAG,CAAC;YACJ,gBAAgB,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI;SACvE,CAAC,CAAC;KACJ,CAAC;AACJ,CAAC"}
@@ -0,0 +1,6 @@
1
+ interface SuggestParams {
2
+ technicianName: string;
3
+ }
4
+ export declare function suggestTechnicianImprovement(mspId: string, params: SuggestParams): Promise<any>;
5
+ export {};
6
+ //# sourceMappingURL=suggest-improvement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-improvement.d.ts","sourceRoot":"","sources":["../../../src/tools/ai/suggest-improvement.ts"],"names":[],"mappings":"AAGA,UAAU,aAAa;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAsB,4BAA4B,CAChD,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa,gBAuFtB"}
@@ -0,0 +1,75 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ import { chatCompletion } from "../../db/azure-openai.js";
3
+ export async function suggestTechnicianImprovement(mspId, params) {
4
+ const [dqScore, recentNotes, profile] = await Promise.all([
5
+ queryRLS(`SELECT technician_name, quality_points, completeness_points,
6
+ accuracy_points, timeliness_points, detail_quality_points,
7
+ unique_tickets, total_notes, avg_note_length
8
+ FROM analytics.technician_dq_leaderboard_daily
9
+ WHERE msp_id = $1 AND LOWER(technician_name) = LOWER($2)
10
+ ORDER BY calculation_date DESC
11
+ LIMIT 1`, [mspId, params.technicianName], mspId),
12
+ queryRLS(`SELECT tns.text, tns.sentiment_score
13
+ FROM ticket_notes_sentiment tns
14
+ JOIN ticket_lifecycle tl ON tl.msp_id = tns.msp_id AND tl.ticket_id = tns.ticket_id
15
+ WHERE tns.msp_id = $1 AND LOWER(tl.owner_name) = LOWER($2)
16
+ AND tl.responded_date >= CURRENT_DATE - INTERVAL '30 days'
17
+ ORDER BY tns.created_at DESC
18
+ LIMIT 5`, [mspId, params.technicianName], mspId),
19
+ queryRLS(`SELECT technician_name, level, hourly_rate
20
+ FROM technicians
21
+ WHERE msp_id = $1 AND LOWER(technician_name) = LOWER($2)
22
+ LIMIT 1`, [mspId, params.technicianName], mspId),
23
+ ]);
24
+ if (dqScore.length === 0) {
25
+ return { found: false, message: `No performance data for "${params.technicianName}"` };
26
+ }
27
+ const dq = dqScore[0];
28
+ const sampleNotes = recentNotes
29
+ .map((n) => `- "${n.text?.substring(0, 150)}..." (sentiment: ${n.sentiment_score})`)
30
+ .join("\n");
31
+ const result = await chatCompletion({
32
+ messages: [
33
+ {
34
+ role: "system",
35
+ content: `You are an MSP service desk coach. Create a personalized coaching plan for a technician based on their documentation quality scores and sample notes. Return JSON with "strengths" (string[]), "improvement_areas" (string[]), "action_plan" (30-day coaching plan as string), and "example_transformations" (array of objects with "before" and "after" note examples). Only return valid JSON.`,
36
+ },
37
+ {
38
+ role: "user",
39
+ content: `Technician: ${dq.technician_name} (Level: ${profile[0]?.level || "unknown"})
40
+ DQ Score: ${dq.quality_points}/100
41
+ - Completeness: ${dq.completeness_points}
42
+ - Accuracy: ${dq.accuracy_points}
43
+ - Timeliness: ${dq.timeliness_points}
44
+ - Detail Quality: ${dq.detail_quality_points}
45
+ Tickets: ${dq.unique_tickets}, Notes: ${dq.total_notes}, Avg length: ${dq.avg_note_length} chars
46
+
47
+ Recent note samples:
48
+ ${sampleNotes || "No recent notes available"}
49
+
50
+ Create a coaching plan for this technician.`,
51
+ },
52
+ ],
53
+ maxTokens: 800,
54
+ temperature: 0.6,
55
+ });
56
+ try {
57
+ const parsed = JSON.parse(result.content);
58
+ return {
59
+ found: true,
60
+ current_score: dq.quality_points,
61
+ level: profile[0]?.level ?? null,
62
+ ...parsed,
63
+ };
64
+ }
65
+ catch {
66
+ return {
67
+ found: true,
68
+ current_score: dq.quality_points,
69
+ action_plan: result.content,
70
+ strengths: [],
71
+ improvement_areas: [],
72
+ };
73
+ }
74
+ }
75
+ //# sourceMappingURL=suggest-improvement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"suggest-improvement.js","sourceRoot":"","sources":["../../../src/tools/ai/suggest-improvement.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAM1D,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,KAAa,EACb,MAAqB;IAErB,MAAM,CAAC,OAAO,EAAE,WAAW,EAAE,OAAO,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACxD,QAAQ,CACN;;;;;;eAMS,EACT,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC,EAC9B,KAAK,CACN;QACD,QAAQ,CACN;;;;;;eAMS,EACT,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC,EAC9B,KAAK,CACN;QACD,QAAQ,CACN;;;eAGS,EACT,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,CAAC,EAC9B,KAAK,CACN;KACF,CAAC,CAAC;IAEH,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,4BAA4B,MAAM,CAAC,cAAc,GAAG,EAAE,CAAC;IACzF,CAAC;IAED,MAAM,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtB,MAAM,WAAW,GAAG,WAAW;SAC5B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAO,CAAC,CAAC,IAAe,EAAE,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,oBAAoB,CAAC,CAAC,eAAe,GAAG,CAAC;SAC/F,IAAI,CAAC,IAAI,CAAC,CAAC;IAEd,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC;QAClC,QAAQ,EAAE;YACR;gBACE,IAAI,EAAE,QAAQ;gBACd,OAAO,EAAE,kYAAkY;aAC5Y;YACD;gBACE,IAAI,EAAE,MAAM;gBACZ,OAAO,EAAE,eAAe,EAAE,CAAC,eAAe,YAAY,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,SAAS;YAChF,EAAE,CAAC,cAAc;kBACX,EAAE,CAAC,mBAAmB;cAC1B,EAAE,CAAC,eAAe;gBAChB,EAAE,CAAC,iBAAiB;oBAChB,EAAE,CAAC,qBAAqB;WACjC,EAAE,CAAC,cAAc,YAAY,EAAE,CAAC,WAAW,iBAAiB,EAAE,CAAC,eAAe;;;EAGvF,WAAW,IAAI,2BAA2B;;4CAEA;aACrC;SACF;QACD,SAAS,EAAE,GAAG;QACd,WAAW,EAAE,GAAG;KACjB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;QAC1C,OAAO;YACL,KAAK,EAAE,IAAI;YACX,aAAa,EAAE,EAAE,CAAC,cAAc;YAChC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,IAAI;YAChC,GAAG,MAAM;SACV,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;YACL,KAAK,EAAE,IAAI;YACX,aAAa,EAAE,EAAE,CAAC,cAAc;YAChC,WAAW,EAAE,MAAM,CAAC,OAAO;YAC3B,SAAS,EAAE,EAAE;YACb,iBAAiB,EAAE,EAAE;SACtB,CAAC;IACJ,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ export declare function getBacklogPressure(mspId: string): Promise<{
2
+ by_priority: {
3
+ priority_name: string;
4
+ green: number;
5
+ yellow: number;
6
+ red: number;
7
+ total: number;
8
+ }[];
9
+ summary: {
10
+ total_open: number;
11
+ critical_aging: number;
12
+ pct_red: number;
13
+ };
14
+ }>;
15
+ //# sourceMappingURL=backlog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backlog.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/backlog.ts"],"names":[],"mappings":"AAGA,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM;;uBAkBjC,MAAM;eAAS,MAAM;gBAAU,MAAM;aAAO,MAAM;eAAS,MAAM;;;;;;;GA+BrF"}
@@ -0,0 +1,41 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ export async function getBacklogPressure(mspId) {
3
+ const rows = await queryRLS(`SELECT priority_name, age_bucket, ticket_count, ticket_ids
4
+ FROM analytics.backlog_pressure_daily
5
+ WHERE msp_id = $1
6
+ AND snapshot_date = (
7
+ SELECT MAX(snapshot_date)
8
+ FROM analytics.backlog_pressure_daily
9
+ WHERE msp_id = $1
10
+ )
11
+ ORDER BY priority_name, age_bucket`, [mspId], mspId);
12
+ // Pivot into per-priority summary
13
+ const byPriority = {};
14
+ for (const row of rows) {
15
+ if (!byPriority[row.priority_name]) {
16
+ byPriority[row.priority_name] = {
17
+ priority_name: row.priority_name,
18
+ green: 0,
19
+ yellow: 0,
20
+ red: 0,
21
+ total: 0,
22
+ };
23
+ }
24
+ const entry = byPriority[row.priority_name];
25
+ const count = Number(row.ticket_count) || 0;
26
+ entry[row.age_bucket] = count;
27
+ entry.total += count;
28
+ }
29
+ const priorities = Object.values(byPriority);
30
+ const totalOpen = priorities.reduce((sum, p) => sum + p.total, 0);
31
+ const totalRed = priorities.reduce((sum, p) => sum + p.red, 0);
32
+ return {
33
+ by_priority: priorities,
34
+ summary: {
35
+ total_open: totalOpen,
36
+ critical_aging: totalRed,
37
+ pct_red: totalOpen > 0 ? Math.round((totalRed / totalOpen) * 100) : 0,
38
+ },
39
+ };
40
+ }
41
+ //# sourceMappingURL=backlog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backlog.js","sourceRoot":"","sources":["../../../src/tools/analytics/backlog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAGtD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,KAAa;IACpD,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB;;;;;;;;wCAQoC,EACpC,CAAC,KAAK,CAAC,EACP,KAAK,CACN,CAAC;IAEF,kCAAkC;IAClC,MAAM,UAAU,GAGZ,EAAE,CAAC;IAEP,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,CAAC;YACnC,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG;gBAC9B,aAAa,EAAE,GAAG,CAAC,aAAa;gBAChC,KAAK,EAAE,CAAC;gBACR,MAAM,EAAE,CAAC;gBACT,GAAG,EAAE,CAAC;gBACN,KAAK,EAAE,CAAC;aACT,CAAC;QACJ,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QAC5C,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAC5C,KAAK,CAAC,GAAG,CAAC,UAAwC,CAAC,GAAG,KAAK,CAAC;QAC5D,KAAK,CAAC,KAAK,IAAI,KAAK,CAAC;IACvB,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC7C,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;IAClE,MAAM,QAAQ,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAE/D,OAAO;QACL,WAAW,EAAE,UAAU;QACvB,OAAO,EAAE;YACP,UAAU,EAAE,SAAS;YACrB,cAAc,EAAE,QAAQ;YACxB,OAAO,EAAE,SAAS,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,GAAG,SAAS,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;SACtE;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { BillingClassification } from "../../types/database.js";
2
+ interface BillingParams {
3
+ fromDate?: string;
4
+ toDate?: string;
5
+ }
6
+ export declare function getBillingOpportunities(mspId: string, params: BillingParams): Promise<{
7
+ summary: {
8
+ total_tickets: number;
9
+ total_dollars: number;
10
+ avg_per_ticket: number;
11
+ };
12
+ tickets: BillingClassification[];
13
+ }>;
14
+ export {};
15
+ //# sourceMappingURL=billing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAErE,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAsB,uBAAuB,CAC3C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,aAAa;;;;;;;GA6BtB"}
@@ -0,0 +1,24 @@
1
+ import { queryRLS } from "../../db/gateway-client.js";
2
+ export async function getBillingOpportunities(mspId, params) {
3
+ const fromDate = params.fromDate ?? new Date(Date.now() - 30 * 86400000).toISOString().split("T")[0];
4
+ const toDate = params.toDate ?? new Date().toISOString().split("T")[0];
5
+ const rows = await queryRLS(`SELECT bc.ticket_id, bc.company_name, bc.classification,
6
+ bc.billable_amount, bc.resolved_date::text
7
+ FROM agents.billing_classifications bc
8
+ WHERE bc.msp_id::uuid = $1::uuid
9
+ AND bc.classification = 'out_of_scope'
10
+ AND bc.resolved_date >= $2::date
11
+ AND bc.resolved_date <= $3::date
12
+ AND bc.billable_amount > 0
13
+ ORDER BY bc.billable_amount DESC`, [mspId, fromDate, toDate], mspId);
14
+ const totalDollars = rows.reduce((sum, r) => sum + (r.billable_amount || 0), 0);
15
+ return {
16
+ summary: {
17
+ total_tickets: rows.length,
18
+ total_dollars: Math.round(totalDollars * 100) / 100,
19
+ avg_per_ticket: rows.length > 0 ? Math.round((totalDollars / rows.length) * 100) / 100 : 0,
20
+ },
21
+ tickets: rows,
22
+ };
23
+ }
24
+ //# sourceMappingURL=billing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"billing.js","sourceRoot":"","sources":["../../../src/tools/analytics/billing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAQtD,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,KAAa,EACb,MAAqB;IAErB,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACrG,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IAEvE,MAAM,IAAI,GAAG,MAAM,QAAQ,CACzB;;;;;;;;sCAQkC,EAClC,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,CAAC,EACzB,KAAK,CACN,CAAC;IAEF,MAAM,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,eAAe,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAEhF,OAAO;QACL,OAAO,EAAE;YACP,aAAa,EAAE,IAAI,CAAC,MAAM;YAC1B,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,YAAY,GAAG,GAAG,CAAC,GAAG,GAAG;YACnD,cAAc,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,YAAY,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,GAAG,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;SAC3F;QACD,OAAO,EAAE,IAAI;KACd,CAAC;AACJ,CAAC"}
@@ -0,0 +1,32 @@
1
+ interface ClientExpParams {
2
+ clientName: string;
3
+ daysBack?: number;
4
+ }
5
+ interface ClientExpRow {
6
+ company_name: string;
7
+ ticket_count: number;
8
+ avg_response_minutes: number;
9
+ avg_resolution_minutes: number;
10
+ avg_sentiment_score: number;
11
+ health_score: number;
12
+ total_cost: number;
13
+ sla_breach_count: number;
14
+ }
15
+ export declare function getClientExperience(mspId: string, params: ClientExpParams): Promise<{
16
+ found: boolean;
17
+ message: string;
18
+ metrics?: undefined;
19
+ top_patterns?: undefined;
20
+ days_analyzed?: undefined;
21
+ } | {
22
+ found: boolean;
23
+ metrics: ClientExpRow;
24
+ top_patterns: {
25
+ pattern_name: string;
26
+ ticket_count: number;
27
+ }[];
28
+ days_analyzed: number;
29
+ message?: undefined;
30
+ }>;
31
+ export {};
32
+ //# sourceMappingURL=client-experience.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-experience.d.ts","sourceRoot":"","sources":["../../../src/tools/analytics/client-experience.ts"],"names":[],"mappings":"AAEA,UAAU,eAAe;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,YAAY;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,eAAe;;;;;;;;;;sBA2ByB,MAAM;sBAAgB,MAAM;;;;GAiB7E"}