--- import "../styles/global.css"; import { loadBudget } from "../data/load.ts"; import { multiYearLayoutBothFlows } from "../lib/icicle.ts"; import { fmtEuroCompact, fmtEuroCompactSigned } from "../lib/format.ts"; import Icicle from "../components/Icicle.astro"; import type { Flow } from "../data/types.ts"; import { buildNotesByPath, type PgSections } from "../lib/pg-notes.ts"; import { buildBreakdownsByPath } from "../lib/breakdowns.ts"; import pgSectionsRaw from "../../data/extracted/pg-sections-2026.json"; const tree = await loadBudget(); const pgNotesByPath = buildNotesByPath(tree, pgSectionsRaw as PgSections); const breakdownsByPath = buildBreakdownsByPath( tree, pgSectionsRaw as Parameters[1], ); // Default scene per the brief §5: Aufwendungen selected, year = the new // draft (2026). Latest data point is 2028 but the *editorial* default is // the current draft year, which is the news. const defaultFlow: Flow = "aufwendungen"; const defaultYear = 2026; const bothFlows = multiYearLayoutBothFlows( tree, defaultYear, defaultFlow, breakdownsByPath, ); // Aufwendungen layout is the active one at SSR (default flow); both // flows render via the Icicle component, but the year slider and the // initial overview totals key off this one. const multiYear = bothFlows.byFlow.aufwendungen; // Year-slider data: each year's two-bar histogram (Aufwendungen + // Erträge), normalized to the larger of the two flows' max year totals // so the heights are directly comparable across flows. const maxErtrag = multiYear.years.reduce( (m, y) => Math.max(m, tree.totals[y]?.ertraege.total ?? 0), 0, ); const sliderMax = Math.max(multiYear.maxTotal, maxErtrag); const defaultYearIdx = multiYear.years.indexOf(defaultYear); // Per-year overview data — drives the sidebar's overview card content // when the user scrubs through years. Includes both flows' totals plus // the largest Bereich for each year (for the "Größter Posten" line). type OverviewEntry = { year: number; aufwand: number; ertrag: number; saldo: number; largestName: string; largestValue: number; largestShare: number; }; const yearOverviews: OverviewEntry[] = multiYear.years.map((year) => { const aufwand = tree.totals[year]?.aufwendungen.total ?? 0; const ertrag = tree.totals[year]?.ertraege.total ?? 0; const saldo = ertrag - aufwand; let largestName = ""; let largestValue = 0; for (const b of tree.produktbereiche) { const v = b.totals[year]?.aufwendungen.total ?? 0; if (v > largestValue) { largestValue = v; largestName = b.name; } } return { year, aufwand, ertrag, saldo, largestName, largestValue, largestShare: aufwand > 0 ? largestValue / aufwand : 0, }; }); const totals = tree.totals[defaultYear]; const ertragTotal = totals?.ertraege.total ?? 0; const aufwandTotal = totals?.aufwendungen.total ?? 0; const saldo = ertragTotal - aufwandTotal; const yearStart = tree.years[0]!; const yearEnd = tree.years.at(-1)!; --- Wo die Stadt Münster ihr Geld ausgibt — Haushaltsentwurf 2026/2027

Stadt Münster · Haushaltsentwurf 2026/2027

Wo die Stadt Münster ihr Geld .

Haushalt 2008–2028 · Gesamtvolumen pro Jahr

{ multiYear.years.map((y, i) => { const aufwand = multiYear.totals[i] ?? 0; const ertrag = tree.totals[y]?.ertraege.total ?? 0; const aufwandRatio = sliderMax > 0 ? aufwand / sliderMax : 0; const ertragRatio = sliderMax > 0 ? ertrag / sliderMax : 0; const isCurrent = y === defaultYear; const isPast = !(y > 2024); return ( ); }) }