v1.3.3: стресс-таблица как основная (EBITDA ≤ 0 / ∞ / nf2), stressRow — guar тоже ×1.10 (долг вместе с гарантиями +10%), agentContext: коэффициенты 6 знаков + готовые на_экране 2 знака, инструкция ИИ использовать на_экране

This commit is contained in:
Moldabayev_d 2026-09-21 09:52:14 +00:00
parent f1cacb5c73
commit 6a494319dd

View File

@ -509,16 +509,18 @@ function calcRow(r){
/* Стресс-тест (та же логика, что используется в расчёте): долг вместе с гарантиями +10%, EBITDA −15%, проценты +20%, капитал −5%.
Расчёт — исходный calcRow() на изменённой копии строки; null-значения сохраняются. */
function stressRow(r){
/* «Долг вместе с гарантиями +10%» — оба компонента (debt и guar) растут ровно на 10%,
поэтому c.debt + c.guar = (r.debt + r.guar) * 1.10 */
const d = r.debt==null ? 0 : r.debt;
const g = r.guar==null ? 0 : r.guar;
const c = {...r};
c.debt = (isNum(r.debt) ? r.debt : 0) * 1.10;
if(isNum(r.debt)) c.debt = d*1.10; else c.debt = (d+g)*1.10;
if(isNum(r.guar)) c.guar = g*1.10;
if(isNum(r.ebitda)) c.ebitda = r.ebitda * 0.85;
if(isNum(r.interest)) c.interest = r.interest * 1.20;
if(isNum(r.equity)) c.equity = r.equity * 0.95;
const res = calcRow(c);
return {...res, debtStress: res.debt==null?null:+res.debt.toFixed(3),
ebitdaStress: res.e==null?null:+res.e.toFixed(3),
interestStress: res.i==null?null:+res.i.toFixed(3),
equityStress: res.q==null?null:+res.q.toFixed(3)};
return {...res};
}
/* ---------- registry ---------- */
@ -618,7 +620,6 @@ function renderCalc(){
t.innerHTML = h;
$('#kpi').innerHTML = `<div><b>${S.registry.length}</b><span>компаний/периодов</span></div><div class="green"><b>${cnt.green}</b><span>зелёная зона</span></div><div class="yellow"><b>${cnt.yellow}</b><span>жёлтая зона</span></div><div class="red"><b>${cnt.red}</b><span>красная зона</span></div><div><b>${cnt.na}</b><span>без оценки (нет целей/порогов)</span></div>`;
}
function kfmt(v){ if(v==null) return '—'; if(v===Infinity) return '∞'; if(typeof v==='string') return esc(v); return nf2.format(v); }
function zoneChip(z){ const nm = z==='na' ? 'н/д' : ZNAME[z]; return `<span class="zone ${z}">${nm}</span>`; }
function renderStress(){
const t = $('#stressTable'); if(!t) return;
@ -626,11 +627,15 @@ function renderStress(){
let h = `<tr><th>Компания</th><th class="num">К1 Долг/EBITDA (база → стресс)</th><th>Зона стресс</th><th class="num">К2 EBITDA/Проценты (база → стресс)</th><th>Зона стресс</th><th class="num">К3 Долг/Капитал (база → стресс)</th><th>Зона стресс</th><th>Итоговая зона (стресс)</th></tr>`;
for(const r of S.registry){
const b = calcRow(r), s = stressRow(r);
const arrow = (base, stress) => `${kfmt(base)} → <b>${kfmt(stress)}</b>`;
const arrow = (base, stress, ebBase, ebStress) => {
const fb = base===Infinity ? '∞' : (b.ebNeg?'EBITDA ≤ 0':fmtX(base));
const fs = stress===Infinity ? '∞' : (s.ebNeg?'EBITDA ≤ 0':fmtX(stress));
return `<td class="num"><span class="muted">${fb}</span> → <b class="${stress===null?'muted':''}">${fs}</b></td>`;
};
h += `<tr class="overall-${s.worst}"><td><b>${esc(displayName(r.key,r.name))}</b></td>
<td class="num">${arrow(b.k1, s.k1)}</td><td>${zoneChip(s.z1)}</td>
<td class="num">${arrow(b.k2, s.k2)}</td><td>${zoneChip(s.z2)}</td>
<td class="num">${arrow(b.k3, s.k3)}</td><td>${zoneChip(s.z3)}</td>
${arrow(b.k1, s.k1, b.ebNeg, s.ebNeg)}<td>${zoneChip(s.z1)}</td>
${arrow(b.k2, s.k2, b.ebNeg, s.ebNeg)}<td>${zoneChip(s.z2)}</td>
${arrow(b.k3, s.k3, false, false)}<td>${zoneChip(s.z3)}</td>
<td>${zoneChip(s.worst)}</td></tr>`;
}
t.innerHTML = h;
@ -1381,16 +1386,29 @@ $('#btnAiMap').onclick = async () => {
};
/* ===================== AGENT CHAT ===================== */
/* Готовое экранные отображение (2 знака) + точное значение (6 знаков) — без повторного округления со стороны ИИ */
const f3 = v => !isNum(v) ? null : +v.toFixed(3);
const f6 = v => v==null ? null : +v.toFixed(6);
const k6 = v => (v===Infinity?'inf':f6(v));
const k2 = v => (v===Infinity?'∞':(v==null?'':nf2.format(v)));
function stressRows(){
return S.registry.map(r=>({компания: displayName(r.key,r.name), период:r.period, ...stressRow(r)}));
return S.registry.map(r=>{ const s=stressRow(r); return {
компания: displayName(r.key,r.name), период:r.period,
К1:{значение:k6(s.k1), на_экране:s.ebNeg?'EBITDA ≤ 0':k2(s.k1), зона:s.z1},
К2:{значение:k6(s.k2), на_экране:s.ebNeg?'EBITDA ≤ 0':k2(s.k2), зона:s.z2},
К3:{значение:k6(s.k3), на_экране:k2(s.k3), зона:s.z3},
итог:s.worst }; });
}
function agentContext(){
const rows = S.registry.map(r=>{ const c=calcRow(r); return {
компания: displayName(r.key,r.name), период:r.period, источник:r.source,
долг_млрд:+((r.debt||0)).toFixed(3), гарантии_млрд:+((r.guar||0)).toFixed(3), итого_долг_млрд:+c.debt.toFixed(3),
ebitda_12м_млрд: isNum(r.ebitda)?+r.ebitda.toFixed(3):null, проценты_12м_млрд:isNum(r.interest)?+r.interest.toFixed(3):null, капитал_млрд:isNum(r.equity)?+r.equity.toFixed(3):null,
К1: c.ebNeg?'EBITDA<=0':(isNum(c.k1)?+c.k1.toFixed(3):null), К2: c.ebNeg?'EBITDA<=0':(c.k2===Infinity?'inf':(isNum(c.k2)?+c.k2.toFixed(3):null)), К3: isNum(c.k3)?+c.k3.toFixed(3):null,
цели:{К1:c.p.t1,К2:c.p.t2,К3:c.p.t3}, пороги:{К1:c.p.p1,К2:c.p.p2,К3:c.p.p3}, зоны:{К1:c.z1,К2:c.z2,К3:c.z3,итог:c.worst},
долг_млрд:f3((r.debt||0)), гарантии_млрд:f3((r.guar||0)), итого_долг_млрд:f3(c.debt),
ebitda_12м_млрд:f3(r.ebitda), проценты_12м_млрд:f3(r.interest), капитал_млрд:f3(r.equity),
К1:{значение:c.ebNeg?'EBITDA<=0':k6(c.k1), на_экране:c.ebNeg?'EBITDA ≤ 0':k2(c.k1)},
К2:{значение:c.ebNeg?'EBITDA<=0':k6(c.k2), на_экране:c.ebNeg?'EBITDA ≤ 0':k2(c.k2)},
К3:{значение:k6(c.k3), на_экране:k2(c.k3)},
цели:{К1:c.p.t1,К2:c.p.t2,К3:c.p.t3}, пороги:{К1:c.p.p1,К2:c.p.p2,К3:c.p.p3},
зоны:{К1:c.z1,К2:c.z2,К3:c.z3,итог:c.worst},
месяцев:r.months||12, ltm:r.ltm!==false, примечание:r.note||'' }; });
return JSON.stringify(rows);
}
@ -1402,8 +1420,9 @@ ${$('#methodText').value}
${$('#agentExtra').value.trim()?'Дополнительные указания пользователя: '+$('#agentExtra').value.trim():''}
Данные реестра и расчёта (JSON):
${agentContext()}
Стресс-тест (уже пересчитан: долг +10%, EBITDA −15%, процентные расходы +20%, капитал −5%) — используй только эти значения, не пересчитывай самостоятельно (JSON):
${JSON.stringify(stressRows())}`;
Стресс-тест (уже пересчитан: долг вместе с гарантиями +10%, EBITDA −15%, процентные расходы +20%, капитал −5%) — используй только эти значения, не пересчитывай самостоятельно (JSON):
${JSON.stringify(stressRows())}
Правило вывода коэффициентов: при упоминании значений К1–К3 в ответе бери готовые на_экране (2 знака, запятая — десятичный разделитель, «x» не добавлять), не округляй «значение» (6 знаков) самостоятельно; «значение» использовать только для вычислений в формулах.`;
}
function chatAppend(role, text){ const log=$('#chatLog'); const d=document.createElement('div'); d.className='msg '+role; d.textContent=text; log.appendChild(d); log.scrollTop=log.scrollHeight; return d; }
async function chatSend(q){