kpi-dashboard/dashboard.html
2026-06-01 10:16:59 +05:00

1171 lines
87 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KPI InDigiCo — Казахтелеком 2026</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
<!-- Токен DeepSeek: положите рядом файл config.js с содержимым: window.DS_KEY = 'sk-...' -->
<script src="config.js" onerror="console.info('config.js не найден')"></script>
<style>
:root {
--color-bg: #F4F6F9;
--color-surface: #FFFFFF;
--color-border: #E5E7EB;
--color-text-primary: #111827;
--color-text-secondary: #6B7280;
--color-brand: #0052CC;
--color-brand-light: #EFF6FF;
--color-green: #10B981;
--color-green-bg: #ECFDF5;
--color-yellow: #F59E0B;
--color-yellow-bg: #FFFBEB;
--color-red: #EF4444;
--color-red-bg: #FEF2F2;
--color-orange: #F97316;
--color-orange-bg: #FFF7ED;
--radius-card: 12px;
--radius-btn: 8px;
--shadow-card: 0 1px 3px rgba(0,0,0,0.08), 0 1px 2px rgba(0,0,0,0.04);
--shadow-card-hover: 0 4px 12px rgba(0,0,0,0.1);
--font-base: 'Inter', system-ui, sans-serif;
--font-mono: 'Roboto Mono', monospace;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: var(--font-base); background: var(--color-bg); color: var(--color-text-primary); font-size: 14px; line-height: 1.5; }
/* ── LOADING / UPLOAD SCREENS ── */
#loading-screen {
min-height: 100vh; display: flex; align-items: center; justify-content: center;
background: linear-gradient(135deg, #0052CC 0%, #0070F3 100%);
}
.loading-box { text-align: center; color: #fff; }
.spinner-lg { width: 48px; height: 48px; border: 4px solid rgba(255,255,255,0.3); border-top-color: #fff; border-radius: 50%; animation: spin 0.8s linear infinite; margin: 0 auto 20px; }
.loading-box p { font-size: 16px; font-weight: 500; opacity: 0.9; }
#upload-screen { min-height: 100vh; display: none; align-items: center; justify-content: center; background: linear-gradient(135deg, #0052CC 0%, #0070F3 100%); }
.upload-box { background: #fff; border-radius: 20px; padding: 48px 56px; text-align: center; max-width: 480px; width: 90%; box-shadow: 0 20px 60px rgba(0,0,0,0.15); }
.upload-logo { display: inline-flex; align-items: center; justify-content: center; width: 64px; height: 64px; background: var(--color-brand); color: #fff; font-size: 24px; font-weight: 700; border-radius: 16px; margin-bottom: 20px; }
.upload-box h1 { font-size: 22px; font-weight: 700; margin-bottom: 8px; }
.upload-box > p { color: var(--color-text-secondary); margin-bottom: 32px; font-size: 14px; }
.upload-note { margin-bottom: 24px; padding: 10px 14px; background: var(--color-yellow-bg); border-radius: 8px; font-size: 12px; color: #92400E; border: 1px solid rgba(245,158,11,0.3); text-align: left; line-height: 1.6; }
.upload-btn-label { display: inline-block; background: var(--color-brand); color: #fff; padding: 14px 32px; border-radius: var(--radius-btn); font-weight: 600; font-size: 15px; cursor: pointer; transition: background 0.2s; }
.upload-btn-label:hover { background: #003fa3; }
#file-input { display: none; }
.upload-hint { margin-top: 14px; font-size: 12px; color: var(--color-text-secondary); }
#upload-error { color: var(--color-red); margin-top: 10px; font-size: 13px; min-height: 18px; }
/* ── HEADER ── */
#dashboard { display: none; }
.header { background: var(--color-brand); color: #fff; padding: 0 24px; height: 64px; display: flex; align-items: center; justify-content: space-between; position: sticky; top: 0; z-index: 100; box-shadow: 0 2px 8px rgba(0,82,204,0.3); }
.header-left { display: flex; align-items: center; gap: 14px; }
.header-logo { width: 40px; height: 40px; background: rgba(255,255,255,0.2); border-radius: 10px; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; flex-shrink: 0; }
.header-left h1 { font-size: 16px; font-weight: 700; line-height: 1.2; }
.header-left p { font-size: 12px; opacity: 0.75; }
.header-right { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
#last-updated { font-size: 12px; opacity: 0.85; }
.btn { padding: 7px 14px; border-radius: var(--radius-btn); font-size: 13px; font-weight: 500; cursor: pointer; border: none; font-family: var(--font-base); transition: all 0.15s; }
.btn-ghost { background: rgba(255,255,255,0.15); color: #fff; }
.btn-ghost:hover { background: rgba(255,255,255,0.25); }
.btn-outline { background: transparent; border: 1px solid rgba(255,255,255,0.4); color: #fff; }
.btn-outline:hover { background: rgba(255,255,255,0.1); }
/* ── SUMMARY BAR ── */
.summary-section { padding: 20px 24px 0; }
.summary-bar { display: grid; grid-template-columns: repeat(5, 1fr); gap: 14px; }
.kpi-card { background: var(--color-surface); border-radius: var(--radius-card); padding: 16px; box-shadow: var(--shadow-card); cursor: pointer; transition: box-shadow 0.2s, transform 0.15s; border-top: 4px solid var(--color-border); }
.kpi-card:hover { box-shadow: var(--shadow-card-hover); transform: translateY(-2px); }
.kpi-card.status-green { border-top-color: var(--color-green); }
.kpi-card.status-yellow { border-top-color: var(--color-yellow); }
.kpi-card.status-red { border-top-color: var(--color-red); }
.kpi-card.status-orange { border-top-color: var(--color-orange); }
.kpi-card-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; }
.kpi-icon { font-size: 20px; }
.kpi-status-badge { font-size: 10px; font-weight: 600; padding: 2px 7px; border-radius: 20px; white-space: nowrap; }
.status-green .kpi-status-badge { background: var(--color-green-bg); color: var(--color-green); }
.status-yellow .kpi-status-badge { background: var(--color-yellow-bg); color: var(--color-yellow); }
.status-red .kpi-status-badge { background: var(--color-red-bg); color: var(--color-red); }
.status-orange .kpi-status-badge { background: var(--color-orange-bg); color: var(--color-orange); }
.kpi-card-value { font-size: 28px; font-weight: 700; font-family: var(--font-mono); line-height: 1; margin-bottom: 4px; }
.status-green .kpi-card-value { color: var(--color-green); }
.status-yellow .kpi-card-value { color: var(--color-yellow); }
.status-red .kpi-card-value { color: var(--color-red); }
.status-orange .kpi-card-value { color: var(--color-orange); }
.kpi-card-label { font-size: 12px; font-weight: 600; color: var(--color-text-secondary); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 8px; }
.kpi-card-meta { display: flex; justify-content: space-between; align-items: center; font-size: 11px; color: var(--color-text-secondary); margin-bottom: 4px; }
.kpi-delta { font-weight: 600; }
.kpi-delta.positive { color: var(--color-green); }
.kpi-delta.negative { color: var(--color-red); }
.kpi-risk-row { font-size: 10px; color: var(--color-orange); font-weight: 500; margin-bottom: 6px; min-height: 13px; }
/* ── TOOLBAR ── */
.toolbar { padding: 14px 24px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.toolbar-label { font-size: 12px; font-weight: 600; color: var(--color-text-secondary); margin-right: 4px; }
.month-btn { padding: 6px 14px; border-radius: 20px; font-size: 13px; font-weight: 500; cursor: pointer; border: 1px solid var(--color-border); background: var(--color-surface); color: var(--color-text-secondary); font-family: var(--font-base); transition: all 0.15s; }
.month-btn:hover { border-color: var(--color-brand); color: var(--color-brand); }
.month-btn.active { background: var(--color-brand); color: #fff; border-color: var(--color-brand); }
/* ── LAYOUT ── */
.main-layout { display: grid; grid-template-columns: 1fr 360px; gap: 24px; padding: 0 24px 32px; align-items: start; }
.charts-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
.charts-grid .chart-section:nth-child(5) { grid-column: 1 / -1; }
.chart-section { background: var(--color-surface); border-radius: var(--radius-card); padding: 20px; box-shadow: var(--shadow-card); }
.chart-section-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
.chart-section-title { font-size: 14px; font-weight: 700; }
.chart-section-subtitle { font-size: 11px; color: var(--color-text-secondary); margin-top: 2px; }
.chart-tabs { display: flex; gap: 4px; }
.chart-tab { padding: 4px 10px; border-radius: 6px; font-size: 12px; font-weight: 500; cursor: pointer; border: 1px solid var(--color-border); background: transparent; color: var(--color-text-secondary); font-family: var(--font-base); transition: all 0.15s; }
.chart-tab.active { background: var(--color-brand); color: #fff; border-color: var(--color-brand); }
.chart-canvas-wrap { position: relative; height: 220px; }
/* Risk banners */
.chart-risk-banner { margin-top: 10px; padding: 7px 12px; border-radius: 8px; font-size: 11px; font-weight: 500; background: var(--color-orange-bg); color: var(--color-orange); border: 1px solid rgba(249,115,22,0.2); }
.chart-risk-banner.hidden { display: none; }
/* FD progress */
.progress-bar-wrap { margin-top: 16px; }
.progress-bar-labels { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 6px; }
.progress-bar-labels b { font-family: var(--font-mono); }
.progress-track { height: 10px; background: var(--color-bg); border-radius: 10px; overflow: hidden; }
.progress-fill { height: 100%; background: linear-gradient(90deg, var(--color-red) 0%, var(--color-yellow) 50%, var(--color-green) 100%); border-radius: 10px; transition: width 0.6s ease; min-width: 2px; }
.annotation-badge { margin-top: 10px; padding: 8px 12px; border-radius: 8px; font-size: 12px; font-weight: 500; background: var(--color-red-bg); color: var(--color-red); display: flex; align-items: center; gap: 6px; }
.annotation-badge.hidden { display: none; }
/* FD trend info */
.fd-trend-info { margin-top: 10px; padding: 8px 12px; border-radius: 8px; font-size: 12px; background: var(--color-brand-light); color: var(--color-brand); border: 1px solid rgba(0,82,204,0.15); line-height: 1.6; }
.fd-trend-info.hidden { display: none; }
/* ── AI PANEL ── */
.ai-panel { background: var(--color-surface); border-radius: var(--radius-card); padding: 20px; box-shadow: var(--shadow-card); position: sticky; top: 76px; }
.ai-panel-title { font-size: 15px; font-weight: 700; margin-bottom: 14px; display: flex; align-items: center; gap: 8px; }
.ai-api-row { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
.api-key-input { width: 100%; padding: 9px 12px; border: 1px solid var(--color-border); border-radius: var(--radius-btn); font-size: 13px; font-family: var(--font-base); color: var(--color-text-primary); outline: none; transition: border-color 0.15s; }
.api-key-input:focus { border-color: var(--color-brand); }
.api-key-input:disabled { background: var(--color-bg); }
.btn-primary { width: 100%; padding: 10px; background: var(--color-brand); color: #fff; border: none; border-radius: var(--radius-btn); font-size: 14px; font-weight: 600; cursor: pointer; font-family: var(--font-base); transition: background 0.15s; }
.btn-primary:hover { background: #003fa3; }
.btn-primary:disabled { background: #93B4DD; cursor: not-allowed; }
.ai-loading { display: flex; align-items: center; gap: 10px; color: var(--color-text-secondary); font-size: 13px; padding: 12px 0; }
.ai-loading.hidden { display: none; }
.spinner { width: 18px; height: 18px; border: 2px solid var(--color-border); border-top-color: var(--color-brand); border-radius: 50%; animation: spin 0.7s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
.ai-result.hidden { display: none; }
#ai-text { font-size: 13px; line-height: 1.65; white-space: pre-wrap; max-height: 420px; overflow-y: auto; padding: 12px; background: var(--color-bg); border-radius: 8px; margin-bottom: 10px; }
.btn-copy { width: 100%; padding: 8px; background: var(--color-bg); color: var(--color-text-secondary); border: 1px solid var(--color-border); border-radius: var(--radius-btn); font-size: 13px; font-weight: 500; cursor: pointer; font-family: var(--font-base); transition: all 0.15s; }
.btn-copy:hover { border-color: var(--color-brand); color: var(--color-brand); }
.ai-error { font-size: 13px; color: var(--color-red); background: var(--color-red-bg); padding: 10px 12px; border-radius: 8px; margin-top: 8px; }
.ai-error.hidden { display: none; }
.ai-divider { height: 1px; background: var(--color-border); margin: 12px 0; }
.ai-hint { font-size: 11px; color: var(--color-text-secondary); text-align: center; line-height: 1.5; }
.ai-key-note { font-size: 11px; color: var(--color-green); text-align: center; margin-bottom: 8px; font-weight: 500; }
/* ── RESPONSIVE ── */
@media (max-width: 1200px) { .summary-bar { grid-template-columns: repeat(3,1fr); } }
@media (max-width: 1024px) { .main-layout { grid-template-columns: 1fr; } .ai-panel { position: static; } }
@media (max-width: 768px) { .charts-grid { grid-template-columns: 1fr; } .charts-grid .chart-section:nth-child(5) { grid-column: auto; } .summary-bar { grid-template-columns: 1fr 1fr; } .main-layout,.summary-section { padding-left: 12px; padding-right: 12px; } .toolbar { padding: 10px 12px; } }
@media (max-width: 480px) { .summary-bar { grid-template-columns: 1fr; } .header-left h1 { font-size: 14px; } }
</style>
</head>
<body>
<!-- LOADING -->
<div id="loading-screen">
<div class="loading-box">
<div class="spinner-lg"></div>
<p>Загрузка данных KPI...</p>
</div>
</div>
<!-- UPLOAD (резерв) -->
<div id="upload-screen">
<div class="upload-box">
<div class="upload-logo">KT</div>
<h1>KPI InDigiCo</h1>
<p id="upload-reason">Выберите CSV файл с данными KPI</p>
<div class="upload-note" id="upload-note" style="display:none"></div>
<label class="upload-btn-label" for="file-input">📂 Выбрать CSV файл</label>
<input type="file" id="file-input" accept=".csv">
<p class="upload-hint">Файл: <code>drb_iliyas_kpi_2026.csv</code> · Разделитель <code>;</code></p>
<p id="upload-error"></p>
</div>
</div>
<!-- DASHBOARD -->
<div id="dashboard">
<header class="header">
<div class="header-left">
<div class="header-logo">KT</div>
<div>
<h1>KPI InDigiCo</h1>
<p>Казахтелеком · 2026</p>
</div>
</div>
<div class="header-right">
<span id="last-updated"></span>
<button class="btn btn-ghost" id="btn-export-csv">↓ Скачать CSV</button>
<button class="btn btn-outline" id="btn-reload">Обновить данные</button>
</div>
</header>
<div class="summary-section"><div class="summary-bar" id="summary-bar"></div></div>
<div class="toolbar" id="toolbar"><span class="toolbar-label">Период:</span></div>
<div class="main-layout">
<div class="charts-grid">
<div class="chart-section" id="section-kpi-registrations">
<div class="chart-section-header">
<div><div class="chart-section-title">👤 KPI 1 — Регистрации</div><div class="chart-section-subtitle">Доля зарег. пользователей · Цель: 60%</div></div>
</div>
<div class="chart-canvas-wrap"><canvas id="chart-registrations"></canvas></div>
<div class="chart-risk-banner hidden" id="risk-registrations"></div>
</div>
<div class="chart-section" id="section-kpi-mau">
<div class="chart-section-header">
<div><div class="chart-section-title">📱 KPI 2 — MAU</div><div class="chart-section-subtitle">Monthly Active Users · Цель: 30%</div></div>
<div class="chart-tabs">
<button class="chart-tab active" id="tab-mau-monthly" onclick="switchMauTab('monthly')">По месяцам</button>
<button class="chart-tab" id="tab-mau-daily" onclick="switchMauTab('daily')">По дням</button>
</div>
</div>
<div class="chart-canvas-wrap"><canvas id="chart-mau"></canvas></div>
<div class="chart-risk-banner hidden" id="risk-mau"></div>
</div>
<div class="chart-section" id="section-kpi-traditional">
<div class="chart-section-header">
<div><div class="chart-section-title">📉 KPI 3 — Традиционные обращения</div><div class="chart-section-subtitle">Снижение доли обращений · Цель: 10%</div></div>
</div>
<div class="chart-canvas-wrap"><canvas id="chart-traditional"></canvas></div>
<div class="chart-risk-banner hidden" id="risk-traditional"></div>
</div>
<div class="chart-section" id="section-kpi-digital-sales">
<div class="chart-section-header">
<div><div class="chart-section-title">💰 KPI 4 — Цифровые продажи</div><div class="chart-section-subtitle">Доля цифровой выручки · Цель: 45%</div></div>
</div>
<div class="chart-canvas-wrap"><canvas id="chart-digital-sales"></canvas></div>
<div class="chart-risk-banner hidden" id="risk-digital-sales"></div>
</div>
<div class="chart-section" id="section-kpi-fd-orders">
<div class="chart-section-header">
<div><div class="chart-section-title">⚡ KPI 5 — Full Digital установки</div><div class="chart-section-subtitle">FD заказы · Цель: 1 000/мес · 12 000/год</div></div>
</div>
<div class="chart-canvas-wrap"><canvas id="chart-fd-orders"></canvas></div>
<div class="progress-bar-wrap">
<div class="progress-bar-labels">
<span>Годовой прогресс: <b id="cum-fd-text"></b></span>
<span id="cum-fd-pct" style="font-weight:600;font-family:var(--font-mono);"></span>
</div>
<div class="progress-track"><div class="progress-fill" id="cum-fd-bar" style="width:0%"></div></div>
</div>
<div class="fd-trend-info hidden" id="fd-trend-info"></div>
<div class="annotation-badge hidden" id="fd-annotation">⚠️ Показатель существенно ниже плана — цель требует пересмотра</div>
</div>
</div>
<!-- AI Panel -->
<div class="ai-panel">
<div class="ai-panel-title">🤖 AI-анализ KPI</div>
<div class="ai-api-row">
<div id="ai-key-note" class="ai-key-note hidden">✓ Токен загружен из config.js</div>
<input type="password" class="api-key-input" id="ai-api-key" placeholder="Введите DeepSeek API ключ">
<button class="btn-primary" id="btn-ai-analyze">Запустить анализ</button>
</div>
<div class="ai-loading hidden" id="ai-loading"><div class="spinner"></div><span>Анализируем данные...</span></div>
<div class="ai-result hidden" id="ai-result"><div id="ai-text"></div><button class="btn-copy" id="btn-ai-copy">📋 Скопировать</button></div>
<div class="ai-error hidden" id="ai-error"></div>
<div class="ai-divider"></div>
<p class="ai-hint">DeepSeek API · Ключ не сохраняется за пределами сессии браузера</p>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-annotation@3.0.1/dist/chartjs-plugin-annotation.min.js"></script>
<script>
// ═══════════════════ CONSTANTS ═══════════════════
// ─── Embedded CSV data (update this when the CSV changes) ───────────────────
// This makes the dashboard work via file:// AND on GitHub Pages without a server.
const EMBEDDED_CSV = `"report_period_id";"entry_date";"abons";"registered_total";"registered_pct";"mau_daily";"mau_per_registered";"traditional_comms_pct";"prev_year_traditional_comms_pct";"traditional_comms_decrease_pct";"cumulative_digital_rap_total";"cumulative_rap_total";"fd_rap_pct";"fd_orders";"fd_orders_goal";"fd_orders_pct";"cum_fd_orders";"cum_fd_orders_goal";"cum_fd_orders_pct"
202605;"2026-05-29";2100718;1067535;0.508;236392;0.221;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-28";2100718;1067532;0.508;236392;0.221;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-27";2100718;1066924;0.508;231473;0.217;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-26";2100718;1066631;0.508;227794;0.214;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-25";2100718;1066084;0.507;222648;0.209;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-24";2100718;1065569;0.507;217146;0.204;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-23";2100718;1065297;0.507;210438;0.198;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-22";2100718;1064996;0.507;206702;0.194;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-21";2100718;1064508;0.507;201861;0.19;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-20";2100718;1064000;0.506;196136;0.184;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-19";2100718;1063493;0.506;190391;0.179;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-18";2100718;1063028;0.506;185169;0.174;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-17";2100718;1062578;0.506;179417;0.169;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-16";2100718;1062398;0.506;175852;0.166;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-15";2100718;1062131;0.506;171740;0.162;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-14";2100718;1061692;0.505;165454;0.156;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-13";2100718;1061183;0.505;158742;0.15;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-12";2100718;1060626;0.505;150930;0.142;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-11";2100718;1060034;0.505;140882;0.133;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-10";2100718;1059741;0.504;133994;0.126;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-09";2100718;1059532;0.504;108417;0.102;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-08";2100718;1059344;0.504;103158;0.097;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-07";2100718;1058907;0.504;93860;0.089;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-06";2100718;1058693;0.504;87307;0.082;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-05";2100718;1058239;0.504;77687;0.073;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-04";2100718;1057748;0.504;61144;0.058;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-03";2100718;1057207;0.503;47949;0.045;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-02";2100718;1057009;0.503;38528;0.036;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202605;"2026-05-01";2100718;1056756;0.503;25816;0.024;0.5346041830749427;0.6678292219732731;0.19948968166544545;2124943881.8517177;4001729145.793371;0.531006423582032;164;1000;0.164;1343;5000;0.2686
202604;"2026-04-30";2106236;1056399;0.502;262629;0.249;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-29";2106236;1055833;0.501;257844;0.244;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-28";2106236;1055340;0.501;253602;0.24;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-27";2106236;1054774;0.501;248967;0.236;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-26";2106236;1054128;0.5;243467;0.231;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-25";2106236;1053911;0.5;240371;0.228;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-24";2106236;1053569;0.5;236545;0.225;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-23";2106236;1053042;0.5;229391;0.218;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-22";2106236;1052586;0.5;223915;0.213;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-21";2106236;1052162;0.5;218407;0.208;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-20";2106236;1051719;0.499;213139;0.203;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-19";2106236;1051232;0.499;207205;0.197;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-18";2106236;1051035;0.499;203784;0.194;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-17";2106236;1050776;0.499;199826;0.19;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-16";2106236;1050388;0.499;194196;0.185;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-15";2106236;1049972;0.499;189147;0.18;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-14";2106236;1049551;0.498;182802;0.174;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-13";2106236;1049115;0.498;176433;0.168;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-12";2106236;1048669;0.498;169981;0.162;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-11";2106236;1048506;0.498;165624;0.158;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-10";2106236;1048275;0.498;159654;0.152;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-09";2106236;1047857;0.498;135848;0.13;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-08";2106236;1047422;0.497;127968;0.122;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-07";2106236;1046968;0.497;118896;0.114;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-06";2106236;1046511;0.497;109255;0.104;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-05";2106236;1046023;0.497;100140;0.096;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-04";2106236;1045862;0.497;89993;0.086;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-03";2106236;1045604;0.496;78934;0.075;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-02";2106236;1045104;0.496;63301;0.061;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202604;"2026-04-01";2106236;1044565;0.496;42523;0.041;0.5338425639244234;0.6673874142558902;0.20010094208978135;1710926678.0289857;2933216654.6453385;0.5832936599890735;267;1000;0.267;1179;4000;0.29475
202603;"2026-03-31";2111350;1043888;0.494;274372;0.263;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-30";2111350;1043214;0.494;269033;0.258;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-29";2111350;1042636;0.494;263479;0.253;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-28";2111350;1042415;0.494;259821;0.249;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-27";2111350;1042108;0.494;255783;0.245;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-26";2111350;1041554;0.493;250261;0.24;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-25";2111350;1040942;0.493;243903;0.234;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-24";2111350;1040609;0.493;239163;0.23;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-23";2111350;1040287;0.493;230597;0.222;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-22";2111350;1040068;0.493;227016;0.218;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-21";2111350;1039913;0.493;224156;0.216;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-20";2111350;1039702;0.492;220464;0.212;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-19";2111350;1039290;0.492;214057;0.206;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-18";2111350;1038815;0.492;207676;0.2;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-17";2111350;1038395;0.492;202020;0.195;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-16";2111350;1037939;0.492;195789;0.189;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-15";2111350;1037451;0.491;190464;0.184;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-14";2111350;1037272;0.491;186579;0.18;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-13";2111350;1036969;0.491;181916;0.175;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-12";2111350;1036447;0.491;175609;0.169;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-11";2111350;1035955;0.491;167891;0.162;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-10";2111350;1035390;0.49;152405;0.147;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-09";2111350;1034771;0.49;132216;0.128;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-08";2111350;1034511;0.49;124335;0.12;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-07";2111350;1034336;0.49;118788;0.115;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-06";2111350;1034044;0.49;107871;0.104;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-05";2111350;1033640;0.49;98376;0.095;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-04";2111350;1033166;0.489;81444;0.079;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-03";2111350;1032627;0.489;67276;0.065;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-02";2111350;1032038;0.489;53611;0.052;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202603;"2026-03-01";2111350;1031337;0.488;28670;0.028;0.5286579372563773;0.6650577906177735;0.20509473805982192;1277504399.5958557;1976256535.1164815;0.646426401074777;302;1000;0.302;912;3000;0.304
202602;"2026-02-28";2115881;1030969;0.487;255186;0.248;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-27";2115881;1030503;0.487;250982;0.244;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-26";2115881;1029917;0.487;245809;0.239;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-25";2115881;1029314;0.486;240418;0.234;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-24";2115881;1028705;0.486;234498;0.228;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-23";2115881;1028056;0.486;223926;0.218;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-22";2115881;1027538;0.486;219188;0.213;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-21";2115881;1027337;0.486;215467;0.21;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-20";2115881;1026963;0.485;211510;0.206;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-19";2115881;1026457;0.485;206671;0.201;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-18";2115881;1026004;0.485;201382;0.196;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-17";2115881;1025488;0.485;195244;0.19;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-16";2115881;1025061;0.484;189087;0.184;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-15";2115881;1024577;0.484;184042;0.18;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-14";2115881;1024357;0.484;179979;0.176;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-13";2115881;1024081;0.484;175645;0.172;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-12";2115881;1023528;0.484;169492;0.166;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-11";2115881;1022916;0.483;162308;0.159;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-10";2115881;1022265;0.483;144628;0.141;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-09";2115881;1021630;0.483;120014;0.117;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-08";2115881;1021035;0.483;104867;0.103;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-07";2115881;1020800;0.482;98639;0.097;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-06";2115881;1020457;0.482;91218;0.089;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-05";2115881;1019955;0.482;81730;0.08;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-04";2115881;1019386;0.482;71492;0.07;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-03";2115881;1018799;0.482;60568;0.059;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-02";2115881;1018100;0.481;46952;0.046;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202602;"2026-02-01";2115881;1017353;0.481;28190;0.028;0.5281336608448282;0.6560082059673001;0.19492827065161145;823396217.71518;1139791017.7953122;0.7224098144832446;236;1000;0.236;610;2000;0.305
202601;"2026-01-31";2119709;1016924;0.48;258199;0.254;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-30";2119709;1016432;0.48;255143;0.251;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-29";2119709;1015746;0.479;251261;0.247;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-28";2119709;1015140;0.479;246361;0.243;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-27";2119709;1014520;0.479;242061;0.239;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-26";2119709;1013886;0.478;237580;0.234;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-25";2119709;1013248;0.478;232829;0.23;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-24";2119709;1012924;0.478;229318;0.226;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-23";2119709;1012500;0.478;222590;0.22;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-22";2119709;1011910;0.477;217002;0.214;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-21";2119709;1011318;0.477;211901;0.21;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-20";2119709;1010784;0.477;207205;0.205;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-19";2119709;1010244;0.477;202734;0.201;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-18";2119709;1009671;0.476;197504;0.196;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-17";2119709;1009441;0.476;194019;0.192;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-16";2119709;1009127;0.476;189558;0.188;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-15";2119709;1008640;0.476;184617;0.183;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-14";2119709;1008138;0.476;177007;0.176;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-13";2119709;1007587;0.475;171155;0.17;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-12";2119709;1007015;0.475;164480;0.163;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-11";2119709;1006355;0.475;156058;0.155;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-10";2119709;1006055;0.475;144870;0.144;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-09";2119709;1005603;0.474;125634;0.125;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-08";2119709;1004940;0.474;117022;0.116;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-07";2119709;1004268;0.474;107934;0.107;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-06";2119709;1003933;0.474;100992;0.101;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-05";2119709;1003255;0.473;89813;0.09;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-04";2119709;1002458;0.473;77277;0.077;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-03";2119709;1002142;0.473;68400;0.068;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-02";2119709;1001753;0.473;57710;0.058;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374
202601;"2026-01-01";2119709;1001226;0.472;40373;0.04;0.5051958171773308;0.6601111271723942;0.2346806524208247;415145489.39247954;492169296.8361092;0.8435013968998591;374;1000;0.374;374;1000;0.374`;
// ─────────────────────────────────────────────────────────────────────────────
const MONTH_NAMES = ['','Янв','Фев','Мар','Апр','Май','Июн','Июл','Авг','Сен','Окт','Ноя','Дек'];
const CSV_FILENAME = 'drb_iliyas_kpi_2026.csv';
const AppState = {
rawRows: [], snapshots: [], dailySeries: {},
selectedPeriod: null, rawCsvText: '', lastAiResponse: '', mauTab: 'monthly',
};
const charts = {};
Chart.defaults.font.family = 'Inter, sans-serif';
Chart.defaults.font.size = 12;
Chart.defaults.color = '#6B7280';
Chart.defaults.plugins.tooltip.backgroundColor = 'rgba(17,24,39,0.92)';
Chart.defaults.plugins.tooltip.titleColor = '#F9FAFB';
Chart.defaults.plugins.tooltip.bodyColor = '#D1D5DB';
Chart.defaults.plugins.tooltip.padding = 10;
Chart.defaults.plugins.tooltip.cornerRadius = 8;
Chart.defaults.plugins.tooltip.borderColor = 'rgba(255,255,255,0.1)';
Chart.defaults.plugins.tooltip.borderWidth = 1;
const KPI_CONFIG = [
{ id:'registrations', name:'Регистрации', icon:'👤', field:'registered_pct', target:0.60, targetLabel:'60%' },
{ id:'mau', name:'MAU', icon:'📱', field:'mau_per_registered', target:0.30, targetLabel:'30%' },
{ id:'traditional', name:'Снижение обращений', icon:'📉', field:'traditional_comms_decrease_pct',target:0.10, targetLabel:'10%' },
{ id:'digital-sales', name:'Цифровые продажи', icon:'💰', field:'fd_rap_pct', target:0.45, targetLabel:'45%' },
{ id:'fd-orders', name:'FD установки', icon:'⚡', field:'cum_fd_orders_pct', target:1.00, targetLabel:'12 000 шт.'},
];
// ═══════════════════ FORMATTING ═══════════════════
const fmtPct = (v, d=1) => (v*100).toFixed(d)+'%';
const fmtInt = n => new Intl.NumberFormat('ru-RU').format(Math.round(n));
const fmtMoney = n => new Intl.NumberFormat('ru-KZ',{style:'currency',currency:'KZT',maximumFractionDigits:0}).format(n);
const periodLabel = pid => { const s=String(pid); return MONTH_NAMES[parseInt(s.slice(4),10)]+' '+s.slice(0,4); };
// ═══════════════════ MATH / REGRESSION ═══════════════════
function linearRegression(xs, ys) {
const n = xs.length;
const sx=xs.reduce((a,b)=>a+b,0), sy=ys.reduce((a,b)=>a+b,0);
const sxy=xs.reduce((s,x,i)=>s+x*ys[i],0), sxx=xs.reduce((s,x)=>s+x*x,0);
const den = n*sxx-sx*sx;
if (den===0) return {slope:0, intercept:sy/n};
const slope=(n*sxy-sx*sy)/den;
return {slope, intercept:(sy-slope*sx)/n};
}
/**
* Exponential regression: fits y = a * e^(bx)
* Works only when all y > 0. Returns null otherwise.
*/
function exponentialRegression(xs, ys) {
if (ys.some(y=>y<=0)) return null;
const logYs = ys.map(y=>Math.log(y));
const {slope:b, intercept:lna} = linearRegression(xs, logYs);
return {a: Math.exp(lna), b};
}
/** R² — coefficient of determination */
function rSquared(ys, predictedYs) {
const mean = ys.reduce((s,v)=>s+v,0)/ys.length;
const ssTot = ys.reduce((s,y)=>s+(y-mean)**2,0);
if (ssTot===0) return 1;
const ssRes = ys.reduce((s,y,i)=>s+(y-predictedYs[i])**2,0);
return 1-ssRes/ssTot;
}
/**
* Smart forecast: automatically picks linear vs exponential based on R².
* Returns { values: number[], modelLabel: string }
* where values is an array of length `totalMonths`.
*/
function smartForecast(snapshots, field, totalMonths=12) {
if (snapshots.length < 2) return {values: Array(totalMonths).fill(null), modelLabel:'недостаточно данных'};
const xs = snapshots.map((_,i)=>i);
const ys = snapshots.map(s=>s[field]);
// Linear model
const lin = linearRegression(xs, ys);
const linPred = xs.map(x=>lin.intercept+lin.slope*x);
const linR2 = rSquared(ys, linPred);
// Exponential model
const exp = exponentialRegression(xs, ys);
let expR2 = -Infinity;
if (exp) {
const expPred = xs.map(x=>exp.a*Math.exp(exp.b*x));
expR2 = rSquared(ys, expPred);
}
// Use exponential only if it meaningfully outperforms linear (threshold: 3pp)
// Use exponential when:
// a) it meaningfully outperforms linear (ΔR² > 0.01), OR
// b) linear gives a physically impossible (negative) December forecast
const linDecForecast = lin.intercept + lin.slope * (totalMonths - 1);
const linIsUnphysical = linDecForecast < 0;
const useExp = exp && (expR2 > linR2 + 0.01 || (linIsUnphysical && expR2 >= linR2 - 0.05));
const values = Array.from({length:totalMonths}, (_,m)=>{
if (useExp) return exp.a * Math.exp(exp.b*m);
return lin.intercept + lin.slope*m;
});
const modelLabel = useExp
? `экспоненциальная модель (R²=${expR2.toFixed(2)})`
: `линейная модель (R²=${linR2.toFixed(2)})`;
return {values, modelLabel, useExp, linR2, expR2: expR2===-Infinity?null:expR2};
}
// Build 12-month forecast array for a KPI (used by multiple places)
function buildSmartForecast(field) {
return smartForecast(AppState.snapshots, field, 12);
}
// ═══════════════════ STATUS WITH RISK ═══════════════════
function getKpiStatusWithRisk(value, target, field) {
const {values: fc, modelLabel} = smartForecast(AppState.snapshots, field, 12);
const dec = fc[11]; // December projection
const aboveTarget = value >= target;
const nearTarget = value >= target * 0.85;
let riskText = '';
if (aboveTarget) {
if (dec !== null && dec < target) {
riskText = `⚠ Прогноз к дек.: ${fmtPct(dec)} — риск падения ниже цели`;
return {status:'orange', riskText, modelLabel, decForecast: dec};
}
return {status:'green', riskText:'', modelLabel, decForecast: dec};
}
if (nearTarget) {
if (dec !== null) riskText = `⚠ Прогноз к дек.: ${fmtPct(dec)}`;
return {status:'yellow', riskText, modelLabel, decForecast: dec};
}
if (dec !== null) riskText = `Прогноз к дек.: ${fmtPct(dec)}`;
return {status:'red', riskText, modelLabel, decForecast: dec};
}
const statusLabel = s => ({green:'✅ Выполнено',orange:'⚠️ Риск снижения',yellow:'⚠️ Близко к цели',red:'🔴 Отставание'}[s]);
const getDelta = (v,t) => { const d=(v-t)*100; return (d>=0?'+':'')+d.toFixed(1)+' п.п.'; };
/**
* Compute a "nice" Y-axis range from a set of values.
* @param {number[]} values - all data points (nulls are ignored)
* @param {object} opts
* padFraction - fraction of range added as padding on each side (default 0.15)
* forceMin - hard floor (e.g. 0 for counts)
* forceMax - hard ceiling (e.g. 100 for %)
*/
function axisRange(values, {padFraction=0.15, forceMin=null, forceMax=null}={}) {
const valid = values.flat().filter(v => v !== null && v !== undefined && isFinite(v));
if (!valid.length) return {min:0, max:100};
let lo = Math.min(...valid);
let hi = Math.max(...valid);
if (lo === hi) { lo -= 5; hi += 5; }
const range = hi - lo;
lo -= range * padFraction;
hi += range * padFraction;
if (forceMin !== null) lo = Math.max(lo, forceMin);
if (forceMax !== null) hi = Math.min(hi, forceMax);
// Snap to a "nice" step so ticks land on round numbers
const roughStep = (hi - lo) / 5;
const mag = Math.pow(10, Math.floor(Math.log10(roughStep || 1)));
const norm = roughStep / mag;
const step = norm <= 1 ? mag : norm <= 2 ? 2*mag : norm <= 5 ? 5*mag : 10*mag;
return {
min: Math.floor(lo / step) * step,
max: Math.ceil(hi / step) * step,
};
}
// ═══════════════════ CSV PARSING ═══════════════════
function parseCSV(text) {
const lines = text.trim().split(/\r?\n/);
let sep = ';';
if (!lines[0].includes(';') && lines[0].includes(',')) sep = ',';
const headers = lines[0].split(sep).map(h=>h.replace(/"/g,'').trim());
return lines.slice(1).filter(l=>l.trim()).map(line=>{
const values=line.split(sep);
const row={};
headers.forEach((h,i)=>{ const v=(values[i]||'').replace(/"/g,'').trim(); row[h]=(v!==''&&!isNaN(v))?Number(v):v; });
return row;
});
}
function buildMonthlySnapshots(rows) {
const months={};
rows.forEach(r=>{ const p=r.report_period_id; if(!months[p]||r.entry_date>months[p].entry_date)months[p]=r; });
return Object.values(months).sort((a,b)=>a.report_period_id-b.report_period_id);
}
function buildDailySeries(rows) {
const s={};
rows.forEach(r=>{ const p=r.report_period_id; if(!s[p])s[p]=[]; s[p].push(r); });
Object.keys(s).forEach(p=>s[p].sort((a,b)=>a.entry_date.localeCompare(b.entry_date)));
return s;
}
function initWithData(text) {
const rows=parseCSV(text);
if(!rows.length||!rows[0].report_period_id) throw new Error('Неверный формат CSV');
AppState.rawCsvText=text; AppState.rawRows=rows;
AppState.snapshots=buildMonthlySnapshots(rows);
AppState.dailySeries=buildDailySeries(rows);
AppState.selectedPeriod=null; AppState.mauTab='monthly';
}
// ═══════════════════ AUTO-LOAD ═══════════════════
async function tryAutoLoad() {
// 1. On HTTP (GitHub Pages, local server): try to fetch the latest CSV first
if (window.location.protocol !== 'file:') {
try {
const resp = await fetch(CSV_FILENAME, {cache:'no-cache'});
if (resp.ok) {
initWithData(await resp.text());
showDashboard();
return;
}
} catch { /* fall through to embedded */ }
}
// 2. Use the embedded CSV (works on file://, GitHub Pages when CSV is missing, anywhere)
if (EMBEDDED_CSV && EMBEDDED_CSV.trim().length > 100) {
try {
initWithData(EMBEDDED_CSV);
showDashboard();
return;
} catch { /* fall through to upload screen */ }
}
// 3. Nothing worked — show manual upload
showUploadScreen(
'Данные не найдены',
'Загрузите файл drb_iliyas_kpi_2026.csv вручную.'
);
}
function showUploadScreen(reason, note) {
document.getElementById('loading-screen').style.display = 'none';
document.getElementById('upload-screen').style.display = 'flex';
if (reason) document.getElementById('upload-reason').textContent = reason;
if (note) {
const noteEl = document.getElementById('upload-note');
noteEl.textContent = note;
noteEl.style.display = 'block';
}
}
// ═══════════════════ RENDER ═══════════════════
function showDashboard() {
document.getElementById('loading-screen').style.display = 'none';
document.getElementById('upload-screen').style.display = 'none';
document.getElementById('dashboard').style.display = 'block';
renderHeader(); renderSummaryBar(); renderToolbar();
destroyAllCharts(); renderAllCharts();
restoreAiCache(); initAiKey();
}
function renderHeader() {
const last=AppState.snapshots[AppState.snapshots.length-1];
document.getElementById('last-updated').textContent='Данные по: '+periodLabel(last.report_period_id);
}
function renderSummaryBar() {
const bar=document.getElementById('summary-bar');
const last=AppState.snapshots[AppState.snapshots.length-1];
bar.innerHTML='';
KPI_CONFIG.forEach(kpi=>{
const val=last[kpi.field];
const {status,riskText}=getKpiStatusWithRisk(val,kpi.target,kpi.field);
const delta=getDelta(val,kpi.target);
const card=document.createElement('div');
card.className=`kpi-card status-${status}`;
card.innerHTML=`
<div class="kpi-card-top"><span class="kpi-icon">${kpi.icon}</span><span class="kpi-status-badge">${statusLabel(status)}</span></div>
<div class="kpi-card-value">${fmtPct(val)}</div>
<div class="kpi-card-label">${kpi.name}</div>
<div class="kpi-card-meta"><span>Цель: ${kpi.targetLabel}</span><span class="kpi-delta ${val>=kpi.target?'positive':'negative'}">${delta}</span></div>
<div class="kpi-risk-row">${riskText}</div>
<canvas class="sparkline" id="spark-${kpi.id}" width="100" height="28"></canvas>`;
card.addEventListener('click',()=>document.getElementById(`section-kpi-${kpi.id}`).scrollIntoView({behavior:'smooth',block:'start'}));
bar.appendChild(card);
const sparkColor={green:'#10B981',orange:'#F97316',yellow:'#F59E0B',red:'#EF4444'}[status];
new Chart(document.getElementById(`spark-${kpi.id}`).getContext('2d'),{
type:'line',
data:{labels:AppState.snapshots.map(s=>s.report_period_id),datasets:[{data:AppState.snapshots.map(s=>+(s[kpi.field]*100).toFixed(2)),borderColor:sparkColor,borderWidth:2,pointRadius:0,tension:0.4}]},
options:{animation:false,plugins:{legend:{display:false},tooltip:{enabled:false}},scales:{x:{display:false},y:{display:false}},responsive:false}
});
});
}
function renderToolbar() {
const toolbar=document.getElementById('toolbar');
toolbar.innerHTML='<span class="toolbar-label">Период:</span>';
const all=document.createElement('button');
all.className='month-btn active'; all.textContent='Все месяцы'; all.dataset.period='all';
all.addEventListener('click',()=>applyFilter('all'));
toolbar.appendChild(all);
AppState.snapshots.forEach(s=>{
const b=document.createElement('button');
b.className='month-btn'; b.textContent=periodLabel(s.report_period_id); b.dataset.period=String(s.report_period_id);
b.addEventListener('click',()=>applyFilter(String(s.report_period_id)));
toolbar.appendChild(b);
});
}
function applyFilter(period) {
AppState.selectedPeriod=period==='all'?null:parseInt(period);
document.querySelectorAll('.month-btn').forEach(b=>b.classList.toggle('active',b.dataset.period===period));
destroyAllCharts(); renderAllCharts();
}
// ═══════════════════ CHART HELPERS ═══════════════════
function destroyAllCharts() {
Object.keys(charts).forEach(k=>{if(charts[k]){charts[k].destroy();delete charts[k];}});
}
function getSnapshots() {
return AppState.selectedPeriod
? AppState.snapshots.filter(s=>s.report_period_id<=AppState.selectedPeriod)
: AppState.snapshots;
}
function allMonthLabels() {
const yr=String(AppState.snapshots[0].report_period_id).slice(0,4);
return MONTH_NAMES.slice(1).map(m=>m+' '+yr);
}
/** Build forecast dataset for a chart using smart model */
function makeForecastDataset(field, color, yAxisID='y') {
const {values:fc} = smartForecast(AppState.snapshots, field, 12);
const n = AppState.snapshots.length;
const data = Array(12).fill(null);
for(let i=n;i<12;i++) data[i] = fc[i]!==null ? +(fc[i]*100).toFixed(2) : null;
if(n<12 && fc[n-1]!==null) data[n-1] = +(fc[n-1]*100).toFixed(2); // connect to last actual
return {label:'Прогноз', data, type:'line', borderColor:color||'rgba(99,102,241,0.55)', borderDash:[5,4], pointRadius:0, borderWidth:2, yAxisID, tension:0.3};
}
/** Show risk banner below chart; returns the December forecast value */
function showRiskBanner(id, field, target) {
const el = document.getElementById(`risk-${id}`);
if (!el) return;
const {values:fc, modelLabel} = smartForecast(AppState.snapshots, field, 12);
const cur = AppState.snapshots[AppState.snapshots.length-1][field];
const dec = fc[11];
if (dec===null) { el.classList.add('hidden'); return; }
if (dec < target) {
const curAbove = cur >= target;
el.innerHTML = `${curAbove?'Сейчас выше цели, но при':'При'} текущем тренде прогноз на декабрь: <b>${fmtPct(dec)}</b> `+
`(цель ${fmtPct(target)}) — риск недовыполнения KPI по итогам года. <i>Модель: ${modelLabel}.</i>`;
el.classList.remove('hidden');
} else {
el.classList.add('hidden');
}
}
// ═══════════════════ KPI 1 — REGISTRATIONS ═══════════════════
function renderChartRegistrations() {
const snaps=getSnapshots(), labels=allMonthLabels();
const actual=Array(12).fill(null), reg2=Array(12).fill(null);
snaps.forEach((s,i)=>{ actual[i]=+(s.registered_pct*100).toFixed(1); reg2[i]=s.registered_total; });
const ctx=document.getElementById('chart-registrations').getContext('2d');
charts['registrations']=new Chart(ctx,{
data:{labels,datasets:[
{label:'Доля регистраций (%)',data:actual,type:'bar',
backgroundColor:snaps.map(s=>s.registered_pct>=0.60?'rgba(16,185,129,0.75)':'rgba(59,130,246,0.75)').concat(Array(12-snaps.length).fill('rgba(59,130,246,0.15)')),borderRadius:6,yAxisID:'y'},
makeForecastDataset('registered_pct','rgba(59,130,246,0.5)'),
{label:'Цель (60%)',data:Array(12).fill(60),type:'line',borderColor:'#EF4444',borderDash:[6,3],pointRadius:0,borderWidth:2,yAxisID:'y'},
{label:'Зарег. пользователей',data:reg2,type:'line',borderColor:'#10B981',backgroundColor:'rgba(16,185,129,0.08)',pointRadius:4,pointBackgroundColor:'#10B981',borderWidth:2,yAxisID:'y2',tension:0.3},
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:12,usePointStyle:true}},
tooltip:{callbacks:{label:ctx=>ctx.dataset.label.includes('пользователей')?` ${ctx.dataset.label}: ${fmtInt(ctx.raw)}`:ctx.raw!==null?` ${ctx.dataset.label}: ${ctx.raw}%`:null}}},
scales:{y:{...axisRange([actual,Array(12).fill(60),makeForecastDataset('registered_pct','').data].flat(),{forceMin:0}),
title:{display:true,text:'% регистраций'},ticks:{callback:v=>v+'%'}},
y2:{position:'right',title:{display:true,text:'Кол-во пользователей'},grid:{drawOnChartArea:false},ticks:{callback:v=>fmtInt(v)}}}}
});
showRiskBanner('registrations','registered_pct',0.60);
}
// ═══════════════════ KPI 2 — MAU ═══════════════════
function renderChartMau() { AppState.mauTab==='daily'?renderChartMauDaily():renderChartMauMonthly(); }
function renderChartMauMonthly() {
const snaps=getSnapshots(), labels=allMonthLabels();
const actual=Array(12).fill(null);
snaps.forEach((s,i)=>{ actual[i]=+(s.mau_per_registered*100).toFixed(1); });
const ctx=document.getElementById('chart-mau').getContext('2d');
charts['mau']=new Chart(ctx,{
data:{labels,datasets:[
{label:'MAU / Зарег. (%)',data:actual,type:'bar',
backgroundColor:snaps.map(s=>s.mau_per_registered>=0.30?'rgba(16,185,129,0.75)':s.mau_per_registered>=0.255?'rgba(245,158,11,0.75)':'rgba(139,92,246,0.75)').concat(Array(12-snaps.length).fill('rgba(139,92,246,0.15)')),borderRadius:6,yAxisID:'y'},
makeForecastDataset('mau_per_registered','rgba(139,92,246,0.5)'),
{label:'Цель (30%)',data:Array(12).fill(30),type:'line',borderColor:'#EF4444',borderDash:[6,3],pointRadius:0,borderWidth:2,yAxisID:'y'},
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:12,usePointStyle:true}},
tooltip:{callbacks:{label:ctx=>ctx.raw!==null?` ${ctx.dataset.label}: ${ctx.raw}%`:null}}},
scales:{y:{...axisRange([actual,Array(12).fill(30),makeForecastDataset('mau_per_registered','').data].flat(),{forceMin:0}),
title:{display:true,text:'% MAU'},ticks:{callback:v=>v+'%'}}}}
});
showRiskBanner('mau','mau_per_registered',0.30);
}
function renderChartMauDaily() {
const pid=AppState.selectedPeriod||AppState.snapshots[AppState.snapshots.length-1].report_period_id;
const daily=AppState.dailySeries[pid]||[];
const ctx=document.getElementById('chart-mau').getContext('2d');
charts['mau']=new Chart(ctx,{type:'line',
data:{labels:daily.map(r=>r.entry_date.slice(5)),datasets:[{label:`MAU нарастающим итогом — ${periodLabel(pid)}`,data:daily.map(r=>r.mau_daily),borderColor:'#8B5CF6',backgroundColor:'rgba(139,92,246,0.1)',fill:true,tension:0.4,pointRadius:2,borderWidth:2}]},
options:{responsive:true,maintainAspectRatio:false,
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:12}},tooltip:{callbacks:{label:ctx=>` MAU: ${fmtInt(ctx.raw)}`}}},
scales:{y:{title:{display:true,text:'Уникальных пользователей'},ticks:{callback:v=>fmtInt(v)}},x:{title:{display:true,text:'Дата (MM-ДД)'}}}}
});
document.getElementById('risk-mau').classList.add('hidden');
}
function switchMauTab(tab) {
AppState.mauTab=tab;
document.getElementById('tab-mau-monthly').classList.toggle('active',tab==='monthly');
document.getElementById('tab-mau-daily').classList.toggle('active',tab==='daily');
if(charts['mau']){charts['mau'].destroy();delete charts['mau'];}
renderChartMau();
}
// ═══════════════════ KPI 3 — TRADITIONAL ═══════════════════
function renderChartTraditional() {
const snaps=getSnapshots(), labels=allMonthLabels();
const cur=[],prev=[],dec2=[];
Array(12).fill(0).forEach((_,i)=>{
const s=snaps[i];
cur.push(s?+(s.traditional_comms_pct*100).toFixed(2):null);
prev.push(s?+(s.prev_year_traditional_comms_pct*100).toFixed(2):null);
dec2.push(s?+(s.traditional_comms_decrease_pct*100).toFixed(2):null);
});
const ctx=document.getElementById('chart-traditional').getContext('2d');
charts['traditional']=new Chart(ctx,{
data:{labels,datasets:[
{label:'Традиц. обращения 2026 (%)',data:cur,type:'bar',backgroundColor:'rgba(239,68,68,0.65)',borderRadius:4,yAxisID:'y'},
{label:'Традиц. обращения 2025 (%)',data:prev,type:'bar',backgroundColor:'rgba(252,165,165,0.5)',borderRadius:4,yAxisID:'y'},
{label:'Снижение (%)',data:dec2,type:'line',borderColor:'#10B981',backgroundColor:'rgba(16,185,129,0.08)',pointRadius:5,pointBackgroundColor:'#10B981',borderWidth:2,yAxisID:'y2',tension:0.3},
{label:'Цель снижения (10%)',data:Array(12).fill(10),type:'line',borderColor:'#EF4444',borderDash:[6,3],pointRadius:0,borderWidth:2,yAxisID:'y2'},
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:10,usePointStyle:true}},
tooltip:{callbacks:{label:ctx=>ctx.raw!==null?` ${ctx.dataset.label}: ${ctx.raw}%`:null}}},
scales:{y:{...axisRange([cur,prev].flat(),{forceMin:0}),
title:{display:true,text:'% обращений'},ticks:{callback:v=>v+'%'}},
y2:{position:'right',...axisRange([dec2,Array(12).fill(10)].flat(),{forceMin:0}),
title:{display:true,text:'% снижения'},grid:{drawOnChartArea:false},ticks:{callback:v=>v+'%'}}}}
});
showRiskBanner('traditional','traditional_comms_decrease_pct',0.10);
}
// ═══════════════════ KPI 4 — DIGITAL SALES ═══════════════════
function renderChartDigitalSales() {
const snaps=getSnapshots(), labels=allMonthLabels();
const data=[],digital=[],total=[];
Array(12).fill(0).forEach((_,i)=>{
const s=snaps[i];
data.push(s?+(s.fd_rap_pct*100).toFixed(1):null);
digital.push(s?s.cumulative_digital_rap_total:null);
total.push(s?s.cumulative_rap_total:null);
});
const fc=makeForecastDataset('fd_rap_pct','rgba(16,185,129,0.4)');
fc.fill=false;
const ctx=document.getElementById('chart-digital-sales').getContext('2d');
charts['digital-sales']=new Chart(ctx,{
data:{labels,datasets:[
{label:'Доля цифровых продаж (%)',data,type:'line',borderColor:'#10B981',backgroundColor:'rgba(16,185,129,0.12)',fill:true,tension:0.4,pointRadius:5,pointBackgroundColor:'#10B981',borderWidth:2.5,yAxisID:'y'},
fc,
{label:'Цель (45%)',data:Array(12).fill(45),type:'line',borderColor:'#EF4444',borderDash:[6,3],pointRadius:0,borderWidth:2,yAxisID:'y'},
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:12,usePointStyle:true}},
tooltip:{callbacks:{label:ctx=>ctx.raw!==null?` ${ctx.dataset.label}: ${ctx.raw}%`:null,
afterBody:items=>{ const i=items[0].dataIndex; return digital[i]!==null?[` Цифр.: ${fmtMoney(digital[i])}`,' Все: '+fmtMoney(total[i])]:[];} }}},
scales:{y:{...axisRange([data,fc.data,Array(12).fill(45)].flat(),{forceMin:0,forceMax:100}),
title:{display:true,text:'% цифровых продаж'},ticks:{callback:v=>v+'%'}}}}
});
showRiskBanner('digital-sales','fd_rap_pct',0.45);
}
// ═══════════════════ KPI 5 — FD ORDERS ═══════════════════
function renderChartFdOrders() {
const snaps=getSnapshots(), labels=allMonthLabels();
const orders=[],goals=[],cumPct=[];
Array(12).fill(0).forEach((_,i)=>{
const s=snaps[i];
orders.push(s?s.fd_orders:null);
goals.push(s?s.fd_orders_goal:null);
cumPct.push(s?+(s.cum_fd_orders_pct*100).toFixed(1):null);
});
// ── Trend line for fd_orders (monthly count) ──
const orderXs = snaps.map((_,i)=>i);
const orderYs = snaps.map(s=>s.fd_orders);
const {slope:ordSlope, intercept:ordIntercept} = linearRegression(orderXs, orderYs);
const orderTrend = Array(12).fill(null);
for(let m=0;m<12;m++) orderTrend[m] = Math.max(0, ordIntercept + ordSlope*m);
// Average monthly orders
const avg = orderYs.reduce((a,b)=>a+b,0)/orderYs.length;
const ctx=document.getElementById('chart-fd-orders').getContext('2d');
charts['fd-orders']=new Chart(ctx,{
data:{labels,datasets:[
{label:'FD заказы (цель)',data:goals,type:'bar',backgroundColor:'rgba(99,102,241,0.18)',borderColor:'rgba(99,102,241,0.4)',borderWidth:1,borderRadius:6,yAxisID:'y'},
{label:'FD заказы (факт)',data:orders,type:'bar',
backgroundColor:snaps.map(s=>s.fd_orders>=s.fd_orders_goal?'rgba(16,185,129,0.75)':s.fd_orders>=s.fd_orders_goal*0.85?'rgba(245,158,11,0.75)':'rgba(99,102,241,0.75)').concat(Array(12-snaps.length).fill('rgba(99,102,241,0.15)')),borderRadius:6,yAxisID:'y'},
// Trend line for monthly orders
{label:'Тренд заказов',data:orderTrend,type:'line',borderColor:'#F97316',borderDash:[4,3],pointRadius:0,borderWidth:2,yAxisID:'y',tension:0.3},
// Average line
{label:`Среднее (${fmtInt(avg)} шт./мес)`,data:Array(12).fill(+avg.toFixed(0)),type:'line',borderColor:'#8B5CF6',borderDash:[6,2],pointRadius:0,borderWidth:1.5,yAxisID:'y'},
// Cumulative %
{label:'Накопл. выполнение (%)',data:cumPct,type:'line',borderColor:'#F59E0B',backgroundColor:'rgba(245,158,11,0.08)',pointRadius:5,pointBackgroundColor:'#F59E0B',borderWidth:2.5,yAxisID:'y2',tension:0.3},
]},
options:{responsive:true,maintainAspectRatio:false,interaction:{mode:'index',intersect:false},
plugins:{legend:{position:'bottom',labels:{boxWidth:12,padding:10,usePointStyle:true}},
tooltip:{callbacks:{label:ctx=>{
if(ctx.raw===null)return null;
return ctx.dataset.yAxisID==='y2'?` ${ctx.dataset.label}: ${ctx.raw}%`:` ${ctx.dataset.label}: ${fmtInt(ctx.raw)} шт.`;
}}}},
scales:{y:{min:0,title:{display:true,text:'Заказы (шт.)'},ticks:{callback:v=>fmtInt(v)}},
y2:{position:'right',min:0,max:100,title:{display:true,text:'% выполнения (кум.)'},grid:{drawOnChartArea:false},ticks:{callback:v=>v+'%'}}}}
});
// Progress bar
const last=AppState.snapshots[AppState.snapshots.length-1];
const cumGoal=last.cum_fd_orders_goal||0, cumOrd=last.cum_fd_orders||0;
const pct=cumGoal>0?cumOrd/cumGoal:0;
document.getElementById('cum-fd-text').textContent=`${fmtInt(cumOrd)} из ${fmtInt(cumGoal)}`;
document.getElementById('cum-fd-pct').textContent=fmtPct(pct);
document.getElementById('cum-fd-bar').style.width=Math.min(pct*100,100)+'%';
document.getElementById('fd-annotation').classList.toggle('hidden',pct>=0.5);
// Trend info box
const decForecast = Math.max(0, ordIntercept + ordSlope*11);
const annualByTrend = orderTrend.slice(0,12).reduce((s,v)=>s+(v||0),0);
const tdEl = document.getElementById('fd-trend-info');
tdEl.innerHTML = `📈 <b>Анализ тренда:</b> среднее ${fmtInt(avg)} шт./мес · `+
`тренд к декабрю: ~${fmtInt(decForecast)} шт./мес · `+
`прогноз на год по тренду: ~${fmtInt(annualByTrend)} шт. `+
`<span style="color:var(--color-text-secondary)">(при текущей цели ${fmtInt(cumGoal)} шт.)</span>`;
tdEl.classList.remove('hidden');
}
function renderAllCharts() {
renderChartRegistrations();
renderChartMau();
renderChartTraditional();
renderChartDigitalSales();
renderChartFdOrders();
}
// ═══════════════════ AI KEY ═══════════════════
function initAiKey() {
const input=document.getElementById('ai-api-key'), note=document.getElementById('ai-key-note');
if(window.DS_KEY&&window.DS_KEY.trim()){
input.value=window.DS_KEY.trim(); input.disabled=true; note.classList.remove('hidden');
}
}
// ═══════════════════ AI — DeepSeek ═══════════════════
function buildPrompt(snapshots) {
// ── Full monthly snapshot data (all columns) ──────────────────────────────
const rawTable = snapshots.map(s => [
periodLabel(s.report_period_id),
`абонентов: ${fmtInt(s.abons)}`,
`зарег.: ${fmtInt(s.registered_total)} (${fmtPct(s.registered_pct)})`,
`MAU: ${fmtInt(s.mau_daily)} (${fmtPct(s.mau_per_registered)} от зарег.)`,
`традиц.обр.тек.год: ${fmtPct(s.traditional_comms_pct)}`,
`традиц.обр.пр.год: ${fmtPct(s.prev_year_traditional_comms_pct)}`,
`снижение обр.: ${fmtPct(s.traditional_comms_decrease_pct)}`,
`цифр.выручка: ${fmtMoney(s.cumulative_digital_rap_total)}`,
`общ.выручка: ${fmtMoney(s.cumulative_rap_total)}`,
`доля цифр.прод.: ${fmtPct(s.fd_rap_pct)}`,
`FD мес.: ${s.fd_orders}шт. из ${s.fd_orders_goal} (${fmtPct(s.fd_orders_pct)})`,
`FD накопл.: ${fmtInt(s.cum_fd_orders)}шт. из ${fmtInt(s.cum_fd_orders_goal)} (${fmtPct(s.cum_fd_orders_pct)})`,
].join(' | ')).join('\n');
// ── Daily MAU summary per month (first / mid / last day) ─────────────────
const mauSummary = Object.entries(AppState.dailySeries).sort().map(([pid, days]) => {
const first = days[0], mid = days[Math.floor(days.length/2)], last = days[days.length-1];
return ` ${periodLabel(Number(pid))}: день1=${fmtInt(first.mau_daily)}, сер.мес.=${fmtInt(mid.mau_daily)}, конец=${fmtInt(last.mau_daily)}`;
}).join('\n');
const rows=snapshots.map(s=>`${periodLabel(s.report_period_id)}: `+
`Рег=${fmtPct(s.registered_pct)}, MAU=${fmtPct(s.mau_per_registered)}, `+
`Снижение обр.=${fmtPct(s.traditional_comms_decrease_pct)}, `+
`Цифр.прод.=${fmtPct(s.fd_rap_pct)}, `+
`FD кум.=${fmtInt(s.cum_fd_orders)}шт.(${fmtPct(s.cum_fd_orders_pct)}), FD мес.=${s.fd_orders}шт.`
).join('\n');
// Smart forecasts for December
const forecastLines=KPI_CONFIG.map(kpi=>{
const {values:fc, modelLabel}=smartForecast(snapshots, kpi.field, 12);
return fc[11]!==null?` ${kpi.name}: ${fmtPct(fc[11])} (${modelLabel})`:null;
}).filter(Boolean).join('\n');
// FD orders trend info
const orderYs=snapshots.map(s=>s.fd_orders);
const {slope:os,intercept:oi}=linearRegression(snapshots.map((_,i)=>i),orderYs);
const avgOrders=orderYs.reduce((a,b)=>a+b,0)/orderYs.length;
const decFdOrders=Math.max(0,oi+os*11);
const annualFdByTrend=Array.from({length:12},(_,m)=>Math.max(0,oi+os*m)).reduce((a,b)=>a+b,0);
return `Ты аналитик KPI отдела цифровизации клиентских путей Казахтелеком.
══ ПОЛНЫЕ ДАННЫЕ ПО МЕСЯЦАМ (все показатели) ══
${rawTable}
══ ЕЖЕДНЕВНЫЙ РОСТ MAU ВНУТРИ МЕСЯЦА (начало / середина / конец) ══
${mauSummary}
══ СВОДКА KPI ПО МЕСЯЦАМ ══
${rows}
Прогноз на декабрь 2026 по каждому KPI:
${forecastLines}
Анализ KPI 5 (Full Digital установки):
Среднее за период: ${fmtInt(avgOrders)} шт./мес
Тренд к декабрю: ~${fmtInt(decFdOrders)} шт./мес
Прогноз суммарно за год по тренду: ~${fmtInt(annualFdByTrend)} шт.
Текущая годовая цель: 12 000 шт.
Целевые показатели:
- Регистрации: 60%
- MAU: 30%
- Снижение традиционных обращений: 10%
- Доля цифровых продаж: 45%
- Full Digital установки: 1 000/мес (12 000 за год)
Примечание по KPI 4 (Цифровые продажи): тренд нелинейный — снижение замедляется (объясняется природой данных: цифровые каналы содержат разовые платежи + подписки, традиционные — только подписки; к концу года ожидается стабилизация доли).
Задача: проанализируй выполнение KPI с учётом прогноза на конец года. Даже выполненные сейчас KPI оцени с точки зрения риска до декабря.
Ответ строго на русском языке. Структура:
1. Краткий вывод (2-3 предложения)
2. KPI в норме (с оценкой устойчивости тренда до конца года)
3. Рисковые и отстающие KPI — причины, прогноз, риски
4. Конкретные рекомендации (3-5 пунктов)
5. Предложение по пересмотру цели KPI 5 (Full Digital) на основе тренда`;
}
async function runAiAnalysis() {
const input=document.getElementById('ai-api-key');
const apiKey=(window.DS_KEY||input.value||'').trim();
if(!apiKey){showAiError('Введите DeepSeek API ключ');return;}
const btn=document.getElementById('btn-ai-analyze');
btn.disabled=true; showAiLoading(true); showAiError('');
document.getElementById('ai-result').classList.add('hidden');
const controller=new AbortController();
const t=setTimeout(()=>controller.abort(),25000);
try {
const resp=await fetch('https://api.deepseek.com/chat/completions',{
method:'POST', signal:controller.signal,
headers:{'Content-Type':'application/json','Authorization':'Bearer '+apiKey},
body:JSON.stringify({
model:'deepseek-chat', max_tokens:1800,
messages:[
{role:'system',content:'Ты опытный аналитик данных. Отвечай структурированно, конкретно, на русском языке.'},
{role:'user', content:buildPrompt(AppState.snapshots)},
],
}),
});
clearTimeout(t);
if(!resp.ok){
let m=`HTTP ${resp.status}`;
try{const e=await resp.json();m=e.error?.message||m;}catch{}
if(resp.status===401)m='Неверный API ключ';
throw new Error(m);
}
const data=await resp.json();
const text=data.choices?.[0]?.message?.content||'';
AppState.lastAiResponse=text;
sessionStorage.setItem('lastAiResponse',text);
showAiResult(text);
} catch(e) {
clearTimeout(t);
showAiError(e.name==='AbortError'?'Превышено время ожидания. Попробуйте снова.':'Ошибка: '+e.message);
} finally { showAiLoading(false); btn.disabled=false; }
}
function showAiLoading(show){document.getElementById('ai-loading').classList.toggle('hidden',!show);}
function showAiResult(text){document.getElementById('ai-text').textContent=text;document.getElementById('ai-result').classList.remove('hidden');}
function showAiError(msg){const el=document.getElementById('ai-error');if(msg){el.textContent=msg;el.classList.remove('hidden');}else el.classList.add('hidden');}
function restoreAiCache(){const c=sessionStorage.getItem('lastAiResponse');if(c)showAiResult(c);}
// ═══════════════════ EVENTS ═══════════════════
document.getElementById('file-input').addEventListener('change',function(e){
const file=e.target.files[0]; if(!file)return;
const reader=new FileReader();
reader.onload=ev=>{try{initWithData(ev.target.result);showDashboard();}catch(err){document.getElementById('upload-error').textContent='Ошибка: '+err.message;}};
reader.readAsText(file,'UTF-8'); this.value='';
});
document.getElementById('btn-export-csv').addEventListener('click',()=>{
if(!AppState.rawCsvText)return;
const blob=new Blob([AppState.rawCsvText],{type:'text/csv;charset=utf-8;'});
const url=URL.createObjectURL(blob);
const a=document.createElement('a'); a.href=url; a.download=CSV_FILENAME; a.click(); URL.revokeObjectURL(url);
});
document.getElementById('btn-reload').addEventListener('click',async()=>{
destroyAllCharts();
document.getElementById('dashboard').style.display='none';
document.getElementById('loading-screen').style.display='flex';
Object.assign(AppState,{rawRows:[],snapshots:[],dailySeries:{},selectedPeriod:null,rawCsvText:'',lastAiResponse:''});
await tryAutoLoad();
});
document.getElementById('btn-ai-analyze').addEventListener('click',runAiAnalysis);
document.getElementById('btn-ai-copy').addEventListener('click',()=>{
if(!AppState.lastAiResponse)return;
navigator.clipboard.writeText(AppState.lastAiResponse).then(()=>{
const b=document.getElementById('btn-ai-copy'); b.textContent='✅ Скопировано!';
setTimeout(()=>b.textContent='📋 Скопировать',2000);
});
});
// ═══════════════════ BOOT ═══════════════════
document.addEventListener('DOMContentLoaded', tryAutoLoad);
</script>
</body>
</html>