fix(strommix): bypass CMS for Energy-Charts production — upstream now needs explicit start/end
Energy-Charts' /public_power endpoint started rejecting requests without
`start` + `end` query params ("HTTP 404: no content available"). The
CMS-cached `strommix_de` external schema cannot pass dynamic time
windows (external-collections v1 has no runtime-vars), so the per-field
fallback chain was effectively dead — when SMARD's load filter lagged
12+ h, both sources returned null and the widget surfaced "Lastwert
nicht verfügbar" with all zeros.
Aggregator now fetches /public_power directly with a 2-hour window,
picks the latest non-null per series, and feeds those values into the
existing per-field fallback. CMS strommix_de schema becomes obsolete
for the widget but stays available for admin debugging.
Same shape as the previous CMS payload, so the fallback merge is
unchanged. Picks `Wind onshore`, `Wind offshore`, `Solar`, `Biomass`,
`Hydro Run-of-River`, `Fossil brown coal / lignite`, `Fossil hard
coal`, `Fossil gas`, `Nuclear`, `Load` from production_types.
This commit is contained in:
@@ -46,6 +46,66 @@ type StrommixRaw = {
|
||||
measured_at_unix?: number | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetch Energy-Charts /public_power directly with an explicit time
|
||||
* window. The CMS-cached `strommix_de` schema cannot do this because
|
||||
* external-collections v1 has no runtime-vars; without start/end the
|
||||
* upstream returns 404 ("no content available"). 2-hour window covers
|
||||
* latest 8 quarter-hour slots; we pick the latest non-null per series.
|
||||
*/
|
||||
type PublicPowerSeries = { name: string; data: (number | null)[] };
|
||||
type PublicPowerResp = {
|
||||
unix_seconds?: number[];
|
||||
production_types?: PublicPowerSeries[];
|
||||
};
|
||||
|
||||
function lastNonNull(arr: (number | null)[]): number | null {
|
||||
for (let i = arr.length - 1; i >= 0; i--) {
|
||||
const v = arr[i];
|
||||
if (typeof v === "number" && Number.isFinite(v)) return v;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function fetchEnergyChartsPublicPower(
|
||||
fetcher: typeof fetch
|
||||
): Promise<{ data: StrommixRaw | null; stale: boolean }> {
|
||||
try {
|
||||
const nowSec = Math.floor(Date.now() / 1000);
|
||||
const fmt = (sec: number) =>
|
||||
new Date(sec * 1000).toISOString().slice(0, 16) + "Z";
|
||||
const start = fmt(nowSec - 2 * 60 * 60);
|
||||
const end = fmt(nowSec);
|
||||
const url =
|
||||
`https://api.energy-charts.info/public_power?country=de` +
|
||||
`&start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
|
||||
const res = await fetcher(url, { signal: AbortSignal.timeout(8_000) });
|
||||
if (!res.ok) return { data: null, stale: false };
|
||||
const d = (await res.json()) as PublicPowerResp;
|
||||
const find = (n: string) =>
|
||||
d.production_types?.find((p) => p.name === n)?.data ?? [];
|
||||
const ts = d.unix_seconds ?? [];
|
||||
return {
|
||||
data: {
|
||||
wind_onshore_mw: lastNonNull(find("Wind onshore")),
|
||||
wind_offshore_mw: lastNonNull(find("Wind offshore")),
|
||||
solar_mw: lastNonNull(find("Solar")),
|
||||
biomass_mw: lastNonNull(find("Biomass")),
|
||||
hydro_mw: lastNonNull(find("Hydro Run-of-River")),
|
||||
lignite_mw: lastNonNull(find("Fossil brown coal / lignite")),
|
||||
hard_coal_mw: lastNonNull(find("Fossil hard coal")),
|
||||
gas_mw: lastNonNull(find("Fossil gas")),
|
||||
nuclear_mw: lastNonNull(find("Nuclear")),
|
||||
load_mw: lastNonNull(find("Load")),
|
||||
measured_at_unix: ts[ts.length - 1] ?? null,
|
||||
},
|
||||
stale: false,
|
||||
};
|
||||
} catch {
|
||||
return { data: null, stale: true };
|
||||
}
|
||||
}
|
||||
|
||||
type CrossborderRaw = {
|
||||
flow_at_gw?: number | null;
|
||||
flow_be_gw?: number | null;
|
||||
@@ -129,10 +189,11 @@ async function fetchPriceAtNow(
|
||||
export const GET: RequestHandler = async ({ fetch }) => {
|
||||
const [smardRaw, energyCharts, cross, priceDe, priceFr] = await Promise.all([
|
||||
fetchSmardSnapshot(fetch),
|
||||
// Energy-Charts via the CMS — used as per-field fallback when SMARD
|
||||
// returns null for a specific filter (notably load 410 when SMARD's
|
||||
// pipeline lags more than the production filters).
|
||||
fetchOne<StrommixRaw>("strommix_de", fetch),
|
||||
// Energy-Charts /public_power direct (with explicit time range —
|
||||
// upstream now returns 404 without start/end). Used as per-field
|
||||
// fallback when SMARD's load filter (id 410) lags 12+ hours behind
|
||||
// the production filters and the SMARD snapshot gets discarded.
|
||||
fetchEnergyChartsPublicPower(fetch),
|
||||
fetchOne<CrossborderRaw>("crossborder_de", fetch),
|
||||
fetchPriceAtNow("DE-LU", fetch),
|
||||
fetchPriceAtNow("FR", fetch),
|
||||
|
||||
Reference in New Issue
Block a user