Deploy v2: Supply headroom bar + View buttons (760895 bytes)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Dockerfile.inline
|
||||||
|
deploy-nogit.sh
|
||||||
61
index.html
61
index.html
@@ -342,11 +342,18 @@ Check your Browserslist config to be sure that your targets are set up correctly
|
|||||||
<span class="font-mono text-[11px] uppercase tracking-[.04em] text-muted-2" x-text="s.loc"></span>
|
<span class="font-mono text-[11px] uppercase tracking-[.04em] text-muted-2" x-text="s.loc"></span>
|
||||||
<h3 class="mt-0.5 text-[17px] font-bold text-ink" x-text="s.name"></h3>
|
<h3 class="mt-0.5 text-[17px] font-bold text-ink" x-text="s.name"></h3>
|
||||||
<span class="mt-0.5 text-[12.5px] text-muted" x-text="s.load"></span>
|
<span class="mt-0.5 text-[12.5px] text-muted" x-text="s.load"></span>
|
||||||
<span class="mt-auto flex items-start gap-2 pt-3.5 text-[13px]">
|
<span class="block" x-html="headroomMini(s)"></span>
|
||||||
<span class="mt-px flex-none rounded-md border px-1.5 py-[3px] font-mono text-[10px] font-bold uppercase tracking-[.06em]"
|
<span class="mt-auto flex items-end justify-between gap-2 pt-3.5 text-[13px]">
|
||||||
:class="{'border-[#F1C7C3] bg-[#FBEAE8] text-red-ink':s.cardStatus.cls==='red','border-[#EED9B4] bg-brandamber-tint text-amber-ink':s.cardStatus.cls==='amber','border-[#BADFCB] bg-[#EAF6EF] text-green-ink':s.cardStatus.cls==='green'}"
|
<span class="flex min-w-0 items-start gap-2">
|
||||||
x-text="s.cardStatus.chip"></span>
|
<span class="mt-px flex-none rounded-md border px-1.5 py-[3px] font-mono text-[10px] font-bold uppercase tracking-[.06em]"
|
||||||
<span class="leading-snug text-ink/80" x-text="s.cardStatus.txt"></span>
|
:class="{'border-[#F1C7C3] bg-[#FBEAE8] text-red-ink':s.cardStatus.cls==='red','border-[#EED9B4] bg-brandamber-tint text-amber-ink':s.cardStatus.cls==='amber','border-[#BADFCB] bg-[#EAF6EF] text-green-ink':s.cardStatus.cls==='green'}"
|
||||||
|
x-text="s.cardStatus.chip"></span>
|
||||||
|
<span class="leading-snug text-ink/80" x-text="s.cardStatus.txt"></span>
|
||||||
|
</span>
|
||||||
|
<span class="viewpill flex-none inline-flex items-center gap-1 rounded-full border border-line bg-card px-2.5 py-1 text-[11px] font-semibold text-muted transition group-hover:border-teal group-hover:bg-teal group-hover:text-white" aria-hidden="true">
|
||||||
|
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M1.5 8s2.4-4.5 6.5-4.5S14.5 8 14.5 8s-2.4 4.5-6.5 4.5S1.5 8 1.5 8Z"/><circle cx="8" cy="8" r="2.2"/></svg>
|
||||||
|
View
|
||||||
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</a>
|
</a>
|
||||||
@@ -410,6 +417,9 @@ Check your Browserslist config to be sure that your targets are set up correctly
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- SUPPLY HEADROOM -->
|
||||||
|
<div class="mt-4" x-html="headroomB()"></div>
|
||||||
|
|
||||||
<!-- LOAD BY PHASE -->
|
<!-- LOAD BY PHASE -->
|
||||||
<div class="mt-4 rounded-lg border border-line bg-card p-[18px] shadow-card">
|
<div class="mt-4 rounded-lg border border-line bg-card p-[18px] shadow-card">
|
||||||
<h2 class="mb-3.5 text-[11px] font-bold uppercase tracking-[.13em] text-muted">Load by phase <span id="phase-unitnote" class="text-[11px] font-medium normal-case tracking-normal text-muted-2">(Mean kW per hour)</span></h2>
|
<h2 class="mb-3.5 text-[11px] font-bold uppercase tracking-[.13em] text-muted">Load by phase <span id="phase-unitnote" class="text-[11px] font-medium normal-case tracking-normal text-muted-2">(Mean kW per hour)</span></h2>
|
||||||
@@ -1242,6 +1252,45 @@ Object.entries(phaseMeta).forEach(([id,m])=>{
|
|||||||
rebalance_kva:d.rebalance_kva, phase_note:m.note(d),
|
rebalance_kva:d.rebalance_kva, phase_note:m.note(d),
|
||||||
aggNote:PHASE_AGGNOTE[id]});
|
aggNote:PHASE_AGGNOTE[id]});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/* ---------- supply headroom (peak demand vs supply capacity, kVA) ---------- */
|
||||||
|
const HEADROOM={
|
||||||
|
kaimana:{supply:60,peak:44,flag:"Peaks near three-quarters of supply at the afternoon milking."},
|
||||||
|
coolstore:{supply:50,peak:41,flag:"Running at 82% of supply on peak — little room left for another load."},
|
||||||
|
riverside:{supply:40,peak:26,flag:"Comfortable — about a third of supply spare at peak."},
|
||||||
|
tikipunga:{supply:60,peak:22,flag:"Plenty of headroom — well under supply capacity."},
|
||||||
|
cookham:{supply:45,peak:28,flag:"Steady — around 60% of supply at the daytime peak."},
|
||||||
|
kamo:{supply:30,peak:12,flag:"Light load — large headroom on the supply."}
|
||||||
|
};
|
||||||
|
Object.entries(HEADROOM).forEach(([id,h])=>{ if(siteById[id]) Object.assign(siteById[id],h); });
|
||||||
|
function headroomTone(pct){ return pct>85?"#E23B34":(pct>=70?"#F59E0B":"#1F9E70"); }
|
||||||
|
function headroomBar(s,variant){
|
||||||
|
if(!s||!s.supply) return "";
|
||||||
|
const pct=Math.round(s.peak/s.supply*100), w=Math.min(100,pct), c=headroomTone(pct);
|
||||||
|
if(variant==="mini"){
|
||||||
|
return `<div class="mt-3" role="img" aria-label="Supply headroom: peak demand is ${pct} percent of the ${s.supply} kVA supply">
|
||||||
|
<div class="flex items-center justify-between text-[10.5px] text-muted">
|
||||||
|
<span class="uppercase tracking-[.06em] font-semibold">Supply headroom</span>
|
||||||
|
<b class="font-mono text-ink/80">${s.peak}/${s.supply} kVA · ${pct}%</b>
|
||||||
|
</div>
|
||||||
|
<div class="mt-1 h-1.5 w-full overflow-hidden rounded-full bg-surface2">
|
||||||
|
<i class="block h-full rounded-full" style="width:${w}%;background:${c}"></i>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
return `<div class="rounded-lg border border-line bg-card p-[18px] shadow-card">
|
||||||
|
<h2 class="mb-3 text-[11px] font-bold uppercase tracking-[.13em] text-muted">Supply headroom</h2>
|
||||||
|
<div class="flex items-baseline justify-between gap-3">
|
||||||
|
<span class="text-[13px] text-muted">Peak demand vs supply capacity</span>
|
||||||
|
<b class="font-mono text-[15px] text-ink">${s.peak} / ${s.supply} kVA · ${pct}%</b>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2.5 h-3 w-full overflow-hidden rounded-full bg-surface2" role="img" aria-label="Peak demand is ${pct} percent of the ${s.supply} kVA supply capacity">
|
||||||
|
<i class="block h-full rounded-full" style="width:${w}%;background:${c}"></i>
|
||||||
|
</div>
|
||||||
|
<p class="mt-3 text-[13px] text-ink/80">${s.flag}</p>
|
||||||
|
<p class="mt-2 text-[11.5px] italic text-muted-2">Peak demand from the meter’s per-phase apparent power; supply capacity is the site’s connection rating. Illustrative sample data.</p>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
const STATUS_LABEL={red:"Action needed",amber:"Opportunity",green:"Normal"};
|
const STATUS_LABEL={red:"Action needed",amber:"Opportunity",green:"Normal"};
|
||||||
const STATUS_RANK={red:0,amber:1,green:2};
|
const STATUS_RANK={red:0,amber:1,green:2};
|
||||||
|
|
||||||
@@ -1358,6 +1407,8 @@ window.dashboard=function(){
|
|||||||
healthB(){ const s=this.currentSite; return s?healthBlock(s):""; },
|
healthB(){ const s=this.currentSite; return s?healthBlock(s):""; },
|
||||||
watchB(){ const s=this.currentSite; return s?watchingList(s):""; },
|
watchB(){ const s=this.currentSite; return s?watchingList(s):""; },
|
||||||
actionB(){ const s=this.currentSite; return s?actionHtml(s):""; },
|
actionB(){ const s=this.currentSite; return s?actionHtml(s):""; },
|
||||||
|
headroomMini(s){ return headroomBar(s,"mini"); },
|
||||||
|
headroomB(){ const s=this.currentSite; return s?headroomBar(s,"full"):""; },
|
||||||
sankeyB(){ const s=this.currentSite; return s?sankeySVG(energyData[s.id].sankey):""; },
|
sankeyB(){ const s=this.currentSite; return s?sankeySVG(energyData[s.id].sankey):""; },
|
||||||
sankeyLeg(){ const s=this.currentSite; return s?sankeyLegend(energyData[s.id].sankey):""; },
|
sankeyLeg(){ const s=this.currentSite; return s?sankeyLegend(energyData[s.id].sankey):""; },
|
||||||
sankeyNote(){ const s=this.currentSite; return s?energyData[s.id].sankey.note:""; },
|
sankeyNote(){ const s=this.currentSite; return s?energyData[s.id].sankey.note:""; },
|
||||||
|
|||||||
Reference in New Issue
Block a user