/** * 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; // 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 | 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 } | 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; };