@ddse/acm-examples 0.5.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.
- package/LICENSE +21 -0
- package/README.md +113 -0
- package/bin/acm-demo.ts +495 -0
- package/data/coaching/agents.json +16 -0
- package/data/coaching/transcripts.json +37 -0
- package/data/documents.json +72 -0
- package/data/entitlement/customers.json +38 -0
- package/data/entitlement/policies.json +38 -0
- package/data/incidents/incidents.json +30 -0
- package/data/incidents/routing_rules.json +36 -0
- package/data/invoices/invoices.json +38 -0
- package/data/invoices/purchase-orders.json +38 -0
- package/data/issues.json +99 -0
- package/data/knowledge/docs/kb-001.md +7 -0
- package/data/knowledge/docs/kb-002.md +7 -0
- package/data/knowledge/docs/kb-003.md +9 -0
- package/data/knowledge/index.json +25 -0
- package/data/orders.json +106 -0
- package/dist/bin/acm-demo.d.ts +3 -0
- package/dist/bin/acm-demo.d.ts.map +1 -0
- package/dist/bin/acm-demo.js +392 -0
- package/dist/bin/acm-demo.js.map +1 -0
- package/dist/src/context/directives.d.ts +3 -0
- package/dist/src/context/directives.d.ts.map +1 -0
- package/dist/src/context/directives.js +325 -0
- package/dist/src/context/directives.js.map +1 -0
- package/dist/src/context/index.d.ts +2 -0
- package/dist/src/context/index.d.ts.map +1 -0
- package/dist/src/context/index.js +2 -0
- package/dist/src/context/index.js.map +1 -0
- package/dist/src/data/coaching.d.ts +19 -0
- package/dist/src/data/coaching.d.ts.map +1 -0
- package/dist/src/data/coaching.js +22 -0
- package/dist/src/data/coaching.js.map +1 -0
- package/dist/src/data/entitlement.d.ts +25 -0
- package/dist/src/data/entitlement.d.ts.map +1 -0
- package/dist/src/data/entitlement.js +26 -0
- package/dist/src/data/entitlement.js.map +1 -0
- package/dist/src/data/incidents.d.ts +23 -0
- package/dist/src/data/incidents.d.ts.map +1 -0
- package/dist/src/data/incidents.js +37 -0
- package/dist/src/data/incidents.js.map +1 -0
- package/dist/src/data/invoices.d.ts +34 -0
- package/dist/src/data/invoices.d.ts.map +1 -0
- package/dist/src/data/invoices.js +49 -0
- package/dist/src/data/invoices.js.map +1 -0
- package/dist/src/data/knowledge.d.ts +11 -0
- package/dist/src/data/knowledge.d.ts.map +1 -0
- package/dist/src/data/knowledge.js +57 -0
- package/dist/src/data/knowledge.js.map +1 -0
- package/dist/src/data/loader.d.ts +4 -0
- package/dist/src/data/loader.d.ts.map +1 -0
- package/dist/src/data/loader.js +69 -0
- package/dist/src/data/loader.js.map +1 -0
- package/dist/src/examples/scenarios.d.ts +23 -0
- package/dist/src/examples/scenarios.d.ts.map +1 -0
- package/dist/src/examples/scenarios.js +609 -0
- package/dist/src/examples/scenarios.js.map +1 -0
- package/dist/src/goals/index.d.ts +8 -0
- package/dist/src/goals/index.d.ts.map +1 -0
- package/dist/src/goals/index.js +12 -0
- package/dist/src/goals/index.js.map +1 -0
- package/dist/src/policy.d.ts +5 -0
- package/dist/src/policy.d.ts.map +1 -0
- package/dist/src/policy.js +24 -0
- package/dist/src/policy.js.map +1 -0
- package/dist/src/registries.d.ts +18 -0
- package/dist/src/registries.d.ts.map +1 -0
- package/dist/src/registries.js +38 -0
- package/dist/src/registries.js.map +1 -0
- package/dist/src/renderer.d.ts +9 -0
- package/dist/src/renderer.d.ts.map +1 -0
- package/dist/src/renderer.js +76 -0
- package/dist/src/renderer.js.map +1 -0
- package/dist/src/search/bm25.d.ts +68 -0
- package/dist/src/search/bm25.d.ts.map +1 -0
- package/dist/src/search/bm25.js +131 -0
- package/dist/src/search/bm25.js.map +1 -0
- package/dist/src/search/index.d.ts +2 -0
- package/dist/src/search/index.d.ts.map +1 -0
- package/dist/src/search/index.js +3 -0
- package/dist/src/search/index.js.map +1 -0
- package/dist/src/tasks/coaching.d.ts +30 -0
- package/dist/src/tasks/coaching.d.ts.map +1 -0
- package/dist/src/tasks/coaching.js +143 -0
- package/dist/src/tasks/coaching.js.map +1 -0
- package/dist/src/tasks/entitlement.d.ts +29 -0
- package/dist/src/tasks/entitlement.d.ts.map +1 -0
- package/dist/src/tasks/entitlement.js +135 -0
- package/dist/src/tasks/entitlement.js.map +1 -0
- package/dist/src/tasks/incidents.d.ts +42 -0
- package/dist/src/tasks/incidents.d.ts.map +1 -0
- package/dist/src/tasks/incidents.js +189 -0
- package/dist/src/tasks/incidents.js.map +1 -0
- package/dist/src/tasks/index.d.ts +7 -0
- package/dist/src/tasks/index.d.ts.map +1 -0
- package/dist/src/tasks/index.js +7 -0
- package/dist/src/tasks/index.js.map +1 -0
- package/dist/src/tasks/invoices.d.ts +40 -0
- package/dist/src/tasks/invoices.d.ts.map +1 -0
- package/dist/src/tasks/invoices.js +180 -0
- package/dist/src/tasks/invoices.js.map +1 -0
- package/dist/src/tasks/knowledge.d.ts +23 -0
- package/dist/src/tasks/knowledge.d.ts.map +1 -0
- package/dist/src/tasks/knowledge.js +115 -0
- package/dist/src/tasks/knowledge.js.map +1 -0
- package/dist/src/tasks/legacy.d.ts +50 -0
- package/dist/src/tasks/legacy.d.ts.map +1 -0
- package/dist/src/tasks/legacy.js +85 -0
- package/dist/src/tasks/legacy.js.map +1 -0
- package/dist/src/tools/coaching/index.d.ts +49 -0
- package/dist/src/tools/coaching/index.d.ts.map +1 -0
- package/dist/src/tools/coaching/index.js +119 -0
- package/dist/src/tools/coaching/index.js.map +1 -0
- package/dist/src/tools/entitlement/index.d.ts +52 -0
- package/dist/src/tools/entitlement/index.d.ts.map +1 -0
- package/dist/src/tools/entitlement/index.js +120 -0
- package/dist/src/tools/entitlement/index.js.map +1 -0
- package/dist/src/tools/incidents/index.d.ts +55 -0
- package/dist/src/tools/incidents/index.d.ts.map +1 -0
- package/dist/src/tools/incidents/index.js +109 -0
- package/dist/src/tools/incidents/index.js.map +1 -0
- package/dist/src/tools/index.d.ts +90 -0
- package/dist/src/tools/index.d.ts.map +1 -0
- package/dist/src/tools/index.js +109 -0
- package/dist/src/tools/index.js.map +1 -0
- package/dist/src/tools/invoices/index.d.ts +56 -0
- package/dist/src/tools/invoices/index.d.ts.map +1 -0
- package/dist/src/tools/invoices/index.js +85 -0
- package/dist/src/tools/invoices/index.js.map +1 -0
- package/dist/src/tools/knowledge/index.d.ts +52 -0
- package/dist/src/tools/knowledge/index.d.ts.map +1 -0
- package/dist/src/tools/knowledge/index.js +120 -0
- package/dist/src/tools/knowledge/index.js.map +1 -0
- package/dist/tests/bm25.test.d.ts +2 -0
- package/dist/tests/bm25.test.d.ts.map +1 -0
- package/dist/tests/bm25.test.js +98 -0
- package/dist/tests/bm25.test.js.map +1 -0
- package/dist/tests/integration.test.d.ts +2 -0
- package/dist/tests/integration.test.d.ts.map +1 -0
- package/dist/tests/integration.test.js +126 -0
- package/dist/tests/integration.test.js.map +1 -0
- package/dist/tests/plan-hydration.test.d.ts +2 -0
- package/dist/tests/plan-hydration.test.d.ts.map +1 -0
- package/dist/tests/plan-hydration.test.js +28 -0
- package/dist/tests/plan-hydration.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/docs/examples-architecture.md +144 -0
- package/docs/successrun.md +1022 -0
- package/package.json +33 -0
- package/src/context/directives.ts +366 -0
- package/src/context/index.ts +1 -0
- package/src/data/coaching.ts +50 -0
- package/src/data/entitlement.ts +60 -0
- package/src/data/incidents.ts +78 -0
- package/src/data/invoices.ts +103 -0
- package/src/data/knowledge.ts +77 -0
- package/src/data/loader.ts +80 -0
- package/src/examples/scenarios.ts +724 -0
- package/src/goals/index.ts +18 -0
- package/src/policy.ts +30 -0
- package/src/registries.ts +48 -0
- package/src/renderer.ts +82 -0
- package/src/search/bm25.ts +173 -0
- package/src/search/index.ts +2 -0
- package/src/tasks/coaching.ts +217 -0
- package/src/tasks/entitlement.ts +197 -0
- package/src/tasks/incidents.ts +277 -0
- package/src/tasks/index.ts +6 -0
- package/src/tasks/invoices.ts +269 -0
- package/src/tasks/knowledge.ts +169 -0
- package/src/tasks/legacy.ts +112 -0
- package/src/tools/coaching/index.ts +197 -0
- package/src/tools/entitlement/index.ts +199 -0
- package/src/tools/incidents/index.ts +185 -0
- package/src/tools/index.ts +192 -0
- package/src/tools/invoices/index.ts +165 -0
- package/src/tools/knowledge/index.ts +203 -0
- package/tests/bm25.test.ts +129 -0
- package/tests/integration.test.ts +163 -0
- package/tests/plan-hydration.test.ts +33 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
getTranscript,
|
|
4
|
+
getAgent,
|
|
5
|
+
type CoachingTranscript,
|
|
6
|
+
type AgentProfile,
|
|
7
|
+
} from '../../data/coaching.js';
|
|
8
|
+
|
|
9
|
+
export type AnalyzeTranscriptInput = {
|
|
10
|
+
transcriptId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type AnalyzeTranscriptOutput = {
|
|
14
|
+
transcript: CoachingTranscript;
|
|
15
|
+
sentimentScore: number;
|
|
16
|
+
complianceScore: number;
|
|
17
|
+
complianceBreaches: string[];
|
|
18
|
+
highlights: string[];
|
|
19
|
+
summary: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
function computeSentimentScore(transcript: CoachingTranscript): number {
|
|
23
|
+
switch (transcript.customerSentiment) {
|
|
24
|
+
case 'POSITIVE':
|
|
25
|
+
return 0.85;
|
|
26
|
+
case 'NEUTRAL':
|
|
27
|
+
return 0.55;
|
|
28
|
+
case 'NEGATIVE':
|
|
29
|
+
return 0.25;
|
|
30
|
+
default:
|
|
31
|
+
return 0.5;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function computeComplianceScore(flags: string[]): number {
|
|
36
|
+
if (flags.length === 0) {
|
|
37
|
+
return 0.95;
|
|
38
|
+
}
|
|
39
|
+
return Math.max(0.2, 0.95 - flags.length * 0.15);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function buildHighlights(transcript: CoachingTranscript): string[] {
|
|
43
|
+
const highlights: string[] = [];
|
|
44
|
+
if (transcript.followUpRequired) {
|
|
45
|
+
highlights.push('Customer requested follow-up action');
|
|
46
|
+
}
|
|
47
|
+
if (transcript.transcript.some(line => /apolog/i.test(line))) {
|
|
48
|
+
highlights.push('Agent provided customer apology');
|
|
49
|
+
}
|
|
50
|
+
const empathyLine = transcript.transcript.find(line => /understand|appreciate|thanks/i.test(line));
|
|
51
|
+
if (empathyLine) {
|
|
52
|
+
highlights.push(`Empathy signal captured: "${empathyLine.trim().slice(0, 120)}"`);
|
|
53
|
+
}
|
|
54
|
+
return highlights.slice(0, 3);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class AnalyzeTranscriptTool extends Tool<
|
|
58
|
+
AnalyzeTranscriptInput,
|
|
59
|
+
AnalyzeTranscriptOutput
|
|
60
|
+
> {
|
|
61
|
+
name(): string {
|
|
62
|
+
return 'analyze_transcript';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async call(input: AnalyzeTranscriptInput): Promise<AnalyzeTranscriptOutput> {
|
|
66
|
+
if (!input?.transcriptId) {
|
|
67
|
+
throw new Error('transcriptId is required');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const transcript = await getTranscript(input.transcriptId);
|
|
71
|
+
if (!transcript) {
|
|
72
|
+
throw new Error(`Transcript ${input.transcriptId} not found`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sentimentScore = computeSentimentScore(transcript);
|
|
76
|
+
const complianceScore = computeComplianceScore(transcript.complianceFlags);
|
|
77
|
+
const summary = transcript.transcript.slice(0, 6).join(' ');
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
transcript,
|
|
81
|
+
sentimentScore,
|
|
82
|
+
complianceScore,
|
|
83
|
+
complianceBreaches: transcript.complianceFlags,
|
|
84
|
+
highlights: buildHighlights(transcript),
|
|
85
|
+
summary,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export type GenerateFeedbackInput = {
|
|
91
|
+
transcriptId: string;
|
|
92
|
+
metrics: Pick<
|
|
93
|
+
AnalyzeTranscriptOutput,
|
|
94
|
+
'sentimentScore' | 'complianceScore' | 'complianceBreaches' | 'highlights' | 'summary'
|
|
95
|
+
>;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
export type GenerateFeedbackOutput = {
|
|
99
|
+
transcriptId: string;
|
|
100
|
+
feedbackSummary: string;
|
|
101
|
+
actionItems: string[];
|
|
102
|
+
escalationRequired: boolean;
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
function formatPercent(score: number): string {
|
|
106
|
+
return `${Math.round(score * 100)}%`;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export class GenerateFeedbackTool extends Tool<
|
|
110
|
+
GenerateFeedbackInput,
|
|
111
|
+
GenerateFeedbackOutput
|
|
112
|
+
> {
|
|
113
|
+
name(): string {
|
|
114
|
+
return 'generate_feedback';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async call(input: GenerateFeedbackInput): Promise<GenerateFeedbackOutput> {
|
|
118
|
+
if (!input?.transcriptId || !input?.metrics) {
|
|
119
|
+
throw new Error('transcriptId and metrics are required');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const transcript = await getTranscript(input.transcriptId);
|
|
123
|
+
if (!transcript) {
|
|
124
|
+
throw new Error(`Transcript ${input.transcriptId} not found`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const escalationRequired = input.metrics.complianceBreaches.length > 0;
|
|
128
|
+
const sentimentStr = formatPercent(input.metrics.sentimentScore);
|
|
129
|
+
const complianceStr = formatPercent(input.metrics.complianceScore);
|
|
130
|
+
|
|
131
|
+
const feedbackSummary = [
|
|
132
|
+
`Overall sentiment scored at ${sentimentStr}.`,
|
|
133
|
+
`Compliance adherence at ${complianceStr}.`,
|
|
134
|
+
].join(' ');
|
|
135
|
+
|
|
136
|
+
const actionItems: string[] = [];
|
|
137
|
+
if (input.metrics.complianceBreaches.length > 0) {
|
|
138
|
+
actionItems.push(
|
|
139
|
+
`Address compliance items: ${input.metrics.complianceBreaches.join(', ')}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
if (input.metrics.sentimentScore < 0.5) {
|
|
143
|
+
actionItems.push('Practice empathy statements to de-escalate frustrated customers');
|
|
144
|
+
}
|
|
145
|
+
actionItems.push('Acknowledge customer feelings before delivering resolution details');
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
transcriptId: input.transcriptId,
|
|
149
|
+
feedbackSummary,
|
|
150
|
+
actionItems,
|
|
151
|
+
escalationRequired,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export type LogCoachingNoteInput = {
|
|
157
|
+
agentId: string;
|
|
158
|
+
feedbackSummary: string;
|
|
159
|
+
actionItems: string[];
|
|
160
|
+
escalationRequired: boolean;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export type LogCoachingNoteOutput = {
|
|
164
|
+
logId: string;
|
|
165
|
+
agent: AgentProfile;
|
|
166
|
+
stored: boolean;
|
|
167
|
+
escalationNotified: boolean;
|
|
168
|
+
timestamp: string;
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
export class LogCoachingNoteTool extends Tool<
|
|
172
|
+
LogCoachingNoteInput,
|
|
173
|
+
LogCoachingNoteOutput
|
|
174
|
+
> {
|
|
175
|
+
name(): string {
|
|
176
|
+
return 'log_coaching_note';
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async call(input: LogCoachingNoteInput): Promise<LogCoachingNoteOutput> {
|
|
180
|
+
if (!input?.agentId || !input?.feedbackSummary) {
|
|
181
|
+
throw new Error('agentId and feedbackSummary are required');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const agent = await getAgent(input.agentId);
|
|
185
|
+
if (!agent) {
|
|
186
|
+
throw new Error(`Agent ${input.agentId} not found`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
logId: `coach-${Date.now()}`,
|
|
191
|
+
agent,
|
|
192
|
+
stored: true,
|
|
193
|
+
escalationNotified: input.escalationRequired,
|
|
194
|
+
timestamp: new Date().toISOString(),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
getCustomerProfile,
|
|
4
|
+
getPolicy,
|
|
5
|
+
type CustomerProfile,
|
|
6
|
+
type EntitlementPolicy,
|
|
7
|
+
} from '../../data/entitlement.js';
|
|
8
|
+
|
|
9
|
+
export type FetchCustomerProfileInput = {
|
|
10
|
+
customerId: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type FetchCustomerProfileOutput = {
|
|
14
|
+
customer: CustomerProfile;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export class FetchCustomerProfileTool extends Tool<
|
|
18
|
+
FetchCustomerProfileInput,
|
|
19
|
+
FetchCustomerProfileOutput
|
|
20
|
+
> {
|
|
21
|
+
name(): string {
|
|
22
|
+
return 'fetch_customer_profile';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async call(input: FetchCustomerProfileInput): Promise<FetchCustomerProfileOutput> {
|
|
26
|
+
if (!input?.customerId) {
|
|
27
|
+
throw new Error('customerId is required');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const customer = await getCustomerProfile(input.customerId);
|
|
31
|
+
if (!customer) {
|
|
32
|
+
throw new Error(`Customer ${input.customerId} not found`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return { customer };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export type EvaluateEntitlementInput = {
|
|
40
|
+
customerId: string;
|
|
41
|
+
benefitCode: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type EvaluateEntitlementViolation = {
|
|
45
|
+
code: 'tier_mismatch' | 'account_age' | 'compliance_hold' | 'customer_missing' | 'policy_missing';
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export type EvaluateEntitlementOutput = {
|
|
50
|
+
decision: 'allow' | 'deny';
|
|
51
|
+
policy: EntitlementPolicy;
|
|
52
|
+
customer: CustomerProfile;
|
|
53
|
+
slaMinutes: number;
|
|
54
|
+
rationale: string[];
|
|
55
|
+
violations: EvaluateEntitlementViolation[];
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const tierRank: Record<CustomerProfile['tier'], number> = {
|
|
59
|
+
STANDARD: 1,
|
|
60
|
+
GOLD: 2,
|
|
61
|
+
PLATINUM: 3,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
export class EvaluateEntitlementTool extends Tool<
|
|
65
|
+
EvaluateEntitlementInput,
|
|
66
|
+
EvaluateEntitlementOutput
|
|
67
|
+
> {
|
|
68
|
+
name(): string {
|
|
69
|
+
return 'evaluate_entitlement';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async call(input: EvaluateEntitlementInput): Promise<EvaluateEntitlementOutput> {
|
|
73
|
+
if (!input?.customerId || !input?.benefitCode) {
|
|
74
|
+
throw new Error('customerId and benefitCode are required');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const [customer, policy] = await Promise.all([
|
|
78
|
+
getCustomerProfile(input.customerId),
|
|
79
|
+
getPolicy(input.benefitCode),
|
|
80
|
+
]);
|
|
81
|
+
|
|
82
|
+
const violations: EvaluateEntitlementViolation[] = [];
|
|
83
|
+
const rationale: string[] = [];
|
|
84
|
+
|
|
85
|
+
if (!customer) {
|
|
86
|
+
violations.push({
|
|
87
|
+
code: 'customer_missing',
|
|
88
|
+
message: `Customer ${input.customerId} was not found in CRM snapshot`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (!policy) {
|
|
93
|
+
violations.push({
|
|
94
|
+
code: 'policy_missing',
|
|
95
|
+
message: `Policy ${input.benefitCode} was not found in entitlement catalog`,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!customer || !policy) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
`Unable to evaluate entitlement: ${violations.map(v => v.message).join('; ')}`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (tierRank[customer.tier] < tierRank[policy.requiredTier]) {
|
|
106
|
+
violations.push({
|
|
107
|
+
code: 'tier_mismatch',
|
|
108
|
+
message: `Customer tier ${customer.tier} does not meet required tier ${policy.requiredTier}`,
|
|
109
|
+
});
|
|
110
|
+
} else {
|
|
111
|
+
rationale.push(`Customer tier ${customer.tier} satisfies required tier ${policy.requiredTier}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (customer.accountAgeDays < policy.minAccountAgeDays) {
|
|
115
|
+
violations.push({
|
|
116
|
+
code: 'account_age',
|
|
117
|
+
message: `Account age ${customer.accountAgeDays} days is below minimum ${policy.minAccountAgeDays} days`,
|
|
118
|
+
});
|
|
119
|
+
} else {
|
|
120
|
+
rationale.push(
|
|
121
|
+
`Account age ${customer.accountAgeDays} days exceeds minimum ${policy.minAccountAgeDays} days`,
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const hasComplianceHold = customer.complianceFlags.length > 0;
|
|
126
|
+
if (policy.requiresComplianceClearance && hasComplianceHold) {
|
|
127
|
+
violations.push({
|
|
128
|
+
code: 'compliance_hold',
|
|
129
|
+
message: `Compliance flags present: ${customer.complianceFlags.join(', ')}`,
|
|
130
|
+
});
|
|
131
|
+
} else if (policy.requiresComplianceClearance) {
|
|
132
|
+
rationale.push('No compliance holds found for customer');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const decision = violations.length === 0 ? 'allow' : 'deny';
|
|
136
|
+
if (decision === 'deny') {
|
|
137
|
+
rationale.push('Entitlement denied due to policy violations');
|
|
138
|
+
} else {
|
|
139
|
+
rationale.push('Entitlement approved according to policy requirements');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
decision,
|
|
144
|
+
policy,
|
|
145
|
+
customer,
|
|
146
|
+
slaMinutes: policy.slaMinutes,
|
|
147
|
+
rationale,
|
|
148
|
+
violations,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export type NotifySupervisorInput = {
|
|
154
|
+
customerId: string;
|
|
155
|
+
message: string;
|
|
156
|
+
channel?: 'email' | 'slack';
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export type NotifySupervisorOutput = {
|
|
160
|
+
notified: boolean;
|
|
161
|
+
channel: string;
|
|
162
|
+
supervisor: {
|
|
163
|
+
name: string;
|
|
164
|
+
email: string;
|
|
165
|
+
};
|
|
166
|
+
messageId: string;
|
|
167
|
+
timestamp: string;
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
export class EntitlementNotifySupervisorTool extends Tool<
|
|
171
|
+
NotifySupervisorInput,
|
|
172
|
+
NotifySupervisorOutput
|
|
173
|
+
> {
|
|
174
|
+
name(): string {
|
|
175
|
+
return 'notify_supervisor';
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async call(input: NotifySupervisorInput): Promise<NotifySupervisorOutput> {
|
|
179
|
+
if (!input?.customerId || !input?.message) {
|
|
180
|
+
throw new Error('customerId and message are required');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const customer = await getCustomerProfile(input.customerId);
|
|
184
|
+
if (!customer) {
|
|
185
|
+
throw new Error(`Customer ${input.customerId} not found`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const channel = input.channel ?? 'email';
|
|
189
|
+
const messageId = `notif-${Date.now()}`;
|
|
190
|
+
|
|
191
|
+
return {
|
|
192
|
+
notified: true,
|
|
193
|
+
channel,
|
|
194
|
+
supervisor: customer.supervisor,
|
|
195
|
+
messageId,
|
|
196
|
+
timestamp: new Date().toISOString(),
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { Tool } from '@ddse/acm-sdk';
|
|
2
|
+
import {
|
|
3
|
+
getIncident,
|
|
4
|
+
findRoutingRule,
|
|
5
|
+
type IncidentRecord,
|
|
6
|
+
type IncidentSeverity,
|
|
7
|
+
type RoutingRule,
|
|
8
|
+
} from '../../data/incidents.js';
|
|
9
|
+
|
|
10
|
+
export type FetchIncidentInput = {
|
|
11
|
+
incidentId: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type FetchIncidentOutput = {
|
|
15
|
+
incident: IncidentRecord;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export class FetchIncidentTool extends Tool<FetchIncidentInput, FetchIncidentOutput> {
|
|
19
|
+
name(): string {
|
|
20
|
+
return 'fetch_incident';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async call(input: FetchIncidentInput): Promise<FetchIncidentOutput> {
|
|
24
|
+
if (!input?.incidentId) {
|
|
25
|
+
throw new Error('incidentId is required');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const incident = await getIncident(input.incidentId);
|
|
29
|
+
if (!incident) {
|
|
30
|
+
throw new Error(`Incident ${input.incidentId} not found`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return { incident };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type ClassifySeverityInput = {
|
|
38
|
+
incident: IncidentRecord;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ClassifySeverityOutput = {
|
|
42
|
+
severity: IncidentSeverity;
|
|
43
|
+
score: number;
|
|
44
|
+
rationale: string[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
function scoreSeverity(incident: IncidentRecord): { severity: IncidentSeverity; score: number; rationale: string[] } {
|
|
48
|
+
const rationale: string[] = [];
|
|
49
|
+
let score = 0;
|
|
50
|
+
|
|
51
|
+
const declaredRank = rank(incident.declaredSeverity);
|
|
52
|
+
score += declaredRank * 20;
|
|
53
|
+
rationale.push(`Declared severity ${incident.declaredSeverity} provides base score ${declaredRank * 20}`);
|
|
54
|
+
|
|
55
|
+
if (incident.customerImpact === 'MAJOR') {
|
|
56
|
+
score += 30;
|
|
57
|
+
rationale.push('Major customer impact adds 30 points');
|
|
58
|
+
} else if (incident.customerImpact === 'MINOR') {
|
|
59
|
+
score += 15;
|
|
60
|
+
rationale.push('Minor customer impact adds 15 points');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
score += Math.min(incident.signalScore, 40);
|
|
64
|
+
rationale.push(`Signal score contribution: ${Math.min(incident.signalScore, 40)}`);
|
|
65
|
+
|
|
66
|
+
if (incident.vipCustomer) {
|
|
67
|
+
score += 10;
|
|
68
|
+
rationale.push('VIP customer flag adds 10 points');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const severity = score >= 80 ? 'CRITICAL' : score >= 60 ? 'HIGH' : score >= 40 ? 'MEDIUM' : 'LOW';
|
|
72
|
+
rationale.push(`Composite score ${score} maps to severity ${severity}`);
|
|
73
|
+
return { severity, score, rationale };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function rank(severity: IncidentSeverity): number {
|
|
77
|
+
switch (severity) {
|
|
78
|
+
case 'LOW': return 1;
|
|
79
|
+
case 'MEDIUM': return 2;
|
|
80
|
+
case 'HIGH': return 3;
|
|
81
|
+
case 'CRITICAL': return 4;
|
|
82
|
+
default: return 0;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class ClassifySeverityTool extends Tool<
|
|
87
|
+
ClassifySeverityInput,
|
|
88
|
+
ClassifySeverityOutput
|
|
89
|
+
> {
|
|
90
|
+
name(): string {
|
|
91
|
+
return 'classify_severity';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async call(input: ClassifySeverityInput): Promise<ClassifySeverityOutput> {
|
|
95
|
+
if (!input?.incident) {
|
|
96
|
+
throw new Error('incident is required');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return scoreSeverity(input.incident);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export type SelectQueueInput = {
|
|
104
|
+
incident: IncidentRecord;
|
|
105
|
+
severityOverride?: IncidentSeverity;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export type SelectQueueOutput = {
|
|
109
|
+
queue: string;
|
|
110
|
+
rule: RoutingRule;
|
|
111
|
+
escalationRequired: boolean;
|
|
112
|
+
rationale: string[];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export class SelectQueueTool extends Tool<SelectQueueInput, SelectQueueOutput> {
|
|
116
|
+
name(): string {
|
|
117
|
+
return 'select_queue';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async call(input: SelectQueueInput): Promise<SelectQueueOutput> {
|
|
121
|
+
if (!input?.incident) {
|
|
122
|
+
throw new Error('incident is required');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const rule = await findRoutingRule(input.incident, input.severityOverride);
|
|
126
|
+
if (!rule) {
|
|
127
|
+
throw new Error(`No routing rule found for incident ${input.incident.id}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const rationale = [
|
|
131
|
+
`Matched routing rule ${rule.id} for service ${rule.service} category ${rule.category}`,
|
|
132
|
+
];
|
|
133
|
+
if (rule.escalatesTo) {
|
|
134
|
+
rationale.push(`Escalation target defined: ${rule.escalatesTo}`);
|
|
135
|
+
}
|
|
136
|
+
if (rule.notes) {
|
|
137
|
+
rationale.push(`Routing notes: ${rule.notes}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
queue: rule.queue,
|
|
142
|
+
rule,
|
|
143
|
+
escalationRequired: Boolean(rule.escalatesTo),
|
|
144
|
+
rationale,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export type EscalateIncidentInput = {
|
|
150
|
+
incidentId: string;
|
|
151
|
+
target: string;
|
|
152
|
+
reason: string;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export type EscalateIncidentOutput = {
|
|
156
|
+
escalated: boolean;
|
|
157
|
+
ticketId: string;
|
|
158
|
+
target: string;
|
|
159
|
+
reason: string;
|
|
160
|
+
timestamp: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export class EscalateIncidentTool extends Tool<
|
|
164
|
+
EscalateIncidentInput,
|
|
165
|
+
EscalateIncidentOutput
|
|
166
|
+
> {
|
|
167
|
+
name(): string {
|
|
168
|
+
return 'escalate_incident';
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async call(input: EscalateIncidentInput): Promise<EscalateIncidentOutput> {
|
|
172
|
+
if (!input?.incidentId || !input?.target || !input?.reason) {
|
|
173
|
+
throw new Error('incidentId, target, and reason are required');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const ticketId = `esc-${Date.now()}`;
|
|
177
|
+
return {
|
|
178
|
+
escalated: true,
|
|
179
|
+
ticketId,
|
|
180
|
+
target: input.target,
|
|
181
|
+
reason: input.reason,
|
|
182
|
+
timestamp: new Date().toISOString(),
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|