Evaluate Quality
Score calls with custom criteria and track compliance with scorecards
Scorecards let you define what "good" looks like, then grade every call against those standards automatically. The workflow is: create a scorecard with weighted criteria, evaluate a call, and read the detailed breakdown.
Installation
npm install @chanl/sdkSetup
import { Chanl } from '@chanl/sdk';
const chanl = new Chanl({
apiKey: process.env.CHANL_API_KEY,
});Create a scorecard
A scorecard is a grading rubric. It has categories (weighted sections) and criteria (individual checks).
const scorecard = await chanl.scorecard.create({
name: 'Customer Service Quality',
description: 'Evaluates customer service call quality',
status: 'active',
passingThreshold: 75,
});
console.log('Created scorecard:', scorecard.id);Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Scorecard name |
description | string | No | What this scorecard evaluates |
status | string | No | active or inactive |
passingThreshold | number | No | Minimum score to pass (0-100) |
Returns: Promise<Scorecard>
Add categories
Categories group related criteria and carry a weight that determines their share of the total score.
const communication = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Communication',
description: 'Agent communication skills',
weight: 40, // 40% of total score
});
const resolution = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Resolution',
description: 'Problem solving effectiveness',
weight: 35,
});
const compliance = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Compliance',
description: 'Regulatory requirements',
weight: 25,
});Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Category name |
description | string | No | What this category measures |
weight | number | Yes | Percentage weight (all categories should sum to 100) |
Add criteria
Each criterion is one thing you are checking for. Chanl supports several evaluation types.
Prompt (AI-evaluated)
The LLM reads the transcript and judges whether the criterion was met. Best for subjective qualities like empathy, clarity, and professionalism.
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: communication.id,
key: 'greeting',
name: 'Professional Greeting',
type: 'prompt',
settings: {
description: 'Agent greeted the customer professionally with name and company',
},
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: communication.id,
key: 'active_listening',
name: 'Active Listening',
type: 'prompt',
settings: {
description: 'Agent demonstrated active listening by acknowledging customer concerns',
},
});Keyword (pattern matching)
Checks whether specific words or phrases appear (or do not appear) in the transcript. Best for compliance requirements.
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: compliance.id,
key: 'recording_disclosure',
name: 'Recording Disclosure',
type: 'keyword',
settings: {
matchType: 'must_contain',
keywords: ['this call may be recorded', 'call is being recorded'],
},
});Response time
Evaluates how quickly the agent responded.
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: resolution.id,
key: 'response_speed',
name: 'Quick Response',
type: 'response_time',
settings: {
maxMs: 3000, // Maximum 3 seconds
},
});Other criteria types
| Type | What it checks | Settings |
|---|---|---|
talk_time | Agent/customer speaking ratio | minAgentPercent, maxAgentPercent |
silence | Dead air duration and frequency | maxSilenceMs, maxSilenceCount |
interruptions | Speaking over the customer | maxCount |
Criteria parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
categoryId | string | Yes | Parent category ID |
key | string | Yes | Unique key within the scorecard |
name | string | Yes | Display name |
type | string | Yes | prompt, keyword, response_time, talk_time, silence, interruptions |
settings | object | Yes | Type-specific configuration |
Evaluate a call
Run a scorecard against a call transcript. The call must have finished processing (status ended).
const result = await chanl.scorecard.evaluate(
callId,
{ scorecardId: scorecard.id }
);
console.log('Score:', result.score + '%');
console.log('Passed:', result.passed ? 'Yes' : 'No');
console.log('Category Scores:', result.categoryScores);Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
callId | string | Yes | The call to evaluate |
scorecardId | string | Yes | The scorecard to grade against |
force | boolean | No | Re-evaluate even if already scored |
Returns: Promise<{ score: number; passed: boolean; status: string; categoryScores: Record<string, number> }>
Get detailed results
The results include per-criteria scores, evidence from the transcript, and improvement suggestions.
const { results } = await chanl.scorecard.getResults(callId);
results.forEach((result) => {
console.log(`\n${result.scorecardName}: ${result.score}%`);
result.criteriaResults.forEach((cr) => {
const status = cr.passed ? 'PASS' : 'FAIL';
console.log(` ${status} ${cr.name}: ${cr.score}%`);
if (cr.evidence) {
console.log(` Evidence: "${cr.evidence}"`);
}
if (cr.suggestion) {
console.log(` Suggestion: ${cr.suggestion}`);
}
});
});Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
callId | string | Yes | The call to get results for |
scorecardId | string | No | Filter to a specific scorecard |
Returns: Promise<{ results: ScorecardResult[] }>
Evaluate on import
Skip a step by evaluating calls at import time. Pass a scorecardId and results are ready as soon as processing completes.
const result = await chanl.calls.import({
transcript: '...',
analysisFields: ['sentiment', 'quality'],
scorecardId: 'scorecard_abc123',
});
// Results available after processing
const { results } = await chanl.scorecard.getResults(result.callId);Batch evaluation
Evaluate a set of calls against the same scorecard and get aggregate statistics.
async function evaluateBatch(callIds: string[], scorecardId: string) {
const results = await Promise.all(
callIds.map((callId) =>
chanl.scorecard.evaluate(callId, { scorecardId })
)
);
const passed = results.filter((r) => r.passed).length;
const avgScore = results.reduce((sum, r) => sum + r.score, 0) / results.length;
console.log(`Evaluated ${results.length} calls`);
console.log(`Passed: ${passed} (${((passed / results.length) * 100).toFixed(1)}%)`);
console.log(`Average Score: ${avgScore.toFixed(1)}%`);
return results;
}Full example
Build a comprehensive customer service scorecard and evaluate a call against it.
import { Chanl } from '@chanl/sdk';
const chanl = new Chanl({
apiKey: process.env.CHANL_API_KEY,
});
async function createQualityScorecard() {
const scorecard = await chanl.scorecard.create({
name: 'Customer Service Excellence',
description: 'Comprehensive evaluation of customer service quality',
status: 'active',
passingThreshold: 80,
});
// Opening (20%)
const opening = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Opening', weight: 20,
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: opening.id,
key: 'greeting',
name: 'Professional Greeting',
type: 'prompt',
settings: { description: 'Agent introduced themselves with name and company name' },
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: opening.id,
key: 'recording',
name: 'Recording Disclosure',
type: 'keyword',
settings: { matchType: 'must_contain', keywords: ['call may be recorded'] },
});
// Communication (30%)
const communication = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Communication', weight: 30,
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: communication.id,
key: 'clarity',
name: 'Clear Explanation',
type: 'prompt',
settings: { description: 'Agent explained information clearly without jargon' },
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: communication.id,
key: 'no_interruptions',
name: 'No Interruptions',
type: 'interruptions',
settings: { maxCount: 2 },
});
// Resolution (35%)
const resolution = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Resolution', weight: 35,
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: resolution.id,
key: 'solution',
name: 'Solution Provided',
type: 'prompt',
settings: { description: 'Agent provided a clear solution or next steps' },
});
// Closing (15%)
const closing = await chanl.scorecard.createCategory(scorecard.id, {
name: 'Closing', weight: 15,
});
await chanl.scorecard.createCriteria(scorecard.id, {
categoryId: closing.id,
key: 'additional_help',
name: 'Offered Additional Help',
type: 'prompt',
settings: { description: 'Agent asked if there was anything else they could help with' },
});
return scorecard;
}
// Create the scorecard, then evaluate a call
const scorecard = await createQualityScorecard();
const evaluation = await chanl.scorecard.evaluate(callId, {
scorecardId: scorecard.id,
});
console.log(`Score: ${evaluation.score}% - ${evaluation.passed ? 'PASSED' : 'FAILED'}`);