fix: captain hours saved metric (#14948)

Reworks the Captain assistant "hours saved" metric so it produces a
believable number. It previously multiplied assistant reply count by
customer reply-wait time, which inflated the figure into the millions of
hours. It now estimates saved time as replies × a fixed ~2 min per-reply
effort assumption. On the overview card the value renders in days once
it passes 100 hours, and the label/hint copy was updated to match.

## How to test
1. Open the Captain assistant overview page.
2. Check the "Time saved" card shows a sensible value (hours, or days
past 100h).
3. Hover the hint and confirm it reflects the per-reply estimate.
This commit is contained in:
Shivam Mishra
2026-07-07 19:16:24 +05:30
committed by GitHub
parent a5fcecb3f6
commit 29d0b92f1c
4 changed files with 23 additions and 23 deletions
@@ -427,8 +427,8 @@
"HINT": "Share of handled conversations escalated to a human agent."
},
"HOURS_SAVED": {
"LABEL": "Hours saved",
"HINT": "Estimate: Captain replies times the team's average response time. Directional, not measured labor."
"LABEL": "Time saved",
"HINT": "Estimate: Captain replies times ~2 minutes of assumed agent effort per reply. Directional, not measured labor."
},
"REOPEN": {
"LABEL": "Reopen rate",
@@ -49,6 +49,11 @@ const resolveTrendGood = (trendValue, direction) => {
// :point, and a plain number for :absolute counts like conversation depth.
const TREND_SUFFIX = { percent: '%', point: ' pts', absolute: '' };
// Hours-saved is reported in hours, but large values read better as days. Past
// 100h we switch the unit so the card stays legible.
const formatDuration = hours =>
hours >= 100 ? `${Math.round(hours / 24)}d` : `${hours}h`;
const metricFor = (statKey, formatValue, direction, trendKind = 'percent') => {
const data = stats.value?.[statKey];
if (!data) return { value: '—', trend: '', trendGood: null };
@@ -84,7 +89,7 @@ const metrics = computed(() => [
key: 'hoursSaved',
label: t('CAPTAIN.OVERVIEW.METRICS.HOURS_SAVED.LABEL'),
hint: t('CAPTAIN.OVERVIEW.METRICS.HOURS_SAVED.HINT'),
...metricFor('hours_saved', v => `${v}h`, 'up'),
...metricFor('hours_saved', formatDuration, 'up'),
},
{
key: 'reopen',
@@ -121,7 +126,7 @@ const metrics = computed(() => [
<CaptainPaywall />
</template>
<template #body>
<div class="flex flex-col gap-6">
<div class="flex flex-col gap-6 pb-8">
<InboxBanner />
<CoverageBanner :knowledge="stats?.knowledge" />