stats B11: EP-Online energy labels (monthly total file → homes per label for NL, Gouda, Gouda PC4s; aggregates only), stats.ep_label_agg, monthly timer with key from ~/.config/stats/ep-online.env, charts on /stats/energy and /stats/gouda
Some checks failed
Deploy production / deploy (push) Has been cancelled
Some checks failed
Deploy production / deploy (push) Has been cancelled
This commit is contained in:
88
src/lib/stats/energylabels.ts
Normal file
88
src/lib/stats/energylabels.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
// EP-Online energy labels on /stats/energy and /stats/gouda (collector: scripts/stats/eponline.ts).
|
||||
import type { ChartTheme, StatPoint } from './chart';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
|
||||
/** Best to worst (NTA 8800 scale). */
|
||||
export const EP_LABELS = ['A++++', 'A+++', 'A++', 'A+', 'A', 'B', 'C', 'D', 'E', 'F', 'G'];
|
||||
/** Dark green → red, like the label sticker. */
|
||||
export const EP_COLORS = [
|
||||
'#00632f',
|
||||
'#00733a',
|
||||
'#008a44',
|
||||
'#00a24f',
|
||||
'#22b14c',
|
||||
'#8cc63e',
|
||||
'#fff200',
|
||||
'#fdb913',
|
||||
'#f37021',
|
||||
'#ed1c24',
|
||||
'#b5121b'
|
||||
];
|
||||
export const GOUDA_PC4 = Array.from({ length: 10 }, (_, i) => String(2800 + i));
|
||||
|
||||
export const epShareId = (label: string, area: string) => `eponline:share:${label}:${area}`;
|
||||
export const epHomesId = (area: string) => `eponline:homes:${area}`;
|
||||
export const epSeriesIds = (areas: string[]) =>
|
||||
areas.flatMap((a) => [epHomesId(a), ...EP_LABELS.map((l) => epShareId(l, a))]);
|
||||
|
||||
/** Share of homes with label A or better per snapshot. */
|
||||
export function aOrBetter(get: (id: string) => StatPoint[], area: string): StatPoint[] {
|
||||
const sum = new Map<string, number>();
|
||||
for (const l of EP_LABELS.slice(0, 5))
|
||||
for (const [p, v] of get(epShareId(l, area))) sum.set(p, (sum.get(p) ?? 0) + v);
|
||||
return [...sum].sort((a, b) => a[0].localeCompare(b[0]));
|
||||
}
|
||||
|
||||
/** Horizontal 100% stacked bars: one row per area, one segment per label (latest snapshot). */
|
||||
export function labelStackOption(
|
||||
theme: ChartTheme,
|
||||
rows: { name: string; shares: number[] }[]
|
||||
): EChartsCoreOption {
|
||||
const pct = new Intl.NumberFormat('en-GB', { maximumFractionDigits: 1 });
|
||||
return {
|
||||
animation: false,
|
||||
grid: { left: 8, right: 16, top: 36, bottom: 8, containLabel: true },
|
||||
legend: {
|
||||
top: 0,
|
||||
itemWidth: 12,
|
||||
itemHeight: 10,
|
||||
textStyle: { color: theme.muted, fontSize: 11 }
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
axisPointer: { type: 'shadow' },
|
||||
backgroundColor: theme.surface,
|
||||
borderColor: theme.border,
|
||||
textStyle: { color: theme.text, fontSize: 12 },
|
||||
valueFormatter: (v: number) => `${pct.format(v)}%`
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
max: 100,
|
||||
axisLabel: { color: theme.muted, fontSize: 11, formatter: '{value}%' },
|
||||
splitLine: { lineStyle: { color: theme.border, opacity: 0.6 } }
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
inverse: true,
|
||||
data: rows.map((r) => r.name),
|
||||
axisLabel: { color: theme.text, fontSize: 11 },
|
||||
axisLine: { show: false },
|
||||
axisTick: { show: false }
|
||||
},
|
||||
series: EP_LABELS.map((label, i) => ({
|
||||
name: label,
|
||||
type: 'bar',
|
||||
stack: 'labels',
|
||||
barMaxWidth: 22,
|
||||
itemStyle: { color: EP_COLORS[i] },
|
||||
data: rows.map((r) => r.shares[i] ?? 0),
|
||||
label: {
|
||||
show: true,
|
||||
color: i >= 6 && i <= 8 ? '#111' : '#fff',
|
||||
fontSize: 10,
|
||||
formatter: (p: { value: number }) => (p.value >= 6 ? `${pct.format(p.value)}` : '')
|
||||
}
|
||||
}))
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user