← Files FinrunARCHIVED FILE
skills/finrun/assets/report.html
10.3 KB · Oct 3, 2026 · 06:35 UTC
<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Finrun</title><style>
:root{--ink:#182e39;--muted:#465e6b;--accent:#075e54;--line:#c7d6d8}*{box-sizing:border-box}body{margin:0;background:#f1f5f4;color:var(--ink);font:16px/1.6 system-ui,sans-serif}main{max-width:1180px;margin:auto;padding:28px}header,section{background:#fff;border:1px solid var(--line);padding:24px;margin-bottom:22px;border-radius:6px}h1{font-size:30px;line-height:1.25;margin:10px 0}h2{font-size:23px;margin:5px 0 15px}h3{font-size:17px}p{margin:10px 0}.meta,small,.note{font-size:13px;color:var(--muted)}.brand{font-weight:750;color:var(--accent);letter-spacing:.08em;font-size:13px}.lead{font-size:19px}a{color:var(--accent)}nav{display:flex;gap:18px;flex-wrap:wrap;margin-top:20px}button,input,select{font:inherit;color:var(--ink);background:#fff;border:1px solid #647d87;padding:6px 10px;border-radius:4px}button,summary{cursor:pointer}button:hover,select:hover{background:#dfede8;color:#163f37}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible{outline:3px solid #086bbb;outline-offset:2px}button[aria-pressed=true]{background:#075e54;color:#fff}.metric{border:0;border-bottom:1px dotted #59877c;border-radius:0;padding:0 2px;background:transparent;color:var(--accent);font:inherit}.scroll{overflow:auto;max-width:100%}table{border-collapse:collapse;width:100%;font-size:14px;font-variant-numeric:tabular-nums}td,th{text-align:left;padding:10px;border-bottom:1px solid var(--line)}th{background:#edf3f2;color:#34515a;white-space:nowrap}th button{border:0;background:transparent;color:#34515a;padding:0;font:inherit}.toolbar{margin:15px 0;display:flex;gap:10px;align-items:center;flex-wrap:wrap}.tag{display:inline-block;white-space:nowrap;font-size:11px;color:#175247;background:#e7f2ec;padding:2px 5px;border-radius:3px;margin-left:6px}.tag.scenario,.tag.estimate,.tag.guidance{background:#fff0dc;color:#75440b}.ref{background:transparent;color:var(--accent);border:0;font-size:12px;text-decoration:underline}.barrow{display:grid;grid-template-columns:130px minmax(0,1fr) 110px;gap:12px;align-items:center;margin:12px 0;font-size:14px}.track{background:#e7efed;height:18px}.bar{height:100%;background:#136b5e}.chart{margin:20px 0}.sources{display:grid;grid-template-columns:1fr 1fr;gap:14px}.source{border:1px solid var(--line);padding:14px;overflow-wrap:anywhere}.source small{display:block}.methods details{padding:12px 0;border-bottom:1px solid var(--line)}dialog{width:min(750px,93vw);max-height:85vh;overflow:auto;color:var(--ink);background:#fff;border:1px solid #657e88;border-radius:6px;padding:22px}dialog::backdrop{background:#132b3680}dialog pre{white-space:pre-wrap;overflow-wrap:anywhere;color:#153f35;background:#edf5f1;padding:12px;font-size:13px}#close{float:right}.empty{color:var(--muted)}
@media(max-width:700px){main{padding:12px}header,section{padding:17px 13px}.sources{grid-template-columns:1fr}h1{font-size:26px}.barrow{grid-template-columns:76px minmax(0,1fr) 80px;gap:7px}.toolbar input{max-width:100%}td,th{padding:8px}nav{gap:12px}}
@media print{body{background:#fff}main{max-width:none;padding:0}header,section{border:0;border-bottom:1px solid #aebfc3;break-inside:auto}nav,.toolbar,.ref,#source-search{display:none}.scroll{overflow:visible}table{font-size:11px}th,td{white-space:normal}dialog{display:none}.sources{grid-template-columns:1fr 1fr}.source{break-inside:avoid}.metric{border:0}.methods details>p{display:block}h2{break-after:avoid}}
</style></head><body><main id="finrunbook-report"></main><dialog id="drawer"><button id="close"></button><h2 id="drawer-title"></h2><div id="evidence"></div></dialog><script id="report-data" type="application/json">__FINRUN_DATA__</script><script>
// Self-contained snapshot of report-data.json. No network, external scripts or host APIs.
const D=JSON.parse(document.querySelector('#report-data').textContent),$=s=>document.querySelector(s),esc=x=>String(x??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const Z=D.meta.language.startsWith('zh'),L=Z?{sources:'来源',methods:'定义与限制',search:'搜索来源',filter:'筛选行',all:'全部',close:'关闭',evidence:'证据与计算',empty:'无匹配结果',missing:'暂无数据',validation:'财务校验',actual:'实际',guidance:'公司指引',estimate:'估计',scenario:'情景',review:'财务、语义和浏览器检查分别进行;程序通过不代表独立审计。'}:{sources:'Sources',methods:'Definitions and limits',search:'Search sources',filter:'Filter rows',all:'All',close:'Close',evidence:'Evidence & calculations',empty:'No matching rows',missing:'N/A',validation:'Financial validation',actual:'Actual',guidance:'Guidance',estimate:'Estimate',scenario:'Scenario',review:'Financial, semantic and browser checks are separate; a mechanical pass is not independent audit assurance.'};
Object.assign(L,D.ui||{});document.documentElement.lang=D.meta.language;document.title=D.meta.title;
const F=Object.fromEntries(D.facts.map(x=>[x.id,x])),C=Object.fromEntries(D.calculations.map(x=>[x.id,x])),E=Object.fromEntries(D.evidence.map(x=>[x.id,x])),S=Object.fromEntries(D.sources.map(x=>[x.id,x]));
const ids=x=>[...(x.calculation_ids||[]),...(x.fact_ids||[])].join(','),ref=x=>ids(x)?`<button class="ref" data-ids="${esc(ids(x))}">${L.evidence}</button>`:'';
function cell(x){if(x===null||x===undefined)return `<span class="empty">${L.missing}</span>`;if(typeof x!=='object')return esc(x);if(x.value===null)return `<span class="empty">${esc(x.reason||L.missing)}</span>`;return `<button class="metric" data-ids="${esc(ids(x))}" title="${esc([x.period,x.units,L[x.basis]||x.basis].join(' · '))}">${Number(x.value).toLocaleString(D.meta.language,{minimumFractionDigits:x.decimals??2,maximumFractionDigits:x.decimals??2})}</button>${x.basis&&x.basis!=='actual'?`<span class="tag ${esc(x.basis)}">${esc(L[x.basis])}</span>`:''}`}
$('#finrunbook-report').innerHTML=`<header><span class="brand">FINRUN</span><p class="meta">${esc(D.meta.as_of_date)} · ${esc(D.meta.coverage_universe)}</p><h1>${esc(D.meta.title)}</h1><p class="lead">${esc(D.executive_view.headline)}</p><p>${esc(D.executive_view.summary)}</p>${ref(D.executive_view)}<nav>${D.sections.map((s,i)=>`<a href="#section-${i}">${esc(s.title)}</a>`).join('')}<a href="#sources">${L.sources}</a></nav></header>${D.sections.map((s,i)=>`<section id="section-${i}"><h2>${esc(s.title)}</h2>${s.text?`<p>${esc(s.text)}</p>`:''}${s.rows?`<div class="toolbar"><label>${L.filter} <input data-filter="${i}" type="search" aria-label="${esc(L.filter+' '+s.title)}"></label></div><div class="chart" id="chart-${i}"></div><div class="scroll" id="table-${i}"></div>`:''}${ref(s)}</section>`).join('')}<section id="sources"><h2>${L.sources}</h2><input id="source-search" type="search" placeholder="${L.search}" aria-label="${L.search}"><div id="source-list" class="sources"></div><h2>${L.methods}</h2><div class="methods">${(D.methodology||[]).map(x=>`<details><summary>${esc(x.title)}</summary><p>${esc(x.text)}</p>${ref(x)}</details>`).join('')}</div><p class="note">${L.validation}: ${esc(D.validation.status)} · ${esc(D.validation.validated_at||'')}</p><p class="note">${L.review}</p></section>`;
const sorts={};
function table(i){let s=D.sections[i],query=($(`[data-filter="${i}"]`)?.value||'').toLowerCase(),rows=s.rows.filter(row=>Object.values(row).some(x=>String(typeof x==='object'?x?.value:x).toLowerCase().includes(query)));if(sorts[i]){let [key,dir]=sorts[i];rows.sort((a,b)=>{let v=x=>typeof x==='object'?x?.value:x,av=v(a[key]),bv=v(b[key]);if(av==null)return bv==null?0:1;if(bv==null)return -1;return (av<bv?-1:av>bv?1:0)*dir})}$('#table-'+i).innerHTML=rows.length?`<table><thead><tr>${s.columns.map(c=>`<th><button data-sort="${i}" data-key="${esc(c.key)}" aria-pressed="${sorts[i]?.[0]===c.key}">${esc(c.label)} ${sorts[i]?.[0]===c.key?(sorts[i][1]===1?'↑':'↓'):''}</button></th>`).join('')}</tr></thead><tbody>${rows.map(row=>`<tr>${s.columns.map(c=>`<td>${cell(row[c.key])}</td>`).join('')}</tr>`).join('')}</tbody></table>`:`<p class="empty">${L.empty}</p>`;if(s.type==='chart'){const max=Math.max(1,...rows.map(r=>Math.abs(r[s.value_key]?.value||0)));$('#chart-'+i).innerHTML=rows.map(r=>`<div class="barrow"><span>${esc(r[s.label_key])}</span><div class="track"><div class="bar" style="width:${Math.abs(r[s.value_key]?.value||0)/max*100}%"></div></div><span>${cell(r[s.value_key])}</span></div>`).join('')}}
function sources(){let q=$('#source-search').value.toLowerCase();$('#source-list').innerHTML=D.sources.filter(s=>[s.id,s.title,s.publisher].join(' ').toLowerCase().includes(q)).map(s=>`<div class="source"><a href="${esc(s.url||'../'+s.local_path)}" target="_blank" rel="noopener">${s.id} · ${esc(s.title)}</a><small>${esc(s.publisher)} · ${esc(s.published_at||s.as_of_date||'')}</small></div>`).join('')}
function drawer(list){let c=list.filter(id=>C[id]),f=[...new Set([...list.filter(id=>F[id]),...c.flatMap(id=>C[id].input_fact_ids)])];$('#evidence').innerHTML=c.map(id=>`<h3>${id} · ${esc(C[id].description)}</h3><pre>${esc(C[id].expression)}\n${esc(JSON.stringify(C[id].inputs||{}))}\n${esc(C[id].result)} ${esc(C[id].units)}</pre>`).join('')+f.map(id=>`<h3>${id}</h3><p>${esc(F[id].statement)}</p><p class="note">${esc(F[id].status)} · ${esc(F[id].period)}</p>${F[id].evidence_ids.map(eid=>{let e=E[eid],s=S[e.source_id];return `<p><a href="${esc(s.url||'../'+s.local_path)}" target="_blank" rel="noopener">${s.id} · ${esc(s.title)}</a><br><small>${esc(e.locator)}</small></p>`}).join('')}`).join('');$('#drawer').showModal()}
document.addEventListener('click',e=>{let a=e.target.closest('[data-ids]');if(a)drawer(a.dataset.ids.split(','));let b=e.target.closest('[data-sort]');if(b){let i=+b.dataset.sort,k=b.dataset.key;sorts[i]=[k,sorts[i]?.[0]===k?-sorts[i][1]:-1];table(i)}});document.addEventListener('input',e=>{if(e.target.matches('[data-filter]'))table(+e.target.dataset.filter)});$('#source-search').oninput=sources;$('#close').textContent=L.close;$('#drawer-title').textContent=L.evidence;$('#close').onclick=()=>$('#drawer').close();D.sections.forEach((s,i)=>{if(s.rows)table(i)});sources();let states=[];window.addEventListener('beforeprint',()=>{states=[...document.querySelectorAll('details')].map(x=>[x,x.open]);states.forEach(([x])=>x.open=true)});window.addEventListener('afterprint',()=>states.forEach(([x,o])=>x.open=o));window.REPORT_READY=true;
</script></body></html>
SHA-256: 54f65257a9f501b6c6a1407194159277262e45723ff9442a424baecea84248df