4790d571fd
- New "hero" variant between compact and full: pct + status + sparkline + trend, no sub-cards or accordion - 24h Wind+Solar sparkline below hero pct, accent-colored - Trend arrow vs previous hour with delta percentage points - Polling pauses on tab hidden, refetches on visibility return if last fetch >5min old - Skeleton loader replaces "Lade…" text (matches final layout) - Retry button on error state (silent vs explicit fetch) - Stale-flag now prominent red badge in header bar - Stand-time wrapped in <time datetime title> for full-date hover - loadMw=0 falls back to last hourly slot instead of red error - Negative-price banner replaces standalone price card (dedup) - Production breakdown grid: clean auto/1fr label-value alignment - Print CSS: details auto-open, chevrons hidden - All German strings moved to translation keys - STROMMIX_DEFAULT_THRESHOLDS const as single source for buckets Includes data-block="LiveStrommix" attribute for the new identifier scheme + new RenewableSparkline.svelte component. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
122 lines
4.9 KiB
TypeScript
122 lines
4.9 KiB
TypeScript
/**
|
||
* Shared types for the Strommix-Live-Widget.
|
||
*
|
||
* The aggregator route at `/api/strommix` builds this payload from the
|
||
* four CMS external collections; the `StrommixBlock.svelte` component
|
||
* consumes it.
|
||
*/
|
||
|
||
/** Default percentage thresholds (Wind+Solar / Last × 100) for bucket
|
||
* classification. Single source of truth — both the CMS schema
|
||
* (`live_strommix.json5`) and the block (`StrommixBlock.svelte`) read
|
||
* these. Editor can override per-instance via `threshold_*` fields. */
|
||
export const STROMMIX_DEFAULT_THRESHOLDS = {
|
||
dunkelflaute: 15,
|
||
niedrig: 35,
|
||
mittel: 60,
|
||
} as const;
|
||
|
||
export type StrommixResponse = {
|
||
// Production-snapshot timestamp (Unix sec) — used as the "anchor" for
|
||
// the rest of the values. Cross-border + prices may have their own,
|
||
// earlier or later, timestamps.
|
||
measuredAtUnix: number | null;
|
||
/** Where production + load came from. `smard` = pure SMARD (canonical);
|
||
* `smard+energy-charts` = SMARD with Energy-Charts as per-field
|
||
* fallback for filters SMARD couldn't resolve at the snapshot's slot
|
||
* (typically the load filter when SMARD's pipeline lags more than
|
||
* the production filters); `energy-charts` = SMARD entirely
|
||
* unreachable, full fallback. */
|
||
dataSource: "smard" | "smard+energy-charts" | "energy-charts";
|
||
// Production (MW)
|
||
windOnshoreMw: number;
|
||
windOffshoreMw: number;
|
||
solarMw: number;
|
||
biomassMw: number;
|
||
hydroMw: number;
|
||
ligniteMw: number;
|
||
hardCoalMw: number;
|
||
gasMw: number;
|
||
nuclearMw: number;
|
||
// Aggregates (MW)
|
||
loadMw: number;
|
||
weatherRenewableMw: number;
|
||
conventionalMw: number;
|
||
// Cross-border (GW; positive = net export from DE, negative = import)
|
||
netFlowGw: number;
|
||
/** When was the cross-border saldo last reported by upstream? cbpf
|
||
* typically lags 1–2 h behind real time. Widget compares to
|
||
* `measuredAtUnix` to decide whether to show "Stand HH:MM". */
|
||
crossborderMeasuredAtUnix: number | null;
|
||
flowsByCountry: Record<string, number>;
|
||
// Prices (EUR/MWh; null if upstream unavailable). Picked from the
|
||
// 15-min day-ahead slot whose interval contains "now", not the last
|
||
// non-null cell of the array.
|
||
priceDeEurMwh: number | null;
|
||
priceFrEurMwh: number | null;
|
||
/** Unix sec of the price-slot start that produced `priceDeEurMwh`. */
|
||
priceMeasuredAtUnix: number | null;
|
||
// Stale flags surfaced from the CMS' `x-rustycms-external-stale` header
|
||
staleStrommix: boolean;
|
||
staleCrossborder: boolean;
|
||
stalePrice: boolean;
|
||
|
||
// ---------------------------------------------------------------------
|
||
// Historical aggregates (sourced from CMS `_history` endpoint over
|
||
// raw upstream snapshots; null = no data yet for this widget).
|
||
// ---------------------------------------------------------------------
|
||
|
||
/** Today's residual-load curve. One entry per upstream slot (typically
|
||
* 15 min). `loadMw - weatherRenewableMw` = the gap conventional plants
|
||
* + imports must fill. `null` when history backend is empty / disabled. */
|
||
todayHourly: ResidualLoadPoint[] | null;
|
||
|
||
/** Negative-price hours year-to-date (DE-LU, day-ahead). Counted by
|
||
* iterating history snapshots and summing 15-min slots whose price
|
||
* was below 0. */
|
||
negPriceHoursYtd: number | null;
|
||
|
||
/** Cross-border net balance YTD in GWh (positive = net export from DE,
|
||
* negative = net import). */
|
||
netCrossborderGwhYtd: number | null;
|
||
|
||
/** Per-country YTD balance, same sign convention as `netFlowGw`. */
|
||
crossborderByCountryGwhYtd: Record<string, number> | null;
|
||
|
||
/** Cross-border balance for *today only*, GWh per country and total.
|
||
* Same sign convention. `null` when crossborder history is empty. */
|
||
crossborderTodayGwh: { total: number; perCountry: Record<string, number> } | null;
|
||
|
||
/** Most recent contiguous run of `(wind+solar) / load < 10%`. `null`
|
||
* when no such run exists in retained history. `endedHoursAgo: 0`
|
||
* means the run is still ongoing (last slot is within the threshold). */
|
||
lastDunkelflaute: DunkelflautePhase | null;
|
||
|
||
/** Top 5 longest Dunkelflaute phases in retained history, newest first
|
||
* if equal length. Empty array when history exists but no qualifying
|
||
* phase found. `null` when history is unavailable. */
|
||
worstDunkelflauten: DunkelflautePhase[] | null;
|
||
};
|
||
|
||
/** One upstream slot in the residual-load series. */
|
||
export type ResidualLoadPoint = {
|
||
/** Unix seconds at the start of the slot. */
|
||
unix: number;
|
||
/** Total electrical load on the German grid (MW). */
|
||
loadMw: number;
|
||
/** Wind onshore + wind offshore + solar (MW). */
|
||
weatherRenewableMw: number;
|
||
};
|
||
|
||
/** A contiguous low-renewables run, computed from history snapshots. */
|
||
export type DunkelflautePhase = {
|
||
/** Unix sec of the first slot below the threshold. */
|
||
startUnix: number;
|
||
/** Run duration in hours (slot count × slot length). */
|
||
durationH: number;
|
||
/** Lowest Wind+Solar share observed during the run, percentage of load. */
|
||
minPct: number;
|
||
/** Hours since the run ended (0 = ongoing). */
|
||
endedHoursAgo: number;
|
||
};
|