// Данные дашборда: 17 регионов Казахстана, полигоны карты, // генерация стабильных показателей за 12 месяцев. var KT_MONTHS = ['Январь','Февраль','Март','Апрель','Май','Июнь','Июль','Август','Сентябрь','Октябрь','Ноябрь','Декабрь']; // 17 регионов: id, полное имя, короткая подпись, центр подписи на карте var KT_REGIONS = [ { id: 'kso', name: 'Костанайская обл.', short: 'Костанайская', cx: 320, cy: 68 }, { id: 'sko', name: 'Сев.-Казахстанская обл.', short: 'Сев.-Казах.', cx: 365, cy: 132 }, { id: 'kgo', name: 'Павлодарская обл.', short: 'Павлодарская', cx: 490, cy: 88 }, { id: 'oko', name: 'Восточно-Казахстанская обл.', short: 'Вост.-Казах.', cx: 575, cy: 184 }, { id: 'wko', name: 'Атырауская обл.', short: 'Атырауская', cx: 120, cy: 214 }, { id: 'zko', name: 'Зап.-Казахстанская обл.', short: 'Зап.-Казах.', cx: 55, cy: 292 }, { id: 'ako', name: 'Актюбинская обл.', short: 'Актюбинская', cx: 152, cy: 324 }, { id: 'kko', name: 'Мангистауская обл.', short: 'Мангистау', cx: 46, cy: 146 }, { id: 'kzo', name: 'Карагандинская обл.', short: 'Караганда', cx: 305, cy: 274 }, { id: 'ulytao',name:'Улытауская обл.', short: 'Улытау', cx: 358, cy: 248 }, { id: 'ast', name: 'Астана', short: 'Астана', cx: 398, cy: 182 }, { id: 'zho', name: 'Жамбылская обл.', short: 'Жамбылская', cx: 215, cy: 262 }, { id: 'tgo', name: 'Туркестанская обл.', short: 'Туркестанская', cx: 270, cy: 342 }, { id: 'alo', name: 'Алматинская обл.', short: 'Алматинская', cx: 150, cy: 368 }, { id: 'alm', name: 'Алматы', short: 'Алматы', cx: 105, cy: 382 }, { id: 'shym', name: 'Шымкент', short: 'Шымкент', cx: 315, cy: 378 }, { id: 'symk', name: 'Южно-Казахстанская обл.', short: 'Южно-Казах.', cx: 330, cy: 340 } ]; // Полигоны карты (стилизованное упрощение, viewBox 0 0 1000 420). // Грани соседних регионов общие — карта читается как единая территория. var KT_MAP = { kso: [[275,35],[400,30],[460,55],[450,95],[400,90],[280,95]], sko: [[280,95],[400,90],[430,140],[390,175],[300,168],[285,135]], kgo: [[460,55],[545,60],[590,105],[565,142],[500,135],[450,95]], oko: [[430,140],[540,120],[650,150],[680,210],[610,250],[520,235],[470,215],[430,175]], wko: [[60,170],[150,160],[185,205],[185,258],[110,262],[20,270],[20,200]], zko: [[20,270],[110,262],[140,298],[110,330],[55,318],[20,300]], ako: [[110,262],[185,258],[205,295],[175,335],[110,330],[140,298]], kko: [[15,100],[95,90],[130,132],[95,170],[60,170],[20,200],[15,150]], kzo: [[185,258],[265,250],[300,285],[275,322],[205,295]], ulytao: [[320,238],[390,230],[405,262],[375,285],[345,270],[300,285]], ast: [[375,160],[412,155],[418,182],[390,196]], zho: [[150,246],[215,240],[245,268],[215,300],[205,295],[185,258]], tgo: [[205,295],[275,322],[300,305],[300,340],[255,352],[215,352]], alo: [[120,350],[175,335],[195,362],[155,390],[110,378]], alm: [[82,362],[120,350],[110,378],[90,390]], symk: [[300,305],[340,318],[345,355],[300,340],[300,340]], shym: [[305,356],[335,352],[345,375],[315,384],[298,372]] }; // Сезонный профиль года (доли): декабрь пик, февраль спад var KT_SEASON = [0.82, 0.74, 0.95, 1.0, 1.05, 1.0, 0.9, 0.95, 1.1, 1.15, 1.2, 1.38]; // Базовые планы, млн ₸ — крупные регионы больше var KT_PLAN_BASE = { ast: 680, alm: 640, symk: 340, kzo: 320, wko: 250, kko: 230, ako: 220, kso: 210, oko: 200, zko: 190, tgo: 185, kgo: 170, zho: 170, sko: 160, alo: 150, ulytao: 110, shym: 260 }; // Склонность региона к выполнению плана (дефолт 1.0) var KT_BIAS = { ast: 1.12, alm: 1.1, shym: 1.05, symk: 1.0, kko: 1.04, kzo: 1.0, wko: 0.92, ako: 1.02, kso: 1.08, sko: 0.88, kgo: 0.95, oko: 0.9, zko: 1.05, tgo: 0.95, zho: 0.94, alo: 0.97, ulytao: 0.9 }; function ktHash(str) { var h = 2166136261; for (var i = 0; i < str.length; i++) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } return h >>> 0; } // Детерминированный псевдослучайный 0..1 по строке function ktRand(seed) { var x = ktHash(seed); x ^= x << 13; x >>>= 0; x ^= x >> 17; x ^= x << 5; x >>>= 0; return x / 4294967296; } function ktMonthLabel(offset) { var now = new Date(); var m = new Date(now.getFullYear(), now.getMonth() - offset, 1); return KT_MONTHS[m.getMonth()] + ' ' + m.getFullYear(); } // Данные региона за месяц (offset от текущего) function ktRegionMonth(rid, offset) { var r = null; for (var i = 0; i < KT_REGIONS.length; i++) { if (KT_REGIONS[i].id === rid) { r = KT_REGIONS[i]; break; } } var planBase = KT_PLAN_BASE[rid]; if (!planBase) planBase = 150; var now = new Date(); var m = new Date(now.getFullYear(), now.getMonth() - offset, 1); var monIdx = m.getMonth(); var plan = Math.round(planBase * (0.9 + ktRand('plan_' + rid + '_' + m.getFullYear() + '_' + monIdx) * 0.2) * 10) / 10; var bias = KT_BIAS[rid] || 1; var seed = 'fact_' + rid + '_' + m.getFullYear() + '_' + monIdx + '_' + (window.KT_SEED || 'base'); var noise = 0.88 + ktRand(seed) * 0.3; var fact = Math.round(plan * bias * KT_SEASON[monIdx] * noise * 10) / 10; var percent = Math.round((fact / plan) * 100); var deals = Math.floor(18 + ktRand('deals_' + seed) * 120 * (KT_SEASON[monIdx] - 0.4)); if (deals < 5) deals = 5; var tone = 'risk'; if (percent >= 100) tone = 'ok'; else if (percent >= 90) tone = 'warn'; return { id: rid, name: r ? r.name : rid, short: r ? r.short : rid, cx: r ? r.cx : 0, cy: r ? r.cy : 0, plan: plan, fact: fact, percent: percent, deals: deals, tone: tone }; } // Свод месяца: { offset, label, rows, kpi } function ktMonthData(offset) { var rows = []; for (var i = 0; i < KT_REGIONS.length; i++) { rows.push(ktRegionMonth(KT_REGIONS[i].id, offset)); } rows.sort(function(a, b) { return b.fact - a.fact; }); var planSum = 0, factSum = 0, dealsSum = 0; for (var j = 0; j < rows.length; j++) { planSum += rows[j].plan; factSum += rows[j].fact; dealsSum += rows[j].deals; } var top5 = {}; for (var t = 0; t < Math.min(5, rows.length); t++) top5[rows[t].id] = true; var leaders = 0; for (var l = 0; l < rows.length; l++) { if (top5[rows[l].id] && rows[l].tone === 'ok') leaders++; } return { offset: offset, label: ktMonthLabel(offset), rows: rows, kpi: { plan: Math.round(planSum), fact: Math.round(factSum), percent: Math.round((factSum / planSum) * 100), deals: dealsSum, leaders: leaders } }; } // Форматирование: m в млн ₸ function ktFormatMillion(m) { if (m >= 1000) { var v = Math.round(m / 10) / 100; return v.toFixed(2).replace('.', ',') + ' млрд'; } return Math.round(m) + ' млн'; } function ktFormatMoney(m) { if (m >= 1000) { var v = Math.round(m / 10) / 100; return v.toFixed(2).replace('.', ',') + ' млрд ₸'; } return Math.round(m) + ' млн ₸'; }