Реализация CES-опроса на сайте
CES (Customer Effort Score) измеряет усилие, которое пользователь приложил для достижения цели: «Насколько легко было решить вашу проблему?» Шкала 1–7 (1 = очень сложно, 7 = очень легко). Низкий CES — предиктор оттока.
Где применять CES
- После завершения онбординга
- После обращения в поддержку
- После сложной формы или multi-step процесса
- После первой успешной интеграции (для B2B SaaS)
CES лучше NPS предсказывает повторные покупки в B2C и churn в B2B.
API
// CesController
public function store(Request $request): JsonResponse
{
$request->validate([
'score' => 'required|integer|min:1|max:7',
'journey' => 'required|string|max:100', // 'onboarding', 'support', 'checkout'
'comment' => 'nullable|string|max:500',
]);
CesResponse::create([
'user_id' => auth()->id(),
'score' => $request->score,
'journey' => $request->journey,
'comment' => $request->comment,
]);
// Если CES <= 3 — создаём задачу для команды поддержки
if ($request->score <= 3) {
SupportTask::create([
'type' => 'low_ces_followup',
'user_id' => auth()->id(),
'notes' => "CES {$request->score} для {$request->journey}. Комментарий: {$request->comment}",
]);
}
return response()->json(['success' => true]);
}
Frontend: 7-балльный виджет
const SCALE = [
{ value: 1, label: 'Очень\nсложно' },
{ value: 2, label: '' },
{ value: 3, label: 'Сложно' },
{ value: 4, label: 'Нейтрально' },
{ value: 5, label: 'Легко' },
{ value: 6, label: '' },
{ value: 7, label: 'Очень\nлегко' },
];
export function CesWidget({ journey }: { journey: string }) {
const [selected, setSelected] = useState<number | null>(null);
const [done, setDone] = useState(false);
const submit = async (value: number) => {
setSelected(value);
await fetch('/api/ces', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ score: value, journey }),
});
setDone(true);
};
if (done) return <p className="text-sm text-green-600">Спасибо! Ваш отзыв помогает нам улучшаться.</p>;
return (
<div>
<p className="text-sm font-medium mb-3">Насколько легко было завершить этот процесс?</p>
<div className="flex gap-2">
{SCALE.map(({ value, label }) => (
<button key={value} onClick={() => submit(value)}
className={`flex-1 py-2 rounded border text-sm font-medium transition-colors
${selected === value ? 'bg-blue-600 text-white border-blue-600' : 'border-gray-300 hover:border-blue-400'}`}>
{value}
{label && <span className="block text-xs text-gray-400 whitespace-pre-line leading-tight">{label}</span>}
</button>
))}
</div>
<div className="flex justify-between text-xs text-gray-400 mt-1">
<span>Очень сложно</span><span>Очень легко</span>
</div>
</div>
);
}
Расчёт среднего CES
SELECT
journey,
ROUND(AVG(score), 2) AS avg_ces,
COUNT(*) AS responses,
COUNT(*) FILTER (WHERE score <= 3) AS low_effort_count
FROM ces_responses
WHERE created_at >= now() - interval '30 days'
GROUP BY journey
ORDER BY avg_ces ASC;
Сроки
CES-виджет с логикой follow-up при низкой оценке: 1–2 рабочих дня.







