@absolutejs/voice 0.0.22-beta.512 → 0.0.22-beta.513
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/dist/agentPerformanceReport.d.ts +40 -0
- package/dist/aiScorecard.d.ts +32 -0
- package/dist/callScorecard.d.ts +53 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +510 -0
- package/dist/qualityDriftDetector.d.ts +44 -0
- package/dist/scorecardCalibration.d.ts +31 -0
- package/dist/vue/VoiceCostDashboard.d.ts +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { VoiceScorecard } from "./callScorecard";
|
|
2
|
+
export type VoiceAgentPerformanceBucket = "day" | "week" | "month";
|
|
3
|
+
export type VoiceAgentPerformanceCriterionSummary = {
|
|
4
|
+
criterionId: string;
|
|
5
|
+
averageScore: number;
|
|
6
|
+
passRate: number;
|
|
7
|
+
trend: "up" | "down" | "flat";
|
|
8
|
+
delta: number;
|
|
9
|
+
};
|
|
10
|
+
export type VoiceAgentPerformanceBucketSummary = {
|
|
11
|
+
bucketKey: string;
|
|
12
|
+
callsScored: number;
|
|
13
|
+
averageWeightedScore: number;
|
|
14
|
+
passRate: number;
|
|
15
|
+
needsReviewRate: number;
|
|
16
|
+
failRate: number;
|
|
17
|
+
};
|
|
18
|
+
export type VoiceAgentPerformanceReport = {
|
|
19
|
+
agentId: string;
|
|
20
|
+
rubricId: string;
|
|
21
|
+
fromMs: number;
|
|
22
|
+
toMs: number;
|
|
23
|
+
bucket: VoiceAgentPerformanceBucket;
|
|
24
|
+
totalCalls: number;
|
|
25
|
+
buckets: VoiceAgentPerformanceBucketSummary[];
|
|
26
|
+
criteria: VoiceAgentPerformanceCriterionSummary[];
|
|
27
|
+
overallPassRate: number;
|
|
28
|
+
overallAverageScore: number;
|
|
29
|
+
worstCriterion: string | null;
|
|
30
|
+
bestCriterion: string | null;
|
|
31
|
+
};
|
|
32
|
+
export type BuildVoiceAgentPerformanceReportInput = {
|
|
33
|
+
agentId: string;
|
|
34
|
+
rubricId: string;
|
|
35
|
+
scorecards: VoiceScorecard[];
|
|
36
|
+
fromMs?: number;
|
|
37
|
+
toMs?: number;
|
|
38
|
+
bucket?: VoiceAgentPerformanceBucket;
|
|
39
|
+
};
|
|
40
|
+
export declare const buildVoiceAgentPerformanceReport: (input: BuildVoiceAgentPerformanceReportInput) => VoiceAgentPerformanceReport;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type VoiceScorecard, type VoiceScorecardRubric } from "./callScorecard";
|
|
2
|
+
export type VoiceAIScorecardCompletion = (input: {
|
|
3
|
+
prompt: string;
|
|
4
|
+
systemPrompt?: string;
|
|
5
|
+
}) => Promise<string>;
|
|
6
|
+
export type VoiceAIScorecardScoringResult = {
|
|
7
|
+
criterionId: string;
|
|
8
|
+
score: number;
|
|
9
|
+
rationale?: string;
|
|
10
|
+
};
|
|
11
|
+
export type VoiceAIScorecardParsedResponse = {
|
|
12
|
+
scores: VoiceAIScorecardScoringResult[];
|
|
13
|
+
comments?: string;
|
|
14
|
+
};
|
|
15
|
+
export type ScoreVoiceCallWithAIInput = {
|
|
16
|
+
rubric: VoiceScorecardRubric;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
transcript: string;
|
|
19
|
+
agentId?: string;
|
|
20
|
+
reviewerId?: string;
|
|
21
|
+
metadata?: Record<string, unknown>;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
};
|
|
24
|
+
export type CreateVoiceAIScorecardOptions = {
|
|
25
|
+
completion: VoiceAIScorecardCompletion;
|
|
26
|
+
systemPrompt?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare const parseVoiceAIScorecardResponse: (raw: string, rubric: VoiceScorecardRubric) => VoiceAIScorecardParsedResponse;
|
|
29
|
+
export declare const createVoiceAIScorecard: (options: CreateVoiceAIScorecardOptions) => {
|
|
30
|
+
scoreCall(input: ScoreVoiceCallWithAIInput): Promise<VoiceScorecard>;
|
|
31
|
+
};
|
|
32
|
+
export type VoiceAIScorecard = ReturnType<typeof createVoiceAIScorecard>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
export type VoiceScorecardCriterion = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
weight: number;
|
|
5
|
+
section?: string;
|
|
6
|
+
passingScore?: number;
|
|
7
|
+
required?: boolean;
|
|
8
|
+
};
|
|
9
|
+
export type VoiceScorecardRubric = {
|
|
10
|
+
id: string;
|
|
11
|
+
label: string;
|
|
12
|
+
scaleMax?: number;
|
|
13
|
+
passingGrade?: number;
|
|
14
|
+
criteria: VoiceScorecardCriterion[];
|
|
15
|
+
};
|
|
16
|
+
export type VoiceScorecardCriterionResult = {
|
|
17
|
+
criterionId: string;
|
|
18
|
+
score: number;
|
|
19
|
+
weight: number;
|
|
20
|
+
rationale?: string;
|
|
21
|
+
passed: boolean;
|
|
22
|
+
};
|
|
23
|
+
export type VoiceScorecard = {
|
|
24
|
+
rubricId: string;
|
|
25
|
+
sessionId: string;
|
|
26
|
+
agentId?: string;
|
|
27
|
+
reviewer: "human" | "llm" | "hybrid";
|
|
28
|
+
reviewerId?: string;
|
|
29
|
+
createdAt: number;
|
|
30
|
+
scaleMax: number;
|
|
31
|
+
passingGrade: number;
|
|
32
|
+
results: VoiceScorecardCriterionResult[];
|
|
33
|
+
weightedScore: number;
|
|
34
|
+
grade: "pass" | "fail" | "needs-review";
|
|
35
|
+
sectionScores: Record<string, number>;
|
|
36
|
+
failedRequiredCriteria: string[];
|
|
37
|
+
comments?: string;
|
|
38
|
+
};
|
|
39
|
+
export type BuildVoiceCallScorecardInput = {
|
|
40
|
+
rubric: VoiceScorecardRubric;
|
|
41
|
+
sessionId: string;
|
|
42
|
+
agentId?: string;
|
|
43
|
+
reviewer: VoiceScorecard["reviewer"];
|
|
44
|
+
reviewerId?: string;
|
|
45
|
+
scores: Record<string, {
|
|
46
|
+
score: number;
|
|
47
|
+
rationale?: string;
|
|
48
|
+
}>;
|
|
49
|
+
comments?: string;
|
|
50
|
+
now?: () => number;
|
|
51
|
+
};
|
|
52
|
+
export declare const buildVoiceCallScorecard: (input: BuildVoiceCallScorecardInput) => VoiceScorecard;
|
|
53
|
+
export declare const DEFAULT_VOICE_SALES_RUBRIC: VoiceScorecardRubric;
|
package/dist/index.d.ts
CHANGED
|
@@ -331,4 +331,14 @@ export { scoreVoiceNoShowRisk, summarizeVoiceNoShowVerdict, } from "./noShowPred
|
|
|
331
331
|
export type { VoiceNoShowHistoricalRecord, VoiceNoShowScoreInput, VoiceNoShowSignal, VoiceNoShowVerdict, } from "./noShowPredictor";
|
|
332
332
|
export { createVoiceReminderScheduler, DEFAULT_VOICE_REMINDER_TRIGGERS, } from "./reminderScheduler";
|
|
333
333
|
export type { CreateVoiceReminderSchedulerOptions, ScheduleVoiceRemindersInput, VoiceReminderChannel, VoiceReminderJob, VoiceReminderScheduler, VoiceReminderTrigger, } from "./reminderScheduler";
|
|
334
|
+
export { buildVoiceCallScorecard, DEFAULT_VOICE_SALES_RUBRIC, } from "./callScorecard";
|
|
335
|
+
export type { BuildVoiceCallScorecardInput, VoiceScorecard, VoiceScorecardCriterion, VoiceScorecardCriterionResult, VoiceScorecardRubric, } from "./callScorecard";
|
|
336
|
+
export { createVoiceAIScorecard, parseVoiceAIScorecardResponse, } from "./aiScorecard";
|
|
337
|
+
export type { CreateVoiceAIScorecardOptions, ScoreVoiceCallWithAIInput, VoiceAIScorecard, VoiceAIScorecardCompletion, VoiceAIScorecardParsedResponse, VoiceAIScorecardScoringResult, } from "./aiScorecard";
|
|
338
|
+
export { buildVoiceAgentPerformanceReport } from "./agentPerformanceReport";
|
|
339
|
+
export type { BuildVoiceAgentPerformanceReportInput, VoiceAgentPerformanceBucket, VoiceAgentPerformanceBucketSummary, VoiceAgentPerformanceCriterionSummary, VoiceAgentPerformanceReport, } from "./agentPerformanceReport";
|
|
340
|
+
export { computeVoiceScorecardCalibration } from "./scorecardCalibration";
|
|
341
|
+
export type { VoiceScorecardCalibrationDivergence, VoiceScorecardCalibrationPair, VoiceScorecardCalibrationReport, } from "./scorecardCalibration";
|
|
342
|
+
export { detectVoiceQualityDrift } from "./qualityDriftDetector";
|
|
343
|
+
export type { DetectVoiceQualityDriftInput, VoiceQualityDriftCriterionAlert, VoiceQualityDriftReport, VoiceQualityDriftSeverity, } from "./qualityDriftDetector";
|
|
334
344
|
export * from "./types";
|
package/dist/index.js
CHANGED
|
@@ -50086,6 +50086,509 @@ var createVoiceReminderScheduler = (options = {}) => {
|
|
|
50086
50086
|
}
|
|
50087
50087
|
};
|
|
50088
50088
|
};
|
|
50089
|
+
// src/callScorecard.ts
|
|
50090
|
+
var clampScore = (raw, max) => Math.max(0, Math.min(max, raw));
|
|
50091
|
+
var buildVoiceCallScorecard = (input) => {
|
|
50092
|
+
const now = input.now ?? (() => Date.now());
|
|
50093
|
+
const scaleMax = input.rubric.scaleMax ?? 5;
|
|
50094
|
+
const passingGrade = input.rubric.passingGrade ?? 0.7;
|
|
50095
|
+
const totalWeight = input.rubric.criteria.reduce((sum, c) => sum + c.weight, 0);
|
|
50096
|
+
if (totalWeight <= 0) {
|
|
50097
|
+
throw new Error("Rubric weights must sum to a positive number");
|
|
50098
|
+
}
|
|
50099
|
+
const results = [];
|
|
50100
|
+
const failedRequiredCriteria = [];
|
|
50101
|
+
const sectionAccum = new Map;
|
|
50102
|
+
for (const criterion of input.rubric.criteria) {
|
|
50103
|
+
const raw = input.scores[criterion.id];
|
|
50104
|
+
if (!raw) {
|
|
50105
|
+
throw new Error(`Missing score for criterion: ${criterion.id}`);
|
|
50106
|
+
}
|
|
50107
|
+
const score = clampScore(raw.score, scaleMax);
|
|
50108
|
+
const passingScore = criterion.passingScore ?? scaleMax * 0.6;
|
|
50109
|
+
const passed = score >= passingScore;
|
|
50110
|
+
const result = {
|
|
50111
|
+
criterionId: criterion.id,
|
|
50112
|
+
passed,
|
|
50113
|
+
score,
|
|
50114
|
+
weight: criterion.weight,
|
|
50115
|
+
...raw.rationale !== undefined ? { rationale: raw.rationale } : {}
|
|
50116
|
+
};
|
|
50117
|
+
results.push(result);
|
|
50118
|
+
if (!passed && criterion.required) {
|
|
50119
|
+
failedRequiredCriteria.push(criterion.id);
|
|
50120
|
+
}
|
|
50121
|
+
const section = criterion.section ?? "default";
|
|
50122
|
+
const entry = sectionAccum.get(section) ?? { weight: 0, weighted: 0 };
|
|
50123
|
+
entry.weighted += score * criterion.weight;
|
|
50124
|
+
entry.weight += criterion.weight;
|
|
50125
|
+
sectionAccum.set(section, entry);
|
|
50126
|
+
}
|
|
50127
|
+
const weightedSum = results.reduce((sum, r) => sum + r.score * r.weight, 0);
|
|
50128
|
+
const weightedScore = weightedSum / (totalWeight * scaleMax);
|
|
50129
|
+
const sectionScores = {};
|
|
50130
|
+
for (const [section, accum] of sectionAccum) {
|
|
50131
|
+
sectionScores[section] = accum.weight === 0 ? 0 : accum.weighted / (accum.weight * scaleMax);
|
|
50132
|
+
}
|
|
50133
|
+
const grade = failedRequiredCriteria.length > 0 ? "fail" : weightedScore >= passingGrade ? "pass" : "needs-review";
|
|
50134
|
+
return {
|
|
50135
|
+
createdAt: now(),
|
|
50136
|
+
failedRequiredCriteria,
|
|
50137
|
+
grade,
|
|
50138
|
+
passingGrade,
|
|
50139
|
+
results,
|
|
50140
|
+
reviewer: input.reviewer,
|
|
50141
|
+
rubricId: input.rubric.id,
|
|
50142
|
+
scaleMax,
|
|
50143
|
+
sectionScores,
|
|
50144
|
+
sessionId: input.sessionId,
|
|
50145
|
+
weightedScore,
|
|
50146
|
+
...input.agentId !== undefined ? { agentId: input.agentId } : {},
|
|
50147
|
+
...input.reviewerId !== undefined ? { reviewerId: input.reviewerId } : {},
|
|
50148
|
+
...input.comments !== undefined ? { comments: input.comments } : {}
|
|
50149
|
+
};
|
|
50150
|
+
};
|
|
50151
|
+
var DEFAULT_VOICE_SALES_RUBRIC = {
|
|
50152
|
+
criteria: [
|
|
50153
|
+
{
|
|
50154
|
+
id: "greeting",
|
|
50155
|
+
label: "Professional greeting",
|
|
50156
|
+
section: "opening",
|
|
50157
|
+
weight: 1
|
|
50158
|
+
},
|
|
50159
|
+
{
|
|
50160
|
+
id: "needs-discovery",
|
|
50161
|
+
label: "Discovers customer needs",
|
|
50162
|
+
required: true,
|
|
50163
|
+
section: "discovery",
|
|
50164
|
+
weight: 2
|
|
50165
|
+
},
|
|
50166
|
+
{
|
|
50167
|
+
id: "objection-handling",
|
|
50168
|
+
label: "Handles objections clearly",
|
|
50169
|
+
section: "objections",
|
|
50170
|
+
weight: 2
|
|
50171
|
+
},
|
|
50172
|
+
{
|
|
50173
|
+
id: "compliance-disclosure",
|
|
50174
|
+
label: "Made required compliance disclosure",
|
|
50175
|
+
required: true,
|
|
50176
|
+
section: "compliance",
|
|
50177
|
+
weight: 3
|
|
50178
|
+
},
|
|
50179
|
+
{
|
|
50180
|
+
id: "close-or-next-step",
|
|
50181
|
+
label: "Closes or sets a next step",
|
|
50182
|
+
section: "close",
|
|
50183
|
+
weight: 2
|
|
50184
|
+
}
|
|
50185
|
+
],
|
|
50186
|
+
id: "default-sales",
|
|
50187
|
+
label: "Default sales QA rubric",
|
|
50188
|
+
passingGrade: 0.75,
|
|
50189
|
+
scaleMax: 5
|
|
50190
|
+
};
|
|
50191
|
+
// src/aiScorecard.ts
|
|
50192
|
+
var DEFAULT_SYSTEM_PROMPT3 = "You are an impartial quality reviewer scoring a voice-agent call transcript. " + "For each criterion, return a numeric score between 0 and the rubric's scaleMax, with a one-sentence rationale grounded in the transcript. " + 'Respond with strict JSON: {"scores":[{"criterionId":"\u2026","score":4,"rationale":"\u2026"}],"comments":"\u2026"}. ' + "Do not return prose outside the JSON.";
|
|
50193
|
+
var buildPrompt2 = (input) => {
|
|
50194
|
+
const { rubric } = input;
|
|
50195
|
+
const scaleMax = rubric.scaleMax ?? 5;
|
|
50196
|
+
const criteriaBlock = rubric.criteria.map((criterion) => `- ${criterion.id}${criterion.required ? " (required)" : ""}: ${criterion.label} (weight=${criterion.weight}${criterion.section ? `, section=${criterion.section}` : ""})`).join(`
|
|
50197
|
+
`);
|
|
50198
|
+
const metadataBlock = input.metadata ? `
|
|
50199
|
+
Metadata:
|
|
50200
|
+
${JSON.stringify(input.metadata, null, 2)}
|
|
50201
|
+
` : "";
|
|
50202
|
+
return `Rubric: ${rubric.label} (scaleMax=${scaleMax})
|
|
50203
|
+
Criteria:
|
|
50204
|
+
${criteriaBlock}
|
|
50205
|
+
${metadataBlock}
|
|
50206
|
+
Transcript:
|
|
50207
|
+
${input.transcript}
|
|
50208
|
+
|
|
50209
|
+
Return JSON only.`;
|
|
50210
|
+
};
|
|
50211
|
+
var extractJson3 = (raw) => {
|
|
50212
|
+
const trimmed = raw.trim();
|
|
50213
|
+
if (!trimmed)
|
|
50214
|
+
throw new Error("AI scorecard returned an empty response");
|
|
50215
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/iu.exec(trimmed);
|
|
50216
|
+
const candidate = fenced ? fenced[1].trim() : trimmed;
|
|
50217
|
+
try {
|
|
50218
|
+
return JSON.parse(candidate);
|
|
50219
|
+
} catch {
|
|
50220
|
+
const start = candidate.indexOf("{");
|
|
50221
|
+
const end = candidate.lastIndexOf("}");
|
|
50222
|
+
if (start >= 0 && end > start) {
|
|
50223
|
+
return JSON.parse(candidate.slice(start, end + 1));
|
|
50224
|
+
}
|
|
50225
|
+
throw new Error(`AI scorecard response was not valid JSON: ${raw.slice(0, 200)}`);
|
|
50226
|
+
}
|
|
50227
|
+
};
|
|
50228
|
+
var parseVoiceAIScorecardResponse = (raw, rubric) => {
|
|
50229
|
+
const payload = extractJson3(raw);
|
|
50230
|
+
if (!payload || typeof payload !== "object") {
|
|
50231
|
+
throw new Error("AI scorecard response is not a JSON object");
|
|
50232
|
+
}
|
|
50233
|
+
const root = payload;
|
|
50234
|
+
const scoresRaw = root.scores;
|
|
50235
|
+
if (!Array.isArray(scoresRaw)) {
|
|
50236
|
+
throw new Error("AI scorecard response missing scores[] array");
|
|
50237
|
+
}
|
|
50238
|
+
const known = new Set(rubric.criteria.map((c) => c.id));
|
|
50239
|
+
const parsed = [];
|
|
50240
|
+
for (const entry of scoresRaw) {
|
|
50241
|
+
if (!entry || typeof entry !== "object")
|
|
50242
|
+
continue;
|
|
50243
|
+
const item = entry;
|
|
50244
|
+
const criterionId = String(item.criterionId ?? "").trim();
|
|
50245
|
+
if (!criterionId || !known.has(criterionId))
|
|
50246
|
+
continue;
|
|
50247
|
+
const scoreValue = Number(item.score);
|
|
50248
|
+
if (Number.isNaN(scoreValue))
|
|
50249
|
+
continue;
|
|
50250
|
+
parsed.push({
|
|
50251
|
+
criterionId,
|
|
50252
|
+
score: scoreValue,
|
|
50253
|
+
...typeof item.rationale === "string" ? { rationale: item.rationale } : {}
|
|
50254
|
+
});
|
|
50255
|
+
}
|
|
50256
|
+
const comments = typeof root.comments === "string" ? root.comments : undefined;
|
|
50257
|
+
return {
|
|
50258
|
+
scores: parsed,
|
|
50259
|
+
...comments !== undefined ? { comments } : {}
|
|
50260
|
+
};
|
|
50261
|
+
};
|
|
50262
|
+
var createVoiceAIScorecard = (options) => {
|
|
50263
|
+
const systemPrompt = options.systemPrompt ?? DEFAULT_SYSTEM_PROMPT3;
|
|
50264
|
+
return {
|
|
50265
|
+
async scoreCall(input) {
|
|
50266
|
+
const prompt = buildPrompt2(input);
|
|
50267
|
+
const raw = await options.completion({ prompt, systemPrompt });
|
|
50268
|
+
const parsed = parseVoiceAIScorecardResponse(raw, input.rubric);
|
|
50269
|
+
const scoreMap = {};
|
|
50270
|
+
for (const entry of parsed.scores) {
|
|
50271
|
+
scoreMap[entry.criterionId] = {
|
|
50272
|
+
score: entry.score,
|
|
50273
|
+
...entry.rationale !== undefined ? { rationale: entry.rationale } : {}
|
|
50274
|
+
};
|
|
50275
|
+
}
|
|
50276
|
+
for (const criterion of input.rubric.criteria) {
|
|
50277
|
+
if (!scoreMap[criterion.id]) {
|
|
50278
|
+
scoreMap[criterion.id] = {
|
|
50279
|
+
rationale: "No rationale returned by AI scorer",
|
|
50280
|
+
score: 0
|
|
50281
|
+
};
|
|
50282
|
+
}
|
|
50283
|
+
}
|
|
50284
|
+
return buildVoiceCallScorecard({
|
|
50285
|
+
reviewer: "llm",
|
|
50286
|
+
rubric: input.rubric,
|
|
50287
|
+
scores: scoreMap,
|
|
50288
|
+
sessionId: input.sessionId,
|
|
50289
|
+
...input.agentId !== undefined ? { agentId: input.agentId } : {},
|
|
50290
|
+
...input.reviewerId !== undefined ? { reviewerId: input.reviewerId } : {},
|
|
50291
|
+
...parsed.comments !== undefined ? { comments: parsed.comments } : {},
|
|
50292
|
+
...input.now !== undefined ? { now: input.now } : {}
|
|
50293
|
+
});
|
|
50294
|
+
}
|
|
50295
|
+
};
|
|
50296
|
+
};
|
|
50297
|
+
// src/agentPerformanceReport.ts
|
|
50298
|
+
var bucketKeyFor = (ms, bucket) => {
|
|
50299
|
+
const date = new Date(ms);
|
|
50300
|
+
const year = date.getUTCFullYear();
|
|
50301
|
+
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
|
|
50302
|
+
const day = String(date.getUTCDate()).padStart(2, "0");
|
|
50303
|
+
if (bucket === "day")
|
|
50304
|
+
return `${year}-${month}-${day}`;
|
|
50305
|
+
if (bucket === "month")
|
|
50306
|
+
return `${year}-${month}`;
|
|
50307
|
+
const firstJan = Date.UTC(year, 0, 1);
|
|
50308
|
+
const week = Math.floor((ms - firstJan) / (7 * 24 * 60 * 60 * 1000)) + 1;
|
|
50309
|
+
return `${year}-W${String(week).padStart(2, "0")}`;
|
|
50310
|
+
};
|
|
50311
|
+
var buildVoiceAgentPerformanceReport = (input) => {
|
|
50312
|
+
const bucket = input.bucket ?? "week";
|
|
50313
|
+
const scorecards = input.scorecards.filter((card) => card.agentId === input.agentId && card.rubricId === input.rubricId).filter((card) => (input.fromMs === undefined || card.createdAt >= input.fromMs) && (input.toMs === undefined || card.createdAt <= input.toMs)).sort((a, b) => a.createdAt - b.createdAt);
|
|
50314
|
+
const bucketMap = new Map;
|
|
50315
|
+
for (const card of scorecards) {
|
|
50316
|
+
const key = bucketKeyFor(card.createdAt, bucket);
|
|
50317
|
+
const entry = bucketMap.get(key) ?? {
|
|
50318
|
+
fail: 0,
|
|
50319
|
+
needsReview: 0,
|
|
50320
|
+
pass: 0,
|
|
50321
|
+
sum: 0,
|
|
50322
|
+
total: 0
|
|
50323
|
+
};
|
|
50324
|
+
entry.total += 1;
|
|
50325
|
+
entry.sum += card.weightedScore;
|
|
50326
|
+
if (card.grade === "pass")
|
|
50327
|
+
entry.pass += 1;
|
|
50328
|
+
else if (card.grade === "needs-review")
|
|
50329
|
+
entry.needsReview += 1;
|
|
50330
|
+
else
|
|
50331
|
+
entry.fail += 1;
|
|
50332
|
+
bucketMap.set(key, entry);
|
|
50333
|
+
}
|
|
50334
|
+
const buckets = Array.from(bucketMap.entries()).sort((a, b) => a[0] < b[0] ? -1 : 1).map(([bucketKey2, e]) => ({
|
|
50335
|
+
averageWeightedScore: e.total > 0 ? e.sum / e.total : 0,
|
|
50336
|
+
bucketKey: bucketKey2,
|
|
50337
|
+
callsScored: e.total,
|
|
50338
|
+
failRate: e.total > 0 ? e.fail / e.total : 0,
|
|
50339
|
+
needsReviewRate: e.total > 0 ? e.needsReview / e.total : 0,
|
|
50340
|
+
passRate: e.total > 0 ? e.pass / e.total : 0
|
|
50341
|
+
}));
|
|
50342
|
+
const criterionMap = new Map;
|
|
50343
|
+
for (const card of scorecards) {
|
|
50344
|
+
for (const result of card.results) {
|
|
50345
|
+
const entry = criterionMap.get(result.criterionId) ?? {
|
|
50346
|
+
firstAvg: null,
|
|
50347
|
+
passes: 0,
|
|
50348
|
+
scoreSum: 0,
|
|
50349
|
+
total: 0
|
|
50350
|
+
};
|
|
50351
|
+
entry.scoreSum += result.score / card.scaleMax;
|
|
50352
|
+
entry.total += 1;
|
|
50353
|
+
if (result.passed)
|
|
50354
|
+
entry.passes += 1;
|
|
50355
|
+
criterionMap.set(result.criterionId, entry);
|
|
50356
|
+
}
|
|
50357
|
+
}
|
|
50358
|
+
const firstHalf = scorecards.slice(0, Math.max(1, Math.floor(scorecards.length / 2)));
|
|
50359
|
+
const secondHalf = scorecards.slice(Math.floor(scorecards.length / 2));
|
|
50360
|
+
const halfAverage = (cards, criterionId) => {
|
|
50361
|
+
const matches = cards.flatMap((c) => c.results.filter((r) => r.criterionId === criterionId).map((r) => r.score / c.scaleMax));
|
|
50362
|
+
if (matches.length === 0)
|
|
50363
|
+
return null;
|
|
50364
|
+
return matches.reduce((a, b) => a + b, 0) / matches.length;
|
|
50365
|
+
};
|
|
50366
|
+
const criteria = [];
|
|
50367
|
+
for (const [criterionId, e] of criterionMap) {
|
|
50368
|
+
const earlier = halfAverage(firstHalf, criterionId);
|
|
50369
|
+
const later = halfAverage(secondHalf, criterionId);
|
|
50370
|
+
let trend = "flat";
|
|
50371
|
+
let delta = 0;
|
|
50372
|
+
if (earlier !== null && later !== null) {
|
|
50373
|
+
delta = later - earlier;
|
|
50374
|
+
if (delta > 0.05)
|
|
50375
|
+
trend = "up";
|
|
50376
|
+
else if (delta < -0.05)
|
|
50377
|
+
trend = "down";
|
|
50378
|
+
}
|
|
50379
|
+
criteria.push({
|
|
50380
|
+
averageScore: e.total > 0 ? e.scoreSum / e.total : 0,
|
|
50381
|
+
criterionId,
|
|
50382
|
+
delta,
|
|
50383
|
+
passRate: e.total > 0 ? e.passes / e.total : 0,
|
|
50384
|
+
trend
|
|
50385
|
+
});
|
|
50386
|
+
}
|
|
50387
|
+
criteria.sort((a, b) => a.criterionId.localeCompare(b.criterionId));
|
|
50388
|
+
const overallTotal = scorecards.length;
|
|
50389
|
+
const overallSum = scorecards.reduce((s, c) => s + c.weightedScore, 0);
|
|
50390
|
+
const overallPasses = scorecards.filter((c) => c.grade === "pass").length;
|
|
50391
|
+
const ranked = [...criteria].sort((a, b) => a.averageScore - b.averageScore);
|
|
50392
|
+
return {
|
|
50393
|
+
agentId: input.agentId,
|
|
50394
|
+
bestCriterion: ranked.at(-1)?.criterionId ?? null,
|
|
50395
|
+
bucket,
|
|
50396
|
+
buckets,
|
|
50397
|
+
criteria,
|
|
50398
|
+
fromMs: input.fromMs ?? scorecards[0]?.createdAt ?? 0,
|
|
50399
|
+
overallAverageScore: overallTotal > 0 ? overallSum / overallTotal : 0,
|
|
50400
|
+
overallPassRate: overallTotal > 0 ? overallPasses / overallTotal : 0,
|
|
50401
|
+
rubricId: input.rubricId,
|
|
50402
|
+
toMs: input.toMs ?? scorecards.at(-1)?.createdAt ?? 0,
|
|
50403
|
+
totalCalls: overallTotal,
|
|
50404
|
+
worstCriterion: ranked[0]?.criterionId ?? null
|
|
50405
|
+
};
|
|
50406
|
+
};
|
|
50407
|
+
// src/scorecardCalibration.ts
|
|
50408
|
+
var normalize = (raw, scaleMax) => scaleMax === 0 ? 0 : raw / scaleMax;
|
|
50409
|
+
var correlation = (xs, ys) => {
|
|
50410
|
+
if (xs.length === 0 || xs.length !== ys.length)
|
|
50411
|
+
return 0;
|
|
50412
|
+
const meanX = xs.reduce((a, b) => a + b, 0) / xs.length;
|
|
50413
|
+
const meanY = ys.reduce((a, b) => a + b, 0) / ys.length;
|
|
50414
|
+
let num = 0;
|
|
50415
|
+
let denomX = 0;
|
|
50416
|
+
let denomY = 0;
|
|
50417
|
+
for (let i = 0;i < xs.length; i++) {
|
|
50418
|
+
const dx = (xs[i] ?? 0) - meanX;
|
|
50419
|
+
const dy = (ys[i] ?? 0) - meanY;
|
|
50420
|
+
num += dx * dy;
|
|
50421
|
+
denomX += dx * dx;
|
|
50422
|
+
denomY += dy * dy;
|
|
50423
|
+
}
|
|
50424
|
+
if (denomX === 0 || denomY === 0)
|
|
50425
|
+
return 0;
|
|
50426
|
+
return num / Math.sqrt(denomX * denomY);
|
|
50427
|
+
};
|
|
50428
|
+
var computeVoiceScorecardCalibration = (pairs, options = {}) => {
|
|
50429
|
+
if (pairs.length === 0) {
|
|
50430
|
+
return {
|
|
50431
|
+
gradeAgreementRate: 0,
|
|
50432
|
+
meanAbsoluteError: 0,
|
|
50433
|
+
pairsCompared: 0,
|
|
50434
|
+
perCriterion: [],
|
|
50435
|
+
rootMeanSquareError: 0,
|
|
50436
|
+
weightedScoreCorrelation: 0,
|
|
50437
|
+
worstDivergences: []
|
|
50438
|
+
};
|
|
50439
|
+
}
|
|
50440
|
+
const topN = options.topDivergences ?? 10;
|
|
50441
|
+
const gapsByCriterion = new Map;
|
|
50442
|
+
const allGaps = [];
|
|
50443
|
+
const divergences = [];
|
|
50444
|
+
const humanWeighted = [];
|
|
50445
|
+
const llmWeighted = [];
|
|
50446
|
+
let gradeAgreed = 0;
|
|
50447
|
+
let comparedPairs = 0;
|
|
50448
|
+
for (const pair of pairs) {
|
|
50449
|
+
if (pair.human.rubricId !== pair.llm.rubricId)
|
|
50450
|
+
continue;
|
|
50451
|
+
comparedPairs += 1;
|
|
50452
|
+
if (pair.human.grade === pair.llm.grade)
|
|
50453
|
+
gradeAgreed += 1;
|
|
50454
|
+
humanWeighted.push(pair.human.weightedScore);
|
|
50455
|
+
llmWeighted.push(pair.llm.weightedScore);
|
|
50456
|
+
const humanByCriterion = new Map(pair.human.results.map((r) => [r.criterionId, r]));
|
|
50457
|
+
const llmByCriterion = new Map(pair.llm.results.map((r) => [r.criterionId, r]));
|
|
50458
|
+
const criteriaIds = new Set([
|
|
50459
|
+
...humanByCriterion.keys(),
|
|
50460
|
+
...llmByCriterion.keys()
|
|
50461
|
+
]);
|
|
50462
|
+
for (const criterionId of criteriaIds) {
|
|
50463
|
+
const h = humanByCriterion.get(criterionId);
|
|
50464
|
+
const l = llmByCriterion.get(criterionId);
|
|
50465
|
+
if (!h || !l)
|
|
50466
|
+
continue;
|
|
50467
|
+
const hn = normalize(h.score, pair.human.scaleMax);
|
|
50468
|
+
const ln = normalize(l.score, pair.llm.scaleMax);
|
|
50469
|
+
const gap = Math.abs(hn - ln);
|
|
50470
|
+
allGaps.push(gap);
|
|
50471
|
+
divergences.push({
|
|
50472
|
+
criterionId,
|
|
50473
|
+
humanScore: hn,
|
|
50474
|
+
llmScore: ln,
|
|
50475
|
+
normalizedGap: hn - ln,
|
|
50476
|
+
sessionId: pair.sessionId
|
|
50477
|
+
});
|
|
50478
|
+
const entry = gapsByCriterion.get(criterionId) ?? {
|
|
50479
|
+
absSum: 0,
|
|
50480
|
+
biasSum: 0,
|
|
50481
|
+
count: 0,
|
|
50482
|
+
humanSum: 0,
|
|
50483
|
+
llmSum: 0
|
|
50484
|
+
};
|
|
50485
|
+
entry.absSum += gap;
|
|
50486
|
+
entry.biasSum += ln - hn;
|
|
50487
|
+
entry.humanSum += hn;
|
|
50488
|
+
entry.llmSum += ln;
|
|
50489
|
+
entry.count += 1;
|
|
50490
|
+
gapsByCriterion.set(criterionId, entry);
|
|
50491
|
+
}
|
|
50492
|
+
}
|
|
50493
|
+
const mae = allGaps.length === 0 ? 0 : allGaps.reduce((a, b) => a + b, 0) / allGaps.length;
|
|
50494
|
+
const rmse = allGaps.length === 0 ? 0 : Math.sqrt(allGaps.reduce((a, b) => a + b * b, 0) / allGaps.length);
|
|
50495
|
+
const perCriterion = Array.from(gapsByCriterion.entries()).map(([criterionId, e]) => ({
|
|
50496
|
+
averageHumanScore: e.count === 0 ? 0 : e.humanSum / e.count,
|
|
50497
|
+
averageLLMScore: e.count === 0 ? 0 : e.llmSum / e.count,
|
|
50498
|
+
bias: e.count === 0 ? 0 : e.biasSum / e.count,
|
|
50499
|
+
criterionId,
|
|
50500
|
+
meanAbsoluteError: e.count === 0 ? 0 : e.absSum / e.count
|
|
50501
|
+
}));
|
|
50502
|
+
return {
|
|
50503
|
+
gradeAgreementRate: comparedPairs === 0 ? 0 : gradeAgreed / comparedPairs,
|
|
50504
|
+
meanAbsoluteError: mae,
|
|
50505
|
+
pairsCompared: comparedPairs,
|
|
50506
|
+
perCriterion,
|
|
50507
|
+
rootMeanSquareError: rmse,
|
|
50508
|
+
weightedScoreCorrelation: correlation(humanWeighted, llmWeighted),
|
|
50509
|
+
worstDivergences: divergences.sort((a, b) => Math.abs(b.normalizedGap) - Math.abs(a.normalizedGap)).slice(0, topN)
|
|
50510
|
+
};
|
|
50511
|
+
};
|
|
50512
|
+
// src/qualityDriftDetector.ts
|
|
50513
|
+
var severityFor = (delta, watch, regression) => {
|
|
50514
|
+
if (delta <= -regression)
|
|
50515
|
+
return "regression";
|
|
50516
|
+
if (delta <= -watch)
|
|
50517
|
+
return "watch";
|
|
50518
|
+
return "ok";
|
|
50519
|
+
};
|
|
50520
|
+
var averageScore = (cards) => cards.length === 0 ? 0 : cards.reduce((sum, c) => sum + c.weightedScore, 0) / cards.length;
|
|
50521
|
+
var averageCriterion = (cards, criterionId) => {
|
|
50522
|
+
const matches = [];
|
|
50523
|
+
for (const card of cards) {
|
|
50524
|
+
for (const result of card.results) {
|
|
50525
|
+
if (result.criterionId === criterionId) {
|
|
50526
|
+
matches.push(result.score / card.scaleMax);
|
|
50527
|
+
}
|
|
50528
|
+
}
|
|
50529
|
+
}
|
|
50530
|
+
return matches.length === 0 ? 0 : matches.reduce((a, b) => a + b, 0) / matches.length;
|
|
50531
|
+
};
|
|
50532
|
+
var detectVoiceQualityDrift = (input) => {
|
|
50533
|
+
const now = input.now ?? (() => Date.now());
|
|
50534
|
+
const currentWindow = input.currentWindowMs ?? 7 * 24 * 60 * 60 * 1000;
|
|
50535
|
+
const baselineWindow = input.baselineWindowMs ?? 30 * 24 * 60 * 60 * 1000;
|
|
50536
|
+
const watch = input.watchThreshold ?? 0.05;
|
|
50537
|
+
const regression = input.regressionThreshold ?? 0.1;
|
|
50538
|
+
const cutoff = now();
|
|
50539
|
+
const currentFrom = cutoff - currentWindow;
|
|
50540
|
+
const baselineFrom = cutoff - currentWindow - baselineWindow;
|
|
50541
|
+
const baselineTo = currentFrom;
|
|
50542
|
+
const relevant = input.scorecards.filter((card) => card.rubricId === input.rubricId);
|
|
50543
|
+
const baselineCards = relevant.filter((c) => c.createdAt >= baselineFrom && c.createdAt < baselineTo);
|
|
50544
|
+
const currentCards = relevant.filter((c) => c.createdAt >= currentFrom && c.createdAt <= cutoff);
|
|
50545
|
+
const baselineAvg = averageScore(baselineCards);
|
|
50546
|
+
const currentAvg = averageScore(currentCards);
|
|
50547
|
+
const overallDelta = currentAvg - baselineAvg;
|
|
50548
|
+
const criterionIds = new Set;
|
|
50549
|
+
for (const card of relevant) {
|
|
50550
|
+
for (const result of card.results)
|
|
50551
|
+
criterionIds.add(result.criterionId);
|
|
50552
|
+
}
|
|
50553
|
+
const criteria = [];
|
|
50554
|
+
for (const criterionId of criterionIds) {
|
|
50555
|
+
const baseline = averageCriterion(baselineCards, criterionId);
|
|
50556
|
+
const current = averageCriterion(currentCards, criterionId);
|
|
50557
|
+
const delta = current - baseline;
|
|
50558
|
+
const severity = severityFor(delta, watch, regression);
|
|
50559
|
+
criteria.push({
|
|
50560
|
+
baselineAverage: baseline,
|
|
50561
|
+
criterionId,
|
|
50562
|
+
currentAverage: current,
|
|
50563
|
+
delta,
|
|
50564
|
+
severity
|
|
50565
|
+
});
|
|
50566
|
+
}
|
|
50567
|
+
criteria.sort((a, b) => a.delta - b.delta);
|
|
50568
|
+
const alertCount = criteria.filter((c) => c.severity !== "ok").length;
|
|
50569
|
+
return {
|
|
50570
|
+
alertCount,
|
|
50571
|
+
baselineWindow: {
|
|
50572
|
+
from: baselineFrom,
|
|
50573
|
+
sampleSize: baselineCards.length,
|
|
50574
|
+
to: baselineTo
|
|
50575
|
+
},
|
|
50576
|
+
criteria,
|
|
50577
|
+
currentWindow: {
|
|
50578
|
+
from: currentFrom,
|
|
50579
|
+
sampleSize: currentCards.length,
|
|
50580
|
+
to: cutoff
|
|
50581
|
+
},
|
|
50582
|
+
overall: {
|
|
50583
|
+
baselineAverage: baselineAvg,
|
|
50584
|
+
currentAverage: currentAvg,
|
|
50585
|
+
delta: overallDelta,
|
|
50586
|
+
severity: severityFor(overallDelta, watch, regression)
|
|
50587
|
+
},
|
|
50588
|
+
rubricId: input.rubricId,
|
|
50589
|
+
scope: { from: baselineFrom, to: cutoff }
|
|
50590
|
+
};
|
|
50591
|
+
};
|
|
50089
50592
|
export {
|
|
50090
50593
|
writeVoiceProofPack,
|
|
50091
50594
|
writeVoiceMediaPipelineArtifacts,
|
|
@@ -50338,6 +50841,7 @@ export {
|
|
|
50338
50841
|
predictVoiceCallCost,
|
|
50339
50842
|
parseVoiceTelephonyWebhookEvent,
|
|
50340
50843
|
parseVoiceSessionSnapshot,
|
|
50844
|
+
parseVoiceAIScorecardResponse,
|
|
50341
50845
|
normalizeVoiceProofTrendReport,
|
|
50342
50846
|
normalizePhoneNumber,
|
|
50343
50847
|
muteVoiceMonitorIssue,
|
|
@@ -50416,6 +50920,7 @@ export {
|
|
|
50416
50920
|
encodeTwilioMulawBase64,
|
|
50417
50921
|
encodeStereoWav,
|
|
50418
50922
|
encodePcmAsWav,
|
|
50923
|
+
detectVoiceQualityDrift,
|
|
50419
50924
|
describeVoiceIVRPlan,
|
|
50420
50925
|
describeVoiceAssistantMode,
|
|
50421
50926
|
describeVoiceAgentUIState,
|
|
@@ -50784,6 +51289,7 @@ export {
|
|
|
50784
51289
|
createVoiceAgentTool,
|
|
50785
51290
|
createVoiceAgentSquad,
|
|
50786
51291
|
createVoiceAgent,
|
|
51292
|
+
createVoiceAIScorecard,
|
|
50787
51293
|
createVoiceAIJudgeCompletion,
|
|
50788
51294
|
createTwilioVoiceRoutes,
|
|
50789
51295
|
createTwilioVoiceResponse,
|
|
@@ -50823,6 +51329,7 @@ export {
|
|
|
50823
51329
|
createAnthropicVoiceAssistantModel,
|
|
50824
51330
|
createAIVoiceModel,
|
|
50825
51331
|
conditionAudioChunk,
|
|
51332
|
+
computeVoiceScorecardCalibration,
|
|
50826
51333
|
computePcmDurationMs,
|
|
50827
51334
|
completeVoiceOpsTask,
|
|
50828
51335
|
compareVoiceEvalBaseline,
|
|
@@ -50916,11 +51423,13 @@ export {
|
|
|
50916
51423
|
buildVoiceCompetitiveCoverageReport,
|
|
50917
51424
|
buildVoiceCampaignObservabilityReport,
|
|
50918
51425
|
buildVoiceCallerMemoryNamespace,
|
|
51426
|
+
buildVoiceCallScorecard,
|
|
50919
51427
|
buildVoiceCallDebuggerReport,
|
|
50920
51428
|
buildVoiceBrowserCallProfileReport,
|
|
50921
51429
|
buildVoiceAuditTrailReport,
|
|
50922
51430
|
buildVoiceAuditExport,
|
|
50923
51431
|
buildVoiceAuditDeliveryReport,
|
|
51432
|
+
buildVoiceAgentPerformanceReport,
|
|
50924
51433
|
buildReplayTimelineReport,
|
|
50925
51434
|
buildOTELTraceId,
|
|
50926
51435
|
buildOTELSpanId,
|
|
@@ -50985,6 +51494,7 @@ export {
|
|
|
50985
51494
|
VOICE_DTMF_DIGITS,
|
|
50986
51495
|
VOICE_CALLER_MEMORY_KEY,
|
|
50987
51496
|
TURN_PROFILE_DEFAULTS,
|
|
51497
|
+
DEFAULT_VOICE_SALES_RUBRIC,
|
|
50988
51498
|
DEFAULT_VOICE_REMINDER_TRIGGERS,
|
|
50989
51499
|
DEFAULT_VOICE_REDACTION_PATTERNS,
|
|
50990
51500
|
DEFAULT_VOICE_PROOF_TREND_PROFILE_DEFINITIONS,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { VoiceScorecard } from "./callScorecard";
|
|
2
|
+
export type VoiceQualityDriftSeverity = "ok" | "watch" | "regression";
|
|
3
|
+
export type VoiceQualityDriftCriterionAlert = {
|
|
4
|
+
criterionId: string;
|
|
5
|
+
baselineAverage: number;
|
|
6
|
+
currentAverage: number;
|
|
7
|
+
delta: number;
|
|
8
|
+
severity: VoiceQualityDriftSeverity;
|
|
9
|
+
};
|
|
10
|
+
export type VoiceQualityDriftReport = {
|
|
11
|
+
rubricId: string;
|
|
12
|
+
scope: {
|
|
13
|
+
from: number;
|
|
14
|
+
to: number;
|
|
15
|
+
};
|
|
16
|
+
baselineWindow: {
|
|
17
|
+
from: number;
|
|
18
|
+
to: number;
|
|
19
|
+
sampleSize: number;
|
|
20
|
+
};
|
|
21
|
+
currentWindow: {
|
|
22
|
+
from: number;
|
|
23
|
+
to: number;
|
|
24
|
+
sampleSize: number;
|
|
25
|
+
};
|
|
26
|
+
overall: {
|
|
27
|
+
baselineAverage: number;
|
|
28
|
+
currentAverage: number;
|
|
29
|
+
delta: number;
|
|
30
|
+
severity: VoiceQualityDriftSeverity;
|
|
31
|
+
};
|
|
32
|
+
criteria: VoiceQualityDriftCriterionAlert[];
|
|
33
|
+
alertCount: number;
|
|
34
|
+
};
|
|
35
|
+
export type DetectVoiceQualityDriftInput = {
|
|
36
|
+
rubricId: string;
|
|
37
|
+
scorecards: VoiceScorecard[];
|
|
38
|
+
baselineWindowMs?: number;
|
|
39
|
+
currentWindowMs?: number;
|
|
40
|
+
now?: () => number;
|
|
41
|
+
watchThreshold?: number;
|
|
42
|
+
regressionThreshold?: number;
|
|
43
|
+
};
|
|
44
|
+
export declare const detectVoiceQualityDrift: (input: DetectVoiceQualityDriftInput) => VoiceQualityDriftReport;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { VoiceScorecard } from "./callScorecard";
|
|
2
|
+
export type VoiceScorecardCalibrationPair = {
|
|
3
|
+
sessionId: string;
|
|
4
|
+
human: VoiceScorecard;
|
|
5
|
+
llm: VoiceScorecard;
|
|
6
|
+
};
|
|
7
|
+
export type VoiceScorecardCalibrationDivergence = {
|
|
8
|
+
sessionId: string;
|
|
9
|
+
criterionId: string;
|
|
10
|
+
humanScore: number;
|
|
11
|
+
llmScore: number;
|
|
12
|
+
normalizedGap: number;
|
|
13
|
+
};
|
|
14
|
+
export type VoiceScorecardCalibrationReport = {
|
|
15
|
+
pairsCompared: number;
|
|
16
|
+
meanAbsoluteError: number;
|
|
17
|
+
rootMeanSquareError: number;
|
|
18
|
+
weightedScoreCorrelation: number;
|
|
19
|
+
gradeAgreementRate: number;
|
|
20
|
+
perCriterion: {
|
|
21
|
+
criterionId: string;
|
|
22
|
+
meanAbsoluteError: number;
|
|
23
|
+
averageHumanScore: number;
|
|
24
|
+
averageLLMScore: number;
|
|
25
|
+
bias: number;
|
|
26
|
+
}[];
|
|
27
|
+
worstDivergences: VoiceScorecardCalibrationDivergence[];
|
|
28
|
+
};
|
|
29
|
+
export declare const computeVoiceScorecardCalibration: (pairs: VoiceScorecardCalibrationPair[], options?: {
|
|
30
|
+
topDivergences?: number;
|
|
31
|
+
}) => VoiceScorecardCalibrationReport;
|
|
@@ -53,5 +53,5 @@ export declare const VoiceCostDashboard: import("vue").DefineComponent<import("v
|
|
|
53
53
|
title: string;
|
|
54
54
|
currency: string;
|
|
55
55
|
emptyMessage: string;
|
|
56
|
-
bucketBy: "day" | "
|
|
56
|
+
bucketBy: "day" | "month" | "hour" | undefined;
|
|
57
57
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|