Опубликовано через кнопку

This commit is contained in:
tore 2026-09-12 14:59:32 +00:00
parent 48602415c6
commit c1de8e30bd

View File

@ -12,6 +12,7 @@
.kt-ai-app { background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); min-height: 100vh; } .kt-ai-app { background: var(--kt-ai-bg); color: var(--kt-ai-fg); font-family: var(--kt-ai-font-sans); min-height: 100vh; }
.metrics-header { display: flex; align-items: center; gap: var(--kt-ai-space-4); padding: var(--kt-ai-space-4) 0; border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg); position: sticky; top: 0; z-index: 10; } .metrics-header { display: flex; align-items: center; gap: var(--kt-ai-space-4); padding: var(--kt-ai-space-4) 0; border-bottom: 1px solid var(--kt-ai-border); background: var(--kt-ai-bg); position: sticky; top: 0; z-index: 10; }
.metrics-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); } .metrics-title { font-size: var(--kt-ai-text-xl); font-weight: var(--kt-ai-weight-semibold); }
.demo-badge { background: var(--kt-ai-chart-orange); color: var(--kt-ai-fg); font-size: var(--kt-ai-text-xs); font-weight: var(--kt-ai-weight-bold); padding: 4px 10px; border-radius: var(--kt-ai-radius-full); text-transform: uppercase; letter-spacing: 0.5px; }
.filters-row { display: flex; gap: var(--kt-ai-space-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); } .filters-row { display: flex; gap: var(--kt-ai-space-3); align-items: center; flex-wrap: wrap; margin-bottom: var(--kt-ai-space-6); }
.kpi-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); } .kpi-strip { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: var(--kt-ai-space-4); margin-bottom: var(--kt-ai-space-6); }
.kpi-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); } .kpi-card { background: var(--kt-ai-card-bg); border: 1px solid var(--kt-ai-card-border); border-radius: var(--kt-ai-radius-xl); padding: var(--kt-ai-space-5); }
@ -52,6 +53,7 @@
<header class="metrics-header"> <header class="metrics-header">
<img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28"> <img src="https://code.vibe42.kz/tore/aitu/kt-logo.svg" alt="Казахтелеком" height="28">
<span class="metrics-title">Business Metrics</span> <span class="metrics-title">Business Metrics</span>
<span class="demo-badge">Демонстрационные данные</span>
<span class="kt-ai-spacer"></span> <span class="kt-ai-spacer"></span>
<button class="kt-ai-btn" data-variant="ghost" id="resetBtn">Сбросить фильтры</button> <button class="kt-ai-btn" data-variant="ghost" id="resetBtn">Сбросить фильтры</button>
</header> </header>
@ -82,7 +84,13 @@
<option value="7d">7 дней</option> <option value="7d">7 дней</option>
<option value="30d">30 дней</option> <option value="30d">30 дней</option>
<option value="90d">90 дней</option> <option value="90d">90 дней</option>
<option value="year">Год</option> </select>
</div>
<div class="kt-ai-field">
<label style="font-size: var(--kt-ai-text-xs); color: var(--kt-ai-fg-muted); display: block; margin-bottom: var(--kt-ai-space-1);">Режим</label>
<select class="kt-ai-select" id="dataModeFilter" style="min-width: 160px;">
<option value="data">С данными</option>
<option value="nodata">Нет данных</option>
</select> </select>
</div> </div>
</div> </div>
@ -141,17 +149,13 @@
<div class="insights-title">Анализ отклонений</div> <div class="insights-title">Анализ отклонений</div>
<div id="insightsContent"> <div id="insightsContent">
<div class="insight-card"> <div class="insight-card">
<div class="insight-label">Объяснение отклонений</div> <div class="insight-label">Гипотеза причин отклонений</div>
<div class="insight-text" id="explanationText">Загрузка...</div> <div class="insight-text" id="explanationText">Загрузка...</div>
</div> </div>
<div class="insight-card"> <div class="insight-card">
<div class="insight-label">Рекомендации</div> <div class="insight-label">Рекомендации</div>
<ul class="recommendation-list" id="recommendationsList"></ul> <ul class="recommendation-list" id="recommendationsList"></ul>
</div> </div>
<div class="insight-card">
<div class="insight-label">Исходные показатели</div>
<div class="insight-text" id="sourceLink">Загрузка...</div>
</div>
</div> </div>
</div> </div>
@ -164,39 +168,115 @@
<script src="./design-system/kt-ai-chart-tip.js"></script> <script src="./design-system/kt-ai-chart-tip.js"></script>
<script> <script>
const DEMO_DATA = { const DEMO_DATA = {
'7d': {
metrics: [ metrics: [
{ channel: 'call-center', segment: 'B2B', nps: 38, csat: 84, fcr: 72, aht: 5.2, requests: 1240 }, { channel: 'call-center', segment: 'B2B', nps: 38, csat: 84, fcr: 72, aht: 5.2, requests: 320 },
{ channel: 'call-center', segment: 'B2C', nps: 45, csat: 89, fcr: 78, aht: 3.8, requests: 3420 }, { channel: 'call-center', segment: 'B2C', nps: 45, csat: 89, fcr: 78, aht: 3.8, requests: 890 },
{ channel: 'online', segment: 'B2B', nps: 52, csat: 92, fcr: 85, aht: 2.1, requests: 890 }, { channel: 'online', segment: 'B2B', nps: 52, csat: 92, fcr: 85, aht: 2.1, requests: 240 },
{ channel: 'online', segment: 'B2C', nps: 48, csat: 88, fcr: 80, aht: 2.5, requests: 2150 }, { channel: 'online', segment: 'B2C', nps: 48, csat: 88, fcr: 80, aht: 2.5, requests: 560 },
{ channel: 'mobile-app', segment: 'B2B', nps: 44, csat: 86, fcr: 75, aht: 3.2, requests: 560 }, { channel: 'mobile-app', segment: 'B2B', nps: 44, csat: 86, fcr: 75, aht: 3.2, requests: 150 },
{ channel: 'mobile-app', segment: 'B2C', nps: 50, csat: 91, fcr: 82, aht: 2.8, requests: 1890 }, { channel: 'mobile-app', segment: 'B2C', nps: 50, csat: 91, fcr: 82, aht: 2.8, requests: 480 },
{ channel: 'office', segment: 'B2B', nps: 35, csat: 80, fcr: 68, aht: 6.5, requests: 320 }, { channel: 'office', segment: 'B2B', nps: 35, csat: 80, fcr: 68, aht: 6.5, requests: 85 },
{ channel: 'office', segment: 'B2C', nps: 40, csat: 85, fcr: 74, aht: 4.5, requests: 980 } { channel: 'office', segment: 'B2C', nps: 40, csat: 85, fcr: 74, aht: 4.5, requests: 260 }
], ],
history: [ history: [
{ period: 'Янв', nps: 39 }, { period: 'Фев', nps: 41 }, { period: 'Мар', nps: 38 }, { period: 'Пн', nps: 40 }, { period: 'Вт', nps: 42 }, { period: 'Ср', nps: 39 },
{ period: 'Апр', nps: 43 }, { period: 'Май', nps: 45 }, { period: 'Июн', nps: 42 }, { period: 'Чт', nps: 44 }, { period: 'Пт', nps: 43 }, { period: 'Сб', nps: 41 }, { period: 'Вс', nps: 42 }
{ period: 'Июл', nps: 44 }, { period: 'Авг', nps: 46 }, { period: 'Сен', nps: 42 },
{ period: 'Окт', nps: 40 }, { period: 'Ноя', nps: 43 }, { period: 'Дек', nps: 42 }
], ],
insights: { insights: {
B2B: { B2B: {
explanation: 'Снижение FCR в B2B-сегменте связано с увеличением сложных технических запросов. AHT выше среднего из-за длительной обработки заявок в офисах.', explanation: 'Возможно, снижение FCR в B2B связано с увеличением сложных технических запросов на этой неделе. AHT выше среднего из-за длительной обработки в офисах.',
recommendations: [
'Усилить техническую подготовку операторов B2B-направления',
'Внедрить шаблоны ответов для типовых сложных запросов'
]
},
B2C: {
explanation: 'Возможно, небольшое снижение NPS в выходные связано с ростом нагрузки в выходные дни.',
recommendations: [
'Поддерживать текущий уровень сервиса в онлайн-каналах',
'Провести работу над ошибками в call-центре для роста FCR'
]
},
all: {
explanation: 'Общие показатели в пределах нормы. Наблюдается стабильная динамика NPS в течение недели.',
recommendations: [
'Продолжить развитие онлайн-каналов обслуживания',
'Провести обучение операторов call-центра'
]
}
}
},
'30d': {
metrics: [
{ channel: 'call-center', segment: 'B2B', nps: 40, csat: 85, fcr: 74, aht: 5.0, requests: 1240 },
{ channel: 'call-center', segment: 'B2C', nps: 47, csat: 90, fcr: 79, aht: 3.6, requests: 3420 },
{ channel: 'online', segment: 'B2B', nps: 54, csat: 93, fcr: 86, aht: 2.0, requests: 890 },
{ channel: 'online', segment: 'B2C', nps: 50, csat: 89, fcr: 81, aht: 2.4, requests: 2150 },
{ channel: 'mobile-app', segment: 'B2B', nps: 46, csat: 87, fcr: 76, aht: 3.0, requests: 560 },
{ channel: 'mobile-app', segment: 'B2C', nps: 52, csat: 92, fcr: 83, aht: 2.6, requests: 1890 },
{ channel: 'office', segment: 'B2B', nps: 37, csat: 81, fcr: 70, aht: 6.2, requests: 320 },
{ channel: 'office', segment: 'B2C', nps: 42, csat: 86, fcr: 75, aht: 4.3, requests: 980 }
],
history: [
{ period: 'Нед 1', nps: 39 }, { period: 'Нед 2', nps: 42 }, { period: 'Нед 3', nps: 44 },
{ period: 'Нед 4', nps: 43 }
],
insights: {
B2B: {
explanation: 'Возможно, снижение FCR в B2B-сегменте связано с увеличением сложных технических запросов в этом месяце.',
recommendations: [
'Усилить техническую подготовку операторов B2B-направления',
'Внедрить шаблоны ответов для типовых сложных запросов',
'Сократить время обработки в офисах через цифровизацию'
]
},
B2C: {
explanation: 'B2C-сегмент показывает стабильные результаты. Возможно, небольшое снижение NPS связано с сезонным ростом нагрузки.',
recommendations: [
'Поддерживать текущий уровень сервиса в онлайн-каналах',
'Провести работу над ошибками в call-центре'
]
},
all: {
explanation: 'Общие показатели в пределах нормы. Наблюдается положительная динамика NPS за месяц.',
recommendations: [
'Продолжить развитие онлайн-каналов обслуживания',
'Провести обучение операторов call-центра',
'Внедрить единую базу знаний для всех каналов'
]
}
}
},
'90d': {
metrics: [
{ channel: 'call-center', segment: 'B2B', nps: 41, csat: 86, fcr: 75, aht: 4.9, requests: 3800 },
{ channel: 'call-center', segment: 'B2C', nps: 48, csat: 91, fcr: 80, aht: 3.5, requests: 10200 },
{ channel: 'online', segment: 'B2B', nps: 55, csat: 94, fcr: 87, aht: 1.9, requests: 2700 },
{ channel: 'online', segment: 'B2C', nps: 51, csat: 90, fcr: 82, aht: 2.3, requests: 6400 },
{ channel: 'mobile-app', segment: 'B2B', nps: 47, csat: 88, fcr: 77, aht: 2.9, requests: 1700 },
{ channel: 'mobile-app', segment: 'B2C', nps: 53, csat: 93, fcr: 84, aht: 2.5, requests: 5600 },
{ channel: 'office', segment: 'B2B', nps: 38, csat: 82, fcr: 71, aht: 6.0, requests: 950 },
{ channel: 'office', segment: 'B2C', nps: 43, csat: 87, fcr: 76, aht: 4.2, requests: 2900 }
],
history: [
{ period: 'Июл', nps: 44 }, { period: 'Авг', nps: 46 }, { period: 'Сен', nps: 42 }
],
insights: {
B2B: {
explanation: 'Возможно, снижение FCR в B2B-сегменте за квартал связано с увеличением объёма сложных технических запросов.',
recommendations: [ recommendations: [
'Усилить техническую подготовку операторов B2B-направления', 'Усилить техническую подготовку операторов B2B-направления',
'Внедрить шаблоны ответов для типовых сложных запросов', 'Внедрить шаблоны ответов для типовых сложных запросов',
'Сократить время обработки в офисах через цифровизацию процессов' 'Сократить время обработки в офисах через цифровизацию процессов'
], ]
source: 'Показатели взяты из системы мониторинга качества за 4 квартал 2025'
}, },
B2C: { B2C: {
explanation: 'B2C-сегмент показывает стабильные результаты. Небольшое снижение NPS в сентябре связано с сезонным ростом нагрузки.', explanation: 'B2C-сегмент показывает стабильные результаты за квартал. Возможно, снижение NPS в сентябре связано с сезонным фактором.',
recommendations: [ recommendations: [
'Поддерживать текущий уровень сервиса в онлайн-каналах', 'Поддерживать текущий уровень сервиса в онлайн-каналах',
'Провести работу над ошибками в call-центре для роста FCR' 'Провести работу над ошибками в call-центре для роста FCR'
], ]
source: 'Показатели взяты из системы мониторинга качества за 4 квартал 2025'
}, },
all: { all: {
explanation: 'Общие показатели в пределах нормы. Наблюдается положительная динамика NPS за последние 3 месяца.', explanation: 'Общие показатели в пределах нормы. Наблюдается положительная динамика NPS за последние 3 месяца.',
@ -204,13 +284,13 @@ const DEMO_DATA = {
'Продолжить развитие онлайн-каналов обслуживания', 'Продолжить развитие онлайн-каналов обслуживания',
'Провести обучение операторов call-центра', 'Провести обучение операторов call-центра',
'Внедрить единую базу знаний для всех каналов' 'Внедрить единую базу знаний для всех каналов'
], ]
source: 'Показатели взяты из системы мониторинга качества за 4 квартал 2025' }
} }
} }
}; };
let currentFilters = { segment: 'all', channel: 'all', period: '7d' }; let currentFilters = { segment: 'all', channel: 'all', period: '7d', dataMode: 'data' };
function loadFilters() { function loadFilters() {
const saved = localStorage.getItem('bm_filters'); const saved = localStorage.getItem('bm_filters');
@ -219,6 +299,7 @@ function loadFilters() {
document.getElementById('segmentFilter').value = currentFilters.segment; document.getElementById('segmentFilter').value = currentFilters.segment;
document.getElementById('channelFilter').value = currentFilters.channel; document.getElementById('channelFilter').value = currentFilters.channel;
document.getElementById('periodFilter').value = currentFilters.period; document.getElementById('periodFilter').value = currentFilters.period;
document.getElementById('dataModeFilter').value = currentFilters.dataMode;
} }
} }
@ -227,7 +308,8 @@ function saveFilters() {
} }
function getFilteredData() { function getFilteredData() {
let data = DEMO_DATA.metrics; const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
let data = periodData.metrics;
if (currentFilters.segment !== 'all') { if (currentFilters.segment !== 'all') {
data = data.filter(m => m.segment === currentFilters.segment); data = data.filter(m => m.segment === currentFilters.segment);
} }
@ -237,6 +319,16 @@ function getFilteredData() {
return data; return data;
} }
function getHistory() {
const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
return periodData.history;
}
function getInsights(segment) {
const periodData = DEMO_DATA[currentFilters.period] || DEMO_DATA['7d'];
return periodData.insights[segment] || periodData.insights.all;
}
function calculateAverages(data) { function calculateAverages(data) {
if (data.length === 0) return null; if (data.length === 0) return null;
const sum = data.reduce((acc, m) => ({ const sum = data.reduce((acc, m) => ({
@ -270,12 +362,16 @@ function renderKPIs(avgs) {
function renderChart() { function renderChart() {
const container = document.getElementById('chartContainer'); const container = document.getElementById('chartContainer');
const maxNPS = Math.max(...DEMO_DATA.history.map(h => h.nps)); const history = getHistory();
container.innerHTML = DEMO_DATA.history.map((h, i) => { if (!history || history.length === 0) {
const height = (h.nps / maxNPS) * 160; container.innerHTML = '<div class="no-data" style="padding: var(--kt-ai-space-10);">Недостаточно данных</div>';
const colorIndex = i % 6; return;
}
const maxNPS = Math.max(...history.map(h => h.nps));
const colors = ['var(--kt-ai-chart-blue)', 'var(--kt-ai-chart-green)', 'var(--kt-ai-chart-orange)', 'var(--kt-ai-chart-pink)', 'var(--kt-ai-chart-red)', 'var(--kt-ai-chart-salmon)']; const colors = ['var(--kt-ai-chart-blue)', 'var(--kt-ai-chart-green)', 'var(--kt-ai-chart-orange)', 'var(--kt-ai-chart-pink)', 'var(--kt-ai-chart-red)', 'var(--kt-ai-chart-salmon)'];
return `<div class="chart-bar" style="height: ${height}px; background: ${colors[colorIndex]}" data-kt-tip="${h.period}: ${h.nps}"></div>`; container.innerHTML = history.map((h, i) => {
const height = (h.nps / maxNPS) * 160;
return `<div class="chart-bar" style="height: ${height}px; background: ${colors[i % colors.length]}" data-kt-tip="${h.period}: ${h.nps}"></div>`;
}).join(''); }).join('');
} }
@ -299,27 +395,40 @@ function renderTable(data) {
} }
function renderInsights(segment) { function renderInsights(segment) {
const insight = DEMO_DATA.insights[segment] || DEMO_DATA.insights.all; if (currentFilters.dataMode === 'nodata') {
document.getElementById('insightsContent').style.display = 'none';
return;
}
document.getElementById('insightsContent').style.display = 'block';
const insight = getInsights(segment);
document.getElementById('explanationText').textContent = insight.explanation; document.getElementById('explanationText').textContent = insight.explanation;
document.getElementById('recommendationsList').innerHTML = insight.recommendations.map(r => `<li>${r}</li>`).join(''); document.getElementById('recommendationsList').innerHTML = insight.recommendations.map(r => `<li>${r}</li>`).join('');
document.getElementById('sourceLink').textContent = insight.source;
} }
function render() { function render() {
const noData = currentFilters.dataMode === 'nodata';
document.getElementById('kpiStrip').style.display = noData ? 'none' : 'grid';
document.getElementById('noDataBlock').style.display = noData ? 'block' : 'none';
document.getElementById('metricsTable').closest('.table-wrap').style.display = noData ? 'none' : 'block';
if (noData) {
renderChart();
renderInsights(null);
return;
}
const data = getFilteredData(); const data = getFilteredData();
const avgs = calculateAverages(data); const avgs = calculateAverages(data);
renderKPIs(avgs); renderKPIs(avgs);
renderChart(); renderChart();
renderTable(data); renderTable(data);
renderInsights(currentFilters.segment); renderInsights(currentFilters.segment);
document.getElementById('noDataBlock').style.display = data.length === 0 ? 'block' : 'none';
} }
function onFilterChange() { function onFilterChange() {
currentFilters = { currentFilters = {
segment: document.getElementById('segmentFilter').value, segment: document.getElementById('segmentFilter').value,
channel: document.getElementById('channelFilter').value, channel: document.getElementById('channelFilter').value,
period: document.getElementById('periodFilter').value period: document.getElementById('periodFilter').value,
dataMode: document.getElementById('dataModeFilter').value
}; };
saveFilters(); saveFilters();
render(); render();
@ -329,10 +438,11 @@ document.getElementById('segmentFilter').addEventListener('change', onFilterChan
document.getElementById('channelFilter').addEventListener('change', onFilterChange); document.getElementById('channelFilter').addEventListener('change', onFilterChange);
document.getElementById('periodFilter').addEventListener('change', onFilterChange); document.getElementById('periodFilter').addEventListener('change', onFilterChange);
document.getElementById('resetBtn').addEventListener('click', () => { document.getElementById('resetBtn').addEventListener('click', () => {
currentFilters = { segment: 'all', channel: 'all', period: '7d' }; currentFilters = { segment: 'all', channel: 'all', period: '7d', dataMode: 'data' };
document.getElementById('segmentFilter').value = 'all'; document.getElementById('segmentFilter').value = 'all';
document.getElementById('channelFilter').value = 'all'; document.getElementById('channelFilter').value = 'all';
document.getElementById('periodFilter').value = '7d'; document.getElementById('periodFilter').value = '7d';
document.getElementById('dataModeFilter').value = 'data';
saveFilters(); saveFilters();
render(); render();
}); });