Maintenance KPI • calculation companion — Build a traceable workbook specification separating due-in-period maintenance completed on time from late work, and calculate asset…

HTML for this example

Select the code and use your copy shortcut. Changes in this preview are temporary.

<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Maintenance KPI • calculation companion</title><style>*{box-sizing:border-box}body{margin:0;background:var(--bg,#eef3f6);color:#172a39;font:16px/1.5 system-ui,sans-serif}main{max-width:1200px;margin:auto;padding:28px}h1{font-size:clamp(27px,4vw,42px);line-height:1.12;margin:8px 0 12px;letter-spacing:-.035em}h2{font-size:23px;line-height:1.2;margin:0 0 18px}h3{font-size:17px;margin:14px 0 8px}p{margin:8px 0 16px}.eyebrow{font-size:12px;text-transform:uppercase;letter-spacing:.15em;font-weight:800;color:var(--accent,#176b71)}.lead{max-width:75ch;color:#405568}.panel{background:#fff;border:1px solid #cdd9e1;border-radius:14px;padding:24px;margin:18px 0}.grid{display:grid;grid-template-columns:1fr 1fr;gap:20px}.wide{grid-template-columns:1.6fr 1fr}.metric-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px}.metric{border-left:4px solid var(--accent,#176b71);background:#f4f8fa;padding:18px}.big{display:block;font-size:36px;font-weight:800;line-height:1.2}.muted,small{color:#536879;font-size:14px}.row{display:flex;gap:10px;flex-wrap:wrap;align-items:center}button{background:var(--accent,#176b71);color:white;border:2px solid transparent;border-radius:8px;padding:10px 16px;min-height:44px;cursor:pointer;font:inherit;font-weight:650}button.secondary{background:#e8eff4;color:#172a39}button:disabled{opacity:.48;cursor:default}button[aria-pressed=true]{outline:3px solid #172a39;outline-offset:2px}button:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible,summary:focus-visible{outline:3px solid #de7616;outline-offset:3px}label{display:block;font-weight:650;margin:12px 0 6px}input,select,textarea{font:inherit;color:inherit;border:1px solid #8b9eae;border-radius:6px;background:white;padding:10px;width:100%;min-height:44px}textarea{resize:vertical;min-height:110px}input[type=checkbox]{width:22px;min-height:22px;vertical-align:middle}table{border-collapse:collapse;width:100%;font-size:14px}th{text-align:left;background:#eff4f7;color:#3d5261}th,td{padding:12px;border-bottom:1px solid #dce5eb;vertical-align:top}.scroll{overflow-x:auto}caption{text-align:left;margin-bottom:12px;font-weight:700}.notice{background:#eef5f9;border-left:4px solid #628ba1;padding:14px;margin:14px 0}.error{color:#9c2924;font-weight:650}.pill{display:inline-block;border-radius:30px;padding:4px 10px;background:#e5f3ee;color:#17614f;font-size:13px;font-weight:750}.draft{white-space:pre-wrap;font:17px/1.65 Georgia,serif}.rule{display:block;width:100%;text-align:left;margin:10px 0}.rule strong{font-size:26px}details{margin:18px 0}summary{cursor:pointer;font-weight:700;padding:12px;background:#e9eff3;border-radius:6px;min-height:44px}footer{margin:30px 0;color:#536879;font-size:14px}ol,ul{padding-left:23px}.time-input{max-width:90px}.section-no{font-size:52px;color:#b5c8d5;font-weight:800;line-height:1}.tag{font-weight:700;color:var(--accent,#176b71)}[hidden]{display:none!important}@media(max-width:760px){main{padding:18px}.grid,.wide{grid-template-columns:1fr}.metric-grid{grid-template-columns:1fr}.panel{padding:18px}.big{font-size:30px}th,td{padding:9px}}:root{--accent:#9c5126;--bg:#f2f3f5}.timeline{margin:20px 0;padding:18px;background:#f2f5f7;min-height:150px}.axis{font-size:13px;margin-bottom:20px}.lane{margin:12px 0}.lane span{font-weight:750;display:inline-block;min-width:60px}.lane b{font-size:13px}.track{height:18px;background:#d4e0e5;position:relative;margin:4px 0}.bar{height:100%;position:absolute;background:#bf602d;border-radius:3px}
.grid>*{min-width:0}.table-scroll{max-width:100%;overflow-x:auto}.table-scroll-hint{display:none}@media(max-width:600px){.table-scroll-hint{display:block;font-size:13px;color:#536879;margin-bottom:8px}}
</style></head><body><main>
<span class="eyebrow">Maintenance / fictional UTC specimen</span><h1>Two measures. Every minute traceable.</h1><p class="lead">Inspect due-date compliance and overlapping downtime with their original record IDs.</p>
<section class="panel"><div class="row"><h2>September 2026 due cohort</h2><span class="pill">Snapshot: Sep 30 · 23:59 UTC</span></div><div class="metric-grid"><div class="metric">Completed on or before due time<strong class="big" id="pm-on-time"></strong><span id="pm-numerator"></span></div><div class="metric">Completed by period end<strong class="big" id="pm-complete"></strong><span>Different measure; includes late completion</span></div><div class="metric">Due records<strong class="big">4</strong><span>P1–P4; P5 is due in October</span></div></div><div class="scroll"><div class="table-scroll" role="region" tabindex="0" aria-label="Maintenance records; scroll horizontally if needed"><span class="table-scroll-hint">Scroll sideways for every column.</span><table><thead><tr><th>ID</th><th>Due UTC</th><th>Completed UTC</th><th>September treatment</th></tr></thead><tbody id="pm-rows"></tbody></table></div></div></section>
<section class="panel"><div class="grid wide"><div><h2>Asset A / October 13, 2026</h2><div id="timeline" class="timeline" aria-label="Downtime interval diagram"></div><div class="row"><button type="button" id="raw-view" class="secondary">Show raw intervals</button><button type="button" id="merged-view">Show merged interval</button></div><p id="interval-explanation"></p></div><div><label for="planned-start">Planned start, UTC (same date)</label><input id="planned-start" type="time" value="09:00"><label for="planned-end">Planned end, UTC (same date)</label><input id="planned-end" type="time" value="17:00"><label for="coverage"><input id="coverage" type="checkbox" checked> Fictional coverage is complete and states are exclusively running/down</label><button type="button" id="restore-window" class="secondary">Restore 09:00–17:00</button></div></div><div class="metric-grid"><div class="metric">Union downtime<strong class="big" id="union-minutes"></strong><span id="overlap"></span></div><div class="metric">Runtime / planned time<strong class="big" id="runtime"></strong><span id="planned-minutes"></span></div><div class="metric">Conditional availability<strong class="big" id="availability"></strong><span id="availability-state"></span></div></div><p id="calc-error" class="error" role="status"></p></section>
<section class="panel"><h2>Original downtime events</h2><div class="table-scroll" role="region" tabindex="0" aria-label="Maintenance records; scroll horizontally if needed"><span class="table-scroll-hint">Scroll sideways for every column.</span><table><thead><tr><th>ID</th><th>Type</th><th>Start UTC</th><th>End UTC</th><th>Raw duration</th></tr></thead><tbody><tr><td>S1</td><td>Scheduled maintenance</td><td>08:30</td><td>09:00</td><td>30 min</td></tr><tr><td>D1</td><td>Unplanned</td><td>10:00</td><td>10:30</td><td>30 min</td></tr><tr><td>D2</td><td>Unplanned</td><td>10:20</td><td>11:00</td><td>40 min</td></tr></tbody></table></div><p>Clip to the planned window first, then merge intersecting intervals for this asset. Start is inclusive and end is exclusive. S1 contributes zero minutes to the original 09:00–17:00 window.</p><label for="calc-text">Calculation record for manual copying</label><textarea id="calc-text" readonly></textarea><button type="button" id="select-calc-text" class="secondary">Select text</button><p id="calc-text-status" class="muted">Select the text, then use your device’s copy command. Nothing is saved.</p></section>
<details><summary>Definitions and workbook handoff</summary><p>Keep five workbook areas: Definitions, PM Records, Downtime Events, Calculations and Dashboard. PM compliance is on-time completions divided by records due in the fixed period. Exact timestamps are supplied for this specimen; a real date-only deadline needs its boundary confirmed.</p><p>Availability here is runtime divided by planned time, conditional on complete coverage and exclusive running/down states. Missing coverage prevents a measured-availability claim. This is neither OEE nor MTBF/(MTBF + MTTR). Invalid or zero-length windows are not calculable.</p><p>This HTML companion does not create or edit an Excel workbook, connect to equipment or validate source coverage. Values are fictional calculation examples.</p></details></main><script>const $=id=>document.getElementById(id);const on=(id,fn)=>$(id).addEventListener('click',fn);const text=(id,value)=>$(id).textContent=value;const bind=(id,fn,event='input')=>$(id).addEventListener(event,fn);const selectText=id=>{if(!$(id).value){text(id+'-status','Complete the required fields before selecting text.');return;}$(id).focus();$(id).select();text(id+'-status','Text selected. Use your device’s copy command; nothing has been sent or saved.');};
const pm=[['P1','2026-09-10T17:00:00Z','2026-09-09T16:00:00Z'],['P2','2026-09-15T17:00:00Z','2026-09-17T16:00:00Z'],['P3','2026-09-20T17:00:00Z','2026-09-20T16:00:00Z'],['P4','2026-09-30T17:00:00Z',null],['P5','2026-10-05T17:00:00Z','2026-09-29T16:00:00Z']];const events=[{id:'S1',start:510,end:540},{id:'D1',start:600,end:630},{id:'D2',start:620,end:660}];let view='merged';function pmMetrics(records){const a=Date.parse('2026-09-01T00:00:00Z'),z=Date.parse('2026-10-01T00:00:00Z'),snap=Date.parse('2026-09-30T23:59:00Z');const rows=records.filter(r=>Date.parse(r[1])>=a&&Date.parse(r[1])<z);return {n:rows.length,onTime:rows.filter(r=>r[2]&&Date.parse(r[2])<=Date.parse(r[1])&&Date.parse(r[2])<=snap).length,complete:rows.filter(r=>r[2]&&Date.parse(r[2])<=snap).length};}function minute(v){if(!/^\d{2}:\d{2}$/.test(v))return null;const [h,m]=v.split(':').map(Number);return h<24&&m<60?h*60+m:null;}function intervals(start,end,list=events){if(start===null||end===null||!Number.isFinite(start)||!Number.isFinite(end)||end<=start||list.some(e=>!Number.isFinite(e.start)||!Number.isFinite(e.end)||e.end<=e.start))return null;const clipped=list.map(e=>({id:e.id,start:Math.max(start,e.start),end:Math.min(end,e.end)})).filter(e=>e.end>e.start).sort((a,b)=>a.start-b.start);const merged=[];for(const e of clipped){const last=merged[merged.length-1];if(last&&e.start<=last.end)last.end=Math.max(last.end,e.end);else merged.push({start:e.start,end:e.end});}const raw=clipped.reduce((n,e)=>n+e.end-e.start,0),union=merged.reduce((n,e)=>n+e.end-e.start,0),planned=end-start;return {clipped,merged,raw,union,overlap:raw-union,planned,runtime:planned-union,availability:100*(planned-union)/planned};}const clock=n=>String(Math.floor(n/60)).padStart(2,'0')+':'+String(n%60).padStart(2,'0');function draw(){const start=minute($('planned-start').value),end=minute($('planned-end').value),r=intervals(start,end);text('calc-error',r?'':'NOT CALCULABLE — enter a valid same-day window with end after start.');['union-minutes','runtime','planned-minutes','availability','overlap','availability-state'].forEach(k=>text(k,'—'));if(!r){$('timeline').innerHTML='';text('interval-explanation','No valid interval calculation.');$('calc-text').value='';return;}text('union-minutes',r.union+' min');text('overlap',r.raw+' raw min − '+r.overlap+' overlapping min');text('runtime',r.runtime+' min');text('planned-minutes','of '+r.planned+' planned min');const covered=$('coverage').checked;text('availability',covered?r.availability.toFixed(1)+'%':'Not measurable');text('availability-state',covered?'Conditional fictional result':'Coverage is unconfirmed; no measured availability claim.');const list=view==='raw'?r.clipped:r.merged;$('timeline').innerHTML='<div class="axis">'+clock(start)+' — planned window — '+clock(end)+' UTC</div>'+list.map((e,i)=>'<div class="lane"><span>'+(view==='raw'?e.id:'Union '+(i+1))+'</span><div class="track"><div class="bar" style="left:'+100*(e.start-start)/r.planned+'%;width:'+100*(e.end-e.start)/r.planned+'%"></div></div><b>'+clock(e.start)+'–'+clock(e.end)+'</b></div>').join('');text('interval-explanation',view==='raw'?'Raw clipped event durations sum to '+r.raw+' minutes. The overlapping '+r.overlap+' minutes cannot be counted twice.':'Merged intervals total '+r.union+' minutes, with overlaps counted once.');$('calc-text').value='Asset A · 2026-10-13 UTC\nPlanned: '+clock(start)+'–'+clock(end)+' = '+r.planned+' min\nRaw clipped total: '+r.raw+' min\nOverlap: '+r.overlap+' min\nUnion downtime: '+r.union+' min\nRuntime: '+r.runtime+' min\nAvailability: '+(covered?r.availability.toFixed(1)+'% (conditional fictional coverage)':'NOT MEASURABLE: coverage unconfirmed')+'\nPM September: 2/4 on time = 50%; 3/4 completed by period end = 75%.';}const p=pmMetrics(pm);text('pm-on-time',(100*p.onTime/p.n).toFixed(0)+'%');text('pm-complete',(100*p.complete/p.n).toFixed(0)+'%');text('pm-numerator',p.onTime+' of '+p.n+' due records');$('pm-rows').innerHTML=pm.map((r,i)=>'<tr><td>'+r[0]+'</td><td>'+r[1].replace('T',' ').replace(':00Z','')+'</td><td>'+(r[2]?r[2].replace('T',' ').replace(':00Z',''):'Open')+'</td><td>'+['On time','Late; completed by snapshot','On time','Open at snapshot','Outside September due cohort'][i]+'</td></tr>').join('');on('raw-view',()=>{view='raw';draw();});on('merged-view',()=>{view='merged';draw();});on('restore-window',()=>{$('planned-start').value='09:00';$('planned-end').value='17:00';$('coverage').checked=true;draw();});['planned-start','planned-end'].forEach(k=>bind(k,draw));bind('coverage',draw,'change');on('select-calc-text',()=>selectText('calc-text'));draw();globalThis.app={pmMetrics,intervals,minute,get:()=>view};</script></body></html>