v1.3.1: isDemo только по кнопке демо (сброс при импорте/JSON-restore, авто-сброс если демо-строк нет), подписи: агент через сервер Vibe AI, import — «Импорт показателей и нормативов со сверкой по сводной книге», убрана ссылка Мониторинг из header (monitor.html на месте)

This commit is contained in:
Moldabayev_d 2026-09-21 09:43:13 +00:00
parent b85d743c5a
commit 70585ea058

View File

@ -144,7 +144,7 @@ tr.overall-green td:first-child{box-shadow:inset 4px 0 0 var(--green)}
<div class="brand"> <div class="brand">
<h1>КФУ-агент</h1> <h1>КФУ-агент</h1>
<span>Расчёт К1–К3 по Политике финансовой устойчивости — Долг/EBITDA, EBITDA/Проценты, Долг/Капитал · версия 1.3</span> <span>Расчёт К1–К3 по Политике финансовой устойчивости — Долг/EBITDA, EBITDA/Проценты, Долг/Капитал · версия 1.3</span>
<a href="monitor.html" style="font-size:12px;color:var(--acc)">Мониторинг →</a>
</div> </div>
<div class="spacer"></div> <div class="spacer"></div>
<div class="field"><label>Единицы вывода</label> <div class="field"><label>Единицы вывода</label>
@ -170,7 +170,7 @@ tr.overall-green td:first-child{box-shadow:inset 4px 0 0 var(--green)}
</nav> </nav>
<main> <main>
<div class="note warn small" style="margin-bottom:16px">Учебный пример: если реестр пуст, значения — синтетические (демо). Для рабочих расчётов загрузите выгрузку СУО или отчётность на шаге 1.</div> <div class="note warn small hidden" id="demoBanner" style="margin-bottom:16px">Учебный пример: реестр содержит синтетические данные (кнопка «Учебный пример»). Для рабочих расчётов загрузите выгрузку СУО, сводную книгу или отчётность на шаге 1.</div>
<!-- ============ STEP 1: DATA ============ --> <!-- ============ STEP 1: DATA ============ -->
<section class="step on" id="step-data"> <section class="step on" id="step-data">
@ -187,7 +187,7 @@ tr.overall-green td:first-child{box-shadow:inset 4px 0 0 var(--green)}
<div class="row" style="align-items:center;flex-wrap:wrap;gap:12px"> <div class="row" style="align-items:center;flex-wrap:wrap;gap:12px">
<button class="small on" id="btnKfuBook">Сводная книга КФУ 2025</button> <button class="small on" id="btnKfuBook">Сводная книга КФУ 2025</button>
<input type="file" id="kfuBookFile" accept=".xlsx,.xlsm,.xls" class="hidden"> <input type="file" id="kfuBookFile" accept=".xlsx,.xlsm,.xls" class="hidden">
<span class="note small" id="kfuBookStatus">Берёт данные только из загруженной книги: долг/прочие/EBITDA/проценты/капитал (млн → млрд), нормативы, источники адресов. Зоны — пересчёт ПГУ.</span> <span class="note small" id="kfuBookStatus">Импорт показателей и нормативов со сверкой по сводной книге. Данные берутся только из загруженной книги: долг/прочие/EBITDA/проценты/капитал (млн → млрд), нормативы, источники адресов.</span>
</div> </div>
</div> </div>
<div class="grid2"> <div class="grid2">
@ -385,7 +385,7 @@ tr.overall-green td:first-child{box-shadow:inset 4px 0 0 var(--green)}
<li>методика из вкладки «Отчётность МСФО»</li> <li>методика из вкладки «Отчётность МСФО»</li>
</ul> </ul>
<div class="row"><button class="small" id="btnChatClear">Очистить диалог</button></div> <div class="row"><button class="small" id="btnChatClear">Очистить диалог</button></div>
<div class="note small warn" style="margin-top:12px">Функции ИИ (извлечение из отчётности, подбор строк, агент) работают при открытии файла внутри Claude. Разбор выгрузки СУО, ручной ввод, расчёт и выгрузка в Excel работают и автономно в браузере.</div> <div class="note small" style="margin-top:12px">ИИ анализирует данные текущего реестра через сервер Vibe AI. Исходный Excel остаётся в браузере; при запросе ИИ передаются показатели и выбранные фрагменты отчётности.</div>
<div class="field" style="margin-top:12px"><label>Дополнительные указания агенту</label><textarea id="agentExtra" placeholder="например: отвечай в формате таблицы; используй запятую как десятичный разделитель"></textarea></div> <div class="field" style="margin-top:12px"><label>Дополнительные указания агенту</label><textarea id="agentExtra" placeholder="например: отвечай в формате таблицы; используй запятую как десятичный разделитель"></textarea></div>
</div> </div>
</div> </div>
@ -638,7 +638,7 @@ function renderPivot(){
if(!keys.length) h+='<tr><td class="muted">Нет данных.</td></tr>'; if(!keys.length) h+='<tr><td class="muted">Нет данных.</td></tr>';
t.innerHTML=h; t.innerHTML=h;
} }
function renderAll(){ S.registry.sort(sortKeys); renderRegistry(); renderParams(); renderCalc(); renderPivot(); } function renderAll(){ S.registry.sort(sortKeys); if(S.isDemo && !S.registry.some(r=>(r.source||'').startsWith('Демо'))){ S.isDemo=false; } $('#demoBanner').hidden = !S.isDemo; renderRegistry(); renderParams(); renderCalc(); renderPivot(); }
/* ---------- export ---------- */ /* ---------- export ---------- */
function calcMatrix(){ function calcMatrix(){
@ -723,14 +723,14 @@ $('#btnCsv').onclick = () => {
/* ---------- state save / load ---------- */ /* ---------- state save / load ---------- */
$('#btnSaveState').onclick = () => { $('#btnSaveState').onclick = () => {
const data = {v:1, saved:new Date().toISOString(), params:S.params, registry:S.registry, method:$('#methodText').value, agentExtra:$('#agentExtra').value}; const data = {v:1, saved:new Date().toISOString(), isDemo:!!S.isDemo, params:S.params, registry:S.registry, method:$('#methodText').value, agentExtra:$('#agentExtra').value};
const blob = new Blob([JSON.stringify(data,null,1)],{type:'application/json'}); const blob = new Blob([JSON.stringify(data,null,1)],{type:'application/json'});
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='КФУ_состояние.json'; a.click(); const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download='КФУ_состояние.json'; a.click();
}; };
$('#btnLoadState').onclick = () => $('#stateFile').click(); $('#btnLoadState').onclick = () => $('#stateFile').click();
$('#stateFile').onchange = e => { $('#stateFile').onchange = e => {
const f=e.target.files[0]; if(!f) return; const f=e.target.files[0]; if(!f) return;
const rd=new FileReader(); rd.onload=()=>{ try{ const d=JSON.parse(rd.result); if(d.params) S.params={...S.params,...d.params}; if(d.registry) S.registry=d.registry; if(d.method) $('#methodText').value=d.method; if(d.agentExtra) $('#agentExtra').value=d.agentExtra; renderAll(); toast('Состояние загружено'); }catch(err){ toast('Не удалось прочитать JSON: '+err.message); } }; rd.readAsText(f); e.target.value=''; const rd=new FileReader(); rd.onload=()=>{ try{ const d=JSON.parse(rd.result); if(d.params) S.params={...S.params,...d.params}; if(d.registry) S.registry=d.registry; if(d.method) $('#methodText').value=d.method; if(d.agentExtra) $('#agentExtra').value=d.agentExtra; S.isDemo = !!(d.isDemo && S.registry.some(r=>(r.source||'').startsWith('Демо'))); renderAll(); toast('Состояние загружено'); }catch(err){ toast('Не удалось прочитать JSON: '+err.message); } }; rd.readAsText(f); e.target.value='';
}; };
/* ---------- demo data (synthetic) ---------- */ /* ---------- demo data (synthetic) ---------- */
@ -749,13 +749,12 @@ const DEMO_ROWS = [
{name:'АО "ФНБ "Самрук-Казына"', period:'2025', debt:0.0, guar:0.0, ebitda:0.0, interest:0.0, equity:510.0, note:'демо, без EBITDA'}, {name:'АО "ФНБ "Самрук-Казына"', period:'2025', debt:0.0, guar:0.0, ebitda:0.0, interest:0.0, equity:510.0, note:'демо, без EBITDA'},
{name:'Консолидированная группа 1', period:'2025', debt:132.0, guar:8.0, ebitda:25.0, interest:9.6, equity:180.0, note:'демо, консолидация'} {name:'Консолидированная группа 1', period:'2025', debt:132.0, guar:8.0, ebitda:25.0, interest:9.6, equity:180.0, note:'демо, консолидация'}
]; ];
S._demo = false;
function loadDemo(){ function loadDemo(){
DEMO_ROWS.forEach(r=>addRow({name:r.name, key:keyFor(r.name), period:r.period, source:'Демо (синтетика)', debt:r.debt, guar:r.guar, ebitda:r.ebitda, interest:r.interest, equity:r.equity, note:'Синтетические данные, '+r.note})); DEMO_ROWS.forEach(r=>addRow({name:r.name, key:keyFor(r.name), period:r.period, source:'Демо (синтетика)', debt:r.debt, guar:r.guar, ebitda:r.ebitda, interest:r.interest, equity:r.equity, note:'Синтетические данные, '+r.note}));
S._demo = true; renderAll(); S.isDemo = true; renderAll();
toast('Демо: синтетические данные — '+DEMO_ROWS.length+' компаний'); toast('Демо: синтетические данные — '+DEMO_ROWS.length+' компаний');
} }
function toggleDemo(){ if(S._demo){ S.registry=S.registry.filter(r=>!(r.source||'').startsWith('Демо')); S._demo=false; renderAll(); toast('Демо-данные убраны'); } else loadDemo(); } function toggleDemo(){ if(S.isDemo && S.registry.length === S.registry.filter(r=>(r.source||'').startsWith('Демо')).length){ S.registry=S.registry.filter(r=>!(r.source||'').startsWith('Демо')); S.isDemo=false; renderAll(); toast('Демо-данные убраны'); } else loadDemo(); }
$('#btnDemo').onclick = toggleDemo; $('#btnDemo').onclick = toggleDemo;
/* ---------- navigation ---------- */ /* ---------- navigation ---------- */
@ -793,6 +792,7 @@ async function importKfuBook(file){
const missing = EXPECT.filter(k=>!names.some(n=>n.includes(k))); const missing = EXPECT.filter(k=>!names.some(n=>n.includes(k)));
if(missing.length || !names.some(n=>n.includes('план'))) throw new Error('в файле нет ожидаемых листов (' + [missing.join(', '),'«План 2026»'].filter(Boolean).join(', ') + '): это не сводная книга КФУ'); if(missing.length || !names.some(n=>n.includes('план'))) throw new Error('в файле нет ожидаемых листов (' + [missing.join(', '),'«План 2026»'].filter(Boolean).join(', ') + '): это не сводная книга КФУ');
const r = window.KfuWorkbook.analyzeWorkbook(wb); const r = window.KfuWorkbook.analyzeWorkbook(wb);
S.isDemo = false;
if(!r.entries || !r.entries.length) throw new Error('в книге нет распознанных записей (листы компаний пустые?)'); if(!r.entries || !r.entries.length) throw new Error('в книге нет распознанных записей (листы компаний пустые?)');
const bad = r.entries.filter(x => x.debt==null && x.ebitda==null && x.equity==null && x.intExp==null); const bad = r.entries.filter(x => x.debt==null && x.ebitda==null && x.equity==null && x.intExp==null);
if(bad.length) throw new Error('по ' + bad.length + ' компаниям не найдено ни одного показателя (' + bad.map(x=>x.name).slice(0,4).join(', ') + '…) — проверьте файл'); if(bad.length) throw new Error('по ' + bad.length + ' компаниям не найдено ни одного показателя (' + bad.map(x=>x.name).slice(0,4).join(', ') + '…) — проверьте файл');