Tonnage-app-IMCO/static/process-capability.html
2026-04-23 10:08:35 +00:00

28 lines
12 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Force Monitor — Process Capability</title>
<script src="/static/chart.umd.min.js"></script>
<style>
:root{--bg1:#050816;--bg2:#0b1224;--panel:rgba(255,255,255,.055);--border:rgba(255,255,255,.1);--text:#f4f4f5;--muted:#a1a1aa;--muted2:#71717a;--btnbg:rgba(255,255,255,.05);--good:#34d399;--warn:#facc15;--bad:#f87171;--info:#93c5fd;}body[data-theme="light"]{--bg1:#eef4ff;--bg2:#f8fafc;--panel:rgba(255,255,255,.84);--border:rgba(15,23,42,.1);--text:#0f172a;--muted:#475569;--muted2:#64748b;--btnbg:rgba(255,255,255,.88);--good:#059669;--warn:#b45309;--bad:#dc2626;--info:#1d4ed8;}*{box-sizing:border-box}body{margin:0;min-height:100vh;color:var(--text);font-family:'Segoe UI',system-ui,sans-serif;background:radial-gradient(circle at 10% 10%, rgba(34,211,238,.12), transparent 18%),radial-gradient(circle at 90% 10%, rgba(168,85,247,.14), transparent 18%),linear-gradient(180deg,var(--bg1),var(--bg2));}.wrap{width:min(95vw,1720px);margin:0 auto;padding:24px}.glass{background:var(--panel);border:1px solid var(--border);border-radius:24px;backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}.nav,.row,.toolbar{display:flex;gap:12px;flex-wrap:wrap;align-items:center}.spacer{flex:1 1 auto}.btn,.input{min-height:42px;border-radius:14px;border:1px solid var(--border);background:var(--btnbg);color:var(--text);padding:10px 14px;font:inherit}.btn{text-decoration:none;font-weight:600;display:inline-flex;align-items:center;justify-content:center;cursor:pointer}.btn.primary{background:rgba(14,165,233,.14);border-color:rgba(14,165,233,.35)}.page{padding:20px 22px;margin-bottom:18px}.kicker{font-size:11px;letter-spacing:.22em;text-transform:uppercase;color:var(--muted2)}.sub{color:var(--muted);margin-top:8px}.grid{display:grid;gap:16px}.cards{grid-template-columns:repeat(auto-fit,minmax(220px,1fr));margin-bottom:18px}.card{padding:18px 20px}.value{font-size:34px;font-weight:800;margin-top:8px}.mono{font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.chart-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px}.chart-box{height:360px}.table-wrap{overflow:auto}table{width:100%;border-collapse:collapse}th,td{padding:12px 10px;border-bottom:1px solid var(--border);text-align:left}th{font-size:12px;color:var(--muted2);text-transform:uppercase;letter-spacing:.16em}.pill{display:inline-flex;align-items:center;justify-content:center;min-width:88px;padding:4px 10px;border-radius:999px;font-size:12px;font-weight:700;border:1px solid var(--border)}.good{color:var(--good)}.warning{color:var(--warn)}.critical{color:var(--bad)}@media(max-width:1080px){.chart-grid{grid-template-columns:1fr}.wrap{padding:16px}}</style></head>
<body data-theme="dark"><div class="wrap">
<div class="nav" style="margin-bottom:18px"><a class="btn" href="/">Dashboard</a><a class="btn" href="/history">History</a><a class="btn" href="/alarms">Alarms</a><a class="btn" href="/kiosk">Kiosk</a><a class="btn primary" href="/process-capability">Process capability</a><a class="btn" href="/reports">Reports</a><a class="btn" href="/license">License</a><div class="spacer"></div><button id="theme-toggle" class="btn" type="button">Light theme</button><button id="fullscreen-toggle" class="btn" type="button">Enter fullscreen</button></div>
<div class="glass page"><div class="row"><div><div class="kicker">Engineering capability</div><h1 style="margin:8px 0 0;font-size:42px;line-height:1.04">Process Capability & Distribution</h1><div class="sub">Histogram-based force and imbalance capability, one-sided CPU/CPK-style indicators against your configured thresholds, correlation between left and right columns, and suggested engineering action.</div></div><div class="spacer"></div><div class="mono sub" id="window-label">Window: --</div></div></div>
<div class="glass page"><div class="toolbar"><button class="btn primary window-btn" data-window="1h">1h</button><button class="btn window-btn" data-window="8h">8h</button><button class="btn window-btn" data-window="24h">24h</button><button class="btn window-btn" data-window="7d">7d</button><input id="custom-window" class="input" style="width:140px" placeholder="e.g. 3h or 2d"><button id="apply-window" class="btn" type="button">Apply</button><div class="spacer"></div><button id="refresh-btn" class="btn" type="button">Refresh</button></div></div>
<div class="grid cards"><div class="glass card"><div class="kicker">Total Cpk @ critical</div><div id="total-cpk-critical" class="value mono">--</div><div id="total-cpk-sub" class="sub">Capability versus critical load limit</div></div><div class="glass card"><div class="kicker">Imbalance Cpk @ critical</div><div id="imb-cpk-critical" class="value mono">--</div><div id="imb-cpk-sub" class="sub">Capability versus critical imbalance limit</div></div><div class="glass card"><div class="kicker">Left ↔ right correlation</div><div id="corr-value" class="value mono">--</div><div class="sub">Closer to 1.00 means both sides move together</div></div><div class="glass card"><div class="kicker">Suggested action</div><div id="action-pill" class="value" style="font-size:28px">--</div><div id="action-text" class="sub">Loading capability guidance…</div></div></div>
<div class="chart-grid"><div class="glass page"><div class="row"><h2 style="margin:0;font-size:28px">Total force distribution</h2><div class="spacer"></div><span class="pill good">histogram</span></div><div class="sub">Distribution of total peak force against configured warning and critical boundaries.</div><div class="chart-box"><canvas id="totalHist"></canvas></div></div><div class="glass page"><div class="row"><h2 style="margin:0;font-size:28px">Imbalance distribution</h2><div class="spacer"></div><span class="pill warning">histogram</span></div><div class="sub">Distribution of imbalance magnitude. A tight distribution below warning is usually what engineering wants.</div><div class="chart-box"><canvas id="imbHist"></canvas></div></div></div>
<div class="grid cards"><div class="glass card"><div class="kicker">Mean / σ total</div><div id="mean-total" class="value mono">--</div><div id="mean-total-sub" class="sub">P95 / P99 and warning occupancy</div></div><div class="glass card"><div class="kicker">Mean / σ imbalance</div><div id="mean-imb" class="value mono">--</div><div id="mean-imb-sub" class="sub">P95 and critical occupancy</div></div><div class="glass card"><div class="kicker">CPU warning / critical</div><div id="cpu-total" class="value mono">--</div><div class="sub">One-sided capability against upper limits</div></div><div class="glass card"><div class="kicker">Stability</div><div id="stability" class="value mono">--</div><div id="stability-sub" class="sub">Loading…</div></div></div>
<div class="glass page"><div class="row"><h2 style="margin:0;font-size:28px">Top outliers</h2><div class="spacer"></div><span class="pill critical">review points</span></div><div class="sub">Combined overload and imbalance stress points worth engineering review.</div><div class="table-wrap" style="margin-top:12px"><table><thead><tr><th>Time</th><th>Total %</th><th>Total kN</th><th>L %</th><th>R %</th><th>Imb %</th></tr></thead><tbody id="outlier-body"><tr><td colspan="6">No data</td></tr></tbody></table></div></div>
</div>
<script src="/static/app-common.js"></script>
<script>
let currentWindow='1h', cfg={ui:{title:'Force Monitor',unit_force:'kN',unit_percent:'%'},thresholds:{warning_percent:80,critical_percent:95,imbalance_warning_percent:10,imbalance_critical_percent:20}}, totalChart=null, imbChart=null;
function fmt(n,d=2){return Number(n||0).toFixed(d)}
function updateChartTheme(){const light=document.body.dataset.theme==='light'; [totalChart,imbChart].forEach(ch=>{ if(!ch) return; ch.options.scales.x.ticks.color=light?'#334155':'#a1a1aa'; ch.options.scales.y.ticks.color=light?'#334155':'#a1a1aa'; ch.options.scales.x.grid.color=light?'rgba(15,23,42,.10)':'rgba(255,255,255,.06)'; ch.options.scales.y.grid.color=light?'rgba(15,23,42,.10)':'rgba(255,255,255,.06)'; ch.update('none');});}
function makeHistChart(id,label,color){return new Chart(document.getElementById(id),{type:'bar',data:{labels:[],datasets:[{label:label,borderColor:color,backgroundColor:color+'55',data:[]}]},options:{responsive:true,maintainAspectRatio:false,animation:false,plugins:{legend:{labels:{color:'#f4f4f5'}}},scales:{x:{grid:{color:'rgba(255,255,255,.06)'},ticks:{color:'#a1a1aa'}},y:{grid:{color:'rgba(255,255,255,.06)'},ticks:{color:'#a1a1aa'}}}}});}
async function loadCfg(){try{cfg=await AppUI.fetchJson('/api/config/public',{timeoutMs:8000});}catch(e){console.warn('Config load error:',e)}}
async function refresh(){const r=await fetch('/api/process-capability?window='+encodeURIComponent(currentWindow),{cache:'no-store'}); if(!r.ok) throw new Error('HTTP '+r.status); const d=await r.json(); document.getElementById('window-label').textContent='Window: '+d.window+' • '+d.from+' → '+d.to; document.getElementById('total-cpk-critical').textContent=fmt(d.total_cpk_critical); document.getElementById('total-cpk-sub').textContent='Warning Cpk '+fmt(d.total_cpk_warning)+' • critical occupancy '+fmt(d.total_above_critical_pct,1)+'%'; document.getElementById('imb-cpk-critical').textContent=fmt(d.imbalance_cpk_critical); document.getElementById('imb-cpk-sub').textContent='Warning Cpk '+fmt(d.imbalance_cpk_warning)+' • critical occupancy '+fmt(d.imbalance_above_critical_pct,1)+'%'; document.getElementById('corr-value').textContent=fmt(d.left_right_correlation,3); document.getElementById('action-pill').textContent=(d.total_cpk_critical<1 || d.imbalance_cpk_critical<1)?'ACT':'OK'; document.getElementById('action-pill').className='value '+((d.total_cpk_critical<1 || d.imbalance_cpk_critical<1)?'critical':'good'); document.getElementById('action-text').textContent=d.suggested_action||'--'; document.getElementById('mean-total').textContent=fmt(d.total_mean_pct,1)+(cfg.ui.unit_percent||'%')+' / '+fmt(d.total_std_pct,2); document.getElementById('mean-total-sub').textContent='P95 '+fmt(d.total_p95_pct,1)+' • P99 '+fmt(d.total_p99_pct,1)+' • above warn '+fmt(d.total_above_warning_pct,1)+'%'; document.getElementById('mean-imb').textContent=fmt(d.imbalance_mean_pct,1)+(cfg.ui.unit_percent||'%')+' / '+fmt(d.imbalance_std_pct,2); document.getElementById('mean-imb-sub').textContent='P95 '+fmt(d.imbalance_p95_pct,1)+' • above warn '+fmt(d.imbalance_above_warning_pct,1)+'%'; document.getElementById('cpu-total').textContent=fmt(d.total_cpu_warning)+' / '+fmt(d.total_cpu_critical); document.getElementById('stability').textContent=String(d.stability||'--').toUpperCase(); document.getElementById('stability').className='value mono '+(d.stability==='unstable'?'critical':d.stability==='caution'?'warning':'good'); document.getElementById('stability-sub').textContent=d.stability_reason||'--';
if(!totalChart){ totalChart=makeHistChart('totalHist','Total %','#22d3ee'); imbChart=makeHistChart('imbHist','Imbalance %','#f59e0b'); updateChartTheme();}
totalChart.data.labels=(d.total_histogram||[]).map(b=>fmt(b.start,0)+'-'+fmt(b.end,0)); totalChart.data.datasets[0].data=(d.total_histogram||[]).map(b=>b.count); totalChart.update('none'); imbChart.data.labels=(d.imbalance_histogram||[]).map(b=>fmt(b.start,0)+'-'+fmt(b.end,0)); imbChart.data.datasets[0].data=(d.imbalance_histogram||[]).map(b=>b.count); imbChart.update('none');
const body=document.getElementById('outlier-body'); const rows=(d.top_outliers||[]).map(p=>'<tr><td>'+p.time+'</td><td>'+fmt(p.total_percent,1)+'</td><td>'+fmt(p.total_kn,1)+'</td><td>'+fmt(p.left_percent,1)+'</td><td>'+fmt(p.right_percent,1)+'</td><td>'+fmt(p.imbalance_percent,1)+'</td></tr>').join(''); body.innerHTML=rows||'<tr><td colspan="6">No data</td></tr>';}
function useWindow(v){currentWindow=v; document.querySelectorAll('.window-btn').forEach(btn=>btn.classList.toggle('primary',btn.dataset.window===v)); refresh().catch(console.warn)}
AppUI.initTheme({ onChange: ()=>{ if(totalChart||imbChart) updateChartTheme(); } }); AppUI.initFullscreen({ buttonId:'fullscreen-toggle' }); document.getElementById('refresh-btn').addEventListener('click',()=>refresh().catch(console.warn)); document.getElementById('apply-window').addEventListener('click',()=>{const v=document.getElementById('custom-window').value.trim(); if(v) useWindow(v)}); document.querySelectorAll('.window-btn').forEach(btn=>btn.addEventListener('click',()=>useWindow(btn.dataset.window))); loadCfg().then(()=>refresh().catch(console.warn));
</script></body></html>