/* КФУ — финансовая устойчивость. Статическое приложение, весь анализ — в браузере. */ (function () { "use strict"; var GROUP_NAME = "Консолидированная группа 1"; var EBI_ROWS = [[26, 1], [27, 1], [28, 1], [31, 1], [32, 1], [40, 1], [41, 1], [45, -1], [56, -1], [64, -1]]; var EBI_ROWS_TXT = "26+27+28+31+32+40+41−45−56−64"; var S = { source: null, sheetNames: [], diag: [], entries: [], isMock: false, sort: { k: "name", d: 1 }, q: "", zone: "", norm: "", onlyRisk: false, scen: {} }; /* ---------- утилиты ---------- */ function $(s) { return document.querySelector(s); } function normName(s) { if (s == null) return ""; return String(s).replace(/[«»„“”]/g, '"').replace(/[’‘]/g, "'").replace(/[–—−]/g, "-") .replace(/\s+/g, " ").trim().toLowerCase(); } function colOfLetter(s) { s = String(s).toUpperCase().replace(/\$/g, "").replace(/\d+$/, "").trim(); var n = 0; for (var i = 0; i < s.length; i++) n = n * 26 + (s.charCodeAt(i) - 64); return n; } function toNum(v) { if (typeof v === "number") return isFinite(v) ? v : null; if (typeof v === "string") { var t = v.replace(/\s|,/g, ""); if (t === "" ) return null; var n = parseFloat(t); return isFinite(n) ? n : null; } return null; } function fmtMoney(v) { if (v == null) return 'нет данных'; return nfmt(v, 1); } function fmtR(v) { if (v == null) return 'нет данных'; if (v === Infinity) return '∞'; return nfmt(v, 2); } /* для отображения: EBITDA ≤ 0 вместо «нет данных» у К1/К2 */ function coefDisplay(e, key) { var v = e[key]; if (v == null && e.why && e.why[key] && /EBITDA/i.test(e.why[key]) && e.ebitda != null && e.ebitda <= 0) { return "EBITDA ≤ 0"; } return typeof v === "number" ? nfmt(v, 2) : fmtR(v); } var _nfCache = {}; function nfmt(v, d) { d = d == null ? 2 : d; if (!_nfCache[d]) { _nfCache[d] = new Intl.NumberFormat("ru-RU", { minimumFractionDigits: d, maximumFractionDigits: d, useGrouping: true }); } return _nfCache[d].format(v).replace(/-\s?/g, "−").replace(/-/g, "−"); } function fmtC(v) { if (v == null) return "—"; if (typeof v === "number") return nfmt(Number.isInteger(v) ? v : Math.round(v * 1e6) / 1e6, 6); return String(v); } function pct(v) { return nfmt(v, 4); } function esc(s) { return String(s).replace(/&/g, "&").replace(//g, ">").replace(/"/g, """); } function toast(msg, isErr) { var el = document.createElement("div"); el.className = "kfu-toast" + (isErr ? " err" : ""); el.textContent = msg; $("#toasts").appendChild(el); setTimeout(function () { el.remove(); }, 4200); } /* ---------- извлечение ячеек из листа (SheetJS: Sheets[name] — объект A1→cell) ---------- */ function getCell(wb, sheet, r, c) { var ws = wb.Sheets[sheet]; if (!ws) return null; var addr = XLSX.utils.encode_cell({ r: r - 1, c: c - 1 }); return ws[addr] || null; } function cellNum(wb, sheet, r, c) { var cell = getCell(wb, sheet, r, c); if (cell == null) return null; if (cell.t === "e") return null; /* #DIV/0! и т.п. — не число */ var n = toNum(cell.v != null ? cell.v : cell.w); return n; } function cellStr(wb, sheet, r, c) { var cell = getCell(wb, sheet, r, c); if (cell == null) return null; var s = cell.w != null ? cell.w : cell.v; s = String(s == null ? "" : s).trim(); return s || null; } function findSheet(names, subs) { var low = names.map(function (n) { return n.toLowerCase(); }); for (var i = 0; i < subs.length; i++) { for (var j = 0; j < low.length; j++) { if (low[j].indexOf(subs[i]) !== -1) return names[j]; } } return null; } /* ---------- чтение одного объекта (строка с именами + данные) ---------- */ function readEntry(wb, name, isGroup, maps) { var d = { name: name, group: isGroup, debt: null, other: null, ebitda: null, intExp: 0, equity: null, addr: {}, issues: [] }; var colL = XLSX.utils.encode_col; var normN = normName(name); if (maps.dol) { var c = isGroup ? GROUP_COL.dol : maps.dol[normN]; var v = c ? cellNum(wb, maps.dolSheet, 24, c) : null; d.debt = v == null ? null : v / -1000; d.addr.debt = colL(c - 1) + "24 / −1000"; d.addr.debtSheet = maps.dolSheet; if (v == null && !isGroup) d.issues.push("долг: пустая ячейка " + colL(c - 1) + "24 (обнулять нельзя)"); } else if (!isGroup) { d.issues.push("долг: компания не найдена в листе долга"); } if (maps.grp) { var gc = isGroup ? null : maps.grp[normN]; if (isGroup) { var s9 = 0, has9 = false; for (var cs = 3; cs <= 17; cs++) { var v9 = cellNum(wb, maps.grpSheet, 9, cs); if (v9 != null) { s9 += v9; has9 = true; } } /* группа: при любом отсутствующем C9:Q9 — null (долг+прочие считается null) */ d.other = has9 ? s9 * 1000 : null; d.addr.other = "C9:Q9 · млрд ₸ × 1000"; if (!has9) d.issues.push("прочие/гарантии: весь диапазон C9:Q9 пуст — долг+прочие недоступен"); } else if (gc) { var gv = cellNum(wb, maps.grpSheet, 9, gc); d.other = gv == null ? null : gv * 1000; d.addr.other = colL(gc - 1) + "9 · млрд ₸ × 1000"; if (gv == null) d.issues.push("прочие/гарантии: пустая ячейка — показатель отсутствует"); } else { d.other = null; d.issues.push("прочие/гарантии: компания не найдена в листе группы — показатель отсутствует"); } d.addr.otherSheet = maps.grpSheet; } else if (!isGroup) { d.other = null; d.issues.push("прочие/гарантии: лист группы не найден — показатель отсутствует"); } if (maps.ebi) { var ec = isGroup ? GROUP_COL.ebi : maps.ebi[normN]; /* итоговая EBITDA (row 83) — проверка наличия; само значение = сумма слагаемых */ var tot = ec ? cellNum(wb, maps.ebiSheet, 83, ec) : null; if (tot == null) { d.ebitda = null; d.issues.push("EBITDA: итог (row 83) пуст — расчёт заблокирован"); } else { var parts = []; var badEbi = null; for (var i = 0; i < EBI_ROWS.length; i++) { var rr = EBI_ROWS[i][0], sg = EBI_ROWS[i][1]; var pv = ec ? cellNum(wb, maps.ebiSheet, rr, ec) : null; if (pv == null) { var raw = getCell(wb, maps.ebiSheet, rr, ec); if (raw && raw.t === "e") { badEbi = rr; break; } /* пустое слагаемое — 0 */ } else parts.push(sg * pv); } if (badEbi != null) { d.ebitda = null; d.issues.push("EBITDA: ошибка в слагаемой строке " + badEbi + " — расчёт заблокирован"); } else d.ebitda = parts.length ? parts.reduce(function (a, b) { return a + b; }, 0) / -1000 : null; } /* проценты: отсутствуют = отсутствием остаются (не 0), делится на 1000 */ var iv = ec ? cellNum(wb, maps.ebiSheet, 43, ec) : null; d.intExp = iv == null ? null : iv / 1000; d.addr.ebitda = colL(ec - 1) + " стр." + EBI_ROWS_TXT + " / −1000 (итог row83)"; d.addr.ebitdaSheet = maps.ebiSheet; d.addr.intexp = colL(ec - 1) + "43 / 1000"; d.addr.intexpSheet = maps.ebiSheet; if (iv == null) d.issues.push("проценты: ячейка 43 пуста — показатель отсутствует (не заменяется нулём)"); } else if (!isGroup) { d.issues.push("EBITDA: компания не найдена в листе EBITDA"); } if (maps.bs) { var bc = isGroup ? GROUP_COL.bs : maps.bs[normN]; var bv = bc ? cellNum(wb, maps.bsSheet, 74, bc) : null; d.equity = bv == null ? null : bv / -1000; d.addr.equity = colL(bc - 1) + "74 / −1000"; d.addr.equitySheet = maps.bsSheet; if (bv == null) d.issues.push("капитал: пустая ячейка (обнулять нельзя)"); } else if (!isGroup) { d.issues.push("капитал: компания не найдена в балансе"); } return d; } /* ---------- парсер книги ---------- */ /* собственный столбец консолидированной группы (ищем по имени из строки имён) */ var GROUP_COL = { dol: null, ebi: null, bs: null }; function analyzeWorkbook(wb, opts) { opts = opts || {}; var names = wb.SheetNames; var out = { diag: [], diagErrors: [], entries: [], sheetNames: names }; /* диагностика: все ячейки с cell.t === "e" по всем листам */ for (var si0 = 0; si0 < names.length; si0++) { var ws0 = wb.Sheets[names[si0]]; if (!ws0) continue; var range = XLSX.utils.decode_range(ws0["!ref"] || "A1"); for (var r0 = range.s.r; r0 <= range.e.r; r0++) { for (var c0 = range.s.c; c0 <= range.e.c; c0++) { var addr0 = XLSX.utils.encode_cell({ r: r0, c: c0 }); var cell0 = ws0[addr0]; if (cell0 && cell0.t === "e") { /* cell.v — числовой код ошибки (23=REF, 7=VALUE); показываем как текст */ var codeMap = { 5: "#NAME?", 7: "#VALUE!", 15: "#DIV/0!", 23: "#REF!", 24: "#N/A", 27: "#NULL!", 29: "#NUM!" }; var txt = cell0.w != null ? cell0.w : (codeMap[cell0.v] != null ? codeMap[cell0.v] : "#" + cell0.v); out.diagErrors.push({ sheet: names[si0], addr: addr0, val: txt }); } } } } function diagErr(wb, sheet, r, c, field) { var addr = XLSX.utils.encode_cell({ r: r - 1, c: c - 1 }); var cell = getCell(wb, sheet, r, c); var val; if (cell == null) val = "пусто"; else if (cell.t === "e") val = "ошибка " + cell.v; else val = String(cell.w != null ? cell.w : cell.v); out.diag.push("«" + sheet + "» " + addr + " (" + field + "): " + val); } var dolSheet = opts.sheetOverrides ? opts.sheetOverrides.dol : findSheet(names, ["долг 4q2025_24042026", "долг"]); var ebiSheet = opts.sheetOverrides ? opts.sheetOverrides.ebi : findSheet(names, ["ebitda 2025_06042026", "ebitda"]); var bsSheet = opts.sheetOverrides ? opts.sheetOverrides.bs : findSheet(names, ["bs 4q2025_06042026", "bs"]); var grpSheet = opts.sheetOverrides ? opts.sheetOverrides.grp : findSheet(names, ["группа фонда", "группа"]); if (!dolSheet) out.diag.push("не найден лист долга — долг недоступен"); if (!ebiSheet) out.diag.push("не найден лист EBITDA"); if (!bsSheet) out.diag.push("не найден лист баланса BS"); if (!grpSheet) out.diag.push("не найден лист «Группа Фонда» — прочие/гарантии и нормативы недоступны"); function findGroupCol(sheet, nr, c1, c2) { if (!sheet) return null; for (var c = c1; c <= c2; c++) { var v = cellStr(wb, sheet, nr, c); if (v && normName(v) === normName(GROUP_NAME)) return c; } return null; } GROUP_COL.dol = findGroupCol(dolSheet, 22, 21, 39); GROUP_COL.ebi = findGroupCol(ebiSheet, 22, 21, 39); GROUP_COL.bs = findGroupCol(bsSheet, 21, 15, 40); if (!GROUP_COL.dol) out.diag.push("лист долга: столбец «" + GROUP_NAME + "» не найден — долг группы недоступен"); if (!GROUP_COL.ebi) out.diag.push("лист EBITDA: столбец «" + GROUP_NAME + "» не найден"); if (!GROUP_COL.bs) out.diag.push("лист BS: столбец «" + GROUP_NAME + "» не найден"); var maps = {}; function readNames(wb, sheet, r, c1, c2) { var m = {}, order = []; for (var c = c1; c <= c2; c++) { var v = cellStr(wb, sheet, r, c); if (v) { var k = normName(v); if (!m[k]) { m[k] = c; order.push(k); } } } return { map: m, order: order }; } var planSheet = opts.sheetOverrides ? opts.sheetOverrides.plan : findSheet(names, ["план 2026", "план"]); var planNames = []; if (planSheet) { for (var r = 5; r <= 20; r++) { var nm = cellStr(wb, planSheet, r, 1); if (nm) planNames.push(nm); } } for (var si = 0; si < 4; si++) { /* строка/диапазон имён для каждого листа */ var defs = [ { key: "dol", sheet: dolSheet, nr: 22, c1: 21, c2: 39 }, { key: "ebi", sheet: ebiSheet, nr: 22, c1: 21, c2: 39 }, { key: "bs", sheet: bsSheet, nr: 21, c1: 15, c2: 40 }, { key: "grp", sheet: grpSheet, nr: 3, c1: 2, c2: 17 } ]; var def = defs[si]; if (def.sheet) { var res = readNames(wb, def.sheet, def.nr, def.c1, def.c2); maps[def.key] = res.map; maps[def.key + "Sheet"] = def.sheet; } } /* нормативы из листа группы: K1 строки 36(порог)/37(цель), K2 40/41, K3 32/33; B — группа, C..Q — компании по строке 3 */ function normVal(sheet, r, c) { var cell = getCell(wb, sheet, r, c); if (cell == null) return null; if (cell.t === "e") return { err: "#" + cell.v }; var n = toNum(cell.v != null ? cell.v : cell.w); if (n != null) return n; var s = String(cell.w != null ? cell.w : cell.v).trim().toLowerCase(); if (s === "n/a" || s === "н/а" || s === "") return null; return { text: s }; } var norms = {}; if (grpSheet) { var names3 = {}; for (var c3 = 2; c3 <= 17; c3++) { var n3 = cellStr(wb, grpSheet, 3, c3); if (n3) names3[normName(n3)] = c3; } for (var nk in names3) { var cc = names3[nk]; norms[nk] = { k1th: normVal(grpSheet, 36, cc), k1t: normVal(grpSheet, 37, cc), k2th: normVal(grpSheet, 40, cc), k2t: normVal(grpSheet, 41, cc), k3th: normVal(grpSheet, 32, cc), k3t: normVal(grpSheet, 33, cc) }; } var keys = ["k1th", "k1t", "k2th", "k2t", "k3th", "k3t"]; for (var nk2 in norms) { for (var ki2 = 0; ki2 < keys.length; ki2++) { var vv = norms[nk2][keys[ki2]]; if (vv && typeof vv === "object" && vv.err) { out.diag.push("норматив " + keys[ki2] + " в «" + grpSheet + "»: ошибка " + vv.err); } } } /* нормативы группы — из столбца B */ var gN = { k1th: normVal(grpSheet, 36, 2), k1t: normVal(grpSheet, 37, 2), k2th: normVal(grpSheet, 40, 2), k2t: normVal(grpSheet, 37, 2), /* исправим ниже */ k3th: normVal(grpSheet, 32, 2), k3t: normVal(grpSheet, 33, 2) }; gN.k2t = normVal(grpSheet, 41, 2); norms.group = gN; } /* список объектов: 15 из плана + группа */ var list = []; if (planNames.length === 15 || planNames.length >= 14) { for (var pi = 0; pi < planNames.length; pi++) list.push({ name: planNames[pi], group: planNames[pi] === GROUP_NAME }); } else { var pool = {}; if (maps.dol) { var dd = order_of(maps.dol); for (var x = 0; x < dd.length; x++) pool[normName(dd[x])] = dd[x]; } for (var k in pool) list.push({ name: k, group: false }); out.diag.push("список компаний из «План 2026» не распознан — использованы имена листов"); } var entries = []; for (var ei = 0; ei < list.length; ei++) { var e = list[ei]; var nm = e.name; var isG = e.group; var en = readEntry(wb, nm, isG, { dol: maps.dol, dolSheet: dolSheet, ebi: maps.ebi, ebiSheet: ebiSheet, bs: maps.bs, bsSheet: bsSheet, grp: maps.grp, grpSheet: grpSheet }); var nkey = isG ? "group" : normName(nm); en.norms = norms[nkey] || null; coefFor(en, 0); entries.push(en); } /* диагностика ключевых ячеек */ for (var di = 0; di < entries.length; di++) { var en2 = entries[di]; (function (en) { if (en.debt == null && en.addr.debtSheet) diagErr(wb, en.addr.debtSheet, 24, colOfLetter(en.addr.debt), "долг"); if (en.ebitda == null && en.addr.ebitdaSheet) diagErr(wb, en.addr.ebitdaSheet, 83, colOfLetter(en.addr.ebitda), "EBITDA"); if (en.equity == null && en.addr.equitySheet) diagErr(wb, en.addr.equitySheet, 74, colOfLetter(en.addr.equity), "капитал"); })(en2); } /* сверка с План 2026!G5:J20 */ var recon = null; if (planSheet && planNames.length) { var rows = []; var total = 0, ok = 0; for (var ri = 0; ri < planNames.length; ri++) { var rn = planNames[ri]; var en = null; for (var ej = 0; ej < entries.length; ej++) if (normName(entries[ej].name) === normName(rn)) en = entries[ej]; if (!en) continue; for (var ci = 0; ci < 4; ci++) { var ref = cellNum(wb, planSheet, ri + 5, 7 + ci); var val = [en.debtTotal, en.ebitda, en.intExp, en.equity][ci]; var labels = ["Долг (+прочие)", "EBITDA", "Проценты", "Капитал"]; var pass = ref == null && val == null ? null : (ref != null && val != null && Math.abs(ref - val) <= 0.01); total++; if (pass === true) ok++; rows.push({ name: rn, field: labels[ci], ref: ref, val: val, pass: pass }); } } recon = { total: total, ok: ok, rows: rows, sheet: planSheet, addr: "G5:J" + (4 + planNames.length) }; } out.entries = entries; out.recon = recon; out.diag = out.diag; return out; } function order_of(map) { var out = []; for (var k in map) out.push(k); return out; } /* ---------- коэфф и зоны ---------- */ function normNum(v) { if (v == null) return null; if (typeof v === "object") return null; if (typeof v === "number") return v; return null; } function coefFor(en, delta) { var other = en.other; /* null сохраняется */ var debtTotal = (en.debt == null || other == null) ? null : en.debt + other + (delta || 0); var e = en.ebitda, q = en.equity, intr = en.intExp; var k1 = null, k2 = null, k3 = null, why = { k1: null, k2: null, k3: null }; if (en.debt == null) why.k1 = "нет долга"; if (en.ebitda == null) { why.k1 = why.k1 || "нет EBITDA"; why.k2 = "нет EBITDA"; } else if (en.ebitda <= 0) { why.k1 = why.k1 || "EBITDA ≤ 0"; why.k2 = "EBITDA ≤ 0"; } else { if (debtTotal != null) k1 = debtTotal / e; if (intr == null) why.k2 = "нет процентов"; else if (intr === 0) { k2 = Infinity; why.k2 = "проценты = 0 (∞)"; } else if (intr < 0) why.k2 = "проценты < 0"; else k2 = e / intr; } if (en.equity == null) why.k3 = "нет капитала"; else if (en.equity === 0) why.k3 = "капитал = 0 (нет данных)"; else if (en.equity < 0) why.k3 = "капитал < 0"; else if (debtTotal != null) k3 = debtTotal / q; z(k1, "k1", "green", why.k1, en.norms && en.norms.k1t, en.norms && en.norms.k1th, false, en, debtTotal); z(k2, "k2", "green", why.k2, en.norms && en.norms.k2t, en.norms && en.norms.k2th, true, en, debtTotal); z(k3, "k3", "green", why.k3, en.norms && en.norms.k3t, en.norms && en.norms.k3th, false, en, debtTotal); en.debtTotal = debtTotal; en.k1 = k1; en.k2 = k2; en.k3 = k3; en.why = why; /* если долг/капитал отсутствует — коэффициент не вычислен и не зелёный */ if (en.debt == null && k1 != null) { en.k1 = null; en.why.k1 = "нет долга"; } if (en.debt == null && k3 != null) { en.k3 = null; en.why.k3 = "нет долга"; } return en; } function z(v, key, dummy, reason, target, threshold, higherBetter, en, debtTotal) { var t = normNum(target), th = normNum(threshold); var zone, label; if (t == null && th == null) { /* «Без норматива» проверяется ПЕРВЫМ — даже при EBITDA ≤ 0 или отсутствующих данных */ zone = "nodefault"; label = "Без норматива"; } else if (v == null) { if (reason && /EBITDA ≤ 0/.test(reason)) { zone = "red"; label = "EBITDA ≤ 0"; } else if (reason && /≤ 0/.test(reason)) { zone = "red"; label = "EBITDA ≤ 0"; } else if (reason && /< 0/.test(reason)) { zone = "red"; label = "нарушено"; } else if (reason && /= 0 \(нет данных\)/.test(reason)) { zone = "nodata"; label = "нет данных"; } else { zone = "nodata"; label = "нет данных"; } } else { var ok = higherBetter ? v >= (t != null ? t : th) : v <= (t != null ? t : th); var bad = th != null && (higherBetter ? v < th : v > th); zone = bad ? "red" : (ok ? "green" : "yellow"); label = zone === "red" ? "нарушен порог" : (zone === "yellow" ? "между целью и порогом" : "в норме"); } en[key + "Zone"] = zone; en[key + "ZoneLabel"] = label; en[key + "Target"] = t; en[key + "Threshold"] = th; if (reason && v == null && zone !== "red") en[key + "ZoneReason"] = reason; } function worst(en) { var order = { nodata: 0, nodefault: 1, green: 2, yellow: 3, red: 4 }; var zs = [en.k1Zone || "nodata", en.k2Zone || "nodata", en.k3Zone || "nodata"]; /* если есть красный или жёлтый — риск, возвращаем его */ for (var i = 0; i < zs.length; i++) if (zs[i] === "red" || zs[i] === "yellow") return zs[i] === "red" ? "red" : "yellow"; /* если есть nodata — итог «Нет данных» (регулируемый показатель отсутствует) */ for (var j = 0; j < zs.length; j++) if (zs[j] === "nodata") return "nodata"; /* иначе — самое худшее из nodefault/green */ var w = zs[0]; for (var k = 1; k < zs.length; k++) if (order[zs[k]] > order[w]) w = zs[k]; return w; } function zoneLabel(z) { return { green: "Зелёная", yellow: "Жёлтая", red: "Красная", nodefault: "Без норматива", nodata: "Нет данных" }[z] || z; } /* ---------- учебный (синтетический) пример ---------- */ function syntheticDemo() { function E(name, debt, other, ebitda, intexp, equity, norms) { var en = { name: name, group: false, debt: debt, other: other || 0, ebitda: ebitda, intExp: intexp == null ? 0 : intexp, equity: equity, addr: {}, issues: [], norms: norms || null }; en.addr.debt = "синтетические данные"; en.addr.ebitda = "синтетические данные"; en.addr.intexp = "синтетические данные"; en.addr.equity = "синтетические данные"; en.addr.other = "синтетические данные"; coefFor(en, 0); return en; } var list = [ E("ТОО «ВостокСтрой»", 12000, 800, 3200, 620, 9500, { k1th: 3, k1t: 2, k2th: 2, k2t: 5, k3th: 1, k3t: 0.7 }), E("АО «МегаХим»", 8500, 120, 4100, 950, 15200, { k1th: 3.5, k1t: 2.5, k2th: 2.5, k2t: 6, k3th: 1.2, k3t: 0.9 }), E("ТОО «ТрансЛогистик»", 2200, 0, 700, 310, 5400, null), E("АО «ЮгСталь»", 6800, 250, 620, 480, 7800, { k1th: 4, k1t: 3, k2th: 1.5, k2t: 4, k3th: 1.5, k3t: 1.1 }), E("ТОО «АгроДержава»", 1500, 60, 40, 55, 2200, null), E("АО «СеверЭнерго»", null, 0, 1800, 300, 6000, { k1th: 3, k1t: 2, k2th: 2, k2t: 5, k3th: 1, k3t: 0.7 }), E("ТОО «АлтынМаркет»", 3200, 0, 0, 0, 4100, null), E("АО «Самрук-Инвест Демо»", 5400, 100, 2900, 12, 300, null), E("ТОО «Полярис Ритейл»", 900, 0, 1400, 0, 2600, { k1th: 2.5, k1t: 1.5, k2th: 1, k2t: 3, k3th: 0.8, k3t: 0.5 }), E("АО «Казах-Нефть Демо»", 9800, 460, 5100, 1500, 12000, { k1th: 3, k1t: 2, k2th: 2, k2t: 6, k3th: 1, k3t: 0.6 }) ]; var g = E(GROUP_NAME, 38200, 2000, 19160, 4227, 58600, { k1th: 3, k1t: 2.5, k2th: 2, k2t: 4, k3th: 1, k3t: 0.8 }); g.group = true; list.push(g); return { source: null, sheetNames: ["<демо>"], diag: [], entries: list, recon: null, isMock: true }; } /* ---------- вычисление зон (обёртка) ---------- */ /* ---------- рендер ---------- */ function screenEntry(e) { return recomputeFor(e, S.scen[e.name] || 0); } function zonePriority(z) { var p = { red: 0, yellow: 1, nodata: 2, green: 3, nodefault: 4 }[z]; return p === undefined ? 9 : p; } function filterSortList(list) { var k = S.sort.k, d = S.sort.d; list.sort(function (a, b) { var va = sortVal(a, k), vb = sortVal(b, k); if (va == null && vb == null) return 0; if (va == null) return 1; if (vb == null) return -1; if (typeof va === "string") return va.localeCompare(vb) * d; return (va - vb) * d; }); list.sort(function (a, b) { return zonePriority(worst(a)) - zonePriority(worst(b)); }); return list; } function sortVal(e, k) { if (k === "name") return e.name; if (k === "debt") return e.debt == null ? null : e.debt; if (k === "other") return e.other; if (k === "ebitda") return e.ebitda; if (k === "intexp") return e.intExp; if (k === "equity") return e.equity; if (k === "k1") return e.k1 == null ? null : e.k1; if (k === "k2") return e.k2 == null ? null : e.k2; if (k === "k3") return e.k3 == null ? null : e.k3; if (k === "k1n") return (e.norms && e.norms.k1t) ? 1 : 0; if (k === "k2n") return (e.norms && e.norms.k2t) ? 1 : 0; if (k === "k3n") return (e.norms && e.norms.k3t) ? 1 : 0; return 0; } /* ---------- отображение: статус, коэффициенты, причина ---------- */ function hasNumericNorm(e) { var n = e.norms; if (!n) return false; var keys = ["k1t", "k1th", "k2t", "k2th", "k3t", "k3th"]; for (var i = 0; i < keys.length; i++) if (typeof n[keys[i]] === "number") return true; return false; } function statusHtml(zone) { var cls = { green: "s-green", yellow: "s-yellow", red: "s-red", nodefault: "s-nodefault", nodata: "s-nodata" }[zone] || "s-nodata"; var screenLabel = { green: "В норме", yellow: "Наблюдение", red: "Нарушение", nodefault: "Без норматива", nodata: "Нет данных" }[zone] || zone; return "" + screenLabel + ""; } function coefCell(e, key) { var v = e[key]; var zn = e[key + "Zone"] || "nodata"; var dotCls = { green: "c-green", yellow: "c-yellow", red: "c-red", nodefault: "c-nodefault", nodata: "c-nodata" }[zn] || "c-nodata"; var valHtml; if (v == null) { if (e.why && e.why[key] && /EBITDA/i.test(e.why[key]) && e.ebitda != null && e.ebitda <= 0) { valHtml = "EBITDA ≤ 0"; } else { valHtml = "—"; } } else { var cvCls = zn === "red" ? " cv-red" : (zn === "yellow" ? " cv-yellow" : ""); valHtml = "" + nfmt(v, 2) + ""; } return "" + valHtml + ""; } function causeFor(e) { var parts = []; /* «Без норматива» — отдельная фраза, без числовых сравнений */ if (worst(e) === "nodefault" && !hasNumericNorm(e)) { return "Нормативы не установлены"; } if (e.ebitda != null && e.ebitda <= 0 && (e.k1Zone === "red" || e.k2Zone === "red")) { parts.push("Отрицательная EBITDA"); } var keys = ["k1", "k2", "k3"]; var missOrder = ["debt", "other", "ebitda", "intExp", "equity"]; var missName = { debt: "долг", other: "прочие/гарантии", ebitda: "EBITDA", intExp: "проценты", equity: "капитал" }; for (var i = 0; i < keys.length; i++) { var k = keys[i]; var zone = e[k + "Zone"] || "nodata"; if (zone === "green" || zone === "nodefault") continue; if (zone === "nodata" || e[k] == null) { /* конкретный недостающий показатель */ var fieldMiss = k === "k1" ? "debt" : k === "k2" ? "intExp" : "equity"; if (fieldMiss === "intExp" && e.ebitda != null && e.ebitda <= 0) continue; /* уже фразой выше */ if (fieldMiss === "debt" && e.debt == null) parts.push("нет долга"); else if (fieldMiss === "debt" && e.other == null) parts.push("нет прочих/гарантий"); else if (fieldMiss === "intExp") parts.push("нет процентов"); else if (fieldMiss === "equity") parts.push("нет капитала"); continue; } var th = normNum(e.norms && e.norms[k + "th"]); var tg = normNum(e.norms && e.norms[k + "t"]); var val = nfmt(e[k], 2); if (zone === "red") { if (k === "k2") parts.push("К2 " + val + " ниже порога " + nfmt(th, 2)); else parts.push("К" + (i + 1) + " " + val + " выше порога " + nfmt(th, 2)); } else if (zone === "yellow") { if (k === "k2") parts.push("К2 " + val + " ниже цели " + nfmt(tg, 2)); else parts.push("К" + (i + 1) + " " + val + " выше цели " + nfmt(tg, 2)); } } if (!parts.length) return "—"; var w = worst(e); var cls = w === "red" ? "danger" : (w === "yellow" ? "caution" : ""); return "" + esc(parts.join("; ")) + ""; } function tableRow(e, isGroup) { var wzone = worst(e); var name = e._name || e.name; return "