feat: initial Münster Haushalt icicle viewer
Editorial single-page viewer for the City of Münster's 2026/2027 budget draft, built as an Astro v6 SPA with a 4-level zoomable icicle (Produktbereich → Produktgruppe → Category → Breakdown). Highlights: - Multi-flow data layer over the official open-data CSVs (Aufwendungen + Erträge, 2008–2028) with overlap reconciliation across plan years. - Year slider as a 21-year mini-histogram of both flows; drag-to-scrub and click-to-jump, with bars morphing via CSS transitions on SVG geometry attributes. - Vertically centred icicle with year-outline rectangles framing each year's relative budget size, à la Bostock's animated treemap. - Headline "ausgibt / einnimmt" toggle; sidebar Aufwendungen/Erträge rows double as flow toggles. Active flow in Aufwendungen-purple / Erträge-orange (OKLCH). - Click-to-zoom via path-keyed lookup with ZOOM_COL_BOUNDS that reallocate the depth axis per zoom state. Zoomed item moves to the sidebar; canvas shows its descendants only (no adjacent-block leaks). - Sidebar shows path-specific Aufwendungen/Erträge/Saldo plus the source-PDF Beschreibung; Erläuterungen behind a collapsed details. - Build-time PDF extraction (scripts/extract-pg-sections.mjs) parses 68 Produktgruppen' Beschreibung + Erläuterungen sections from Band 1, including 10 cells of structured Mio.-€ breakdowns (Steuern, Transferaufwendungen, etc.) that drive the level-4 view. - URL state sync for path, year, and flow via history.replaceState so any zoom is shareable. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+44
@@ -0,0 +1,44 @@
|
|||||||
|
# build output
|
||||||
|
dist/
|
||||||
|
# generated types
|
||||||
|
.astro/
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# logs
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
pnpm-debug.log*
|
||||||
|
|
||||||
|
|
||||||
|
# environment variables
|
||||||
|
.env
|
||||||
|
.env.production
|
||||||
|
|
||||||
|
# macOS-specific files
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
# jetbrains setting folder
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# collaborator (canvas) tool config — local-only
|
||||||
|
.collaborator/
|
||||||
|
|
||||||
|
# Source PDF archives — re-fetch via the URLs documented in
|
||||||
|
# CLAUDE.md. Excluded from git to keep the repo small (~190MB of
|
||||||
|
# binary content otherwise).
|
||||||
|
docs/sources/
|
||||||
|
|
||||||
|
# Source budget data (CSV/XLSX from opendata.stadt-muenster.de).
|
||||||
|
# Re-fetch via the URLs documented in CLAUDE.md. data/extracted/ is
|
||||||
|
# kept since it's derived from PDFs the runtime needs.
|
||||||
|
data/2023/
|
||||||
|
data/2024/
|
||||||
|
data/2025/
|
||||||
|
data/jahresabschluesse/
|
||||||
|
|
||||||
|
# Original font archives. The committed copies live in fonts/ (TTF
|
||||||
|
# source) and public/fonts/ (WOFF2 build output).
|
||||||
|
*.zip
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
# .impeccable.md — Design Context for ms-haushalt
|
||||||
|
|
||||||
|
## Design Context
|
||||||
|
|
||||||
|
### Users
|
||||||
|
Engaged Münster citizens and journalists. Mixed audience: some have read the actual Haushaltsplan, most haven't. They arrive because the new 2026/2027 draft is news, or because they want to make sense of "where their tax money goes." They're intelligent, time-pressured, and German-speaking. Many will share specific findings on social media or cite them in articles, so deep-linking and quoteable numbers matter. They're not finance experts — but treat them like adults, not like they need pictograms of houses and books.
|
||||||
|
|
||||||
|
### Brand Personality
|
||||||
|
**Editorial, generous, opinionated.** This is not a municipal portal and not a dashboard — it's a piece of civic data journalism, the kind of thing the SZ or ZEIT graphics desk would publish. It has a point of view about what's interesting in the data and uses generous space, strong typography, and confident hierarchy to make that view land.
|
||||||
|
|
||||||
|
Voice: composed, curious, never cute. Captions, not slogans. Numbers presented with precision and context, never as "look at this big number!" hero stats.
|
||||||
|
|
||||||
|
### Aesthetic Direction
|
||||||
|
**Print-rooted editorial.** Strong serif (or strong slab) display type for headlines and section openers. Asymmetric grid — not the seven-equal-cards reflex. Generous whitespace and rhythmic spacing. Off-white "paper" background with one or two confident accent colors that come from the data, not from a default palette.
|
||||||
|
|
||||||
|
The treemap is the protagonist. Everything else — typography, captions, side annotations, the time slider — frames it like a magazine spread frames its lead photograph.
|
||||||
|
|
||||||
|
**References to lean toward:** NYT/SZ/ZEIT graphics desks; the Pudding's longer pieces; Christoph Niemann's editorial illustration restraint; Massimo Vignelli's NYC subway wayfinding (typographic confidence + clarity). Swiss-grid discipline applied to a warmer print sensibility.
|
||||||
|
|
||||||
|
**Anti-references (do not look like):**
|
||||||
|
- Generic SaaS dashboards (card grids, primary-action buttons everywhere)
|
||||||
|
- German municipal websites (boxy, default fonts, forms-first)
|
||||||
|
- AI-slop hero pages (gradient text, glassmorphism, purple-blue gradients, neon accents on dark, oversized rounded-icon-above-heading)
|
||||||
|
- PowerPoint infographics (cute icons, decorative chart junk, pie charts, clip art)
|
||||||
|
|
||||||
|
### Theme
|
||||||
|
**Light only, paper-feeling.** Off-white background tinted slightly warm (think uncoated stock, not pure white). Treemap tiles carry the saturated color; the rest of the page is restrained.
|
||||||
|
|
||||||
|
### Language
|
||||||
|
**German only.** Source data is German, audience is local, register matches the underlying budget docs (formal but not bureaucratic).
|
||||||
|
|
||||||
|
### Design Principles
|
||||||
|
1. **The treemap is the page.** Layout, type, color, and motion all serve the act of reading it. No competing focal points.
|
||||||
|
2. **Print logic first.** Asymmetric, typographic, quiet. If it could appear in a Sunday paper supplement, it's on track.
|
||||||
|
3. **Numbers in context, never alone.** Every figure is paired with what it's a share of, how it changed, or what it pays for. Bare figures are forbidden.
|
||||||
|
4. **Data does the talking.** Where copy exists, it points at the data — it doesn't compete with it. No editorializing tone, no exclamation marks, no "did you know?" framings.
|
||||||
|
5. **Restraint is the accent.** One serif display face, one body face, one or two functional sans usages, one accent color, one motion vocabulary. Variety comes from rhythm, not from adding more elements.
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project intent
|
||||||
|
|
||||||
|
Build an interactive treemap-based viewer of the City of Münster budget so non-experts can navigate where money comes from and where it goes. The 2026/2027 draft (`Haushaltsplanentwurf`) is the primary target; prior years and historical actuals are included for trend/context views.
|
||||||
|
|
||||||
|
No application code exists yet — this repo currently holds only source data.
|
||||||
|
|
||||||
|
## Data layout
|
||||||
|
|
||||||
|
```
|
||||||
|
data/
|
||||||
|
2023/ Haushaltsplan 2023 (xlsx + csv)
|
||||||
|
2024/ Haushaltsplan 2024-2027 (xlsx + csv)
|
||||||
|
2025/ Haushaltsplan 2025-2028 (xlsx + csv) ← latest machine-readable plan
|
||||||
|
jahresabschluesse/ Jahresabschlüsse 2008-2022 (xlsx + csv) historical actuals
|
||||||
|
docs/sources/
|
||||||
|
2024/ 2025/ 2026_2027/ Original PDFs (Bände 1+2, Satzung, Reden)
|
||||||
|
```
|
||||||
|
|
||||||
|
Source: opendata.stadt-muenster.de (CSV/XLSX) and stadt-muenster.de (PDFs). License: Datenlizenz Deutschland Namensnennung 2.0.
|
||||||
|
|
||||||
|
The 2026/2027 draft is **not yet on the open-data portal** — only PDF. The 2025 file does include 2026/2027/2028 *planning* values, which is the closest machine-readable forward projection until the new draft is published as data.
|
||||||
|
|
||||||
|
## Data schema (the part you must read multiple files to understand)
|
||||||
|
|
||||||
|
CSV files share one shape across all years:
|
||||||
|
|
||||||
|
- **Separator:** `;` — **encoding:** UTF-8 with BOM
|
||||||
|
- **Number format:** German — `.` thousands, `,` decimal (e.g. `-733.670.000,00`)
|
||||||
|
- **Sign convention:** **revenues are negative, expenses are positive** (NRW kameral/doppik convention). Normalize/flip before display.
|
||||||
|
- **Hierarchy keys:** `Produktbereich` → `Produktgruppe`. Both can take the literal value `Gesamt` to denote a roll-up row — those rows are subtotals/totals, not leaves. Filter or use them deliberately; never sum them alongside leaves.
|
||||||
|
- **Time key:** `Geschäftsjahr` — each plan file contains multiple years (current + 3 planning years), so a single CSV is long-format across years.
|
||||||
|
- **Categories (columns):** ~19 fixed financial line items. Revenue side includes `Steuern und ähnliche Abgaben`, `Zuwendungen und allgemeine Umlagen`, `Öffentlich-rechtliche Leistungsentgelte`, etc. Expense side includes `Personalaufwendungen`, `Aufwendungen für Sach- und Dienstleistungen`, `Transferaufwendungen`, `Bilanzielle Abschreibungen`, etc. The plan files have one extra column `Globaler Minderaufwand` that the Jahresabschluss file doesn't.
|
||||||
|
- **Header typo to preserve:** `Öffentlich-rechtliche Leistunngsentgelte` (double-n) appears in the source — don't "fix" it on read; map it.
|
||||||
|
- **Spelling drift across files:** 2025 plan uses `algemeine Umlagen` (single-l), Jahresabschluss uses `allgemeine`. Treat as the same field.
|
||||||
|
|
||||||
|
The two-level `Produktbereich/Produktgruppe` hierarchy is the natural treemap nesting; the ~19 category columns are switchable views (revenue vs expense, or a single category drilled across products).
|
||||||
|
|
||||||
|
## Design Context
|
||||||
|
|
||||||
|
(Mirror of `.impeccable.md`. The full design brief lives in `docs/design-brief.md`.)
|
||||||
|
|
||||||
|
### Users
|
||||||
|
Engaged Münster citizens and journalists. German-speaking, intelligent, time-pressured. Mixed familiarity with budget docs. Many will share findings or cite specific numbers, so deep-linking and quoteable figures matter. Treat them like adults — no pictograms, no hand-holding tone.
|
||||||
|
|
||||||
|
### Brand Personality
|
||||||
|
**Editorial, generous, opinionated.** A piece of civic data journalism, not a municipal portal and not a dashboard. Voice: composed, curious, never cute. Numbers presented with precision and context, never as "look at this big number" hero stats.
|
||||||
|
|
||||||
|
### Aesthetic Direction
|
||||||
|
**Print-rooted editorial.** Strong serif/slab display type, asymmetric grid, generous whitespace, off-white "paper" background tinted slightly warm. The treemap is the protagonist; everything else frames it like a magazine spread frames its lead photograph. Lean toward NYT/SZ/ZEIT graphics-desk references, the Pudding's longer pieces, Vignelli-grade typographic confidence.
|
||||||
|
|
||||||
|
**Anti-references** (do not look like): generic SaaS dashboards, German municipal portals, AI-slop hero pages (gradient text, glassmorphism, purple-blue gradients), PowerPoint infographics.
|
||||||
|
|
||||||
|
### Theme & Language
|
||||||
|
Light only, paper-feeling. German only.
|
||||||
|
|
||||||
|
### Design Principles
|
||||||
|
1. **The treemap is the page.** Layout, type, color, and motion all serve reading it.
|
||||||
|
2. **Print logic first.** Asymmetric, typographic, quiet. Sunday-paper-supplement-grade.
|
||||||
|
3. **Numbers in context, never alone.** Every figure paired with its share, change, or what it pays for.
|
||||||
|
4. **Data does the talking.** Where copy exists, it points at data — never competes with it.
|
||||||
|
5. **Restraint is the accent.** One display face, one body face, one accent system, one motion vocabulary.
|
||||||
|
|
||||||
|
### Color decided
|
||||||
|
Two-hue system: *Aufwendungen* and *Erträge* each get a hue family in OKLCH on the warm-paper background. Tile lightness/chroma encodes value within a flow. No red/green moralizing. Specific hues to be auditioned during craft.
|
||||||
|
|
||||||
|
### Stack decided
|
||||||
|
Astro + D3 islands. Static-first. Deep-linkable URLs. SSR for share targets.
|
||||||
|
|
||||||
|
## When extending
|
||||||
|
|
||||||
|
- Re-downloading data: see the URLs in conversation history or fetch the dataset pages on opendata.stadt-muenster.de (`/dataset/haushaltsplan-YYYY-der-stadt-münster`). Watch for the 2026/2027 plan to appear there — that should replace the 2025 file as the primary source.
|
||||||
|
- PDFs in `docs/sources/` are the authoritative narrative; the CSVs are the data. Don't try to OCR the PDFs while structured data exists.
|
||||||
@@ -1,3 +1,53 @@
|
|||||||
|
⠀
|
||||||
|
|
||||||
# ms-haushalt
|
# ms-haushalt
|
||||||
|
|
||||||
Visualisierung der Haushalte der Stadt Münster
|
# Visualisierung der Haushalte der Stadt Münster
|
||||||
|
|
||||||
|
# Astro Starter Kit: Minimal
|
||||||
|
|
||||||
|
```sh
|
||||||
|
pnpm create astro@latest -- --template minimal
|
||||||
|
```
|
||||||
|
|
||||||
|
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||||
|
|
||||||
|
## 🚀 Project Structure
|
||||||
|
|
||||||
|
Inside of your Astro project, you'll see the following folders and files:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/
|
||||||
|
├── public/
|
||||||
|
├── src/
|
||||||
|
│ └── pages/
|
||||||
|
│ └── index.astro
|
||||||
|
└── package.json
|
||||||
|
```
|
||||||
|
|
||||||
|
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
|
||||||
|
|
||||||
|
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
|
||||||
|
|
||||||
|
Any static assets, like images, can be placed in the `public/` directory.
|
||||||
|
|
||||||
|
## 🧞 Commands
|
||||||
|
|
||||||
|
All commands are run from the root of the project, from a terminal:
|
||||||
|
|
||||||
|
| Command | Action |
|
||||||
|
| ---------------------- | ------------------------------------------------ |
|
||||||
|
| `pnpm install` | Installs dependencies |
|
||||||
|
| `pnpm dev` | Starts local dev server at `localhost:4321` |
|
||||||
|
| `pnpm build` | Build your production site to `./dist/` |
|
||||||
|
| `pnpm preview` | Preview your build locally, before deploying |
|
||||||
|
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||||
|
| `pnpm astro -- --help` | Get help using the Astro CLI |
|
||||||
|
|
||||||
|
## 👀 Want to learn more?
|
||||||
|
|
||||||
|
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||||
|
|
||||||
|
> a8d3b5f (feat: initial Münster Haushalt icicle viewer)
|
||||||
|
|
||||||
|
⠀
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
// @ts-check
|
||||||
|
import { defineConfig } from 'astro/config';
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
export default defineConfig({});
|
||||||
@@ -0,0 +1,730 @@
|
|||||||
|
{
|
||||||
|
"1001": {
|
||||||
|
"pgNumber": "1001",
|
||||||
|
"name": "Bauaufsicht und baurechtliche Beratung",
|
||||||
|
"beschreibung": "Diese Produktgruppe umfasst alle von der Stadt Münster wahrzunehmenden Aufgaben der Unteren Bauaufsichtsbehörde. Hierzu gehört die Beratung, Prüfung und Überwachung, so dass bei der Errichtung, der\nÄnderung, dem Abbruch, der Nutzung, der Nutzungsänderung sowie der Instandhaltung baulicher Anlagen sowie anderer Anlagen und Einrichtungen die öffentlich rechtlichen Vorschriften eingehalten werden.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1002": {
|
||||||
|
"pgNumber": "1002",
|
||||||
|
"name": "Denkmalschutz und Denkmalpflege",
|
||||||
|
"beschreibung": "Die Produktgruppe \"Denkmalschutz und Denkmalpflege\" ist deckungsgleich mit dem einzigen gleichnamigen Produkt, so dass die Beschreibung identisch ist.\n\nDie Produktgruppe umfasst folgende Schwerpunkte:\n- die Identifizierung bzw. Unterschutzstellung von Bau-, Boden- und Gartendenkmalen, Eintragung in die Denkmalliste der Stadt Münster soweit diese nicht in der Liste der Bodendenkmäler beim Denkmalfachamt\ngeführt werden\n- sonderbehördliche Genehmigungen (denkmalrechtliche Erlaubnisse) sowie Ordnungswidrigkeits- und Verwaltungsstreitverfahren\n- die Ermittlung und fachliche Bewertung der Grundlagen für bestehende und potenzielle neue Bau- und Bodendenkmale\n- die Aufstellung von Denkmalbereichssatzungen\n- die Denkmalförderung einschl. Ausstellung steuerlicher Bescheinigungen\n- die intensive Beratung von Eigentümern, Bauherrn und Investoren einschl. Öffentlichkeitsarbeit\n- archäologische Recherchen und Untersuchungen einschl. Grabungsmanagement und Dokumentation/Öffentlichkeitsarbeit.\n\nEs besteht ein enger inhaltlicher Bezug zum Produkt Stadtgestaltung, der sowohl konzeptionell als auch flächen- und objektbezogen wirkt (städtebaulicher Denkmalschutz).",
|
||||||
|
"erlaeuterungen": "zu Zeile 6\nKostenerstattung von Eigentümern für die Durchführung von archäologischen Untersuchungen auf ihren Grundstücken - Grundlage jeweils vertragliche Grabungsvereinbarung\n\nzu Zeile 11\nIn Zeile 11 sind zweckgebundene Personalaufwendungen für die Durchführung von archäologischen Untersuchungen veranschlagt, die zu 100 % durch Kostenerstattungen von Eigentü-\nmern in Zeile 6 ausgeglichen werden.\n\nZu Zeile 28\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells)",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1003": {
|
||||||
|
"pgNumber": "1003",
|
||||||
|
"name": "Wohnen",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst die vier Produkte:\n- Wohnraumförderung\n- Hilfen zur Wohnraumversorgung\n- Wohnungsaufsicht- und -sicherung, Mietspiegel\n- Planung- und Quartiersentwicklung.\nGrundsätzliche Zielrichtung bei der Erfüllung aller Aufgaben der Produktgruppe ist, für die Bürgerinnen und Bürger bezahlbaren Wohnraum mit gesunden Wohnverhältnissen ausgestattet nach ihren individuellen\nBedürfnissen zu guten, insbesondere auch energetisch sparsamen Qualitäten mit möglichst geringem CO²-Ausstoß zu schaffen und zu sichern. Das Land Nordrhein-Westfalen und die Stadt Münster stellen für diese\nZwecke Mittel zur Wohnraumförderung bzw. zur Schaffung klimafreundlicher Wohngebäude sowie zum passiven Lärmschutz zur Verfügung.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nFür das Förderprogramm „Klimafreundliche Wohngebäude“ werden im Jahr 2026 Mittel in Höhe von 2.510.000 Euro und in den Jahren 2027 - 2030 jährlich jeweils\nMittel in Höhe von 2.410.000 Euro bereitgestellt.\n\nzu Zeile 16:\nBeim Projekt Quartiersarchitekt sind für das Jahr 2026 Aufwendungen in Höhe von 104.000 Euro und Erträge in Höhe von 56.000 Euro geplant.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1101": {
|
||||||
|
"pgNumber": "1101",
|
||||||
|
"name": "Abwasserbeseitigung",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die ordnungsgemäße, sichere, umweltgerechte und wirtschaftliche Ableitung und Reinigung von Abwasser in Verbindung mit der Planung, dem Bau und der Unterhaltung der dafür\nnotwendigen Anlagen (Kanäle, Druckrohrleitungen, Regenwasserbehandlungsanlagen, Pumpwerke und Kläranlagen). Die Abwasserbeseitigung unterteilt sich in die Abwasserableitung und -reinigung. Es handelt sich\num eine gebührenrechnende Einrichtung, die sich zu 100 % aus Gebühren- und Beitragseinnahmen finanziert.",
|
||||||
|
"erlaeuterungen": "zu Zeile 27:\nBei den internen Leistungsbeziehungen werden die Erträge von der Produktgruppe 1201 für die Erstattung der Niederschlagswassergebühr für die Entwässerung der öffentlichen Straßen\nabgebildet.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1102": {
|
||||||
|
"pgNumber": "1102",
|
||||||
|
"name": "Abfallwirtschaft (AWM)",
|
||||||
|
"beschreibung": "Die \"Abfallwirtschaftsbetriebe Münster\" sind eine eigenbetriebsähnliche Einrichtung der Stadt Münster. Sie werden gemäß der Eigenbetriebsverordnung sowie nach den Bestimmungen der Betriebssatzung für die\n\"Abfallwirtschaftsbetriebe Münster\" geführt. Zweck der eigenbetriebsähnlichen Einrichtung ist die Stadtreinigung inklusive Winterdienst und die Abfallwirtschaft im gesamten Stadtgebiet.\n\nErstmalig in 2024 ist ein Managementkontrakt vereinbart worden. Die Laufzeit beträgt 5 Jahre (vom 01.01.2024 bis zum 31.12.2028).",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1201": {
|
||||||
|
"pgNumber": "1201",
|
||||||
|
"name": "Bereitstellung von Verkehrsflächen und –anlagen",
|
||||||
|
"beschreibung": "Die Bereitstellung der öffentlichen Verkehrsflächen und -anlagen beinhaltet die konzeptionelle Mobilitätsplanung des Gesamtnetzes sowie die Planung, den Bau, die Erhaltung und die Finanzierung der öffentlichen\nStraßen, Wege, Plätze, Brücken, Tunnel, Lärmschutzwände, Beleuchtung, Lichtsignalanlagen (Ampeln), Parkscheinautomaten und des Parkleitsystems.\nDie konzeptionelle Mobilitätsplanung erfolgt entsprechend den Anforderungen an eine nachhaltige, zukunftsfähige, stadt- und umweltverträgliche Mobilitäts- und Verkehrsinfrastruktur. Die Planung erfolgt entsprechend\nden Anforderungen an die sichere und leistungsfähige Gestaltung des Straßenraumes unter Berücksichtigung der verkehrspolitischen bzw. verkehrsplanerischen Vorgaben. Im Rahmen der konkreten Straßenplanung\nmüssen entsprechende Bau- und Finanzierungsbeschlüsse sichergestellt, Analysen im Straßenverkehr vorgenommen, Öffentlichkeitsarbeit für Politiker, Bürger und sonstige Nutzer/Innen des öffentlichen\nVerkehrsnetzes sowie die Betreuung von Investoren und Ingenieurbüros geleistet werden. Anschließend erfolgt der Bau der öffentlichen Verkehrsflächen und -anlagen. Ein weiterer Schwerpunkt liegt in der Erhaltung\n(Unterhaltung, Instandsetzung und Erneuerung) des öffentlichen Verkehrsnetzes. Die Verkehrsflächen und -anlagen werden unter Beachtung der gesetzlichen Vorgaben so sicher gebaut und betrieben, dass von\nihnen keine Gefahr für die öffentliche Sicherheit und Ordnung ausgeht.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden folgende Aufwendungen abgebildet:\n- Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter-/Vermietermodells)\n- Aufwendungen für die Produktgruppe 1101 für die Erstattung der Niederschlagswassergebühr für die Entwässerung der öffentlichen Straßen",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1301": {
|
||||||
|
"pgNumber": "1301",
|
||||||
|
"name": "Grün- und Freiflächen",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet vor allem den Entwurf, den Bau und die Unterhaltung von öffentlichen Grünanlagen und Grün- und Freiflächen von Ämtern und Einrichtungen. Im Einzelnen zählen zur Produktgruppe\nGrünanlagen, öffentliche Spielflächen, Rad- und Wanderwege, Kleingärten (Produkt 1), Verkehrsgrün, Außenanlagen von städtischen Gebäuden und Sportanlagen (Produkt 2). Die Produktgruppe besteht damit zum\nTeil aus verwaltungsinternen Serviceleistungen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden sowohl die Verrechnungen zwischen den Teilplänen des Amtes als auch die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1302": {
|
||||||
|
"pgNumber": "1302",
|
||||||
|
"name": "Friedhöfe",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die Konzeptionierung, den Bau und die Unterhaltung von Friedhofsanlagen, die Durchführung von Bestattungen und Beratung von Angehörigen (= Produkt 1) sowie die Pflege von\nKriegsgräbern (= Produkt 2). Bei dem Betrieb der städtischen Friedhöfe handelt es sich um eine gebührenrechnende Einrichtung.",
|
||||||
|
"erlaeuterungen": "allgemein:\nDas Ergebnis des Teilplans weicht aufgrund der NKF-Vorschriften vom Ergebnis der Gebührenrechnung ab.\n\nzu Zeile 2:\nZuweisung für die Kriegsgräberpflege, Anzahl der Grabstätten: 2.776\n\nzu Zeile 4:\nDie Gebühren werden nach der Gebührensatzung für die Friedhofseinrichtungen der Stadt Münster in der aktuellen Fassung erhoben.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden sowohl die Verrechnungen zwischen den Teilplänen des Amtes als auch die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1303": {
|
||||||
|
"pgNumber": "1303",
|
||||||
|
"name": "Natur, Landschaft, Erholung, Wasserschutz",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst die Aufgaben der Unteren Landschaftsbehörde, der Unteren Wasserbehörde und darüber hinausgehende konzeptionell/planerische, wissenschaftliche und öffentlichkeitsbezogene\nAktivitäten zum Schutz und zur Entwicklung von Natur, Landschaft, Erholungsmöglichkeiten und des Wasserhaushalts.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden sowohl die Verrechnungen zwischen den Teilplänen des Amtes als auch die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1304": {
|
||||||
|
"pgNumber": "1304",
|
||||||
|
"name": "Fließende Gewässer",
|
||||||
|
"beschreibung": "Die Produktgruppe \"Fließende Gewässer\" ist deckungsgleich mit dem einzigen, gleichnamigen Produkt. Produktgruppen- und Produktbeschreibung sind daher identisch.\nZum Leistungsspektrum gehören der Bau und die Unterhaltung fließender Gewässer. Sie dienen der Schaffung und Bewahrung eines ordnungsgemäßen und umweltgerechten Zustandes der Fließgewässer als\nErholungs- und Lebensraum sowie der Gewährleistung eines ordnungsgemäßen Wasserabflusses. Die Aufgaben der Gewässerunterhaltung werden von der Stadt Münster und 5 weiteren Unterhaltungsverbänden\nerledigt. Wesentlicher Teil der Produktgruppe \"Fließende Gewässer\" ist der Gebührenhaushalt \"Gewässerunterhaltung\", der über die Erträge aus Gewässergebühren einen Teil der Aufwendungen der Produktgruppe\ndeckt.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienanagement abgebildet (Umsetzung des\nMieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1305": {
|
||||||
|
"pgNumber": "1305",
|
||||||
|
"name": "Wald und Forstwirtschaft",
|
||||||
|
"beschreibung": "Die Produktgruppe \"Wald und Forstwirtschaft\" ist deckungsgleich mit dem einzigen, gleichnamigen Produkt. Produktgruppen- und Produktbeschreibung sind daher identisch. Zur Leistungspalette von Produktgruppe\nund Produkt gehören der städtische Forstbetrieb und Aufgaben des Jagdwesens. Hierzu zählen im Einzelnen\n- die Planung, Organisation, Förderung und Durchführung aller Maßnahmen zur Neubegründung, Pflege und nachhaltigen naturverträglichen (Erholungs-) Nutzung von Waldbeständen, einschließlich Verkauf von\nWaldprodukten und Forstschutz/Gefahrenabwehr;\n- Wildbewirtschaftung und Jagdverpachtung zu möglichst günstigen Konditionen;\n- die Bewirtschaftung (Beratung, Betreuung und betriebstechnische Unterstützung) der Waldfläche der Stadtwerke Münster GmbH und der Stiftungen, einschließlich Verkauf von Waldprodukten, Forstschutz und\nJagdangelegenheiten, daneben Antragstellung für waldbauliche Förderungsmaßnahmen;\n- Maßnahmen zur Erhaltung und Entwicklung des Ökosystems \"Wald\", insbesondere durch Sicherstellung und Förderung seiner natürlichen Abläufe und seiner Biotop- und Artenvielfalt;\n- die Feststellung und Durchführung von Verkehrssicherungsaufgaben;\n- die Öffentlichkeitsarbeit durch Führungen, Fortbildungsveranstaltungen, Ausstellungen, Wald- und Umweltpädagogik, Waldlehrpfade, Medienbetreuung.\n\nEs sollen ertragreiche, naturnahe und langfristig stabile Wälder unter Berücksichtigung der besonderen Nutz-, Schutz- und Erholungsfunktion entwickelt und bewirtschaftet werden. Die Waldflächen werden in\nregelmäßigen Abständen auf der Grundlage des Forstbetriebsplans gestaltet und kontinuierlich gepflegt.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 140 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden sowohl die Verrechnungen zwischen den Teilplänen des Amtes als auch die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet ( Umsetzung des Mieter-/Vermietermodells).\n\n.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1401": {
|
||||||
|
"pgNumber": "1401",
|
||||||
|
"name": "Übergr. Umweltschutz, Klima, Nachhaltigkeit,",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die Aufgabenbereiche Luftreinhaltung, Immissionsschutz, Bodenschutz, Abfallüberwachung, Klimaschutz und Nachhaltigkeit sowie den übergreifenden Umweltschutz\n(Umweltverträglichkeitsprüfungen, Umweltplanung, Umweltinformation, Förderwesen, Öko-Audit, Öl- und Giftalarm). Es handelt sich sowohl um behördliche Umweltschutzaufgaben als auch um darüber hinaus\ngehende städtische Aktivitäten. Das Leistungsspektrum der Produktgruppe ist vielfältig.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 31.000 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden sowohl die Verrechnungen zwischen den Teilplänen des Amtes als auch die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1501": {
|
||||||
|
"pgNumber": "1501",
|
||||||
|
"name": "Anteile an Unternehmen",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die im Rahmen des Beteiligungsmanagements betreuten Anteile an Unternehmen der Stadt Münster. Abgebildet werden u. a. die Ausschüttungen der Unternehmen bzw. Zuschüsse an\ndie Unternehmen, Erträge aus Konzessionszahlungen, Zinszahlungen aufgrund von Darlehensverträgen etc. sowie die Ein- und Auszahlungen aus Investitionstätigkeit.\n\nDie unmittelbaren \"wesentlichen Beteiligungen\" sind in Form einzelner Produkte in der Produktgruppe dargestellt. Als \"wesentliche Beteiligung\" sind nach entsprechendem Ratsbeschluss solche Unternehmen\nklassifiziert, deren Beteiligung von strategischer Bedeutung für die Stadt Münster ist. Mit den unmittelbaren \"wesentlichen Beteiligungen\" werden zu Steuerungszwecken in der Regel Managementkontrakte\nabgeschlossen. Unter dem Produkt \"Übrige Beteiligungen\" sind Anteile an allen weiteren unmittelbaren und mittelbaren Beteiligungen der Stadt Münster ausgewiesen.\n\nUnabhängig von der Abbildung der Ausschüttung der \"Stadtwerke Münster GmbH\" und der damit im Zusammenhang stehenden Ertragsteuern bzw. Steuererstattungen in dieser Produktgruppe aufgrund der Vorgaben\ndes Produktrahmenplans, ist der Betrieb gewerblicher Art \"Bäder\" (Produktgruppe 0802) alleiniger Anteilseigner der \"Stadtwerke Münster GmbH\".\n\nNicht in dieser Produktgruppe abgebildet sind eigenbetriebsähnliche Einrichtungen, die im Gegensatz zu den Unternehmen, an denen die Stadt Münster beteiligt ist, keine rechtliche Selbstständigkeit aufweisen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 07:\nVon den sonstigen ordentlichen Erträgen entfallen 2026 und 2027 jeweils 16,90 Mio. Euro auf die Konzessionsabgaben der „Stadtwerke Münster GmbH“ und 1,625 Mio. Euro auf Körper-\nschaftsteuererstattungen.\n\nzu Zeile 13:\nDie Aufwendungen für Sach- und Dienstleistungen beinhalten den Ausgleich für unterbliebene Tarifmaßnahmen bei der Stadtwerke Münster GmbH.\n\nzu Zeile 15:\nDie Transferaufwendungen beinhalten im Wesentlichen Zuschüsse, insbesondere an die Beteiligungsgesellschaften\n- Westfälischer Zoologischer Garten Münster GmbH i. H. v. 4,10 Mio. Euro (2026) / 4,1 Mio Euro (2027)\n- Stadtwerke Münster GmbH i. H. v. 1,733 Mio. Euro - hier: für das Münster-Abo / 29-Euro-Ticket (2026) / 1,733 Mio Euro (2027)\n- Wirtschaftsförderung Münster GmbH i. H. v. 1,90 Mio. Euro (2026) / 1,90 Mio € (2027)\n\nzu Zeile 16:\nDie sonstigen ordentlichen Aufwendungen stehen i. d. R. hauptsächlich im Zusammenhang mit Ausschüttungen insbesondere der \"Stadtwerke Münster GmbH\" an den Betrieb gewerbli-\ncher Art (BgA) \"Bäder\" und beinhalten diesbezüglich Kapitalertragsteuern und Solidaritätszuschläge.\n\nzu Zeile 19:\nDie Finanzerträge beinhalten insbesondere nachstehende Brutto-Ausschüttungen:\n- Stadtwerke Münster GmbH an den Betrieb gewerblicher Art (BgA) „Bäder“ i. H. v. 6,5 Mio. Euro (2026) / 8,00 Mio Euro (2027)\n- Sparkasse Münsterland Ost i. H. v. 2,80 Mio. Euro (2026) / 2,80 Mio Euro (2027)\n\nUnabhängig von der Abbildung der Ausschüttungen der „Stadtwerke Münster GmbH“ und der damit im Zusammenhang stehenden Ertragsteuern bzw. Steuererstattungen in\ndieser Produktgruppe aufgrund der Vorgaben des Produktrahmenplanes bleibt der BgA „Bäder“ (Produktgruppe 0802) alleiniger Anteilseigner der „Stadtwerke Münster GmbH\".",
|
||||||
|
"breakdowns": {
|
||||||
|
"15": [
|
||||||
|
{
|
||||||
|
"name": "Westfälischer Zoologischer Garten Münster GmbH",
|
||||||
|
"values": {
|
||||||
|
"2026": 4099999.9999999995,
|
||||||
|
"2027": 4099999.9999999995
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Stadtwerke Münster GmbH i. H. v. 1,733 Mio. Euro",
|
||||||
|
"values": {
|
||||||
|
"2027": 1733000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Wirtschaftsförderung Münster GmbH",
|
||||||
|
"values": {
|
||||||
|
"2026": 1900000,
|
||||||
|
"2027": 1900000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"19": [
|
||||||
|
{
|
||||||
|
"name": "Stadtwerke Münster GmbH an den Betrieb gewerblicher Art (BgA) „Bäder“",
|
||||||
|
"values": {
|
||||||
|
"2026": 6500000,
|
||||||
|
"2027": 8000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Sparkasse Münsterland Ost",
|
||||||
|
"values": {
|
||||||
|
"2026": 2800000,
|
||||||
|
"2027": 2800000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1502": {
|
||||||
|
"pgNumber": "1502",
|
||||||
|
"name": "Stadtmarketing (MM)",
|
||||||
|
"beschreibung": "Die Produktgruppe Stadtmarketing beinhaltet den Zuschuss an die eigenbetriebsähnliche Einrichtung \"Münster Marketing (MM)\".\n\n\"Münster Marketing\" wird gemäß der Eigenbetriebsverordnung sowie nach den Bestimmungen der Betriebssatzung für \"Münster Marketing\" geführt. Zweck der eigenbetriebsähnlichen Einrichtung sind die Profilierung\nund Stärkung von Münster im Wettbewerb der Städte und Regionen durch Instrumente des Stadtmarketings und alle den Betriebszweck fördernden Geschäfte.\n\nEs besteht ein aktuell gültiger Managementkontrakt mit einer Laufzeit bis zum 31.12.2026.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1503": {
|
||||||
|
"pgNumber": "1503",
|
||||||
|
"name": "Stadthalle Hiltrup",
|
||||||
|
"beschreibung": "Diese Produktgruppe umfasst die Bewirtschaftung der Stadthalle Hiltrup. Dargestellt werden die Vermietung und Verwaltung der Räume für verschiedene Zwecke (Schule, Vereine, politische Gremien, Kultur u. a.)",
|
||||||
|
"erlaeuterungen": "Bei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1504": {
|
||||||
|
"pgNumber": "1504",
|
||||||
|
"name": "Öffentliche Toilettenanlagen",
|
||||||
|
"beschreibung": "Derzeit stehen 7 öffentliche Toilettenanlagen im Eigentum der Stadt Münster. Hinzu kommt die neu errichtete Hocktoilette am Bremer Platz, welche jedoch keine vollwertige Anlage darstellt. Die Sanitäranlagen sind\nherkömmlicher Bauart und müssen manuell durch einen beauftragen Dienstleiter gereinigt werden.\n\nDie Produktverantwortung für öffentliche Toilettenanlagen obliegt dem Ordnungsamt. Dies umfasst das Beschwerdemanagement, die Umsetzung des Konzeptes in Zusammenarbeit mit anderen städtischen Ämtern\nund die finanzielle Zuständigkeit.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"1601": {
|
||||||
|
"pgNumber": "1601",
|
||||||
|
"name": "Allgemeine Finanzwirtschaft",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die Erträge und Aufwendungen sowie die Einzahlungen und Auszahlungen aus Investitions- und Finanzierungstätigkeit, die aufgrund ihrer Eigenschaften nicht einer anderen\nProduktgruppe zugeordnet werden können.\n\nVor dem Hintergrund kommunaler Beeinflussbarkeit erfolgt eine Differenzierung in die Produkte\n- Gemeindesteuern\n- Sonstige Steuern, allgemeine Zuweisungen und allgemeine Umlagen\n- Sonstige allgemeine Finanzwirtschaft\nWährend die Gemeindesteuern über die Hebe- bzw. Steuersätze beeinflussbar sind, werden die Ergebnisse der beiden anderen Produkte extern bestimmt.",
|
||||||
|
"erlaeuterungen": "zu Zeile 01:\nDie Erträge aus Steuern und ähnlichen Abgaben setzen sich in den Haushaltsjahren 2026 und 2027 zusammen aus:\n\nAufwandsart Ansatz 2026 in Mio. € Ansatz 2027 in Mio. €\nGewerbesteuer 385,0 385,0\nGrundsteuer A 0,4 0,4\nGrundsteuer B 66,5 67,5\nVergnügungssteuer 2,8 2,8\nHundesteuer 1,8 1,85\nZweitwohnungssteuer 1,0 1,25\nBeherbergungsteuer 4,3 4,46\nGemeindeanteil an der Einkommensteuer 226,0 236,0\nGemeindeanteil an der Umsatzsteuer 59,0 73,0\nLeist. nach dem Familienleistungsausgleich 21,5 21,9\n\nzu Zeile 02:\nDiese Position beinhaltet insbesondere die Schlüsselzuweisungen in Höhe von 108,1 Mio. Euro (2026) / 95 Mio. Euro (2027), die Schulpauschale/Bildungspauschale in Höhe von 16,8\nMio. Euro (2026) / 16,8 Mio. Euro (2027), die Sportpauschale in Höhe von 1,25 Mio. Euro (2026) / 1,25 Mio. Euro (2027), sowie die Aufwands- und Unterhaltungspauschale in Höhe von\n2,2 Mio Euro (2026) / 2,2 Mio. Euro (2027).\n\nzu Zeile 15:\nZu den Transferaufwendungen gehören die\n- Gewerbesteuerumlage in Höhe von 29,3 Mio. Euro (2026) / 29,3 Mio. Euro (2027)\n- Landschaftsumlage in Höhe von 136,7 Mio. Euro (2026) / 142,9 Mio. Euro (2027)\n\nzu Zeile 19:\nDie Finanzerträge ergeben sich insbesondere aus den Zinsen für gewährte Darlehen im Rahmen der Konzernfinan-zierung in Höhe von 7 Mio. Euro (2026) / 7,5 Mio. Euro (2027).\n\nzu Zeile 20:\nUnter dieser Position werden die Zinsen aus der Inanspruchnahme von Fremdkapital (Kredite für Investitionen und Kredite zur Liquiditätssicherung) abgebildet.",
|
||||||
|
"breakdowns": {
|
||||||
|
"1": [
|
||||||
|
{
|
||||||
|
"name": "Gewerbesteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 385000000,
|
||||||
|
"2027": 385000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Grundsteuer A",
|
||||||
|
"values": {
|
||||||
|
"2026": 400000,
|
||||||
|
"2027": 400000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Grundsteuer B",
|
||||||
|
"values": {
|
||||||
|
"2026": 66500000,
|
||||||
|
"2027": 67500000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Vergnügungssteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 2800000,
|
||||||
|
"2027": 2800000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Hundesteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 1800000,
|
||||||
|
"2027": 1850000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zweitwohnungssteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 1000000,
|
||||||
|
"2027": 1250000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Beherbergungsteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 4300000,
|
||||||
|
"2027": 4460000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gemeindeanteil an der Einkommensteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 226000000,
|
||||||
|
"2027": 236000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gemeindeanteil an der Umsatzsteuer",
|
||||||
|
"values": {
|
||||||
|
"2026": 59000000,
|
||||||
|
"2027": 73000000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Leist. nach dem Familienleistungsausgleich",
|
||||||
|
"values": {
|
||||||
|
"2026": 21500000,
|
||||||
|
"2027": 21900000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"15": [
|
||||||
|
{
|
||||||
|
"name": "Gewerbesteuerumlage",
|
||||||
|
"values": {
|
||||||
|
"2026": 29300000,
|
||||||
|
"2027": 29300000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Landschaftsumlage",
|
||||||
|
"values": {
|
||||||
|
"2026": 136700000,
|
||||||
|
"2027": 142900000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"1701": {
|
||||||
|
"pgNumber": "1701",
|
||||||
|
"name": "Rechtlich unselbständige Stiftungen",
|
||||||
|
"beschreibung": "Örtliche Stiftungen sind nach dem Stiftungsgesetz des Landes Nordrhein-Westfalen rechtlich selbständige und rechtlich unselbständige Stiftungen, die nach dem Willen des Stifters von einer Gemeinde verwaltet\nwerden und die überwiegend Zwecken dienen, die von der Gemeinde in ihrem Bereich als öffentliche Aufgaben erfüllt werden können.\nDie Kommunalen Stiftungen Münster sind zum jetzigen Zeitpunkt alle Sozialstiftungen. Ihre Stiftungszwecke werden in den Handlungsfeldern \"Leben im Alter\", \"Bürgerschaftliches Engagement und Freiwilligenarbeit\"\nsowie \"Chancen für Kinder\" verwirklicht.\n\nZu den rechtlich unselbständigen kommunalen Stiftungen zählen in Münster zurzeit:\n- \"Friedrich und Irmgard Buschmann-Stiftung\"\n- \"Generalarmenfonds\"\n- \"fair für Frauen\"",
|
||||||
|
"erlaeuterungen": "Die Jahresüberschüsse/-fehlbeträge der Erfolgspläne der rechtlich unselbständigen Stiftungen werden in einer Summe in der Zeile 07 „Sonstige ordentliche Erträge“ bzw. 16 „Sonstige\nordentliche Aufwendungen“ ausgewiesen.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0101": {
|
||||||
|
"pgNumber": "0101",
|
||||||
|
"name": "",
|
||||||
|
"beschreibung": "Die Stadt Münster ist in sechs Stadtbezirke aufgeteilt, in denen jeweils eine eigene Bezirksvertretung gewählt wird. Die Bezirksvertretungen entscheiden über bezirksbezogene Angelegenheiten ihres Stadtbezirks.\nDazu gehören beispielsweise die Unterhaltung und Ausstattung der im Stadtbezirk gelegenen Schulen, Sportplätze oder Friedhöfe, Straßenaus- und -umbaumaßnahmen, die Ausgestaltung von Park- und\nGrünanlagen, Fragen des Denkmalschutzes und die Betreuung von örtlichen Vereinen und Initiativen. Darüber hinaus können die Bezirksvertretungen Stellungnahmen zu wichtigen überbezirklichen Angelegenheiten\ndes Rates oder eines Ausschusses abgeben, wenn sie ihren Stadtbezirk berühren.\nDiese Produktgruppe dient insbesondere der Darstellung der Finanzmittel, die den einzelnen Bezirksvertretungen im jeweiligen Haushaltsjahr zur freien Verfügung (im Sinne der Gemeindeordnung) stehen. Diese\nProduktgruppe dient ausnahmsweise nicht zur Darstellung von Entscheidungs- und Handlungsergebnissen, da es sich bei den Bezirksvertretungen um politische Gremien handelt. Daher sind auch keine Ziele und\nZielkennzahlen gebildet worden. Die Ziele der Bezirksvertretungen ergeben sich aus den gesetzlichen Grundlagen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 13 – 16:\n\nAufteilung der frei verfügbaren Mittel der Bezirksvertretungen gemäß vorliegender Beschlüsse der Bezirksvertretungen im Rahmen der Etatberatungen:\n\n2026/2027\n- BV Mitte: 33.580 Euro\n- BV Nord: 90.240 Euro\n- BV Ost: 81.120 Euro\n- BV Südost: 62.590 Euro\n- BV Hiltrup: 64.970 Euro\n- BV West: 72.060 Euro\n\nInsgesamt: 404.560 Euro",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0102": {
|
||||||
|
"pgNumber": "0102",
|
||||||
|
"name": "GF für polit. Gremien, internationale Beziehungen",
|
||||||
|
"beschreibung": "Die kommunale Selbstverwaltung sichert die demokratischen Rechte der Bürger/innen und Einwohner/innen der Stadt Münster. Dazu werden nach der Gemeindeordnung NRW politische Gremien gebildet und\nbesetzt, deren Arbeits- und Beschlussfähigkeit durch die Geschäftsführung gewährleistet wird. Diese umfasst beispielhaft die Vor- und Nachbereitung von Sitzungen, die Schriftführung, die Erstellung und Prüfung von\nVorlagen, die Prüfung und Auszahlung von Zahlungsansprüchen sowie die Betreuung des Sitzungsdienstprogramms.\n\nDarüber hinaus pflegt das Amt für Bürger- und Ratsservice für und mit dem Oberbürgermeister, dem Rat und der Bürgerschaft die internationalen Kontakte der Stadt Münster. Dies umfasst die Pflege der\nPartner-/Patenstädte und der befreundeten Städte, die Initiierung/Betreuung von europäischen Projekten und Netzwerken sowie die Bearbeitung sonstiger internationaler Anfragen.\n\nZusätzlich umfasst das Aufgabenspektrum die Bereitstellung von Räumen für städtische Veranstaltungen und die Vergabe von Räumlichkeiten an Dritte nebst deren Betreuung sowie die Vornahme von Ehrungen und\nGratulationen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nDie Erträge aus Zuschüssen von 200.000 € sind in Höhe eines Mittelwertes veranschlagt, jedoch werden je nach Erfolg der Förderanträge auch größere Projekte durchgeführt. Hieraus\nresultieren zweckgebundene Aufwendungen einschließlich des kommunalen Eigenanteils in Zeile 13 und Zeile 16.\n\nzu Zeile 15 und Zeile 16:\nIn diesen Aufwendungen sind auch die Beträge für ständige kommunale Gremien bzw. Projekte enthalten. Davon stehen zur Verfügung für:\n- Beirat für kommunale Entwicklungszusammenarbeit: 23.000 € (2027 - 23.000 €)\n- Kommunale Seniorenvertretung: 4.720 € (2027 - 4.720 €)\n- Ausschuss für Chancengerechtigkeit und Integration: 64.440 € (davon 29.440 € für Zuschüsse in Zeile 15) (2027 - 64.440 € (davon 29.440 € für Zuschüsse in Zeile 15)\n- Fairtrade-Stadt Münster: 10.000 € (2027 - 10.000 €)\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung von Gebäuden durch das Gebäudemanagement abgebildet (Umsetzung des Mie-\nter/Vermietermodells).",
|
||||||
|
"breakdowns": {
|
||||||
|
"15": [
|
||||||
|
{
|
||||||
|
"name": "Beirat für kommunale Entwicklungszusammenarbeit:",
|
||||||
|
"values": {
|
||||||
|
"2027": 23000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Kommunale Seniorenvertretung:",
|
||||||
|
"values": {
|
||||||
|
"2027": 4720
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Fairtrade-Stadt Münster:",
|
||||||
|
"values": {
|
||||||
|
"2027": 10000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0103": {
|
||||||
|
"pgNumber": "0103",
|
||||||
|
"name": "OB, BM und Verwaltungsführung",
|
||||||
|
"beschreibung": "Der Oberbürgermeister ist Vorsitzender des Rates und repräsentiert gemeinsam mit den Bürgermeistern die Stadt Münster.\n\nDarüber hinaus leitet der Oberbürgermeister die Verwaltung, legt mit Unterstützung der Beigeordneten die Ziele des Verwaltungshandelns im Rahmen der gesetzlichen und der Vorgaben des Rates fest, bereitet die\nBeschlüsse des Rates vor und steuert deren Umsetzung.\n\nDie Produktgruppe enthält keine Zielformulierung, da sich die Ziele des Oberbürgermeisters, der Bürgermeister und der Verwaltungsführung aus den kommunalverfassungsrechtlichen Bestimmungen ergeben.",
|
||||||
|
"erlaeuterungen": "zu Zeile 16:\nDie sonstigen ordentlichen Aufwendungen beinhalten die Verfügungsmittel des Oberbürgermeisters im Sinne von § 14 der Kommunalhaushaltsverordnung. Sie betragen jährlich\n6.140 Euro.\n\nzu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mie-\nter/Vermietermodells) abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0104": {
|
||||||
|
"pgNumber": "0104",
|
||||||
|
"name": "Gleichstellung aller Geschlechter",
|
||||||
|
"beschreibung": "In dieser Produktgruppe sind alle Aktivitäten des Amtes für Gleichstellung zur Förderung der Gleichstellung aller Menschen in der Stadtverwaltung und in der Stadt Münster zusammengefasst. Zu den Aufgaben\ngehören Öffentlichkeits- und Informationsarbeit, Netzwerkarbeit und Kooperationen, finanzielle und organisatorische Unterstützung von frauen- und männerspezifischen Einrichtungen, Trägern und Projekten und\nsolchen, die zu sexuellen und geschlechtlichen Identitäten arbeiten, sowie die Beratung von Bürger*innen sowie Beschäftigten der Stadt Münster.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nDer Ansatz umfasst die Förderung von\n- Frauenprojekten,\n- Männerprojekten,\n- Projekten aus dem Bereich LSBTIQ* und die\n- Trägerförderung (vgl. Zuschussbericht).\n\nMit den Mitteln der „Förderung von Frauenprojekten“ werden auf der Grundlage von Richtlinien Projekte, Programme und Öffentlichkeitsarbeit aus den Bereichen Selbsthilfe, Soziales,\nKultur etc. von Frauenorganisationen, –gruppen und –vereinen bezuschusst. Mit den Mitteln \"Förderung von Männerprojekten\" werden auf der Grundlage von Richtlinien Projekte, Pro-\ngramme und Öffentlichkeitsarbeit aus den Bereichen Selbsthilfe, Soziales, Kultur etc. von Männerorganisationen, -gruppen und -vereinen bezuschusst. Mit den Mitteln \"Förderung von\nProjekten aus dem Bereich LSBTIQ* werden auf der Grund-lage von Richtlinien Projekte, Programme und Öffentlichkeitsarbeit aus den Bereichen Selbsthilfe, Soziales, Kultur etc. von\nOrganisationen, Gruppen und Vereinen aus dem Bereich lesbischer, schwuler, bisexueller, trans*, inter* und queerer Menschen bezuschusst.\n\nMit den Mitteln zur „Trägerförderung“ werden auf der Grundlage von Leistungsvereinbarungen Träger bezuschusst.\n\nDie in Zeile 15 - Transferaufwendungen - genannte Summe ist im Zuschussbericht detailliert dargestellt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0105": {
|
||||||
|
"pgNumber": "0105",
|
||||||
|
"name": "Personal- und Schwerbehindertenvertretung",
|
||||||
|
"beschreibung": "Die Personalvertretung der Stadtverwaltung Münster vertritt die Interessen aller Beschäftigten (einschl. der Beamtinnen und Beamten) der Stadtverwaltung und nimmt zu diesem Zweck vor allem Rechte und Pflichten\nnach dem Landespersonalvertretungsgesetz für das Land Nordrhein-Westfalen wahr. Die Personalvertretung innerhalb der Stadtverwaltung Münster besteht aus dem Gesamtpersonalrat, drei örtlichen\nPersonalvertretungen und der Jugend- und Auszubildendenvertretung. Zu den örtlichen Personalvertretungen gehören der Personalrat der allgemeinen Verwaltung, der Personalrat der Feuerwehr und der Personalrat\ndes Theater Münster.\n\nDie Schwerbehindertenvertretung fördert die Eingliederung schwerbehinderter Menschen in die Stadtverwaltung Münster, vertritt ihre\nInteressen und steht ihnen beratend und helfend zur Seite. Zu diesem Zweck nimmt sie vor allem Rechte und Pflichten nach dem Sozialgesetzbuch (vor allem §§ 178 ff. SGB IX) wahr.\n\nLegitimation und Zielsetzungen von Personal- und Schwerbehindertenvertretung ergeben sich aus den genannten gesetzlichen Grundlagen und den hier geregelten Wahlen der jeweiligen Vertreterinnen und Vertreter\ndurch die Beschäftigten. Daher entzieht sich die Personal- und Schwerbehindertenvertretung einer weiteren Zielsetzung durch den Rat.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0106": {
|
||||||
|
"pgNumber": "0106",
|
||||||
|
"name": "Wirtschaftlichkeitsprüfung und Revision",
|
||||||
|
"beschreibung": "Die Produktgruppe enthält die Leistungen des Amtes für Wirtschaftlichkeitsprüfung und Revision (AWR). Hauptaufgabe des AWR ist die begleitende und/oder nachgehende neutrale Prüfung/Revision, die das\nVerwaltungshandeln der Stadt und ihrer Einrichtungen hinsichtlich der Wirtschaftlichkeit, Zweckmäßigkeit und Ordnungsmäßigkeit hinterfragt, Defizite ermittelt und deren Ursachen analysiert. Über den gesetzlich\nvorgegebenen Umfang hinaus erstellt das AWR auch Gutachten und berät auf Grund eines Auftrages bzw. auf Anforderung.\n\nIm Wesentlichen handelt es sich um Prüfungen\n- der Jahresabschlüsse, der Finanzen, der Kassen, des Vermögens, der Vorräte,\n- der laufenden Vorgänge der Finanzbuchhaltung,\n- der Auftragsvergaben, der Bautechnik,\n- der DV-Programme der Haushaltswirtschaft,\n- der Wirtschaftsführung und des Rechnungswesens der Sondervermögen,\n- der Betätigung der Stadt als Gesellschafterin sowie\n- bei wesentlichen Änderungen der Organisation.\n\nBei den Gutachten und Beratungen stehen im Vordergrund\n- die Wirtschaftlichkeitsuntersuchungen und die Folgekostenberechnungen,\n- die Mitarbeit bei zentralen Verwaltungsaufgaben (auch Projektbeteiligung/-arbeit) sowie\n- besondere Analysen i. V. m. einem fachübergreifendem Controlling.\n\nProduktgruppe und Produkt sind inhaltlich identisch. Ein zusätzlicher Ausweis des untergeordneten deckungsgleichen Produktes ist deshalb entbehrlich.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0107": {
|
||||||
|
"pgNumber": "0107",
|
||||||
|
"name": "Public Relations",
|
||||||
|
"beschreibung": "Das Amt für Kommunikation leistet mit dieser Produktgruppe integrierte Medienarbeit (Text, Print, Online, Bild, Film) für die Stadt Münster. Es schafft für Medienpartner Voraussetzungen für die Berichterstattung über\nLeistungen und Ziele der Stadt, informiert die Einwohner/-innen der Stadt durch die Veröffentlichungen der Medien und in städtischen Publikationen, stärkt den Medienstandort und sorgt durch seine Arbeit bundesweit\nund international für einen hohen Bekanntheitsgrad Münsters.\n\nSchwerpunkte der Leistungen sind\n\n- Pressearbeit und Medienservice: Pressemitteilungen, Pressetermine, Auskunft und Recherchehilfe für Journalisten, Vermittlung von Ansprechpartnern, Bilderservice, Bilder- und Videoservice, Münster-Filme\n- Printpublikationen: Veröffentlichungen des Presseamtes, redaktionelle Betreuung für sonstige städtische Veröffentlichungen, Stadt-CD\n- Online-Publikationen: städtische Seiten im Portal www.muenster.de einschließlich Virtuelles Rathaus; Konzeption und redaktionelle Betreuung des Portals www.muenster.de gemeinsam mit Verein\nBürgernetz - büne e.V., zentrale Facebook-, Instagram- und Twitter-Auftritte der Stadt, You Tube-Kanal der Stadt\n- Filmservice: Unterstützung Dreharbeiten, Regionalmarketing,\nInformationsarbeit und Kooperationen für Drehort Münster/Münsterland, \"Münster Tatort\" - und \"Wilsberg\"-Kinopremieren\n- kontinuierliches Reporting an Stadtverwaltung und Verwaltungsvorstand zur täglichen Medienlage, Schwerpunkten der städtischen Außenkommunikation und zu Reichweiten/Trends der Web- und\nSocial-Media-Produktion des Amtes für Kommunikation",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0108": {
|
||||||
|
"pgNumber": "0108",
|
||||||
|
"name": "Personal- und Organisationsmanagement",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst das \"Personalmanagement\" und das \"Organisationsmanagement\" und damit die Erbringung verwaltungsinterner Dienstleistungen für städtische Ämter und Einrichtungen sowie für die\nVerwaltungsführung. Das \"Personalmanagement\" enthält im Wesentlichen Leistungen, die die Stadtverwaltung als Arbeitgeber bzw. Dienstherr gegenüber den Beschäftigten und Beamten sowie den städtischen\nÄmtern und Einrichtungen erbringt. Das \"Organisationsmanagement\" beinhaltet insbesondere zentral angebundene Entscheidungen bzw. Maßnahmen zur Weiterentwicklung der Organisationsstrukturen der\nStadtverwaltung einschließlich ihrer Vor- und Nachbereitung, die gesamtstädtische Prozessoptimierung sowie die Unterstützung der Ämter und Einrichtungen in organisatorischen Belangen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter-\n/Vermietermodells) abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0109": {
|
||||||
|
"pgNumber": "0109",
|
||||||
|
"name": "Finanz- und Beteiligungsmanagement",
|
||||||
|
"beschreibung": "\"Die Gemeinde hat ihre Haushaltswirtschaft so zu planen und zu führen, dass die stetige Erfüllung ihrer Aufgaben gesichert ist. Der Haushalt muss in jedem Jahr in Planung und Rechnung ausgeglichen sein.\" (§ 75\nGemeindeordnung NRW). Die Erfüllung dieser sowie der damit in Verbindung stehenden Vorgaben der Gemeindeordnung NRW (§§ 75 - 97, 100, 116 - 117 GO), der Kommunalhaushaltsverordnung NRW und\nweiterer haushaltsrechtlicher Regelungen wird durch das Finanz- und Beteiligungsmanagement gewährleistet. Die Aufgabenfelder der Produktgruppe umfassen insbesondere die Bereiche\n\n- Haushaltssteuerung und -controlling,\n- Geschäftsbuchführung inkl. Jahresabschluss,\n- Zahlungsabwicklung, Liquiditäts- und Schuldenmanagement,\n- Vollstreckung,\n- Veranlagung von Steuern und Grundbesitzabgaben sowie\n- Beteiligungsmanagement.",
|
||||||
|
"erlaeuterungen": "zu Zeile 06:\nDiese Position beinhaltet die Kostenerstattungen der eigenbetriebsähnlichen Einrichtungen Abfallwirtschaftsbetriebe Münster (AWM), citeq, Münster Marketing (MM) und\nTheater Münster.\n\nzu Zeile 07:\nDie sonstigen ordentlichen Erträge werden durch die Mahn- und Pfändungsgebühren etc. i. H. v. 1.500.000 Euro (2026) / 1.500.000 Euro (2027) im Zusammenhang mit der Realisierung\nvon Forderungen durch die Stadtkasse geprägt.\n\nzu Zeile 16:\nDie größten Posten bei den sonstigen ordentlichen Aufwendungen sind:\n- Prüfung, Beratung, Rechtsschutz i. H. v. 1,67 Mio Euro (2026) / 1,47 Mio Euro (2027)\n- Gebühren i. H. v. 150.000 Euro (2026) / 150.000 Euro (2027)\n- Porto i. H. v. 153.000 Euro (2026) / 153.000 Euro (2027)\n\nzu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Erträge aus den mit dem Jobcenter abzurechnenden Leistungen und die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {
|
||||||
|
"16": [
|
||||||
|
{
|
||||||
|
"name": "Prüfung, Beratung, Rechtsschutz",
|
||||||
|
"values": {
|
||||||
|
"2026": 1670000,
|
||||||
|
"2027": 1470000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Gebühren",
|
||||||
|
"values": {
|
||||||
|
"2026": 150000,
|
||||||
|
"2027": 150000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Porto",
|
||||||
|
"values": {
|
||||||
|
"2026": 153000,
|
||||||
|
"2027": 153000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0110": {
|
||||||
|
"pgNumber": "0110",
|
||||||
|
"name": "Recht",
|
||||||
|
"beschreibung": "Die Produktgruppe \"Recht\" ist deckungsgleich mit dem einzigen gleichnamigen Produkt. Produktgruppen- und Produktbeschreibung sind daher identisch.\n\nDie Produktgruppe umfasst die durch die Zentrale Recht wahrgenommene Rechtsberatung, Prozessführung, besondere Rechts- und Versicherungsangelegenheiten sowie das Zentrale Vergabemanagement. Die\nProduktgruppe trägt dazu bei, die Recht- und Zweckmäßigkeit der Entscheidungen der Verwaltung zu sichern und Haftpflichten wegen rechtswidriger Amtsführung zu vermeiden. Dazu berät die Zentrale Recht\nbestimmte städtische Dezernate, Ämter und Einrichtungen und vertritt den Oberbürgermeister vor Gericht. Besondere Rechts- und Versicherungsangelegenheiten sind die Standesamtsaufsicht, die Risikovorsorge, die\nGewinnung und Betreuung von Schiedspersonen, die Abwicklung von Fremdschäden und die Aufgaben des Zentralen Vergabemanagements.",
|
||||||
|
"erlaeuterungen": "zu Zeile 11:\nNeben den Personalaufwendungen für die Mitarbeiter des Amtes für Zentrale Rechtsdienstleistungen und Vergabemanagement werden hier auch die Beiträge zur Unfallversicherung für\ntariflich Beschäftigte der Abfallwirtschaftsbetriebe Münster, citeq, Münster Marketing und Theater Münster geplant.\n\nzu Zeile 16:\nHierin enthalten ist die Umlage an den Kommunalen Schadenausgleich westdeutscher Städte für die Abfallwirtschaftsbetriebe Münster, die citeq, Münster Marketing, die Sozialholding\nKlarastift GmbH, die Westfälische Bauindustrie GmbH, die Wohn- und Stadtbau GmbH, das Theater Münster, die Messe und Congress Centrum Halle Münsterland GmbH, die Wirt-\nschaftsförderung Münster GmbH, die CeNTech GmbH, die Technologieförderung Münster GmbH, die Nanobioanalytik Zentrum GmbH, die KonvOY GmbH und die Bauwerke GmbH\n\nzu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Erträge aus den mit dem Jobcenter abzurechnenden Leistungen und die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0111": {
|
||||||
|
"pgNumber": "0111",
|
||||||
|
"name": "Immobilienmanagement",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst\n- den Erwerb, die Entwicklung und den Verkauf von Immobilien,\n- die Vermietung und Verpachtung sowie die Anmietung und Anpachtung von Immobilien und\n- die Bereitstellung von Immobilien zur städtischen Aufgabenerfüllung.\n\nZu den Aufgaben des Immobilienmanagement gehören die bedarfs-, zeit- und kostengerechte Bereitstellung geeigneter Grundstücke und Gebäude im Rahmen der finanziellen Spielräume der Stadt. Dies erfolgt auf\nder Grundlage einer kontinuierlichen Portfoliooptimierung im immobilen Vermögensbestand zur Anpassung des Vermögens an aktuelle Bedarfsverhältnisse. Dazu gehören die Vorbereitungen und Umsetzungen von\nInvestitions- und Desinvestitionsentscheidungen als auch ein effektives Immobiliencontrolling vor allem unter Berücksichtigung von Stadtentwicklungs- und Wirtschaftlichkeitszielen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 5:\nDie privatrechtl. Leistungsentgelte beinhalten im Wesentlichen die Erträge aus Mieten, Pachten und Erbbauzinsen.\n\nzu Zeile 13:\nDie Aufwendungen für Sach- und Dienstleistungen setzen sich vor allem aus Aufwendungen für die Unterhaltung bebauter Grundstücke, Reinigungskosten, Energiekosten und Grundbe-\nsitzabgaben zusammen.\n\nzu Zeile 15: Vergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen\nUmfang von rund 98.530 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 16:\nIm Wesentlichen beinhaltet diese Position Aufwendungen für angemietete Gebäude und Feuerversicherung.\n\nzu Zeile 27:\nBei den internen Leistungsbeziehungen werden die Erträge für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0113": {
|
||||||
|
"pgNumber": "0113",
|
||||||
|
"name": "Zentrale Dienste",
|
||||||
|
"beschreibung": "Zur Leistungspalette der Produktgruppe \"Zentrale Dienste\" gehören diverse verwaltungsinterne Dienstleistungen, die allen Ämtern und Einrichtungen der Stadt Münster zur Verfügung stehen. Hierzu zählen im\nEinzelnen:\n\n- Beschaffung und Unterhaltung von Büroausstattung\n- Beschaffung Büromaterial, Dienst- und Schutzkleidung, PKW, Fahrräder\n- zentraler Post- und Zustelldienst\n- zentrale Scanstelle\n- betriebliches Mobilitätsmanagement\n- Druckerei\n- Kantinen incl. interner Veranstaltungsbewirtung",
|
||||||
|
"erlaeuterungen": "zu Zeile 5:\nDie privatrechtlichen Leistungsentgelte enthalten im Wesentlichen die Verkaufserlöse der Kantine. Des Weiteren sind Entgelte für Lieferungen und Leistungen aus dem Bereich\n\"Expedition & Druck\" veranschlagt.\n\nzu Zeile 6:\nDiese Position wird durch die Erstattungen der citeq für Dienstleistungen im Bereich \"Expedition & Druck\" (Personal- und Sachaufwendungen) geprägt.\n\nzu Zeile 13:\nDie Aufwendungen für Sach- und Dienstleistungen umfassen insbesondere die Aufwendungen für das zentrale und dezentrale amtsinterne IT-Budget, die Aufwendungen für die Bewirt-\nschaftung der Dienstgebäude und Sicherheitsdienstleistungen sowie die Aufwendungen für den Wareneinsatz im Kantinenbereich.\n\nzu Zeile 16:\nDie sonstigen ordentlichen Aufwendungen beinhalten überwiegend die Scankosten, die Aufwendungen für geringwertige Wirtschaftsgüter, die Mietaufwendungen der Hochleistungsdruck-\nkopiersysteme für den Bereich \"Expedition & Druck\" sowie das Büromaterial, welches für die vorgenannten Hochleistungsdruckkopiersysteme eingesetzt wird.\n\nzu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter-\n/Vermietermodells) abgebildet",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0114": {
|
||||||
|
"pgNumber": "0114",
|
||||||
|
"name": "Verwaltung der Kommunalen Stiftungen",
|
||||||
|
"beschreibung": "Die Geschäftsstelle steuert und begleitet das Wirken der kommunalen Stiftungen nach den Vorschriften der Gemeindeordnung NW und des Stiftungsgesetzes NW. Zu den Aufgaben gehören\n- die Stiftungszwecke gemäß den Stiftungssatzungen zu erfüllen und die Stiftungserträge im Sinne der Stiftungszwecke zeitnah zu verwenden. Dies geschieht durch die Entwicklung und Durchführung innovativer\nKonzepte für Stiftungsprogramme und Stiftungsprojekte sowie durch die Bereitstellung und den Betrieb von Wohnmöglichkeiten für besondere Bevölkerungsgruppen (z.B. alte Menschen und Menschen mit\nBehinderungen);\n- das in Geld angelegte und das immobile Stiftungsvermögen ertragreich zu bewirtschaften;\n- Öffentlichkeitsarbeit zu betreiben, um die Stiftungsarbeit für die Öffentlichkeit transparent zu machen und potentielle Neu- und Zustiftungen zu gewinnen;\n- Ratsuchende in allen Fragen der Neuerrichtung von Stiftungen oder Zustiftungen zu bestehenden Stiftungen zu beraten.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0115": {
|
||||||
|
"pgNumber": "0115",
|
||||||
|
"name": "IT-Management (citeq)",
|
||||||
|
"beschreibung": "Die Produktgruppe IT-Management beinhaltet im Wesentlichen die Förderungen und Aufwendungen für den Breitbandausbau in Münster und die Ausschüttung der eigenbetriebsähnlichen Einrichtung \"citeq\".\n\nDie \"citeq\" wird gemäß der Eigenbetriebsverordnung sowie nach den Bestimmungen der Betriebssatzung für die \"citeq\" geführt. Zweck der eigenbetriebsähnlichen Einrichtung ist die Erbringung von Dienstleistungen\nim Bereich der Informationstechnologie einschließlich der Kommunikationstechnologie für die Stadt Münster, die übrigen Kooperationspartner (der öffentlich-rechtlichen Vereinbarung) und sonstige Kunden im Rahmen\ndes § 107 der Gemeindeordnung.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0116": {
|
||||||
|
"pgNumber": "0116",
|
||||||
|
"name": "Migrations- und Integrationsmanagement",
|
||||||
|
"beschreibung": "Das Kommunale Integrationszentrum (KI) berät, qualifiziert und vernetzt die professionellen und ehrenamtlichen Organisationen und Träger der Integrationsarbeit in Münster. Die Stadtverwaltung, die externen\nInstitutionen sowie die Bürger*innen werden für die Herausforderungen der Integration sensibilisiert und durch Kooperationen unterstützt.\n\nDie Rahmenbedingungen, Grundlagen und Leitlinien werden durch das Gesetz zur Förderung der gesellschaftlichen Teilhabe und Integration in Nordrhein-Westfalen (Teilhabe- und Integrationsgesetz NRW) sowie das\nLeitbild Migration und Integration Münster (Migrationsleitbild 2025 - 2030) gesetzt. Das KI steuert die vom Rat beschlossene Überarbeitung des Migrationsleitbildes mit breiter stadtgesellschaftlicher Beteiligung im\n5-Jahres-Rhythmus sowie das Integrationsmonitoring.\n\nWesentliche strukturelle Optimierungen soll das Landesprogramm Kommunales Integrationsmanagement NRW bewirken, das vom KI koordiniert wird. Die bei der individuellen Beratung von Menschen mit\nEinwanderungsgeschichte und durch eine intensivere Vernetzung der lokalen Integrationsakteure gewonnenen Erkenntnisse sollen genutzt werden, um systemische Hindernisse zu beseitigen das gemeinsame Ziel zu\nverfolgen: Gleichberechtigte Teilhabe für ALLE.\n\nDie Überprüfung der Erreichung der Arbeitsziele erfolgt über die jährliche Fachdatenerhebung des Landes NRW. Das kontinuierliche städtische Integrationsmonitoring als Beobachtungs- und Steuerungsinstrument\ndes Migrationsleitbildes unterstützt die Ausrichtung kommunalen Handels und trägt dazu bei, kommunale Handlungsbedarfe zu erkennen.",
|
||||||
|
"erlaeuterungen": "Zu Zeile 15:\nDie Position beinhaltet u. a. folgende Teilbeträge:\n- Zuschuss für das Haus der Familie (Weiterbildung \"Kulturmittlerin\") in Höhe von 23.295 € (2026), 23.487 € (2027)\n- Zuschuss für das Haus der Familie (Weiterbildung \"Kulturmittler\") in Höhe von 30.730 € (2026), 31.001 € (2027)\n- Zuschuss an den Verein Iriba-Brunnen e. V. in Höhe von 9.220 €\n- Zuschuss an die Träger des Case Managements im Programm \"Kommunales Integrationsmanagement NRW!\" in Höhe von 58.200 €\n- Zuschüsse für Einzelprojekte (Projekttöpfe) in Höhe von 41.032 €\n- Weiterleitungsmittel aus dem Ehrenamts-Programm des Landes NRW in Höhe von 58.000 €\n- Weiterleitungsmittel aus dem Programm \"Kommunales Integrationsmanagement NRW\" in Höhe von 85.500 €\n- Finanzierung von Antidiskriminierungsstellen (1,25 VZÄ) in Höhe von 125.000 € (2027)\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {
|
||||||
|
"15": [
|
||||||
|
{
|
||||||
|
"name": "Zuschuss für das Haus der Familie",
|
||||||
|
"values": {
|
||||||
|
"2026": 23295,
|
||||||
|
"2027": 23487
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Zuschuss für das Haus der Familie",
|
||||||
|
"values": {
|
||||||
|
"2026": 30730,
|
||||||
|
"2027": 31001
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Finanzierung von Antidiskriminierungsstellen",
|
||||||
|
"values": {
|
||||||
|
"2027": 125000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0201": {
|
||||||
|
"pgNumber": "0201",
|
||||||
|
"name": "Ordnungsrechtliche Angelegenheiten",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst alle von der Stadt Münster als örtliche Ordnungsbehörde und Kreisordnungsbehörde wahrzunehmenden Aufgaben auf der Grundlage von Vorschriften, die die Abwehr von Gefahren und\ndie Beseitigung von Störungen für die öffentliche Sicherheit oder Ordnung zum Gegenstand haben, soweit diese nicht anderen Produktgruppen zugeordnet sind. Neben dem allgemeinen Ordnungsrecht (OBG NW)\nfinden sich die Aufgaben in zahlreichen spezialgesetzlichen Vorschriften des Bundes- und Landesrechts (z.B. Jugendschutzgesetz, Jagd- und Fischereirecht, Immissionsschutzrecht und Ordnungswidrigkeitenrecht)\nsowie des Ortsrechts wieder.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0202": {
|
||||||
|
"pgNumber": "0202",
|
||||||
|
"name": "Gewerberechtliche Angelegenheiten",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst alle von der Stadt Münster als örtliche Ordnungsbehörde und Kreisordnungsbehörde wahrzunehmenden Aufgaben im Kernbereich des Wirtschaftsverwaltungsrechts (z.B. Gewerberecht,\nGaststättenrecht, Handwerksrecht, Ladenöffnungs- und Feiertagsrecht) und die Durchführung von Märkten und Jahrmärkten (Send). Schwerpunkt der Pflichtaufgaben ist die Überwachung erlaubnisfreier und\nerlaubnispflichtiger Gewerbe und deren Registrierung.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0203": {
|
||||||
|
"pgNumber": "0203",
|
||||||
|
"name": "Straßenverkehrsrechtliche Angelegenheiten",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst alle von der Stadt Münster als örtliche Ordnungsbehörde und Kreisordnungsbehörde wahrzunehmenden Aufgaben im Bereich der Zulassung von Personen und Fahrzeugen zum\nStraßenverkehr, der Verkehrssicherung und -lenkung sowie der Verkehrsüberwachung auf der Grundlage bundes- und landesgesetzlicher Vorschriften ( Straßenverkehrsgesetz, Fahrzeugzulassungs- und\nStraßenverkehrs-Zulassungsordnung, Fahrerlaubnis-Verordnung, Straßenverkehrs-Ordnung u.a.).",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0204": {
|
||||||
|
"pgNumber": "0204",
|
||||||
|
"name": "Bürgerangelegenheiten",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst im Wesentlichen die von der Stadt Münster wahrzunehmenden Aufgaben aus den Bereichen des Melderechts, Pass- und Ausweisrechts, Einbürgerungs- und Staatsangehörigkeitsrechts,\nSozialversicherungsrechts (insbesondere Rentenversicherungsrecht), sowie des Fundrechtes. Charakteristisch für die in dieser Produktgruppe zusammengefassten Leistungen sind persönliche Kurzkontakte und das\nwohnortnahe Angebot über die Bezirksverwaltungen. Die Leistungen werden in der Innenstadt und in allen Bezirksverwaltungen angeboten. In dieser Produktgruppe wird auf eine hohe Bürger(Kunden)-orientierung\nabgestellt.",
|
||||||
|
"erlaeuterungen": "Bei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0205": {
|
||||||
|
"pgNumber": "0205",
|
||||||
|
"name": "Standesamtsangelegenheiten",
|
||||||
|
"beschreibung": "Beim Standesamt werden Ehen geschlossen und Lebenspartnerschaftsregister fortgeführt. Die rechtlichen Voraussetzungen zur Schließung einer Ehe sind zuvor anhand des Heimatrechtes des jeweiligen\nHeiratswilligen zu prüfen. Erfordert die Vorbereitung die Entscheidung eines Oberlandesgerichtes, wird das Verfahren durch das Standesamt initiiert und begleitet.\nAlle Geburten und Sterbefälle in dieser Stadt werden im Standesamt beurkundet. Das Einzugsgebiet der Geburtskliniken deckt inzwischen das gesamte Münsterland und das nördliche Ruhrgebiet ab. Darüber hinaus\nkooperieren die Unikliniken mit Kliniken im Ausland.\nAlle Register - Ehe- und Lebenspartnerschaftsregister, Geburten- und Sterberegister - sind über den gesamten gesetzlichen Fortführungszeitraum bis zu 110 Jahre lang zu aktualisieren: Scheidungen, Tod eines\nEhepartners, Vaterschaftsanerkennungen, Namensänderungen, Eheschließungen von Eltern etc. werden nachgetragen.\nAus allen Registern werden Urkunden ausgestellt.\nWie bei Notaren werden status- und namensrechtliche Erklärungen (Vaterschaftsanerkennungen, Zustimmungserklärungen, Namenserteilungen für Kinder und Erklärungen zur Namensführung in der Ehe) beim\nStandesamt beurkundet.\n\nJeder Standesbeamte und jede Standesbeamtin handelt weisungsunabhängig. Er oder sie ist als Urkundsbeamter nur den Anweisungen des Personenstandsrichters unterworfen. Einheitliches Verwaltungshandeln\ngewährleistet das Standesamt durch interne Beratung und regelmäßige fachliche Schulungen.\n\nIst aus besonderen Gründen der Name eines Münsteraner Bürgers oder einer Münsteraner Bürgerin untragbar, entscheidet das Standesamt auf Antrag des Betroffenen über eine öffentlich-rechtliche\nNamensänderung.",
|
||||||
|
"erlaeuterungen": "Bei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0206": {
|
||||||
|
"pgNumber": "0206",
|
||||||
|
"name": "Ausländerangelegenheiten",
|
||||||
|
"beschreibung": "Diese Produktgruppe umfasst alle von der Stadt Münster als Ausländerbehörde wahrzunehmenden Aufgaben. Bei der Anwendung der zu Grunde liegenden ausländerrechtlichen Bestimmungen finden die Aufnahme-\nund Integrationsfähigkeit, die wirtschaftlichen und arbeitsmarktpolitischen Interessen sowie die humanitären Verpflichtungen der Bundesrepublik Deutschland Berücksichtigung. Gesetzlicher Auftrag ist es, den Zuzug\nvon Ausländer:innen in die Bundesrepublik Deutschland zu begrenzen und zu steuern. Entsprechend dieser gesetzlichen Pflichtaufgabe wird der Aufenthalt von hier lebenden Ausländer:innen gewährt oder beendet.",
|
||||||
|
"erlaeuterungen": "Keine",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0207": {
|
||||||
|
"pgNumber": "0207",
|
||||||
|
"name": "Statistik",
|
||||||
|
"beschreibung": "Die Produktgruppe Statistik umfasst zwei Produkte:\nDie zentrale Dienstleistung bildet das zielgruppenorientierte Informationsmanagement. Hierzu zählen die Aufbereitung und Bereitstellung von\n- statistischen Daten\n- (kleinräumigen) Bevölkerungsprognosen\n- Raumbezügen mit der kleinräumigen Gebietsgliederung für unterschiedliche Fachthemen und\n- Planungs-, Entscheidungs- und allgemeinen Informationsgrundlagen.\nDas zweite Produkt bilden die Aufgaben der Auftragsstatistik für das Land NRW (Agrar-/Baustatistik, sonstige Pflichterhebungen) und Bereitstellung an IT.NRW.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells)",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0208": {
|
||||||
|
"pgNumber": "0208",
|
||||||
|
"name": "Wahlen",
|
||||||
|
"beschreibung": "Diese Produktgruppe umfasst die rechtmäßige Vorbereitung, Durchführung (Abwicklung) und Nachbereitung aller Wahlen\n(Europawahl, Bundestagswahl, Landtagswahl, Kommunalwahlen, Integrationsrat, Jugendrat sowie Abstimmungen (Bürgerbegehren, Bürgerentscheid, Volksentscheid).",
|
||||||
|
"erlaeuterungen": "Keine",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0209": {
|
||||||
|
"pgNumber": "0209",
|
||||||
|
"name": "Brandschutz und feuerwehrtechnische Hilfeleistung",
|
||||||
|
"beschreibung": "Der Brandschutz und die Hilfeleistung der Feuerwehr umfasst die Aufgabenwahrnehmung der Stadt Münster als Gefahrenabwehrbehörde nach dem Gesetz über den Brandschutz, die Hilfeleistung und den\nKatastrophenschutz (BHKG), dem Gesetz über den Zivilschutz und die Katastrophenhilfe (ZSKG) und der Bauordnung NRW (BauO NRW sowie nach dem Gesetz über Hilfen und Schutzmaßnahmen bei psychischen\nKrankheiten (PsychKG).\n\nSchwerpunkte der Tätigkeiten hierbei sind insbesondere:\n- Rettung von Menschen und Tieren bei Schadenfeuern und/oder technischen Gefahrenlagen\n- Bekämpfung von Schadenfeuern\n- Maßnahmen zur Verhütung von Bränden\n- Hilfeleistung bei Unglücksfällen und bei solchen öffentlichen Notständen, die durch Naturereignisse\nExplosionen oder ähnliche Vorkommnisse verursacht werden\n- Katastrophenschutz / Abwehr von Großeinsatzlagen\n- Zivilschutzmaßnahmen.\n\nDes Weiteren sind den feuerwehrtechnischen Hilfeleistungen auch zugeordnet:\n- Ordnungsbehördliche Durchführung zur sofortigen Unterbringung nach dem PsychKG,\n- Ordnungsbehördliche Maßnahmen bei der Überprüfung von Grundstücken auf Kampfmittel.",
|
||||||
|
"erlaeuterungen": "zu Zeile 04 und 05:\nBei den Erträgen handelt es sich um öffentlich-rechtliche und privatrechtliche Leistungsentgelte für Feuerwehreinsätze, soweit diese nicht unentgeltlich sind.\n\nzu Zeile 13:\nIn den \"Aufwendungen für Sach- und Dienstleistungen\" sind vornehmlich enthalten:\n- Aufwendungen für die Unterhaltung von Fahrzeugen\n- Aufwendungen für die Unterhaltung der Betriebs- und Geschäftsausstattung\n- Aufwendungen für sonstige Sach- und Dienstleistungen\n\nzu Zeile 16:\nZu den \"Sonstigen ordentlichen Aufwendungen\" zählen insbesondere:\n- Aufwendungen für Dienst- und Schutzkleidung\n- Aufwendungen für Aus- und Fortbildung\n- Aufwendungen für Telekommunikationsleistungen\n- Aufwendungen für die Umlage KSA\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0210": {
|
||||||
|
"pgNumber": "0210",
|
||||||
|
"name": "Rettungsdienst",
|
||||||
|
"beschreibung": "Der Rettungsdienst umfasst die Aufgabenwahrnehmung der Stadt Münster als Träger des Rettungsdienstes nach dem Rettungsgesetz (RettG NRW).\n\nSchwerpunkte der Tätigkeit hierbei sind insbesondere:\n- Organisation des Rettungsdienstes\n- Versorgung von Notfallpatienten\n- Notärztliche Versorgung von Notfallpatienten\n- Transport von Kranken, Verletzten oder sonstigen\nhilfebedürftigen Personen unter fachlicher Betreuung\n- Gefahrenabwehr bei einem Massenanfall von Verletzten\n\nBei dieser Produktgruppe handelt es sich um eine gebührenrechnende Einrichtung, die sich zu 100% aus Gebühreneinnahmen finanziert.",
|
||||||
|
"erlaeuterungen": "allgemein:\nDas Ergebnis des Teilplanes weicht auf Grund der NKF-Vorschriften vom Ergebnis der Gebührenrechnung ab.\n\nzu Zeile 04:\nBei den öffentlich-rechtlichen Leistungsentgelten handelt es sich um Einnahmen nach der \"Gebührensatzung für den Rettungsdienst der Stadt Münster\".\n\nBenutzungsgebühren ab dem 09.10.2021:\nKrankentransportwagen (KTW) 287,00 Euro\nRettungswagen (RTW) 833,00 Euro\nNotarzteinsatzfahrzeug (NEF) 890,00 Euro\nIntensivtransportwagen (ITW) 1.604,00 Euro\nKm-Gebühr (außerhalb des Stadtgebietes) 3,60 Euro\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0211": {
|
||||||
|
"pgNumber": "0211",
|
||||||
|
"name": "Veterinärwesen und Lebensmittelüberwachung",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst alle von der Stadt Münster als Kreisordnungsbehörde wahrzunehmenden Aufgaben im Veterinär- und Lebensmittelüberwachungsbereich.\nBegleitend zu den ordnungsbehördlichen Aufgaben führt das Gesundheits- und Veterinäramt in dieser Produktgruppe Beratungen und Öffentlichkeitsarbeit durch. Im Focus der Öffentlichkeit stehen insbesondere die\nTierseuchenbekämpfung und der Verbraucherschutz.",
|
||||||
|
"erlaeuterungen": "zu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0301": {
|
||||||
|
"pgNumber": "0301",
|
||||||
|
"name": "Leistungen für Schulen",
|
||||||
|
"beschreibung": "Das Amt für Schule und Weiterbildung stellt mit dieser Produktgruppe für die städtischen Schulen den erforderlichen Schulraum, einschließlich der notwendigen Ausstattung und das ergänzende kommunale Personal\nzur Verfügung. Darüber hinaus gestaltet es die schulische Bildung durch Steuerung, Koordination und Impulsgebung.\nHierdurch sollen die Schulen in die Lage versetzt werden,\n- einen den Lehrplänen entsprechenden\n- qualitativ guten\n- die besonderen Rahmenbedingungen und Bedarfe berücksichtigenden\nUnterricht anzubieten und flankierende Angebote zu ermöglichen. Bildungsergänzende Einrichtungen und nichtstädtische Schulen werden unterstützt. Die Aufgabenerledigung erfolgt in Abgrenzung zu den Aufgaben\nund Zuständigkeiten des Landes in Kooperation mit allen handelnden Personen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 13:\nDie „Aufwendungen für Sach- und Dienstleistungen“ enthalten in 2026/2027 u.a. folgende Teilbeträge:\n- Lernmittel (Schulbücher und digitale LMI) = 1.435.000 Euro (2026), 1.435.000 Euro (2027)\n- Schuletatmittel (Unterricht) = 670.000 Euro (Gesamtsumme: 1.900.000 Euro (2026)), 670.000 Euro (Gesamtsumme: 1.910.000 Euro (2027))\n- Mittel für Medienentwicklungsplan = 4.372.500 Euro (Gesamtsumme: 4.747.500 Euro (2026)), 4.416.650 Euro (Gesamtsumme: 4.791.650 Euro(2027))\n\nzu Zeile 15:\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 3.750 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 16:\nDie „Sonstigen ordentlichen Aufwendungen“ umfassen für 2026/2027 u.a. folgende Teilbeträge:\n- Schuletatmittel (Unterricht) = 1.230.000 Euro (Gesamtsumme: 1.900.000 Euro (2026)), 1.240.000 Euro (Gesamtsumme: 1.910.000 Euro (2027))\n- Beiträge an die Gemeindeunfallversicherung in Höhe von 2.668.400 Euro (2026), 2.668.400 Euro (2027)\n- Mittel für Medienentwicklungsplan 375.000 Euro (Gesamtsumme: 4.747.500 Euro (2026)), 375.000 Euro (Gesamtsumme: 4.791.650 Euro (2027))\n\nzu Zeile 28:\nBei den „Internen Leistungsverrechnungen“ werden folgende Positionen abgebildet:\n- Erträge für die BuT-Lernförderung (Jobcenter) in Höhe von 247.630 Euro (2026), 252.680 Euro (2027)\n- Aufwendungen für die Bereitstellung u. Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter- / Vermietermodells) von\n31.268.000 Euro (2026), 31.268.000 Euro (2027)\n- Aufwendungen für die offenen Ganztagsschulen an Grund- und Förderschulen durch das Amt für Kinder, Jugendliche und Familien (Saldo ohne Gebäudekosten-/aufwand) in\nHöhe von 11.500.000 Euro (2026), 11.800.000 Euro (2027)",
|
||||||
|
"breakdowns": {
|
||||||
|
"13": [
|
||||||
|
{
|
||||||
|
"name": "Lernmittel",
|
||||||
|
"values": {
|
||||||
|
"2026": 1435000,
|
||||||
|
"2027": 1435000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Schuletatmittel",
|
||||||
|
"values": {
|
||||||
|
"2026": 670000,
|
||||||
|
"2027": 670000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mittel für Medienentwicklungsplan",
|
||||||
|
"values": {
|
||||||
|
"2026": 4372500,
|
||||||
|
"2027": 4416650
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"16": [
|
||||||
|
{
|
||||||
|
"name": "Schuletatmittel",
|
||||||
|
"values": {
|
||||||
|
"2026": 1230000,
|
||||||
|
"2027": 1240000
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Beiträge an die Gemeindeunfallversicherung",
|
||||||
|
"values": {
|
||||||
|
"2026": 2668400,
|
||||||
|
"2027": 2668400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mittel für Medienentwicklungsplan",
|
||||||
|
"values": {
|
||||||
|
"2026": 375000,
|
||||||
|
"2027": 375000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"28": [
|
||||||
|
{
|
||||||
|
"name": "Erträge für die BuT-Lernförderung",
|
||||||
|
"values": {
|
||||||
|
"2026": 247630,
|
||||||
|
"2027": 252680
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"0302": {
|
||||||
|
"pgNumber": "0302",
|
||||||
|
"name": "Zentrale Leistungen für am Schulleben Beteiligte",
|
||||||
|
"beschreibung": "Das Amt für Schule und Weiterbildung sorgt mit dieser Produktgruppe für die Bereitstellung von\n- Unterstützungsangeboten\n- Fördermöglichkeiten\n- Beratungsangeboten\n- Schülerbeförderung und\n- sonstigen finanziellen Unterstützungsleistungen für Schüler/innen und am Schulleben Beteiligte und nimmt seit 2012 die Organisation der BuT-Lernförderung im Rahmen von schulnahen Angeboten wahr, deren\nFinanzierung über das Sozialamt/Jobcenter erfolgt.",
|
||||||
|
"erlaeuterungen": "zu Zeile 13:\nDie \"Aufwendungen für Sach- und Dienstleistungen\" enthalten in 2026/2027 u. a. einen Ansatz von 10.033.000 Euro (2026), 9.984.000 Euro (2027) für Schülerfahrkosten.\n\nzu Zeile 28:\nBei den \"internen Leistungsverrechnungen\" werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mie-\nter-/Vermietermodells) in Höhe von 90.810 Euro (2026), 90.810 Euro (2027) abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0401": {
|
||||||
|
"pgNumber": "0401",
|
||||||
|
"name": "Kulturmanagement / Kulturförderung",
|
||||||
|
"beschreibung": "Ausgangspunkt von Kulturmanagement/Kulturförderung ist das Kooperationsgeflecht von Einrichtungen untereinander, mit der Stadt, aber auch mit der kulturellen Szene außerhalb von Institutionen. Aufgabe ist es,\nAkteur*innen miteinander in Verbindung zu bringen, in die Stadt zu integrieren, Synergien zu entdecken und Experimente zu wagen. In enger Abstimmung und Anbindung an die Kulturträger und die Kulturpolitik\nwerden im Rahmen von Kulturmanagement/Kulturförderung Förderstrukturen und -konzepte entwickelt sowie Qualifizierungs- und Arbeitsmöglichkeiten für Künstler*innen geschaffen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmä-\nßigen Umfang von rund 162.850 Euro (2026) und 149.720 Euro (2027) durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbau-\nrechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abge-\nbildet (Umsetzung des Mieter-/Vermietermodells).\n\nallgemein:\nDie unterschiedliche Höhe der geplanten Erträge und Aufwendungen in den einzelnen Jahren beruht im Wesentlichen auf der Durchführung des Int. Jazzfestivals, das\nim Biennale-Rhythmus stattfindet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0402": {
|
||||||
|
"pgNumber": "0402",
|
||||||
|
"name": "Volkshochschule",
|
||||||
|
"beschreibung": "Die Volkshochschule ist kommunales Dienstleistungszentrum für Bildung, Begegnung und Kultur. Sie erfüllt den im Weiterbildungsgesetz NRW beschriebenen Auftrag und schafft sowohl ein nachfrageorientiertes als\nauch ein Interesse weckendes Angebot für alle Bevölkerungsgruppen. In Projekten erprobt sie neue Methoden und Konzepte der Weiterbildung. Mit speziellen Angeboten reagiert sie auf die besonderen\nWeiterbildungs- und Beratungsbedarfe relevanter Adressat*innengruppen. Sie kooperiert in Netzwerken der regionalen und überregionalen Bildungslandschaft um ihren Auftrag innovativ und ressourcenschonend zu\nerfüllen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nDie \"Zuwendungen und allgemeinen Umlagen\" enthalten die Landeszuweisung nach dem Weiterbildungsgesetz (WbG) in Höhe von 750.000 Euro (2026), 750.000 Euro (2027)\n\nzu Zeile 5:\nDie \"Privatrechtlichen Leistungsentgelte\" enthalten u. a. die Teilnehmerentgelte in Höhe von 1.965.000 Euro (2026), 1.965.000 Euro (2027)\n\nzu Zeile 16:\nDie \"Sonstigen ordentlichen Aufwendungen\" umfassen für 2026/2027 u. a. die Honorare für Dozentinnen und Dozenten in Höhe von 1.207.760 Euro/1.207.760 Euro\n\nzu Zeile 28:\nBei den intenen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Amt für Immobilenmanagement (Umsetzung des\nMieter-/Vermietermodells) in Hölhe von 813.220 Euro (2026), 813.220 Euro (2027) abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0403": {
|
||||||
|
"pgNumber": "0403",
|
||||||
|
"name": "Westf. Schule f. Musik u. Förd.der Stadtteilmusikschulen",
|
||||||
|
"beschreibung": "Die Westfälische Schule für Musik ist eine öffentliche Bildungseinrichtung für Kinder, Jugendliche und Erwachsene. Ihre Aufgaben sind:\n- kulturelle Grundversorgung auf dem Gebiet der musikalischen Bildung,\n- Befähigung zum aktiven Musizieren durch individuelle, inklusive und vielschichtige Angebote,\n- Förderung des Ensemblespiels,\n- Begabtenförderung und Berufsvorbereitung,\n- Mitgestaltung des münsterschen Musiklebens sowie innerhalb der kulturellen Bildungslandschaft Münsters eigene Akzente in der Darstellung Münsters als Kulturstadt.\n\nDie Westfälische Schule für Musik trägt mit ihrem Leistungsangebot zur Förderung von sozialer Kompetenz, Kreativität und Lernfähigkeit bei.\n(Anmerkung: Kinder und Jugendliche i.S. dieser Produktgruppe sind Kinder und Jugendliche unter 18 Jahren und darüber hinaus, soweit ein Kindergeldanspruch besteht.)",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nZu den Zuwendungen und allgemeinen Umlagen gehört ein Landeszuschuss für die regelmäßige Musikschularbeit und zu Förderprojekten sowie ein Zuschuss zum Klassikpreis.\n\nzu Zeile 15:\nVon den Transferaufwendungen entfallen\n2026 1.097.570 EUR\n2027 1.115.980 EUR\n2028 1.059.720 EUR\n2029 1.078.800 EUR\n2030 1.098.220 EUR auf die Förderung der Stadtteilmusikschulen in freier Trägerschaft.\nDie Transferaufwendungen enthalten außderdem Zuschüsse an den Regional-Wettbewerb \"Jugend musiziert\" und für den Landeswettbewerb \"Jugend musiziert\", der 2028 in Münster\nausgetragen wird.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0404": {
|
||||||
|
"pgNumber": "0404",
|
||||||
|
"name": "Stadtbücherei u. Förderung v. Büchereien freier Träger",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet den Betrieb der Stadtbücherei mit einem zentralen Standort, fünf Stadtteilbüchereien und einem Bücherbus für Einwohnerinnen und Einwohner der Stadt Münster und des Umlandes.\nDarüber hinaus ist in dieser Produktgruppe auch die kooperative Förderung von Stadtteilbüchereien freier Träger in Hiltrup (St. Clemens) und Gievenbeck (St. Michael) sowie die Zuschussgewährung für öffentliche\nBüchereien freier Träger zum Aufbau des Buch- und Medienangebotes erfasst.",
|
||||||
|
"erlaeuterungen": "zu Zeile 04:\nBei den öffentlich-rechtlichen Leistungsentgelten handelt es sich um Gebühren gemäß § 12 der Benutzungs- und Gebührenordnung für die Stadtbücherei.\n\nzu Zeile 05:\nZu den privatrechtlichen Leistungsentgelten gehören Erlöse aus dem Verkauf von Büchern, durch zusätzliche Veranstaltungen, Vermietung von Räumen und Kopierentgelte.\n\nzu Zeile 07:\nBei den sonstigen ordentlichen Erträgen handelt es sich um Schadenersatz für Bücher und Medien.\n\nzu Zeile 13:\nIn den Aufwendungen für Sach- und Dienstleistungen des Jahres 2026 sind für die Beschaffung von Medien (einschließlich e-Medien) für die\nHauptstelle (einschl. Bücherbus) 221.842 Euro\nLizenzen für e-Medien 98.500 Euro\nLizenzen für digitale Angebote 91.000 Euro\nMedienservice für Schulen 29.700 Euro\nZweigstelle Aaseemarkt 16.251 Euro\nZweigstelle Hansaplatz 14.595 Euro\nZweigstelle Coerdemarkt 11.974 Euro\nZweigstelle Kinderhaus 14.595 Euro\nZweigstelle Gievenbeck 19.084 Euro enthalten.\n\nDie Verteilung der Mittel für die Beschaffung von Medien für das Jahr 2027 wird anhand von Ausleih- und Besucherstatistiken des Jahres 2026 ermittelt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmangement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0405": {
|
||||||
|
"pgNumber": "0405",
|
||||||
|
"name": "Stadtmuseum",
|
||||||
|
"beschreibung": "Das Stadtmuseum soll die Geschichte der Stadt Münster auf der Grundlage wissenschaftlicher Forschung in populärer Gestalt deutlich machen. Dies erfolgt durch\n- Sammeln, Bewahren, Erforschen und Dokumentieren des städtischen Kunstbesitzes,\n- Weiterentwicklung der ständigen Ausstellung,\n- Sonderausstellungen zu besonderen Themen und Anlässen,\n- Führungen, Veranstaltungen und Publikationen,\n- museumspädagogische Angebote,\n- Erforschung und Bewahrung des Mahnmals und der Gedenkstätte \"Zwinger\" incl. Führungen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 5:\nVon den 219.830 Euro entfallen 119.950 Euro auf Verkaufserlöse im Museumsshop, 75.000 Euro auf das \"Pay what you want\"-System und 24.880 Euro auf Führungsentgelte (im Stadt-\nmuseum und im Zwinger).\n\nzu Zeile 13:\nDie Aufwendungen für Sach- und Dienstleistungen enthalten unter anderem\n- 278.790 Euro für die Bewachung des Stadtmuseums\n- 68.000 Euro für Waren für den Museumsshop\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung der Gebäude Stadtmuseum Münster durch das Immobilienmanagement\nabgebildet (Umsetzung des Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0406": {
|
||||||
|
"pgNumber": "0406",
|
||||||
|
"name": "Stadtarchiv",
|
||||||
|
"beschreibung": "Die Produktgruppe beinhaltet die archivfachliche Aufgabenerfüllung nach den gesetzlichen Vorgaben (Archivgesetz NRW) und der Archivsatzung vom 23.06.2021. Darunter ist folgendes zu fassen: Bewertung,\nÜbernahme, Erhalt und Erschließung amtlicher und nichtamtlicher Unterlagen, die Beratung und Betreuung der Nutzenden des Stadtarchivs sowie die Erforschung, Dokumentation und Vermittlung der\nStadtgeschichte.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0407": {
|
||||||
|
"pgNumber": "0407",
|
||||||
|
"name": "Theater Münster",
|
||||||
|
"beschreibung": "Die Produktgruppe Theater Münster beinhaltet im Wesentlichen den Zuschuss an die eigenbetriebsähnliche Einrichtung \"Theater Münster\".\n\nDas \"Theater Münster\" wird gemäß der Eigenbetriebsverordnung sowie nach den Bestimmungen der Betriebssatzung für das \"Theater Münster\" geführt. Zweck der eigenbetriebsähnlichen Einrichtung ist die Förderung\ndes kulturellen Lebens durch die Erbringung eigener Leistungen und die Unterstützung Dritter.\n\nEs besteht ein aktuell gültiger Managementkontrakt mit einer Laufzeit bis zum 31.08.2027.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0408": {
|
||||||
|
"pgNumber": "0408",
|
||||||
|
"name": "Geschichtsort Villa ten Hompel",
|
||||||
|
"beschreibung": "Die Villa ten Hompel - ehemaliges Fabrikantenwohnhaus, Sitz der Ordnungspolizei im Nationalsozialismus, Dienstsitz für Entnazifizierungsfragen und anschließend Dezernat für Wiedergutmachung für NS-Opfer im\nNachkriegsdeutschland - ist heute ein Geschichtsort, der sich seit seiner Gründung im Jahr 1999 schwerpunktmäßig mit der Zeit des Nationalsozialismus und seiner Vor- und Nachgeschichte beschäftigt. Die Villa ten\nHompel ist nicht nur ein Museum und Forschungsinstitut, sondern auch ein kommunikativ ausgerichteter, im kommunalen Gemeinwesen fest verankerter Sammlungs-, Dokumentations- und Lernort. Die hier\nentwickelten Bildungsangebote richten sich an Schulen und Hochschulen, sind aber ebenso für unterschiedliche Berufsgruppen wie Polizei oder Pflege konzipiert. Kennzeichnend für die historisch-politische Bildung\nam Geschichtsort Villa ten Hompel ist ihre methodisch und thematisch breite Ausrichtung: Kommunikation, Multiperspektivität und Gegenwartsorientierung sind die drei zentralen Komponenten, die für die Arbeit in der\nVilla ten Hompel kennzeichnend sind. Kern sind Präventionsstrategien gegen Antisemitismus, Rassismus und Gewalt.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0501": {
|
||||||
|
"pgNumber": "0501",
|
||||||
|
"name": "Leistungen der Grundsicherung für Arbeitsuchende",
|
||||||
|
"beschreibung": "Bestandteil dieser Produktgruppe sind die Leistungen an Leistungsberechtigte der Grundsicherung für Arbeitssuchende - Zweites Buch Sozialgesetzbuch (SGB II).\nHierzu gehören neben den Leistungen zur Sicherung des Lebensunterhaltes,\nLeistungen zur Eingliederung in den Arbeitsmarkt, Leistungen für Bildung und Teilhabe und arbeitsmarktpolitische Projekte des Jobcenters sowie mit kommunalen Haushaltsmitteln geförderte\nBeschäftigungsverhältnisse.\n\nErforderliche Leistungen werden unter Berücksichtigung der Grundsätze von Wirtschaftlichkeit und Sparsamkeit erbracht. Passgenaue Unterstützung der erwerbsfähigen Leistungsberechtigten und den mit ihnen in\neiner Bedarfsgemeinschaft lebenden Personen sollen die nachhaltige Heranführung und Eingliederung in den Arbeitsmarkt fördern und so Hilfebedürftigkeit verringern oder beenden und Langzeitarbeitslosigkeit\nvermeiden.\n\nZu der abgebildeten Zielkennzahl zur Veränderung des Bestands an Langzeitleistungsbeziehenden wird mit dem Ministerium für Arbeit, Gesundheit und Soziales in Nordrhein-Westfalen (MAGS NRW) regelmäßig\njeweils zum Jahresende eine Zielvereinbarung für das Folgejahr abgeschlossen. Im Rahmen der im vierten Quartal eines Jahres im ASGVAf beschlossenen Zielkorridore werden Zielverhandlungen mit dem MAGS\nNRW geführt. Zur endgültigen Zielvereinbarung kann frühestens im ersten Quartal des Folgejahres berichtet werden. Auf Grund der nicht konform verlaufenden Planungszeitpunkte können die in der Zielvereinbarung\nfestgelegten Kennzahlen von den hier abgebildeten Quoten abweichen.",
|
||||||
|
"erlaeuterungen": "zu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter-\n/Vermietermodells) und die Erträge/Aufwendungen aus den mit den anderen Ämtern abzurechnenden Leistungen abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0502": {
|
||||||
|
"pgNumber": "0502",
|
||||||
|
"name": "Sicherung des Lebensunterhalts",
|
||||||
|
"beschreibung": "Diese Produktgruppe umfasst die gesetzlichen Aufgaben der Stadt Münster zur Existenzsicherung für bedürftige Personen.\nHierzu gehören insbesondere Leistungen nach dem SGB XII, Asylbewerberleistungsgesetz (AsylbLG) und dem Bundesausbildungsförderungsgesetz (BAföG).",
|
||||||
|
"erlaeuterungen": "zu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement (Umsetzung des Mieter-\n/Vermietermodells) und die Erträge/Aufwendungen aus den mit dem Jobcenter abzurechnenden Leistungen abgebildet.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0503": {
|
||||||
|
"pgNumber": "0503",
|
||||||
|
"name": "Sicherung besonderer sozialer Bedarfe",
|
||||||
|
"beschreibung": "Zu dieser Produktgruppe gehören zum einen Leistungen nach den Sozialgesetzbüchern bei Pflegebedürftigkeit, Behinderung, zur Gesundheit, bei Wohnungslosigkeit sowie in anderen persönlichen/sozialen\nProblemsituationen und zum anderen die Bereitstellung bzw. Förderung weiterer sozialer Dienstleistungsangebote und Infrastruktur.",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nIn den Zuwendungen und allgemeine Umlagen ist für die Jahre 2026 und 2027 jeweils eine Zuwendung für das Sozialticket NRW (Münster-Pass) in Höhe von 534.370 € enthalten.\n\nzu Zeile 15:\nIn den Transferaufwendungen ist für das Jahr 2026 ein Betrag in Höhe von 997.750 € und für das Jahr 2027 ein Betrag in Höhe von 777.750 € für den Münster-Pass enthalten.\n\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 5.070 € durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0504": {
|
||||||
|
"pgNumber": "0504",
|
||||||
|
"name": "Wohngeld",
|
||||||
|
"beschreibung": "Wohngeld ist ein vom Bund und dem Land NRW jeweils zur Hälfte getragener Zuschuss zu den Wohnkosten. Es wird für Mieterinnen und Mieter als Mietzuschuss und für Eigentümerinnen und Eigentümer selbst\ngenutzten Wohnraums als Lastenzuschuss gewährt. Mit der Leistung von Wohngeld trägt das Amt für Wohnungswesen und Quartiersentwicklung dazu bei, dass einkommensschwache Haushalte ihre Wohnkosten\ntragen können.",
|
||||||
|
"erlaeuterungen": "zu Zeile 13:\nFür das Jahr 2026 werden 200.000 Euro für den Einsatz von Künstlicher Intelligenz bei der Wohngeldbearbeitung bereitgestellt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0601": {
|
||||||
|
"pgNumber": "0601",
|
||||||
|
"name": "Förderung von Kindern in Tagesbetreuung",
|
||||||
|
"beschreibung": "Kindertageseinrichtungen und Kindertagespflege haben einen eigenständigen Bildungs-, Erziehungs- und Betreuungsauftrag. Sie ergänzen damit frühzeitig die Förderung\ndes Kindes in der Familie und unterstützen Eltern in der Erziehungsaufgabe. Dadurch leisten sie einen wichtigen Beitrag zur Prävention und zum Kinderschutz.\nDie Förderung der Persönlichkeitsentwicklung des Kindes sowie die Beratung und Information der Eltern insbesondere in Fragen der Bildung und Erziehung gehören\nzu deren Kernaufgaben. Die Zusammenarbeit von Kindertageseinrichtungen und Kindertagespflege soll\ngefördert werden.\nGesetzliche Grundlagen: §§ 22 - 26 SGB VIII",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nDiese Position beinhaltet im Wesentlichen die gesetzlichen Zuweisungen des Landes zu den Betriebskosten von Kindertageseinrichtungen und Kindertagespflege. Zur Detailplanung wird\ninsbesondere auf die Vorlage V/00642/2025 „Genehmigung der Pauschalmeldung gemäß § 33 KiBiz für das Kindergartenjahr 2026/2027“ verwiesen.\n\nFür die Planung der Ansätze für ein Haushaltsjahr ist dabei immer die Entwicklung innerhalb von zwei anteiligen Kindergartenjahren (z. B. 7/12 des Kindergartenjahres 2025/2026 und\n5/12 des Kindergartenjahres 2026/2027) zu berücksichtigen. Des Weiteren ist zu berücksichtigen, dass das Land ein Jahr nach der Endabrechnung für ein Kindergartenjahr seinen Rück-\nforderungsanspruch verrechnet.\n\nzu Zeile 3:\nEs handelt sich überwiegend um die Kostenbeiträge zur Tagespflege.\n\nzu Zeile 4:\nBei den öffentlich-rechtlichen Leistungsentgelten handelt es sich um die Elternbeiträge für den Besuch von Kindertageseinrichtungen. Grundlage für die Bildung der Haushaltsansätze sind\ndie für das abgeschlossene Haushaltsjahr festgesetzten Elternbeiträge sowie die für das laufende Jahr hochgerechnete Prognose. Darüber hinaus sind die aktuellen Satzungen sowie die\nfür den Betrachtungszeitraum wichtigen Änderungen (z. B. gesetzliche Neuregelungen) zu berücksichtigen.\n\nzu Zeile 5:\nDiese Position beinhaltet überwiegend die Verpflegungskosten in städtischen Kindertageseinrichtungen.\n\nzu Zeile 15:\nDiese Position beinhaltet im Wesentlichen\n- die gesetzlichen Zuschüsse zu den Betriebskosten von Kindertageseinrichtungen freier Träger,\n- freiwillige städtische Zuschüsse zum Betrieb von Kindertageseinrichtungen freier Träger,\n- Maßnahmen im Rahmen des u3-Programms,\n- die Förderung von Tagespflegestellen und\n- wirtschaftliche Hilfen für Kinder in Tageseinrichtungen.\n\nBei der Ansatzbildung wird der aktuelle Planungsstand bei den Investitionsmaßnahmen (Inbetriebnahme neuer Kindertageseinrichtungen) berücksichtigt.\n\nWeitere Einzelheiten sind dem Bericht über die Vergabe von Zuschüssen an Vereine, Vereinigungen und Verbände zu entnehmen.\n\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 314.860 Euro (2026) und 313.390 Euro (2027) durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nHaushaltsplan 2026 Förderung von Kindern in Tagesbetreuung Dezernat IV\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0602": {
|
||||||
|
"pgNumber": "0602",
|
||||||
|
"name": "Kinder- und Jugendarbeit",
|
||||||
|
"beschreibung": "Die Kinder- und Jugendarbeit gliedert sich in Angebote der offenen Kinder- und Jugendarbeit (OKJA) und der Jugendverbandsarbeit. Die Angebote werden durch die freien und den öffentlichen Träger der Jugendhilfe\nvorgehalten (§§ 11, 12 SGB VIII).\nAn 47 Grund- und Förderschulen wird der Offene Ganztag durchgeführt (§ 9 SchulG, BASS 12 - 63 Nr. 2).",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nDiese Position beinhaltet im Wesentlichen die Zuwendungen des Landes zum Betrieb der Jugendeinrichtungen und der offenen Ganztagsschule.\n\nzu Zeile 5:\nBei den privatrechtlichen Leistungsentgelten handelt es sich um die Entgelte für die Nutzung von Angeboten der städtischen Jugendeinrichtungen.\n\nzu Zeile 15:\nDiese Position beinhaltet die Zuschüsse an freie Träger der offenen Kinder- und Jugendarbeit einschließlich der Zuschüsse im Rahmen der Richtlinie zur Förderung der Kinder- und\nJugendarbeit. Ebenso sind an dieser Stelle die Aufwendungen für die OGS in freier Trägerschaft veranschlagt.\nWeitere Einzelheiten sind dem Zuschussbericht zu entnehmen.\n\nzu Zeile 16:\nDiese Position beinhaltet u. a. die Beschäftigungsentgelte, Programm- und Betriebsmittel der städtischen Jugendeinrichtungen sowie der offenen Ganztagsschule.\n\nzu Zeile 27:\nBei den Erträgen aus internen Leistungsbeziehungen handelt es sich um die Kosten für den Betrieb der offenen Ganztagsschulen, die im Sinne einer verursachungsgerechten Darstellung\nvon der Produktgruppe 0301 „Leistungen für Schulen“ erstattet werden.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0603": {
|
||||||
|
"pgNumber": "0603",
|
||||||
|
"name": "Förderung von benachteiligten jungen Menschen",
|
||||||
|
"beschreibung": "Junge Menschen, die individuell beeinträchtigt und/oder sozial benachteiligt sind, werden durch die Angebote von freien Trägern und dem öffentlichen Träger der Jugendhilfe gefördert. Aufgabe ist es, jungen\nMenschen zum Ausgleich sozialer Benachteiligungen oder zur Überwindung individueller Beeinträchtigungen sozialpädagogische Hilfen anzubieten.\nGesetzliche Grundlage: §§ 13, 13a SGB VIII",
|
||||||
|
"erlaeuterungen": "zu Zeile 2:\nDiese Position beinhaltet vor allem die Zuwendungen des Landes für die Drogenhilfe Münster.\n\nzu Zeile 15:\nDiese Position beinhaltet im Wesentlichen die Zuschüsse an freie Träger im Bereich der Jugendsozialarbeit, der Jugendhilfe an den Schulen und der Drogenhilfe.\nWeitere Einzelheiten sind dem Zuschussbericht zu entnehmen.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0604": {
|
||||||
|
"pgNumber": "0604",
|
||||||
|
"name": "Familienförderung",
|
||||||
|
"beschreibung": "Familienförderung umfasst die allgemeine Förderung der Erziehung in der Familie. Leistungen zur Förderung der Erziehung in der Familie sind insbesondere Bildungs- und- und Beratungsangebote zur Vermittlung\nerzieherischer Kompetenz sowie zur Stärkung der Erziehungskraft und des Selbsthilfepotentials.\nZu dieser Produktgruppe gehören zudem die Bereiche der Frühen Hilfen und der Präventionskette. Die Frühe Hilfen umfassen die Lebensphasen ab Geburt und die der ersten drei Lebensjahre. Innerhalb der\nPräventionskette werden zudem die Lebensphasen über die frühe Kindheit hinaus bis zur Volljährigkeit in den Blick genommen. Beides sind Unterstützungssysteme mit koordinierten Hilfsangeboten, häufig in\nKooperation mit der Familienbildung und den Erziehungsberatungsstellen der freien Träger. Ein weiterer Baustein sind die Angebote des Familienbüros, die der frühseitigen Unterstützung und Beratung junger Familien\nin Münster dienen.\nEbenfalls zu dieser Produktgruppe zählen die Elterngeldstelle, die Eltern und Arbeitgeber/-innen zu Fragen und Möglichkeiten der Elternzeit und des Elterngelds berät.\nGesetzliche Grundlagen: §§ 16 (Allgemeine Förderung der Erziehung in der Familie), 17 (Beratung in Fragen der Partnerschaft, Trennung und Scheidung) und 18 (Beratung\nund Unterstützung bei der Ausübung der Personensorge und des Umgangsrechts) SGB VIII, Bundeselterngeld- und Elternzeitgesetz (BEEG), Gesetz zur Kooperation und Information im Kinderschutz (KKG)",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nDiese Position beinhaltet die Zuschüsse an freie Träger im Bereich der Familienförderung.\nWeitere Einzelheiten sind dem Bericht über die Vergabe von Zuschüssen an Vereine, Vereinigungen und Verbände zu entnehmen.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0605": {
|
||||||
|
"pgNumber": "0605",
|
||||||
|
"name": "Erzieherische und wirtschaftliche Hilfen für Familien",
|
||||||
|
"beschreibung": "Die Leistungen dieser komplexen Produktgruppe umfassen den gesamten pädagogischen und wirtschaftlichen Bereich der ambulanten und stationären Hilfen zur Erziehung\n(HzE), der Eingliederungshilfen und Adoptionsaufgaben. Ferner gehören dazu die aufsuchenden Tätigkeiten der Bezirkssozialarbeit in den Stadtteilen einschließlich\neiniger Serviceaufgaben für das Sozialamt, die Wahrnehmung der Gerichtshilfen (Familien- und Jugendgericht) und des Kinderschutzes.\nEbenso zählen dazu auch die Aufgaben der Beistandschaften und des Unterhaltsvorschusses.\nGesetzliche Grundlagen:\n§§ 8a, 14, 18, 19, 20, 21, 27 - 35, 35a, 39 - 42, 50 - 52a, 55, 58a, 59 - 60, 85 - 97 SGB VIII, § 1712 BGB, SGB XII und Unterhaltsvorschussgesetz (UVG).",
|
||||||
|
"erlaeuterungen": "zu Zeile 3:\nDiese Position umfasst die Beiträge und Kostenerstattungen für geleistete Hilfen zur Erziehung, soweit sie nicht von öffentlichen Trägern sozialer Leistungen getragen werden.\n\nzu Zeile 6:\nEs handelt sich um Kostenerstattungen vom Land, vom Landschaftsverband oder von Gemeinden für geleistete Hilfen zur Erziehung.\n\nzu Zeile 13:\nDiese Position beinhaltet im Wesentlichen Kostenerstattungen an das Land, den Landschaftsverband oder an Gemeinden für geleistete Hilfen zur Erziehung.\n\nzu Zeile 15:\nDiese Position umfasst im Wesentlichen\n- die Leistung der ambulanten und stationären Hilfen zur Erziehung,\n- Unterhaltsvorschussleistungen und\n- Zuschüsse an freie Träger in den Bereichen Erziehungshilfen, Betreuungsvereine, Schutz von Kindern und Jugendlichen sowie Jugendgerichtshilfe.\n\nGrundsätzliche Kostensteigerungen ergeben sich u.a. durch Entgeltzahlungen an freie Träger (ambulante und stationäre Hilfen zur Erziehung), die sich durch Personalkos-\ntensteigerungen in Anlehnung an den TVöD bzw. kirchliche Tarifverträge ergeben.\n\nWeitere Einzelheiten sind dem Bericht über die Vergabe von Zuschüssen an Vereine, Vereinigungen und Verbände zu entnehmen.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0701": {
|
||||||
|
"pgNumber": "0701",
|
||||||
|
"name": "Gesundheitsdienste",
|
||||||
|
"beschreibung": "Hauptaufgabe des Gesundheits- und Veterinäramtes als untere Gesundheitsbehörde ist es, die Gesundheit der Bevölkerung zu erhalten, zu schützen und zu fördern. Dazu werden die gesundheitlichen Verhältnisse\nund Versorgungsstrukturen erfasst, bewertet und fortentwickelt. Gesundheitsrisiken oder Ansteckungsgefahren für die Bevölkerung werden durch Prävention, Aufklärung und qualitätssichernde Maßnahmen verhindert\nbzw. begrenzt. Das Amt wirkt auf eine angemessene gesundheitliche Versorgung insbesondere für Menschen in gesundheitlichen und sozialen Problemlagen hin. Ärztliche Zeugnisse und Gutachten werden als\nEntscheidungshilfe im Auftrag der Ämter der Stadtverwaltung und anderer Behörden erstellt.",
|
||||||
|
"erlaeuterungen": "zu Zeile 15:\nDiese Position beinhaltet im Wesentlichen die Krankenhausumlage mit 5,3 Mio. Euro.\n\nzu Zeile 27, 28:\nBei den internen Leistungsbeziehungen werden die Erträge aus den mit dem Jobcenter abzurechnenden Leistungen und die Aufwendungen für die Bereitstellung und Bewirtschaftung von\nGebäuden durch das Immobilienmanagement abgebildet (Umsetzung des Mieter-/Vermietermodells).\n.",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0801": {
|
||||||
|
"pgNumber": "0801",
|
||||||
|
"name": "Sportinfrastruktur,Sportförderung,Sportveranstaltungen",
|
||||||
|
"beschreibung": "Der Verfassungsauftrag, den Sport zu pflegen und zu fördern, wird auf der Basis des Subsidiaritätsprinzips umgesetzt. Mit der \"Integrierten gesamtstädtischen Sportentwicklungsplanung\" wird der Weiterentwicklung\ndes Sports in Münster Rechnung getragen. Ziel ist die Beteiligung und Berücksichtigung des Sports im Rahmen des integrierten Stadtentwicklungskonzepts, um allen Menschen in Münster ein vielfältiges Sportangebot\nanbieten zu können und somit die Lebensqualität in der Stadt zu erhöhen. Dazu gehört:\n- die inhaltliche Gestaltung der Förderstrukturen für den Sport in Münster sowie die Weiterentwicklung der Sportstätteninfrastruktur\n- die Infrastruktur an städtischen und vereinseigenen Turn- und Sporthallen, Sportanlagen und -plätzen sowie Sondersportanlagen bedarfsorientiert zu sichern und gegebenenfalls auszubauen\n- die infrastrukturelle und organisatorische Sicherung des Schulsports bei gleichzeitiger Initiierung der inhaltlichen Weiterentwicklung sowie die Förderung von Bewegungserziehung im Vorschulalter und\nFörderangebote vom Grundschulalter bis zum Übergang an weiterführende Bildungseinrichtungen\n- die Mitgestaltung des Stadtmarketings mit und durch Sport im Rahmen von Großveranstaltungen im Leistungs-/Breiten- und Freizeitsportbereich, um den Sport als attraktiven Standortfaktor für die Bürgerschaft zu\nsichern.",
|
||||||
|
"erlaeuterungen": "Die in der Zeile 15 - Transferaufwendungen - genannte Summe ist im Zuschussbericht detailliert dargestellt.\n\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 34.240 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0802": {
|
||||||
|
"pgNumber": "0802",
|
||||||
|
"name": "Bäder",
|
||||||
|
"beschreibung": "Das Sportamt unterhält und betreibt zur Sicherung angemessener Schwimm- und Bademöglichkeiten sieben Hallenbäder und drei Freibäder.\nErgänzend fördert das Sportamt das vereinseigene Freibad der Schwimmvereinigung Münster von 1891 e. V in Sudmühle und das Bad der Bürgerbad Handorf gGmbH.",
|
||||||
|
"erlaeuterungen": "Die in der Zeile 15 - Transferaufwendungen - genannte Summe ist im Zuschussbericht detailliert dargestellt.\n\nzu Zeile 15:\nVergünstigungen: Ergänzend zu den in den Transferaufwendungen enthaltenen Zuschüssen werden Vereine, Verbände und andere Organisationen in einem wertmäßigen Umfang von\nrund 7.430 Euro durch die vergünstigte Bereitstellung von Immobilien im Rahmen von Miet-, Pacht- und Erbbaurechtsverträgen unterstützt.\n\nzu Zeile 28:\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter-/Vermietermodells).\n\nAnmerkung:\nMit der Umstellung auf einen produktorientierten Haushalt im Rahmen der Einführung des Neuen Kommunalen Finanzmanagements (NKF) sind die Ausschüttungen der \"Stadtwerke\nMünster GmbH\" an den Betrieb gewerblicher Art (BgA) \"Bäder\" sowie die damit im Zusammenhang stehenden Ertragssteuern bzw. Steuererstattungen in der Produktgruppe 1501 \"Anteile\nan Unternehmen\" auszuweisen. Ausschlaggebend hierfür sind die im Einvernehmen mit dem Innenministerium bekannt gegebenen Zuordnungsvorschriften zum finanzstatistischen Pro-\nduktrahmenplan, nach dem die Ressourcen- bzw. Finanzbeziehungen mit Unternehmen in der Produktgruppe 1501 \"Anteile an Unternehmen\" abzubilden sind.\n\nUnabhängig von der Abbildung der Ausschüttungen der \"Stadtwerke Münster GmbH\" und der damit im Zusammenhang stehenden Ertragsteuern bzw. Steuererstattungen in\nder Produktgruppe 1501 \"Anteile an Unternehmen\" aufgrund der Vorgaben des Produktrahmenplans bleibt der BgA \"Bäder\" alleiniger Anteilseigner der \"Stadtwerke Münster\nGmbH\".",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0901": {
|
||||||
|
"pgNumber": "0901",
|
||||||
|
"name": "Stadt- und Regionalentwicklung, Stadtplanung",
|
||||||
|
"beschreibung": "Die Produktgruppe umfasst\n- die Münster-spezifischen Aufgaben bei der Regional- und Stadtumlandentwicklung\n- Geschäftsführung und Stadtentwicklungsaufgaben in der Stadtumlandentwicklung der Stadtregion Münster,\n- Geschäftsführung und Stadtentwicklungsaufgaben für das landesweite Netzwerk Stadtentwicklung NRW,\n- die Aufgaben der konzeptionellen, räumlichen und bürgerorientierten Stadt- und Stadtteilentwicklung\n- die Aufgaben der vorbereitenden und der verbindlichen Bauleitplanung (Flächennutzungs- und Bebauungsplanung), einschließlich sonstiger städtebaulicher Satzungen\n- die Aufgaben stadtplanerischer und städtebaulicher Standort- und Projektentwicklungen\n- die Aufgaben der Stadterneuerung und der Stadtgestaltung\n- Vollzug der Sozialen Erhaltungssatzung\n- die Aufgaben der strategischen integrierten und nachhaltigen Stadtentwicklung\n- die Aufgaben der wissensbasierten Stadtentwicklung und kooperativen Wissenschaftsentwicklung\n- die Aufgaben der klimagerechten Stadtentwicklung\n- die Aufgaben der Stadtentwicklung im digitalen Zeitalter (Smart City)\n- die Aufgaben der mitgestaltenden Öffentlichkeitsbeteiligung (analog, digital)",
|
||||||
|
"erlaeuterungen": "zu Zeile 28\nBei den internen Leistungsbeziehungen werden die Aufwendungen für die Bereitstellung und Bewirtschaftung von Gebäuden durch das Immobilienmanagement abgebildet (Umsetzung\ndes Mieter / Vermietermodells)",
|
||||||
|
"breakdowns": {}
|
||||||
|
},
|
||||||
|
"0902": {
|
||||||
|
"pgNumber": "0902",
|
||||||
|
"name": "Vermessung, Kataster und Geoinformation",
|
||||||
|
"beschreibung": "In dieser Produktgruppe sind die Aufgaben des Vermessungs- und Katasterwesens gebündelt. Sie umfassen die Erhebung von raumbezogenen Sachverhalten oder die Übertragung von Planungsdaten in die\nÖrtlichkeit durch Vermessung und bilden damit die Grundlage für die Führung des Liegenschaftskatasters, der amtlichen Kartenwerke und der raumbezogenen Informationssysteme. Im Liegenschaftskataster werden\nflächendeckend für das Stadtgebiet alle Flurstücke und Gebäude sowie die Topographie beschrieben, dargestellt und ständig fortgeschrieben. Im Rahmen des Geodatenmanagements werden die gewonnenen\nInformationen weiterentwickelt z.B. durch Harmonisierung von Datenbanksystemen, ggf. mit weiteren Daten verknüpft und als Grundlage für diverse Anwendungen in analoger und digitaler Form bereitgestellt. Die\nBodenordnung schafft durch Grundstücksneuordnung die Voraussetzung für eine planungskonforme Nutzung von Grundstücken und die Grundstücksbewertung sorgt mit der Einrichtung des Gutachterausschusses für\nGrundstückswerte für die notwendige Transparenz auf dem Grundstücksmarkt und liefert Wertermittlungen für zahlreiche private und kommunale Zwecke. Die amtliche Lagebezeichnung mit Straßenname und\nHausnummer bildet den eindeutigen Raumbezug für alle adressbezogenen Sachverhalte in der Stadt Münster.",
|
||||||
|
"erlaeuterungen": null,
|
||||||
|
"breakdowns": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,197 @@
|
|||||||
|
# Design Brief — ms-haushalt
|
||||||
|
|
||||||
|
A single-page editorial app for navigating the City of Münster budget. Output of `/impeccable shape`. Intended to be handed to `/impeccable craft` or any implementation skill.
|
||||||
|
|
||||||
|
## 1. Feature Summary
|
||||||
|
|
||||||
|
A print-rooted, editorial single-page app that lets a Münster resident or journalist navigate the city's budget as a living treemap. Both money flows — revenues coming in, expenses going out — are first-class. A time slider runs from 2008 (historical actuals) through 2028 (planning projection). Clicking any tile semantically zooms into its components. Each tile carries data-derived context plus, where authored, short editorial summaries pulled from the source PDFs. The deliverable is a static-feeling site that reads like a Sunday-paper data feature.
|
||||||
|
|
||||||
|
## 2. Primary User Action
|
||||||
|
|
||||||
|
**Understand the budget as two flows: where money comes from, and where it goes.** The visitor leaves with intuition for the rough shape of both sides — and, on click, a clear sense of what dominates a particular Produktbereich.
|
||||||
|
|
||||||
|
## 3. Design Direction
|
||||||
|
|
||||||
|
Refer to `.impeccable.md` for the full design context. Specific to this feature:
|
||||||
|
|
||||||
|
* **Editorial, not exploratory.** This is not "a tool for power users to slice data." It's a guided document that happens to be interactive. Defaults must be opinionated: the page on first paint should already say something specific about the 2026/2027 draft.
|
||||||
|
|
||||||
|
* **Treemap as protagonist.** Every other element — type, side panel, time slider, controls — is in service to the treemap. Nothing else gets to be visually loud.
|
||||||
|
|
||||||
|
* **Two flows, not two charts.** The page does not show two competing treemaps. It shows one canvas that the visitor pivots between *Aufwendungen* (expenses) and *Erträge* (revenues) via a confident toggle that's part of the headline area, not a hidden control.
|
||||||
|
|
||||||
|
* **Time as scrubbable narrative.** The slider is not a filter — it's a storytelling device. Default position lands on the latest plan year; scrubbing reveals continuity (15+ years of actuals) and projection (3 forward years). Tile sizes tween smoothly; tile colors track delta vs. previous slider position.
|
||||||
|
|
||||||
|
## 4. Layout Strategy
|
||||||
|
|
||||||
|
**Asymmetric magazine spread, single scroll.**
|
||||||
|
|
||||||
|
```text
|
||||||
|
┌────────────────────────────────────────────────────────────────┐
|
||||||
|
│ WO MÜNSTER │
|
||||||
|
│ SEIN GELD Aufwendungen │ Erträge │
|
||||||
|
│ AUSGIBT. ───────────────────── │
|
||||||
|
│ ───── │
|
||||||
|
│ Stadt Münster · Haushaltsentwurf 2026/2027 │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────────┐ ┌──────────────────────┐ │
|
||||||
|
│ │ │ │ DETAIL │ │
|
||||||
|
│ │ │ │ ───── │ │
|
||||||
|
│ │ T R E E M A P │ │ Selected node name │ │
|
||||||
|
│ │ (lead canvas) │ │ → 312,4 Mio. € │ │
|
||||||
|
│ │ │ │ 24 % der Ausgaben │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ │ Veränderung ggü. 2024 │ │
|
||||||
|
│ │ │ │ Sparkline 2008–2028 │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ │ Editorial-Note │ │
|
||||||
|
│ └────────────────────────────────┘ │ (sourced or written) │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ ◄──────●────────────────────────► │ ↳ Quelle: Band 1, S.… │ │
|
||||||
|
│ 2008 2028 └──────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Wie liest man das? · Methodik · Datenquelle │
|
||||||
|
└────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
* **Headline area** (left-anchored, asymmetric): a vertical-stacked display headline in a strong serif/slab. Period after the headline is set as a typographic accent. Subhead in a smaller, refined body face. **Aufwendungen | Erträge** toggle sits to the right of the headline at top, set as a typographic switch (active state = solid, inactive = thin underline), not as buttons.
|
||||||
|
|
||||||
|
* **Treemap canvas**: occupies ~2/3 of horizontal space below the headline. Asymmetric — the panel is *not* centered. Tiles are flat fields of color; numbers and labels are typeset *on* the tile, not floated above it.
|
||||||
|
|
||||||
|
* **Detail panel**: ~1/3 width, right side. Hairline rule separates it from the canvas. No card, no shadow, no border-stripe — just typography and white space. Typeset like a magazine sidebar: small caps eyebrow, headline, body, footnote.
|
||||||
|
|
||||||
|
* **Time slider**: lives below the canvas, full editorial width. The slider track is a typographic timeline — years marked in small caps, with subtle marks distinguishing actuals (filled) from plan/projection (hollow).
|
||||||
|
|
||||||
|
* **Bottom matter**: small-caps links to "Wie liest man das?" / methodology / data source. Set like a magazine's footer.
|
||||||
|
|
||||||
|
**Mobile spread:**
|
||||||
|
|
||||||
|
* Headline collapses to single column, toggle drops below subhead.
|
||||||
|
|
||||||
|
* Treemap takes full width but reflows: at the deepest level it becomes a vertical proportional list (rectangles stacked, height = share). This preserves the "shape of the budget" reading on a phone where small-area tiles become unreadable.
|
||||||
|
|
||||||
|
* Detail panel becomes a bottom sheet, summoned by tapping a tile. Drag to expand/dismiss.
|
||||||
|
|
||||||
|
* Time slider sits above the bottom sheet, pinned.
|
||||||
|
|
||||||
|
## 5. Key States
|
||||||
|
|
||||||
|
* **First paint (default):** *Aufwendungen* selected, slider at 2026/2027, no tile selected. Detail panel shows totals + a one-paragraph editorial lead about the draft (the "this is the news" beat).
|
||||||
|
|
||||||
|
* **Hover (desktop) / focus:** tile lifts slightly via opacity/contrast change (no scale, no shadow). Detail panel previews that tile's figures without commitment. Cursor: zoom-in.
|
||||||
|
|
||||||
|
* **Selected (drilled in):** clicked tile's children fill the canvas; siblings have animated offscreen; breadcrumb appears above canvas (`Gesamt › Soziale Leistungen › Hilfen für Asylbewerber`). Esc / breadcrumb-click zooms back out.
|
||||||
|
|
||||||
|
* **Compare mode (slider scrubbing):** tile sizes and positions tween smoothly; tile fill briefly desaturates and is overlaid with a delta encoding (subtle directional tint — warmer if growing, cooler if shrinking, calibrated quietly, never traffic-light red/green).
|
||||||
|
|
||||||
|
* **Empty / missing data** (a Produktgruppe has no value for the chosen year, common for old years before reorganizations): tile becomes a hatched/textured placeholder with the label preserved; detail panel explains "Keine Daten für dieses Jahr — diese Produktgruppe wurde XYZ neu strukturiert." (text TBD by data inspection)
|
||||||
|
|
||||||
|
* **Loading:** the treemap layout itself is the loader — tiles materialize from a single block via opacity stagger, ~600ms, ease-out-quart. No spinner.
|
||||||
|
|
||||||
|
* **Error / data fetch failure:** quiet inline notice in the detail panel — "Daten konnten nicht geladen werden." with a retry link. No modal, no toast.
|
||||||
|
|
||||||
|
* **No-JS / progressive enhancement:** show the totals as a static typeset table and a link to the source CSV/XLSX. The Astro architecture supports this naturally.
|
||||||
|
|
||||||
|
## 6. Interaction Model
|
||||||
|
|
||||||
|
* **Drill: semantic zoom.** Click a tile → CSS transform-based animation expands its rect to the canvas bounds while siblings translate offscreen (opacity to 0). On arrival, children fade in within the new bounds. Reverse on zoom-out. ~450ms, ease-out-quart. Transform-only — no width/height animation. Reduced-motion: crossfade replacement at 150ms.
|
||||||
|
|
||||||
|
* **Toggle Aufwendungen ↔ Erträge:** typographic switch in the headline. Treemap re-tiles; tiles tween from current rects to new rects. The toggle is also a keyboard shortcut (e.g., `1`/`2` or `←`/`→`).
|
||||||
|
|
||||||
|
* **Time slider:** drag to scrub, click to jump. Snaps to year ticks. Tiles tween rect + fill on each step. Slider is keyboard-operable (arrow keys). Selected node persists across years; if a Produktgruppe didn't exist in the scrubbed-to year, its tile shows the empty/hatched state without losing selection.
|
||||||
|
|
||||||
|
* **Hover preview** (desktop only): tile lifts; detail panel updates in place, but doesn't lock until clicked. Cursor changes to indicate zoom is available.
|
||||||
|
|
||||||
|
* **Deep linking & sharing:** URL captures `{flow, year, drill-path}` (e.g., `/aufwendungen/2026/soziale-leistungen/hilfen-asyl`). Browser back/forward navigates the drill stack. Sharing a link lands the recipient on the same scene.
|
||||||
|
|
||||||
|
* **Source links:** every detail panel includes "Quelle: Band 1, S. ###" linking to the PDF (anchored where possible). For values that span data files (e.g., 2025 appears in two plans), a "Welcher Wert?" footnote explains which plan is canonical.
|
||||||
|
|
||||||
|
## 7. Content Requirements
|
||||||
|
|
||||||
|
**Display headline** (one of these or similar — to author):
|
||||||
|
|
||||||
|
* "WO MÜNSTER SEIN GELD AUSGIBT." (when Aufwendungen)
|
||||||
|
|
||||||
|
* "WORAUS MÜNSTERS HAUSHALT BESTEHT." (when Erträge)
|
||||||
|
|
||||||
|
* Typeset in two or three vertically-stacked lines, period as typographic accent.
|
||||||
|
|
||||||
|
**Subhead:** "Stadt Münster · Haushaltsentwurf 2026/2027 · interaktiv erkundet"
|
||||||
|
|
||||||
|
**Editorial lead** (~80–120 words, hand-written): the "this is the news" paragraph for the 2026/2027 draft. Replaces the empty detail panel on first paint.
|
||||||
|
|
||||||
|
**Per-node content (data-derived, automatic for all 87+ nodes):**
|
||||||
|
|
||||||
|
* Display name (from CSV)
|
||||||
|
|
||||||
|
* Current year value (formatted German: `312,4 Mio. €`)
|
||||||
|
|
||||||
|
* Share of parent and share of total
|
||||||
|
|
||||||
|
* Year-over-year change (absolute and %)
|
||||||
|
|
||||||
|
* Sparkline 2008–2028 (where data exists)
|
||||||
|
|
||||||
|
* Top 3 sub-items by value (when applicable)
|
||||||
|
|
||||||
|
**Per-node editorial note (optional, hand-written or LLM-summarized from PDFs):**
|
||||||
|
|
||||||
|
* 1–3 sentences pointing at what's interesting about this node
|
||||||
|
|
||||||
|
* Stored as `content/notes/{produktbereich-slug}/{produktgruppe-slug}.md` with frontmatter
|
||||||
|
|
||||||
|
* Build-time fallback: short LLM summary derived from the relevant section of Band 1/2
|
||||||
|
|
||||||
|
**Microcopy:**
|
||||||
|
|
||||||
|
* Toggle: `Aufwendungen` | `Erträge`
|
||||||
|
|
||||||
|
* Breadcrumb separator: `›`
|
||||||
|
|
||||||
|
* Empty data: `Keine Daten · Produktgruppe in diesem Jahr nicht ausgewiesen`
|
||||||
|
|
||||||
|
* Source link: `↳ Quelle: …`
|
||||||
|
|
||||||
|
* Methodik footer link: `Wie liest man das? · Methodik · Datenquelle: opendata.stadt-muenster.de`
|
||||||
|
|
||||||
|
**Number formatting:**
|
||||||
|
|
||||||
|
* German formatting throughout (`.` thousands, `,` decimal)
|
||||||
|
|
||||||
|
* Auto-scale at the tile level: `Mio. €` / `Mrd. €` for compactness; full euros only in detail panel
|
||||||
|
|
||||||
|
* Revenues displayed as positive numbers (the source CSV's negative sign convention is normalized away from the user's view)
|
||||||
|
|
||||||
|
## 8. Recommended References
|
||||||
|
|
||||||
|
For implementation, prioritize:
|
||||||
|
|
||||||
|
* `reference/spatial-design.md` — asymmetric magazine layouts, fluid grids, container queries for the side panel
|
||||||
|
|
||||||
|
* `reference/typography.md` — display-vs-body pairing, OpenType features, fluid clamp scales
|
||||||
|
|
||||||
|
* `reference/motion-design.md` — semantic-zoom timing, slider-tween orchestration, reduced-motion fallbacks
|
||||||
|
|
||||||
|
* `reference/color-and-contrast.md` — OKLCH-driven warm-paper palette, treemap fill scales that survive both small and large tiles
|
||||||
|
|
||||||
|
* `reference/responsive-design.md` — the mobile reflow from treemap to vertical proportional stack
|
||||||
|
|
||||||
|
## 9. Open Questions
|
||||||
|
|
||||||
|
These should be resolved during implementation, not in the brief:
|
||||||
|
|
||||||
|
1. **Display font choice.** The brief calls for "strong serif or slab with editorial-print rooting." We will go with Söhne Breit, and Söhne Mono for numbers.
|
||||||
|
|
||||||
|
2. **Canonical-year resolution.** When the same year appears in multiple plan files (e.g., 2025 is in the 2024 plan and the 2025 plan, with different values), the most recent plan that contains it is the slider's source of truth, with a footnote when overlapping plans disagree by >5 %.
|
||||||
|
|
||||||
|
3. **2026/2027 machine-readable data.** Currently only PDF. Implementation needs a one-time tabula-py / camelot extract from the PDF Band 1.
|
||||||
|
|
||||||
|
4. **LLM summary pipeline.** The hybrid editorial model needs a build-time job that extracts per-Produktbereich sections from Band 1/2 PDFs and summarizes them. Open: which model, prompt, and review gate before the summaries are exposed to readers? Flag-each-summary-as-AI-generated should be the default until reviewed.
|
||||||
|
|
||||||
|
5. **Color encoding — decided: two-hue system, one per flow.** *Aufwendungen* and *Erträge* each get their own hue family, constructed in OKLCH on the warm-paper background. Within a flow, tiles vary in lightness (and a touch of chroma) to encode value — bigger tiles sit darker/more saturated, smaller tiles lighter. The two hues should be far enough apart on the OKLCH wheel to read as a clear pivot when toggling, but both must hold their own against the off-white surface. Recommended exploration: a deep purple for *Aufwendungen* (money leaving) and an orange for *Erträge* (money coming in) — to be auditioned in OKLCH. Anti-pattern: red/green (traffic-light) or any pairing that signals "good/bad," since neither flow is morally loaded. The delta-while-scrubbing encoding (warmer = growing, cooler = shrinking) operates *within* the active hue's family, never as a second color overlay.
|
||||||
|
|
||||||
|
6. **Hosting / deploy target.** Static-first via Astro simply placed via sftp on an uberspace account. domain: faz.ms/haushalt/ Analytics Matomo without cookies
|
||||||
|
|
||||||
|
7. **2008–2022 actuals integration.** The Jahresabschluss file has fewer columns and slightly different spelling (`allgemeine` vs `algemeine`). The data layer must reconcile both into one long-format table.
|
||||||
|
|
||||||
|
⠀
|
||||||
@@ -0,0 +1,384 @@
|
|||||||
|
# Spec: Saldo "Flüsse" view (parallel sets)
|
||||||
|
|
||||||
|
**Status:** Draft\
|
||||||
|
**Date:** 2026-05-07\
|
||||||
|
**Owner:** Flo
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A second visualisation that lives on the budget page and answers a question the icicle cannot: *what kinds of money come in, where do they pool, and what kinds of money go out again?* It is a parallel-sets diagram — Ertragsarten on the left, Aufwandsarten on the right, and a middle column that reflects the user's current zoom level in the icicle.
|
||||||
|
|
||||||
|
The view is reached by clicking the **Saldo** total in the sidebar (currently inert). Clicking it again, pressing Escape, or activating one of the existing Aufwendungen/Erträge flow rows returns to the icicle. The zoom level of the icicle at the moment Saldo is activated determines the diagram:
|
||||||
|
|
||||||
|
* **Level 1 (no zoom, Bereich overview):** three columns, *Ertragsarten → Produktbereiche → Aufwandsarten*.
|
||||||
|
|
||||||
|
* **Level 2 (Bereich zoomed in):** three columns, *Ertragsarten → Produktgruppen of that Bereich → Aufwandsarten*.
|
||||||
|
|
||||||
|
* **Level 3 (Produktgruppe zoomed in):** two columns, *Ertragsarten → Aufwandsarten* for that single Gruppe — the middle column collapses because there is only one node in it.
|
||||||
|
|
||||||
|
Visual style follows the d3 parallel-sets reference (<https://observablehq.com/@d3/parallel-sets>): flat categorical bands, bezier ribbons flush to band edges, paper-toned default fills, hue reserved for highlight.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
The icicle shows the spending side as a hierarchy of Bereiche → Gruppen → Categories. It cannot show, in one frame, *what types of revenue feed which areas* or *which expense types each area consumes*. The Saldo view exposes that cross-cut without overloading the icicle with a second encoding.
|
||||||
|
|
||||||
|
For a citizen looking at the Bereich overview, the three-column diagram answers "where does Gewerbesteuer go, and what does it pay for?" After zooming into "Schule und Kultur," the same diagram redraws to show only that Bereich's incoming revenue mix and outgoing expense mix. Zooming further to a single Gruppe collapses the middle column, isolating the revenue-to-expense composition of one budget item.
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
* **No standalone page or top-level toggle.** The Saldo view is a sidebar-driven mode of the existing budget page.
|
||||||
|
|
||||||
|
* **No year switcher.** v1 covers the 2026 planning column from the 2025 plan file.
|
||||||
|
|
||||||
|
* **No historical actuals** (Jahresabschlüsse).
|
||||||
|
|
||||||
|
* **No deeper than Produktgruppe.** Icicle depths 4 (Breakdown) are not represented as their own column.
|
||||||
|
|
||||||
|
* **No four-column variant** that would split Bereich AND Gruppe simultaneously.
|
||||||
|
|
||||||
|
* **No replacement of the icicle as the default view.** Default is the icicle; Saldo is opt-in and dismissible.
|
||||||
|
|
||||||
|
* **No CSV/PNG export, no embed snippet.** Sharing relies on URL state alone.
|
||||||
|
|
||||||
|
* **No mobile-specific layout** beyond falling back to a simpler rendering at small widths (see Edge Cases).
|
||||||
|
|
||||||
|
## User Stories
|
||||||
|
|
||||||
|
1. **Citizen exploring the whole budget.** Lara sees the icicle at the Bereich overview, glances at the sidebar's Saldo figure, clicks it. The icicle is replaced by a three-column parallel set: Ertragsarten on the left, Produktbereiche in the middle, Aufwandsarten on the right. She hovers "Steuern und ähnliche Abgaben" and watches its ribbons fan out to every Bereich and from there to expense types.
|
||||||
|
|
||||||
|
2. **Journalist tracing an area.** Jonas zooms into "Schule und Kultur" on the icicle, then clicks Saldo. The middle column now shows the Produktgruppen of Schule und Kultur. He pins "Personalaufwendungen" on the right and sees which Gruppen consume most of it.
|
||||||
|
|
||||||
|
3. **Reader following a single budget item.** Mira zooms into a Produktgruppe, clicks Saldo, and sees a two-column diagram: which Ertragsarten fund this Gruppe, and which Aufwandsarten it spends on.
|
||||||
|
|
||||||
|
4. **Returning reader via deep link.** A shared URL `/?pfad=schule-und-kultur&ansicht=saldo` opens the budget page with that Bereich zoomed and the Saldo view active.
|
||||||
|
|
||||||
|
## Functional Requirements
|
||||||
|
|
||||||
|
### Activation and dismissal
|
||||||
|
|
||||||
|
* The Saldo total in the sidebar (`ov-saldo` in overview state, `sb-saldo` in node state) MUST become an interactive control: button-role, keyboard-operable, with a hover/focus affordance.
|
||||||
|
|
||||||
|
* Clicking Saldo activates the parallel-sets view, replacing the icicle in its canvas region. The sidebar remains visible and unchanged.
|
||||||
|
|
||||||
|
* Clicking Saldo again, pressing Escape, or activating one of the existing Aufwendungen/Erträge flow rows MUST deactivate the Saldo view and restore the icicle.
|
||||||
|
|
||||||
|
* Activating Saldo while one of the existing flow rows is active MUST first clear that flow filter, then activate Saldo. Only one of {flow filter, Saldo view} can be active.
|
||||||
|
|
||||||
|
* The Saldo control MUST visually reflect its active state.
|
||||||
|
|
||||||
|
### Level resolution
|
||||||
|
|
||||||
|
* The Saldo view reads the icicle's current zoom path at activation time and continues to track it while active:
|
||||||
|
|
||||||
|
* No zoom / Bereich overview (depth 0 zoom, depth 1 visible) → **three columns**, middle = all Produktbereiche.
|
||||||
|
|
||||||
|
* Bereich zoomed (depth 1 zoom) → **three columns**, middle = all Produktgruppen of that Bereich.
|
||||||
|
|
||||||
|
* Produktgruppe zoomed (depth 2 zoom) → **two columns**, no middle.
|
||||||
|
|
||||||
|
* Zooming deeper than Produktgruppe (Category, Breakdown) while the Saldo view is active MUST be treated as the Produktgruppe level — the deeper hierarchy is not represented.
|
||||||
|
|
||||||
|
* If the user changes the zoom path via icicle controls outside this view (e.g. via deep link), the Saldo view MUST redraw to the new level on activation. Saldo is not active during icicle interaction; the user must dismiss Saldo to re-zoom.
|
||||||
|
|
||||||
|
* Decision: **changing zoom while Saldo is active is not supported in v1.** Re-zooming requires dismissing the view first. (See Open Questions if this proves frustrating in testing.)
|
||||||
|
|
||||||
|
### Data
|
||||||
|
|
||||||
|
* Source: `data/2025/HH_Plan_Muenster_25-28.csv`, rows where `Geschäftsjahr = 2026`.
|
||||||
|
|
||||||
|
* Rows where `Produktbereich = "Gesamt"` or `Produktgruppe = "Gesamt"` MUST be excluded from leaf aggregation.
|
||||||
|
|
||||||
|
* Aggregation by level:
|
||||||
|
|
||||||
|
* Level 1: per-`Produktbereich` sum of each financial-category column over its leaf Gruppen.
|
||||||
|
|
||||||
|
* Level 2: per-`Produktgruppe` value of each financial-category column for the leaf row, restricted to the active Bereich.
|
||||||
|
|
||||||
|
* Level 3: the single leaf row's per-category values.
|
||||||
|
|
||||||
|
* Sign normalisation: revenue columns are negative in the source; flip to positive magnitudes before display. Expense columns stay positive.
|
||||||
|
|
||||||
|
* Empty/blank cells MUST be treated as 0.
|
||||||
|
|
||||||
|
* Header typo `Öffentlich-rechtliche Leistunngsentgelte` and the variant `algemeine Umlagen` MUST be read as-is from the source. Display labels are corrected.
|
||||||
|
|
||||||
|
### Category sets
|
||||||
|
|
||||||
|
* **Ertragsarten (left column):** `Steuern und ähnliche Abgaben`, `Zuwendungen und algemeine Umlagen`, `Sonstige Transfererträge`, `Öffentlich-rechtliche Leistunngsentgelte`, `Privatrechtliche Leistungsentgelte`, `Kostenerstattungen und Kostenumlagen`, `Sonstige ordentliche Erträge`, `Aktivierte Eigenleistungen`, `Bestandsveränderungen`, `Finanzerträge`, `Außerordentliche Erträge`.
|
||||||
|
|
||||||
|
* **Aufwandsarten (right column):** `Personalaufwendungen`, `Versorgungsaufwendungen`, `Aufwendungen für Sach- und Dienstleistungen`, `Bilanzielle Abschreibungen`, `Transferaufwendungen`, `Sonstige ordentliche Aufwendungen`, `Zinsen und sonstige Finanzaufwendungen`, `Außerordentliche Aufwendungen`, `Globaler Minderaufwand`.
|
||||||
|
|
||||||
|
* Categories whose total in the active level is 0 MUST be omitted.
|
||||||
|
|
||||||
|
### Ribbon allocation (the imputation rule)
|
||||||
|
|
||||||
|
The source data does not record category-to-category flows; each row gives totals per financial category but no direct linkage between a specific Ertragsart and a specific Aufwandsart. To draw three-column ribbons we MUST impute the linkage by **proportional allocation through the middle column**:
|
||||||
|
|
||||||
|
* Left-to-middle ribbon between Ertragsart *e* and middle node *m*: the value of column *e* in node *m*'s aggregated row.
|
||||||
|
|
||||||
|
* Middle-to-right ribbon between middle node *m* and Aufwandsart *a*: the value of column *a* in node *m*'s aggregated row.
|
||||||
|
|
||||||
|
* For the **two-column case (level 3)**, ribbons between Ertragsart *e* and Aufwandsart *a* are imputed as `e_total · a_total / total_expense_or_revenue`. The implementation MUST normalise on whichever side has the smaller absolute total so total ribbon mass equals `min(Σe, Σa)`. Where revenues exceed or fall short of expenses, a single grey "Saldo" pseudo-band on the deficit/surplus side absorbs the difference.
|
||||||
|
|
||||||
|
* A caption MUST disclose the imputation: e.g. *"Die Verbindungen zwischen Ertrags- und Aufwandsarten sind anteilig berechnet — die Datenquelle weist keine direkten Geldflüsse aus."*
|
||||||
|
|
||||||
|
### Rendering
|
||||||
|
|
||||||
|
* Bands are flat coloured rectangles, no stroked outlines, with band height proportional to the node's total magnitude.
|
||||||
|
|
||||||
|
* Ribbons are bezier curves meeting bands flush. No gap, no outline.
|
||||||
|
|
||||||
|
* Default state: bands rendered in paper-toned neutrals; ribbons rendered at low base opacity in the same neutrals.
|
||||||
|
|
||||||
|
* Hover state: hovering a band raises its ribbons to full opacity and tints them in the band's hue family (Ertragsart hue for left, Aufwandsart hue for right; middle bands take a neutral tint). Other ribbons drop further.
|
||||||
|
|
||||||
|
* Pinned state: clicking (or pressing Enter on a focused) band pins the highlight. Same dismissal rules as the prior spec — second click, background click, or Escape unpins.
|
||||||
|
|
||||||
|
* Only one band can be pinned at a time.
|
||||||
|
|
||||||
|
* Flows < 0,1 % of the level total MAY render at a minimum visible thickness; they MUST remain selectable and faithful in tooltips.
|
||||||
|
|
||||||
|
### Tooltips and labels
|
||||||
|
|
||||||
|
* Hovering a band MUST show its name, total in €, and share of the level total in %.
|
||||||
|
|
||||||
|
* Hovering a ribbon MUST show source band, target band, absolute € value, share of source band's total %, share of target band's total %. For imputed ribbons the tooltip MUST tag the value as *anteilig berechnet*.
|
||||||
|
|
||||||
|
* Numbers MUST follow German formatting (`.` thousands, `,` decimal) and use the symbol `€` after the value with a non-breaking space.
|
||||||
|
|
||||||
|
* Band labels MUST be visible without hover for any band whose share is ≥ 2 % of the level total.
|
||||||
|
|
||||||
|
### URL state
|
||||||
|
|
||||||
|
* The active mode is encoded as `?ansicht=icicle|saldo` (default `icicle`). The icicle's existing zoom-path parameter (e.g. `pfad=…`) is retained unchanged.
|
||||||
|
|
||||||
|
* Pinned band is encoded as `&pin=<slug>` where `<slug>` is a stable, lowercased, hyphenated form of the band label (e.g. `personalaufwendungen`, `schule-und-kultur`).
|
||||||
|
|
||||||
|
* Loading a URL with `pin` that does not match any band in the resolved level MUST drop the param silently.
|
||||||
|
|
||||||
|
* Loading `?ansicht=saldo` with a `pfad` deeper than Produktgruppe MUST resolve to the Produktgruppe level (truncate the path).
|
||||||
|
|
||||||
|
* Browser back/forward MUST traverse mode and pin changes.
|
||||||
|
|
||||||
|
### Accessibility
|
||||||
|
|
||||||
|
* All bands MUST be reachable by keyboard, in reading order: left column top-to-bottom, then middle (if present), then right.
|
||||||
|
|
||||||
|
* Focused bands MUST have a visible focus ring distinct from the hover state.
|
||||||
|
|
||||||
|
* Each band MUST have an accessible name including label, total, and share. Imputation disclosure MUST be reachable as text, not only as a tooltip.
|
||||||
|
|
||||||
|
* The view MUST not rely on color alone to convey selection.
|
||||||
|
|
||||||
|
## UI/UX Requirements
|
||||||
|
|
||||||
|
* The parallel set occupies the same canvas region as the icicle; sidebar and page chrome do not change.
|
||||||
|
|
||||||
|
* A short editorial caption above the diagram names the year, the active level (Gesamtstadt / Bereichname / Gruppenname), the total revenue and total expense in €, and the imputation disclosure.
|
||||||
|
|
||||||
|
* **Visual style: parallel sets** per <https://observablehq.com/@d3/parallel-sets>. Each column is a flat categorical band (a stack of contiguous block fills, no stroked node boxes); ribbons are bezier curves meeting band segments flush, with no gap or outline. Default rendering is quiet and near-monochrome — neutral paper-toned fills for bands and ribbons — and the active hue family is reserved for highlighted (hovered or pinned) flows.
|
||||||
|
|
||||||
|
* Color: Erträge and Aufwendungen each use their respective hue family from the existing two-hue OKLCH system, applied only on highlight. Middle column highlights take a neutral tint. Default-state fills are paper-toned neutrals derived from the same OKLCH system at low chroma.
|
||||||
|
|
||||||
|
* Motion: a single transition vocabulary — opacity and fill changes on hover/pin/level change. No band-position morphing between levels (a level change is a redraw, not an animation).
|
||||||
|
|
||||||
|
* Below ~720 px viewport width, the diagram MAY fall back to a simpler list-of-bars rendering — see Edge Cases.
|
||||||
|
|
||||||
|
## Edge Cases & Error States
|
||||||
|
|
||||||
|
### Data
|
||||||
|
|
||||||
|
* A middle-column node whose total magnitude on either side is 0 MUST be omitted (e.g. a Bereich that has only expenses but the level needs both sides — the band is rendered with the side it has and a single "Saldo" pseudo-band absorbing the difference).
|
||||||
|
|
||||||
|
* A category whose total in the level is 0 MUST be omitted.
|
||||||
|
|
||||||
|
* Negative net values within a single side after sign normalisation are not expected; if encountered, log a build-time warning and render absolute values.
|
||||||
|
|
||||||
|
### Interaction
|
||||||
|
|
||||||
|
* Activating Saldo while no Bereich is zoomed but the path indicates a Bereich (race) MUST resolve in favour of the path.
|
||||||
|
|
||||||
|
* Clicking the SVG background or pressing Escape unpins, but does NOT dismiss the Saldo view (only Saldo-toggle, ESC twice, or the flow rows do). Decision: Escape unpins on first press; second press dismisses the view.
|
||||||
|
|
||||||
|
* Tooltips that would extend past the viewport MUST flip.
|
||||||
|
|
||||||
|
### Viewport
|
||||||
|
|
||||||
|
* Below 720 px width: render the fallback bar-list view with three (or two) sections stacked vertically (Ertragsarten, [Bereiche/Gruppen], Aufwandsarten), each as a sorted bar list. Pinning still highlights cross-section. The Saldo activation control still works; deep links continue to work.
|
||||||
|
|
||||||
|
* Below 360 px width: same fallback with reduced label density.
|
||||||
|
|
||||||
|
### Failure modes
|
||||||
|
|
||||||
|
* The Saldo view is statically pre-computed at build time per level. If data load fails at build, the page build MUST fail loudly.
|
||||||
|
|
||||||
|
## Pre-Mortem
|
||||||
|
|
||||||
|
*It's six months from now and this view has clearly failed. What went wrong?*
|
||||||
|
|
||||||
|
### Likely failure modes
|
||||||
|
|
||||||
|
* **Imputation misleads.** Readers treat the proportional ribbons as a real flow record. → Mitigated by the mandatory caption, the *anteilig berechnet* tooltip tag, and the accessible-text disclosure. Accepted residual risk: any imputation can mislead a determined misreader; the disclosure is the limit of what we can do without real flow data.
|
||||||
|
|
||||||
|
* **Saldo click is undiscoverable.** Readers never realise the figure is interactive. → Mitigated by the active-state affordance on the control. Partially accepted risk: discoverability is a craft problem; if telemetry shows < 5 % of sessions activate it, we revisit.
|
||||||
|
|
||||||
|
* **Visual noise.** Three columns with up to ~16 middle nodes and ~9–11 outer nodes generate too many ribbons. → Mitigated by the dimmed-default + hover/pin highlight pattern, the 0,1 % minimum-thickness rule, and the level-2 collapse to one Bereich's Gruppen.
|
||||||
|
|
||||||
|
* **Numbers don't reconcile with the icicle / sidebar.** A reader compares totals between views and finds a mismatch from Gesamt-row leakage or sign bugs. → Mitigated by explicit Gesamt-exclusion and sign-normalisation rules and the reconciliation acceptance test.
|
||||||
|
|
||||||
|
* **Re-zooming while Saldo is active is awkward.** Users want to pivot levels without dismissing. → Accepted constraint for v1; tracked in Open Questions.
|
||||||
|
|
||||||
|
* **Mobile is unusable.** → Mitigated by the bar-list fallback below 720 px.
|
||||||
|
|
||||||
|
* **Slug collisions in URL pins.** Two bands across columns share a slug (e.g. a Bereich name colliding with a category name in another level). → Mitigated by interpreting `pin` only within the resolved level's band set; the level itself disambiguates.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
### Must Have
|
||||||
|
|
||||||
|
* [ ] **Saldo is an interactive control**
|
||||||
|
|
||||||
|
* Given: budget page is loaded with the icicle visible
|
||||||
|
|
||||||
|
* When: user clicks the Saldo total in the sidebar
|
||||||
|
|
||||||
|
* Then: the icicle canvas region is replaced by the parallel-sets view, the URL gains `?ansicht=saldo`, the Saldo control reflects an active state, and any active Aufwendungen/Erträge flow filter is cleared
|
||||||
|
|
||||||
|
* [ ] **Activation level matches icicle zoom**
|
||||||
|
|
||||||
|
* Given: icicle is at the Bereich overview / a Bereich zoom / a Gruppe zoom
|
||||||
|
|
||||||
|
* When: user activates Saldo
|
||||||
|
|
||||||
|
* Then: the parallel set renders with three columns (middle = Bereiche) / three columns (middle = Gruppen of that Bereich) / two columns, respectively
|
||||||
|
|
||||||
|
* [ ] **Dismissal restores icicle**
|
||||||
|
|
||||||
|
* Given: Saldo view is active
|
||||||
|
|
||||||
|
* When: user clicks Saldo again, presses Escape twice, or activates Aufwendungen/Erträge flow row
|
||||||
|
|
||||||
|
* Then: the icicle is restored, the `ansicht` and `pin` params are removed from the URL, and the Saldo control returns to default state
|
||||||
|
|
||||||
|
* [ ] **Totals reconcile with the sidebar**
|
||||||
|
|
||||||
|
* Given: any active level
|
||||||
|
|
||||||
|
* When: summing all Ertragsarten band magnitudes / all Aufwandsarten band magnitudes
|
||||||
|
|
||||||
|
* Then: the totals equal the sidebar's Erträge and Aufwendungen totals for the same path, to the cent. The Saldo (revenue minus expense) equals the sidebar Saldo
|
||||||
|
|
||||||
|
* [ ] **Gesamt rows are excluded from aggregation**
|
||||||
|
|
||||||
|
* Given: source CSV contains `Produktbereich = "Gesamt"` and `Produktgruppe = "Gesamt"` rows
|
||||||
|
|
||||||
|
* When: any level is rendered
|
||||||
|
|
||||||
|
* Then: no band or flow derives from those rows; the reconciliation criterion above passes
|
||||||
|
|
||||||
|
* [ ] **Imputation is disclosed**
|
||||||
|
|
||||||
|
* Given: any level with three columns or the two-column case
|
||||||
|
|
||||||
|
* When: the diagram is rendered
|
||||||
|
|
||||||
|
* Then: a visible caption states that ribbons across the diagram are proportionally imputed; ribbon tooltips for imputed flows include the *anteilig berechnet* tag; the disclosure is reachable as text by screen reader
|
||||||
|
|
||||||
|
* [ ] **Hovering a band highlights only its ribbons**
|
||||||
|
|
||||||
|
* Given: Saldo view is active and unpinned
|
||||||
|
|
||||||
|
* When: user hovers a band
|
||||||
|
|
||||||
|
* Then: that band's ribbons render at high opacity and tint in the band's hue family; all other ribbons drop further; mouseleave restores default
|
||||||
|
|
||||||
|
* [ ] **Clicking a band pins the highlight**
|
||||||
|
|
||||||
|
* Given: Saldo view is active and unpinned
|
||||||
|
|
||||||
|
* When: user clicks a band
|
||||||
|
|
||||||
|
* Then: the highlight persists, the URL gains `&pin=<slug>`, and clicking the same band, the canvas background, or pressing Escape once unpins and removes the param
|
||||||
|
|
||||||
|
* [ ] **Deep link with valid pin opens pinned**
|
||||||
|
|
||||||
|
* Given: a URL like `/?pfad=schule-und-kultur&ansicht=saldo&pin=personalaufwendungen`
|
||||||
|
|
||||||
|
* When: a fresh browser opens it
|
||||||
|
|
||||||
|
* Then: the Saldo view renders at the Bereich-level (level 2) for Schule und Kultur with Personalaufwendungen pinned
|
||||||
|
|
||||||
|
* [ ] **Deep link with deeper-than-Gruppe path resolves to Gruppe**
|
||||||
|
|
||||||
|
* Given: a URL whose `pfad` reaches Category or Breakdown depth and `ansicht=saldo`
|
||||||
|
|
||||||
|
* When: the page loads
|
||||||
|
|
||||||
|
* Then: the path is truncated to the containing Produktgruppe and the level-3 (two-column) diagram is rendered
|
||||||
|
|
||||||
|
* [ ] **Tooltips show absolute and relative figures in German formatting**
|
||||||
|
|
||||||
|
* Given: a hovered ribbon
|
||||||
|
|
||||||
|
* When: the tooltip appears
|
||||||
|
|
||||||
|
* Then: it contains source name, target name, € value formatted as `1.234.567 €`, source-share %, target-share %, and the imputation tag for cross-column flows
|
||||||
|
|
||||||
|
* [ ] **Keyboard navigation reaches every band**
|
||||||
|
|
||||||
|
* Given: focus is on the Saldo control
|
||||||
|
|
||||||
|
* When: user activates and tabs through
|
||||||
|
|
||||||
|
* Then: focus moves through every band in defined column order, each focused band shows a visible focus ring, Enter pins/unpins the focused band
|
||||||
|
|
||||||
|
* [ ] **Sub-720 px viewports render the bar-list fallback**
|
||||||
|
|
||||||
|
* Given: viewport width < 720 px
|
||||||
|
|
||||||
|
* When: Saldo view is active
|
||||||
|
|
||||||
|
* Then: the diagram is replaced by stacked bar-list sections, the activation control still works, and pinning still highlights cross-section bars
|
||||||
|
|
||||||
|
### Should Have
|
||||||
|
|
||||||
|
* [ ] **Band labels visible without hover for share ≥ 2 %**
|
||||||
|
* [ ] **Sub-0,1 % flows render at a minimum visible thickness**, hoverable, with truthful tooltip values
|
||||||
|
* [ ] **Pinning persists across level changes** when the pinned band exists at the new level (e.g. an Aufwandsart pinned at level 1 stays pinned when the user re-opens at level 2 of a Bereich). Otherwise the pin is dropped silently
|
||||||
|
|
||||||
|
### Could Have
|
||||||
|
|
||||||
|
* [ ] Animated cross-fade between icicle and parallel-set (≤ 200 ms), no band morphing
|
||||||
|
* [ ] A "Saldo" pseudo-band rendered explicitly on the smaller side at level 3 to absorb deficit/surplus
|
||||||
|
|
||||||
|
### Won't Have (v1)
|
||||||
|
|
||||||
|
* Year switcher
|
||||||
|
|
||||||
|
* Re-zooming the icicle while Saldo is active
|
||||||
|
|
||||||
|
* Four-column variant (Bereich AND Gruppe)
|
||||||
|
|
||||||
|
* Real category-to-category flow data (would require data we don't have)
|
||||||
|
|
||||||
|
* Historical actuals
|
||||||
|
|
||||||
|
* Mobile-specific layout beyond the bar-list fallback
|
||||||
|
|
||||||
|
* Export, embed, screenshot tooling
|
||||||
|
|
||||||
|
## Dependencies
|
||||||
|
|
||||||
|
* The icicle view exists, owns the budget page, and exposes its current zoom path. This spec assumes the data-loading pipeline (`data/2025/HH_Plan_Muenster_25-28.csv`) is in place.
|
||||||
|
|
||||||
|
* The sidebar's Saldo elements (`ov-saldo`, `sb-saldo`) exist and can be made interactive.
|
||||||
|
|
||||||
|
* The two-hue OKLCH color system is defined and accessible to both views.
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
* Should re-zooming work while the Saldo view is active (allow the icicle's bar set to remain the click target underneath)? Deferred to v2 unless craft testing proves the dismissal-then-re-zoom flow is too clunky.
|
||||||
|
|
||||||
|
* Should a Bereich whose Saldo is approximately 0 (Erträge ≈ Aufwendungen) be visually distinguished, given the diagram's central narrative is exactly that balance?
|
||||||
|
|
||||||
|
⠀
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2022 The Figtree Project Authors (https://github.com/erikdkennedy/figtree)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
https://openfontlicense.org
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
Figtree Variable Font
|
||||||
|
=====================
|
||||||
|
|
||||||
|
This download contains Figtree as both variable fonts and static fonts.
|
||||||
|
|
||||||
|
Figtree is a variable font with this axis:
|
||||||
|
wght
|
||||||
|
|
||||||
|
This means all the styles are contained in these files:
|
||||||
|
Figtree/Figtree-VariableFont_wght.ttf
|
||||||
|
Figtree/Figtree-Italic-VariableFont_wght.ttf
|
||||||
|
|
||||||
|
If your app fully supports variable fonts, you can now pick intermediate styles
|
||||||
|
that aren’t available as static fonts. Not all apps support variable fonts, and
|
||||||
|
in those cases you can use the static font files for Figtree:
|
||||||
|
Figtree/static/Figtree-Light.ttf
|
||||||
|
Figtree/static/Figtree-Regular.ttf
|
||||||
|
Figtree/static/Figtree-Medium.ttf
|
||||||
|
Figtree/static/Figtree-SemiBold.ttf
|
||||||
|
Figtree/static/Figtree-Bold.ttf
|
||||||
|
Figtree/static/Figtree-ExtraBold.ttf
|
||||||
|
Figtree/static/Figtree-Black.ttf
|
||||||
|
Figtree/static/Figtree-LightItalic.ttf
|
||||||
|
Figtree/static/Figtree-Italic.ttf
|
||||||
|
Figtree/static/Figtree-MediumItalic.ttf
|
||||||
|
Figtree/static/Figtree-SemiBoldItalic.ttf
|
||||||
|
Figtree/static/Figtree-BoldItalic.ttf
|
||||||
|
Figtree/static/Figtree-ExtraBoldItalic.ttf
|
||||||
|
Figtree/static/Figtree-BlackItalic.ttf
|
||||||
|
|
||||||
|
Get started
|
||||||
|
-----------
|
||||||
|
|
||||||
|
1. Install the font files you want to use
|
||||||
|
|
||||||
|
2. Use your app's font picker to view the font family and all the
|
||||||
|
available styles
|
||||||
|
|
||||||
|
Learn more about variable fonts
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
|
||||||
|
https://variablefonts.typenetwork.com
|
||||||
|
https://medium.com/variable-fonts
|
||||||
|
|
||||||
|
In desktop apps
|
||||||
|
|
||||||
|
https://theblog.adobe.com/can-variable-fonts-illustrator-cc
|
||||||
|
https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
|
||||||
|
|
||||||
|
Online
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/getting_started
|
||||||
|
https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
|
||||||
|
https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
|
||||||
|
|
||||||
|
Installing fonts
|
||||||
|
|
||||||
|
MacOS: https://support.apple.com/en-us/HT201749
|
||||||
|
Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
|
||||||
|
Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
|
||||||
|
|
||||||
|
Android Apps
|
||||||
|
|
||||||
|
https://developers.google.com/fonts/docs/android
|
||||||
|
https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
|
||||||
|
|
||||||
|
License
|
||||||
|
-------
|
||||||
|
Please read the full license text (OFL.txt) to understand the permissions,
|
||||||
|
restrictions and requirements for usage, redistribution, and modification.
|
||||||
|
|
||||||
|
You can use them in your products & projects – print or digital,
|
||||||
|
commercial or otherwise.
|
||||||
|
|
||||||
|
This isn't legal advice, please consider consulting a lawyer and see the full
|
||||||
|
license for all details.
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "ms-haushalt",
|
||||||
|
"type": "module",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=22.12.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"dev": "astro dev",
|
||||||
|
"build": "astro build",
|
||||||
|
"preview": "astro preview",
|
||||||
|
"astro": "astro"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource-variable/geist": "^5.2.8",
|
||||||
|
"@fontsource-variable/geist-mono": "^5.2.7",
|
||||||
|
"astro": "^6.2.2",
|
||||||
|
"d3-dsv": "^3.0.1",
|
||||||
|
"d3-hierarchy": "^3.1.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@astrojs/check": "^0.9.9",
|
||||||
|
"@types/d3-dsv": "^3.0.7",
|
||||||
|
"@types/d3-hierarchy": "^3.1.7",
|
||||||
|
"@types/node": "^25.6.0",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^6.0.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+3786
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 655 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
|
||||||
|
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
|
||||||
|
<style>
|
||||||
|
path { fill: #000; }
|
||||||
|
@media (prefers-color-scheme: dark) {
|
||||||
|
path { fill: #FFF; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 749 B |
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,333 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
//
|
||||||
|
// Extract per-Produktgruppe Beschreibung + Erläuterungen sections from
|
||||||
|
// the Haushaltsplan 2026/2027 Band 1 PDF.
|
||||||
|
//
|
||||||
|
// Each Produktgruppe in Münster's NKF-style budget has a stable section
|
||||||
|
// layout. We use pdftotext -layout to get text with form-feed page
|
||||||
|
// breaks, find each PG's pages by its running header, then split each
|
||||||
|
// PG's combined text by its section headings ("Beschreibung",
|
||||||
|
// "Erläuterungen", etc.).
|
||||||
|
//
|
||||||
|
// Usage:
|
||||||
|
// node scripts/extract-pg-sections.mjs
|
||||||
|
// Output:
|
||||||
|
// data/extracted/pg-sections-2026.json
|
||||||
|
|
||||||
|
import { execSync } from "node:child_process";
|
||||||
|
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||||
|
import { tmpdir } from "node:os";
|
||||||
|
import { join } from "node:path";
|
||||||
|
|
||||||
|
const PDF_PATH = "docs/sources/2026_2027/Haushaltsplanentwurf_2026-2027_Band_1.pdf";
|
||||||
|
const OUT_PATH = "data/extracted/pg-sections-2026.json";
|
||||||
|
|
||||||
|
// ── Step 1: PDF → text with form-feed page boundaries ─────────────
|
||||||
|
const txtPath = join(tmpdir(), "ms-haushalt-band1.txt");
|
||||||
|
execSync(`pdftotext -layout "${PDF_PATH}" "${txtPath}"`);
|
||||||
|
const rawText = readFileSync(txtPath, "utf8");
|
||||||
|
const pages = rawText.split("\f");
|
||||||
|
console.log(`PDF pages: ${pages.length}`);
|
||||||
|
|
||||||
|
// ── Step 2: Group pages by Produktgruppe number ───────────────────
|
||||||
|
// Each Teilplan page has a running header with this layout:
|
||||||
|
// Haushaltsplan 2026/2027 <PG-NAME> Dezernat <X>
|
||||||
|
// Ausschuss: <ABBR> Produktgruppe NNNN <Amt>
|
||||||
|
// The PG number appears on the second header line. The name appears on
|
||||||
|
// the first header line, sandwiched between the "Haushaltsplan" cell
|
||||||
|
// and the "Dezernat …" cell.
|
||||||
|
const HEADER_RE_PG = /Produktgruppe\s+(\d{4})\b/;
|
||||||
|
const HEADER_RE_NAME = /^Haushaltsplan\s+\d{4}\/\d{4}\s{2,}(.+?)\s{2,}Dezernat\s/m;
|
||||||
|
|
||||||
|
const pagesByPg = new Map(); // pgNumber → array of page texts
|
||||||
|
const namesByPg = new Map(); // pgNumber → display name
|
||||||
|
|
||||||
|
for (const page of pages) {
|
||||||
|
const headerSlice = page.slice(0, 600);
|
||||||
|
const pgMatch = headerSlice.match(HEADER_RE_PG);
|
||||||
|
if (!pgMatch) continue;
|
||||||
|
const pgNum = pgMatch[1];
|
||||||
|
if (!pagesByPg.has(pgNum)) pagesByPg.set(pgNum, []);
|
||||||
|
pagesByPg.get(pgNum).push(page);
|
||||||
|
if (!namesByPg.has(pgNum)) {
|
||||||
|
const nameMatch = headerSlice.match(HEADER_RE_NAME);
|
||||||
|
if (nameMatch) namesByPg.set(pgNum, nameMatch[1].trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`Produktgruppen found: ${pagesByPg.size}`);
|
||||||
|
|
||||||
|
// ── Step 3: Extract sections per PG ───────────────────────────────
|
||||||
|
const SECTION_HEADINGS = [
|
||||||
|
"Beschreibung",
|
||||||
|
"Besonderheiten in den Planjahren",
|
||||||
|
"Ziele",
|
||||||
|
"Zielkennzahlen",
|
||||||
|
"Standardkennzahlen",
|
||||||
|
"Bewirtschaftungsregeln",
|
||||||
|
"Erläuterungen",
|
||||||
|
];
|
||||||
|
|
||||||
|
/** Strip page-running headers/footers and stray page numbers. */
|
||||||
|
function cleanPageNoise(text) {
|
||||||
|
return text
|
||||||
|
.split("\n")
|
||||||
|
.filter((line) => {
|
||||||
|
const t = line.trim();
|
||||||
|
if (t === "") return true;
|
||||||
|
if (/^Haushaltsplan\s+\d{4}\/\d{4}/.test(t)) return false;
|
||||||
|
if (/^Ausschuss:/.test(t)) return false;
|
||||||
|
// Page-number lines: a single integer (with possible surrounding ws)
|
||||||
|
if (/^\d{1,4}$/.test(t)) return false;
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Find a section by its heading and return text up to the next known
|
||||||
|
* heading. Headings are matched as full-line tokens — they appear
|
||||||
|
* flush-left, sometimes with trailing colon. */
|
||||||
|
function extractSection(text, heading, otherHeadings) {
|
||||||
|
const startRe = new RegExp(`^${escapeRe(heading)}:?\\s*$`, "m");
|
||||||
|
const startMatch = startRe.exec(text);
|
||||||
|
if (!startMatch) return null;
|
||||||
|
const sliceStart = startMatch.index + startMatch[0].length;
|
||||||
|
const tail = text.slice(sliceStart);
|
||||||
|
|
||||||
|
let endIdx = tail.length;
|
||||||
|
for (const h of otherHeadings) {
|
||||||
|
if (h === heading) continue;
|
||||||
|
const r = new RegExp(`^${escapeRe(h)}:?\\s*$`, "m");
|
||||||
|
const m = r.exec(tail);
|
||||||
|
if (m && m.index < endIdx) endIdx = m.index;
|
||||||
|
}
|
||||||
|
// Also stop at the start of a per-Produkt block ("Produkt NNNNNN -")
|
||||||
|
// and at the financial table headers — these can sit indented with
|
||||||
|
// leading whitespace on the line, so allow `^\s*`.
|
||||||
|
for (const r of [
|
||||||
|
/^\s*Produkt\s+\d{6}\s*-/m,
|
||||||
|
/^\s*Teilergebnisplan\b/m,
|
||||||
|
/^\s*Teilfinanzplan\b/m,
|
||||||
|
/^\s*Investitionsmaßnahmen\b/m,
|
||||||
|
/^\s*Investitionen\s+gesamt\b/m,
|
||||||
|
/^\s*Verpflichtungsermächtigungen\b/m,
|
||||||
|
]) {
|
||||||
|
const m = r.exec(tail);
|
||||||
|
if (m && m.index < endIdx) endIdx = m.index;
|
||||||
|
}
|
||||||
|
return tail.slice(0, endIdx).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRe(s) {
|
||||||
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Strip leading + trailing whitespace per line, collapse multiple
|
||||||
|
* blank lines to one. Preserves paragraph breaks. */
|
||||||
|
function tidyParagraphs(text) {
|
||||||
|
if (!text) return text;
|
||||||
|
return text
|
||||||
|
.split("\n")
|
||||||
|
.map((l) => l.trim())
|
||||||
|
.join("\n")
|
||||||
|
.replace(/\n{3,}/g, "\n\n")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse breakdown tables embedded in Erläuterungen text.
|
||||||
|
*
|
||||||
|
* Format we recognize (PG 1601's "zu Zeile 01"):
|
||||||
|
* zu Zeile NN:
|
||||||
|
* …prose…
|
||||||
|
* Aufwandsart Ansatz YYYY in Mio. € Ansatz YYYY in Mio. €
|
||||||
|
* <name> <value> <value>
|
||||||
|
* …
|
||||||
|
*
|
||||||
|
* Returns: { lineNumber: [ { name, values: { year: euro } } ] }
|
||||||
|
*
|
||||||
|
* Numbers in the table are written in Mio. €; we convert to plain
|
||||||
|
* euros to match our CSV value units.
|
||||||
|
*/
|
||||||
|
function parseBreakdowns(erlText) {
|
||||||
|
if (!erlText) return {};
|
||||||
|
const out = {};
|
||||||
|
|
||||||
|
// Split into "zu Zeile NN" sections (case-insensitive — some PGs
|
||||||
|
// capitalize "Zu Zeile" at the start of a paragraph).
|
||||||
|
const sectionRe = /zu\s+Zeile\s+(\d{1,2})(?:[\s,–-]+\d+)*\s*:?/gi;
|
||||||
|
const sections = [];
|
||||||
|
let m;
|
||||||
|
while ((m = sectionRe.exec(erlText)) !== null) {
|
||||||
|
const lineNum = parseInt(m[1], 10);
|
||||||
|
sections.push({ lineNum, start: m.index, headerEnd: sectionRe.lastIndex });
|
||||||
|
}
|
||||||
|
for (let i = 0; i < sections.length; i++) {
|
||||||
|
const s = sections[i];
|
||||||
|
const end = i + 1 < sections.length ? sections[i + 1].start : erlText.length;
|
||||||
|
const body = erlText.slice(s.headerEnd, end);
|
||||||
|
|
||||||
|
// Look for a table header line: at least one "Ansatz YYYY" near a
|
||||||
|
// "Mio." unit. If there's no such header, skip the table parse
|
||||||
|
// and try the bullet-list fallback at the bottom of this loop.
|
||||||
|
const headerLineRe = /^.*Ansatz\s+\d{4}.*Mio\..*$/m;
|
||||||
|
const hMatch = body.match(headerLineRe);
|
||||||
|
let tableItems = [];
|
||||||
|
if (hMatch) {
|
||||||
|
const years = [...hMatch[0].matchAll(/Ansatz\s+(\d{4})/g)].map(
|
||||||
|
(m) => Number(m[1])
|
||||||
|
);
|
||||||
|
if (years.length > 0) {
|
||||||
|
tableItems = parseTableRows(body, hMatch, years);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tableItems.length > 0) {
|
||||||
|
out[s.lineNum] = tableItems;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// No table — try bullet-list parsing on the section body.
|
||||||
|
const bulletItems = parseBulletItems(body);
|
||||||
|
if (bulletItems.length > 0) out[s.lineNum] = bulletItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Extract table rows from a section body given the matched header
|
||||||
|
* line and the years that header announced. Returns an array of
|
||||||
|
* { name, values: { year: euro } } items. */
|
||||||
|
function parseTableRows(body, hMatch, years) {
|
||||||
|
const headerEndIdx = (hMatch.index ?? 0) + hMatch[0].length;
|
||||||
|
const remainder = body.slice(headerEndIdx);
|
||||||
|
const rowLines = [];
|
||||||
|
for (const line of remainder.split("\n")) {
|
||||||
|
const trimmed = line.trim();
|
||||||
|
if (trimmed === "") {
|
||||||
|
if (rowLines.length > 0) break; // table ended
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Heuristic: a row has a decimal/integer number near the end.
|
||||||
|
// Lines without trailing numbers are paragraph continuations.
|
||||||
|
if (!/[\d]+(?:[.,]\d+)?\s*$/.test(trimmed)) {
|
||||||
|
if (rowLines.length > 0) break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rowLines.push(trimmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = [];
|
||||||
|
for (const row of rowLines) {
|
||||||
|
const numRe = /(-?\d{1,3}(?:\.\d{3})*(?:,\d+)?|-?\d+(?:,\d+)?)/g;
|
||||||
|
const nums = [...row.matchAll(numRe)].map((m) => m[0]);
|
||||||
|
if (nums.length < years.length) continue;
|
||||||
|
const tailNums = nums.slice(-years.length);
|
||||||
|
let name = row;
|
||||||
|
for (const n of [...tailNums].reverse()) {
|
||||||
|
const idx = name.lastIndexOf(n);
|
||||||
|
if (idx >= 0) name = name.slice(0, idx);
|
||||||
|
}
|
||||||
|
name = name.trim();
|
||||||
|
if (!name) continue;
|
||||||
|
|
||||||
|
const values = {};
|
||||||
|
for (let k = 0; k < years.length; k++) {
|
||||||
|
const raw = tailNums[k];
|
||||||
|
const num = parseFloat(raw.replace(/\./g, "").replace(",", "."));
|
||||||
|
if (!Number.isFinite(num)) continue;
|
||||||
|
values[years[k]] = num * 1_000_000; // Mio. € → €
|
||||||
|
}
|
||||||
|
if (Object.keys(values).length > 0) items.push({ name, values });
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse bullet-list breakdowns. Each line starting with "-" or "•"
|
||||||
|
* with one or more "X Mio. Euro (YYYY)" / "X Euro (YYYY)" patterns
|
||||||
|
* becomes an item. Common forms in Münster's Erläuterungen:
|
||||||
|
*
|
||||||
|
* - Westf. Zoo Münster GmbH i. H. v. 4,10 Mio. Euro (2026) / 4,1 Mio Euro (2027)
|
||||||
|
* - Lernmittel = 1.435.000 Euro (2026), 1.435.000 Euro (2027)
|
||||||
|
* - Erträge … in Höhe von 247.630 Euro (2026), 252.680 Euro (2027)
|
||||||
|
*/
|
||||||
|
function parseBulletItems(text) {
|
||||||
|
const items = [];
|
||||||
|
const valueRe =
|
||||||
|
/(\d{1,3}(?:\.\d{3})*(?:,\d+)?|\d+(?:,\d+)?)\s*(Mio\.?\s*(?:Euro|€)|Euro|€)\s*\(?(\d{4})\)?/gi;
|
||||||
|
// Strip "(Gesamtsumme: NNN Euro (YYYY))" parentheticals down to
|
||||||
|
// just "(YYYY)" so the per-item parser sees a clean
|
||||||
|
// `value Euro (year)` shape. The PG 0301 line 13/16 bullets use
|
||||||
|
// this nested form to reference the category total inline; we keep
|
||||||
|
// the year tag (which the item's value belongs to) and drop the
|
||||||
|
// total reference.
|
||||||
|
const gesamtRe =
|
||||||
|
/\(\s*Gesamtsumme:[^()]*\((20\d{2})\)[^()]*\)/g;
|
||||||
|
|
||||||
|
for (const rawLine of text.split("\n")) {
|
||||||
|
if (!/^\s*[-•]\s+/.test(rawLine)) continue;
|
||||||
|
const line = rawLine.replace(gesamtRe, "($1)");
|
||||||
|
|
||||||
|
const values = [];
|
||||||
|
let firstValIdx = -1;
|
||||||
|
let m;
|
||||||
|
valueRe.lastIndex = 0;
|
||||||
|
while ((m = valueRe.exec(line)) !== null) {
|
||||||
|
if (firstValIdx === -1) firstValIdx = m.index;
|
||||||
|
const num = parseFloat(
|
||||||
|
m[1].replace(/\./g, "").replace(",", ".")
|
||||||
|
);
|
||||||
|
if (!Number.isFinite(num)) continue;
|
||||||
|
const isMio = /Mio/i.test(m[2]);
|
||||||
|
const value = isMio ? num * 1_000_000 : num;
|
||||||
|
const year = parseInt(m[3], 10);
|
||||||
|
values.push({ year, value });
|
||||||
|
}
|
||||||
|
if (values.length === 0) continue;
|
||||||
|
|
||||||
|
// The name is everything before the first value, minus the
|
||||||
|
// bullet marker and trailing value indicators.
|
||||||
|
let name = line.slice(0, firstValIdx).replace(/^\s*[-•]\s+/, "").trim();
|
||||||
|
name = name
|
||||||
|
.replace(/\s+(?:i\.\s*H\.\s*v\.?|in\s+H[öo]he\s+von|=)\s*$/i, "")
|
||||||
|
.replace(/\s+\(.*?\)\s*$/, "") // drop trailing parenthetical
|
||||||
|
.replace(/\s+[-–]\s+hier:.*$/i, "") // drop "- hier: ..." asides
|
||||||
|
.trim();
|
||||||
|
if (!name || name.length < 2) continue;
|
||||||
|
|
||||||
|
const valuesByYear = {};
|
||||||
|
for (const v of values) valuesByYear[v.year] = v.value;
|
||||||
|
items.push({ name, values: valuesByYear });
|
||||||
|
}
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = {};
|
||||||
|
let withBeschreibung = 0;
|
||||||
|
let withErlauterungen = 0;
|
||||||
|
for (const [pgNum, pgPages] of pagesByPg) {
|
||||||
|
const fullText = cleanPageNoise(pgPages.join("\n\n"));
|
||||||
|
const beschreibung = tidyParagraphs(
|
||||||
|
extractSection(fullText, "Beschreibung", SECTION_HEADINGS)
|
||||||
|
);
|
||||||
|
const erlaeuterungen = tidyParagraphs(
|
||||||
|
extractSection(fullText, "Erläuterungen", SECTION_HEADINGS)
|
||||||
|
);
|
||||||
|
if (beschreibung) withBeschreibung++;
|
||||||
|
if (erlaeuterungen) withErlauterungen++;
|
||||||
|
const breakdowns = parseBreakdowns(erlaeuterungen);
|
||||||
|
out[pgNum] = {
|
||||||
|
pgNumber: pgNum,
|
||||||
|
name: namesByPg.get(pgNum) ?? "",
|
||||||
|
beschreibung: beschreibung ?? null,
|
||||||
|
erlaeuterungen: erlaeuterungen ?? null,
|
||||||
|
breakdowns, // { lineNum: [ { name, values: { year: euro } } ] }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(` with Beschreibung: ${withBeschreibung}`);
|
||||||
|
console.log(` with Erläuterungen: ${withErlauterungen}`);
|
||||||
|
|
||||||
|
// ── Step 4: Write JSON ────────────────────────────────────────────
|
||||||
|
mkdirSync("data/extracted", { recursive: true });
|
||||||
|
writeFileSync(OUT_PATH, JSON.stringify(out, null, 2), "utf8");
|
||||||
|
console.log(`Wrote ${OUT_PATH}`);
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,61 @@
|
|||||||
|
// Ad-hoc sanity check, not a real test runner.
|
||||||
|
// pnpm exec tsx src/data/__check.ts
|
||||||
|
|
||||||
|
import { loadBudget } from "./load.js";
|
||||||
|
|
||||||
|
function fmtEUR(n: number): string {
|
||||||
|
return new Intl.NumberFormat("de-DE", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "EUR",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
}).format(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
const tree = await loadBudget();
|
||||||
|
|
||||||
|
console.log(`Years: ${tree.years.length} (${tree.years[0]}–${tree.years.at(-1)})`);
|
||||||
|
console.log(`Produktbereiche: ${tree.produktbereiche.length}`);
|
||||||
|
console.log(
|
||||||
|
`Produktgruppen total: ${tree.produktbereiche.reduce(
|
||||||
|
(n, b) => n + b.produktgruppen.length,
|
||||||
|
0
|
||||||
|
)}`
|
||||||
|
);
|
||||||
|
console.log(`Overlap notes (>5% disagreement): ${tree.overlaps.length}`);
|
||||||
|
|
||||||
|
// Bucket overlaps by year to confirm they're concentrated in the expected
|
||||||
|
// triple-counted years (2025/26/27 appear in two plans each).
|
||||||
|
const byYear: Record<number, number> = {};
|
||||||
|
for (const o of tree.overlaps) byYear[o.year] = (byYear[o.year] ?? 0) + 1;
|
||||||
|
console.log("Overlaps by year:", byYear);
|
||||||
|
|
||||||
|
// Sample 3 overlaps to confirm they're plausible plan revisions, not bugs.
|
||||||
|
console.log("\nSample overlaps:");
|
||||||
|
for (const o of tree.overlaps.slice(0, 3)) {
|
||||||
|
console.log(
|
||||||
|
` ${o.produktbereich} > ${o.produktgruppe} · ${o.year} · ${o.category}`
|
||||||
|
);
|
||||||
|
for (const v of o.values) {
|
||||||
|
console.log(` ${v.source.padEnd(50)} ${fmtEUR(v.value)}`);
|
||||||
|
}
|
||||||
|
console.log(` Δ = ${(o.divergencePct * 100).toFixed(1)}%`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const year of [2022, 2025, 2026, 2028]) {
|
||||||
|
const t = tree.totals[year];
|
||||||
|
if (!t) continue;
|
||||||
|
console.log(
|
||||||
|
`\n${year}: Aufw ${fmtEUR(t.aufwendungen.total)} · Ertr ${fmtEUR(
|
||||||
|
t.ertraege.total
|
||||||
|
)} · Saldo ${fmtEUR(t.ertraege.total - t.aufwendungen.total)}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("\nTop 5 Produktbereiche by Aufwand 2026:");
|
||||||
|
const ranked = [...tree.produktbereiche]
|
||||||
|
.map((b) => ({ name: b.name, aufwand: b.totals[2026]?.aufwendungen.total ?? 0 }))
|
||||||
|
.sort((a, b) => b.aufwand - a.aufwand)
|
||||||
|
.slice(0, 5);
|
||||||
|
for (const r of ranked) {
|
||||||
|
console.log(` ${r.name.padEnd(42)} ${fmtEUR(r.aufwand)}`);
|
||||||
|
}
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
// Build-time data loader. Reads the four source CSVs from data/, merges them,
|
||||||
|
// resolves overlaps between plan files, and produces a BudgetTree consumed by
|
||||||
|
// the page and treemap components.
|
||||||
|
//
|
||||||
|
// Conflict resolution policy (per design brief §9, open question 2):
|
||||||
|
// • When the same {produktbereich, produktgruppe, year, category} cell
|
||||||
|
// appears in two plan files, the NEWER plan wins (later planYear).
|
||||||
|
// • Disagreements >5 % are recorded in `overlaps` so we can footnote them.
|
||||||
|
// • Jahresabschluss values (actuals) always win against plan values for the
|
||||||
|
// same year — actuals supersede projections.
|
||||||
|
|
||||||
|
import { promises as fs } from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
BudgetRow,
|
||||||
|
BudgetTree,
|
||||||
|
CategoryKey,
|
||||||
|
Flow,
|
||||||
|
FlowTotals,
|
||||||
|
OverlapNote,
|
||||||
|
ProduktbereichNode,
|
||||||
|
ProduktgruppeNode,
|
||||||
|
SourceFile,
|
||||||
|
} from "./types.js";
|
||||||
|
import { AUFWAND_KEYS, ERTRAG_KEYS } from "./types.js";
|
||||||
|
import { parseCsv } from "./parse.js";
|
||||||
|
|
||||||
|
// Astro runs both `dev` and `build` from the project root, so cwd is stable.
|
||||||
|
// Avoiding `import.meta.url` here because the bundled output ends up under
|
||||||
|
// dist/.prerender/chunks/ and the path arithmetic doesn't survive that move.
|
||||||
|
const DATA_DIR = path.join(process.cwd(), "data");
|
||||||
|
|
||||||
|
const SOURCES: SourceFile[] = [
|
||||||
|
{
|
||||||
|
path: "jahresabschluesse/Jahresabschluss-Muenster-2008-2022.csv",
|
||||||
|
planYear: 2022,
|
||||||
|
kind: "actual",
|
||||||
|
},
|
||||||
|
{ path: "2023/Haushaltsplan-Muenster-2023.csv", planYear: 2023, kind: "plan" },
|
||||||
|
{ path: "2024/Haushaltsplan-Muenster-2024-2027.csv", planYear: 2024, kind: "plan" },
|
||||||
|
{ path: "2025/HH_Plan_Muenster_25-28.csv", planYear: 2025, kind: "plan" },
|
||||||
|
];
|
||||||
|
|
||||||
|
const ERTRAG_SET: ReadonlySet<CategoryKey> = new Set(ERTRAG_KEYS);
|
||||||
|
const AUFWAND_SET: ReadonlySet<CategoryKey> = new Set(AUFWAND_KEYS);
|
||||||
|
|
||||||
|
const SLUG_REPLACEMENTS: Array<readonly [RegExp, string]> = [
|
||||||
|
[/ä/g, "ae"],
|
||||||
|
[/ö/g, "oe"],
|
||||||
|
[/ü/g, "ue"],
|
||||||
|
[/ß/g, "ss"],
|
||||||
|
];
|
||||||
|
|
||||||
|
function slugify(input: string): string {
|
||||||
|
let s = input.toLowerCase();
|
||||||
|
for (const [pat, rep] of SLUG_REPLACEMENTS) s = s.replace(pat, rep);
|
||||||
|
s = s.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||||
|
return s || "n-a";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CellKey {
|
||||||
|
bereich: string;
|
||||||
|
gruppe: string;
|
||||||
|
year: number;
|
||||||
|
category: CategoryKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
function cellKeyString(k: CellKey): string {
|
||||||
|
return `${k.bereich}${k.gruppe}${k.year}${k.category}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CellEntry {
|
||||||
|
value: number;
|
||||||
|
source: SourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pick the canonical value among multiple sources for the same cell. Returns
|
||||||
|
* the chosen entry plus a possible OverlapNote when sources disagreed.
|
||||||
|
*/
|
||||||
|
function resolveCell(key: CellKey, entries: CellEntry[]): {
|
||||||
|
chosen: CellEntry;
|
||||||
|
overlap?: OverlapNote;
|
||||||
|
} {
|
||||||
|
// Prefer actuals over plans; among plans prefer the newer planYear.
|
||||||
|
const sorted = [...entries].sort((a, b) => {
|
||||||
|
if (a.source.kind !== b.source.kind) {
|
||||||
|
return a.source.kind === "actual" ? -1 : 1;
|
||||||
|
}
|
||||||
|
return b.source.planYear - a.source.planYear;
|
||||||
|
});
|
||||||
|
const chosen = sorted[0]!;
|
||||||
|
|
||||||
|
if (entries.length < 2) return { chosen };
|
||||||
|
const values = entries.map((e) => e.value);
|
||||||
|
const max = Math.max(...values.map(Math.abs));
|
||||||
|
const min = Math.min(...values.map(Math.abs));
|
||||||
|
// Divergence as fraction of the larger magnitude. 0 if both are 0.
|
||||||
|
const divergencePct = max === 0 ? 0 : (max - min) / max;
|
||||||
|
if (divergencePct > 0.05) {
|
||||||
|
return {
|
||||||
|
chosen,
|
||||||
|
overlap: {
|
||||||
|
produktbereich: key.bereich,
|
||||||
|
produktgruppe: key.gruppe,
|
||||||
|
year: key.year,
|
||||||
|
category: key.category,
|
||||||
|
values: entries.map((e) => ({
|
||||||
|
source: e.source.path,
|
||||||
|
value: e.value,
|
||||||
|
})),
|
||||||
|
divergencePct,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { chosen };
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyFlowTotals(): FlowTotals {
|
||||||
|
return { total: 0, byCategory: {} };
|
||||||
|
}
|
||||||
|
|
||||||
|
function emptyYearTotals(): Record<Flow, FlowTotals> {
|
||||||
|
return { aufwendungen: emptyFlowTotals(), ertraege: emptyFlowTotals() };
|
||||||
|
}
|
||||||
|
|
||||||
|
function addToFlow(target: FlowTotals, key: CategoryKey, value: number): void {
|
||||||
|
target.total += value;
|
||||||
|
target.byCategory[key] = (target.byCategory[key] ?? 0) + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read all source CSVs and return the merged, normalized BudgetTree. */
|
||||||
|
export async function loadBudget(): Promise<BudgetTree> {
|
||||||
|
// Read & parse every file.
|
||||||
|
const allRows: BudgetRow[] = [];
|
||||||
|
for (const source of SOURCES) {
|
||||||
|
const full = path.join(DATA_DIR, source.path);
|
||||||
|
const text = await fs.readFile(full, "utf8");
|
||||||
|
const rows = parseCsv(text, source);
|
||||||
|
allRows.push(...rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For overlap resolution we operate on LEAF rows only. Subtotal rows (kind
|
||||||
|
// !== "leaf") are discarded — we recompute subtotals ourselves to guarantee
|
||||||
|
// consistency between the levels of the treemap.
|
||||||
|
const leafRows = allRows.filter((r) => r.kind === "leaf");
|
||||||
|
|
||||||
|
// Bucket each cell value by its CellKey, then resolve.
|
||||||
|
const buckets = new Map<string, { key: CellKey; entries: CellEntry[] }>();
|
||||||
|
for (const row of leafRows) {
|
||||||
|
for (const [catKey, value] of Object.entries(row.categories) as Array<
|
||||||
|
[CategoryKey, number]
|
||||||
|
>) {
|
||||||
|
const key: CellKey = {
|
||||||
|
bereich: row.produktbereich,
|
||||||
|
gruppe: row.produktgruppe,
|
||||||
|
year: row.year,
|
||||||
|
category: catKey,
|
||||||
|
};
|
||||||
|
const ks = cellKeyString(key);
|
||||||
|
const existing = buckets.get(ks);
|
||||||
|
if (existing) {
|
||||||
|
existing.entries.push({ value, source: row.source });
|
||||||
|
} else {
|
||||||
|
buckets.set(ks, { key, entries: [{ value, source: row.source }] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve buckets and re-fold into a per-(bereich, gruppe, year) structure.
|
||||||
|
type LeafYearMap = Map<number, Record<Flow, FlowTotals>>;
|
||||||
|
type LeafMap = Map<string, LeafYearMap>; // key = bereich|gruppe
|
||||||
|
const leafIndex: LeafMap = new Map();
|
||||||
|
// Track display names (first observed wins; consistent across files).
|
||||||
|
const displayNames = new Map<string, { bereich: string; gruppe: string }>();
|
||||||
|
const overlaps: OverlapNote[] = [];
|
||||||
|
|
||||||
|
for (const { key, entries } of buckets.values()) {
|
||||||
|
const { chosen, overlap } = resolveCell(key, entries);
|
||||||
|
if (overlap) overlaps.push(overlap);
|
||||||
|
|
||||||
|
const leafKey = `${key.bereich}${key.gruppe}`;
|
||||||
|
if (!displayNames.has(leafKey)) {
|
||||||
|
displayNames.set(leafKey, { bereich: key.bereich, gruppe: key.gruppe });
|
||||||
|
}
|
||||||
|
let yearMap = leafIndex.get(leafKey);
|
||||||
|
if (!yearMap) {
|
||||||
|
yearMap = new Map();
|
||||||
|
leafIndex.set(leafKey, yearMap);
|
||||||
|
}
|
||||||
|
let totals = yearMap.get(key.year);
|
||||||
|
if (!totals) {
|
||||||
|
totals = emptyYearTotals();
|
||||||
|
yearMap.set(key.year, totals);
|
||||||
|
}
|
||||||
|
if (ERTRAG_SET.has(key.category)) {
|
||||||
|
addToFlow(totals.ertraege, key.category, chosen.value);
|
||||||
|
} else if (AUFWAND_SET.has(key.category)) {
|
||||||
|
addToFlow(totals.aufwendungen, key.category, chosen.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group leaves by Produktbereich and aggregate up.
|
||||||
|
const bereichMap = new Map<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
gruppen: ProduktgruppeNode[];
|
||||||
|
totals: Record<number, Record<Flow, FlowTotals>>;
|
||||||
|
}
|
||||||
|
>();
|
||||||
|
|
||||||
|
// Track all years seen (across all leaves) so the slider has a stable axis.
|
||||||
|
const yearSet = new Set<number>();
|
||||||
|
|
||||||
|
for (const [leafKey, yearMap] of leafIndex.entries()) {
|
||||||
|
const names = displayNames.get(leafKey)!;
|
||||||
|
const gruppe: ProduktgruppeNode = {
|
||||||
|
name: names.gruppe,
|
||||||
|
slug: slugify(names.gruppe),
|
||||||
|
totals: {},
|
||||||
|
};
|
||||||
|
for (const [year, perFlow] of yearMap.entries()) {
|
||||||
|
gruppe.totals[year] = perFlow;
|
||||||
|
yearSet.add(year);
|
||||||
|
}
|
||||||
|
|
||||||
|
let bereich = bereichMap.get(names.bereich);
|
||||||
|
if (!bereich) {
|
||||||
|
bereich = {
|
||||||
|
name: names.bereich,
|
||||||
|
slug: slugify(names.bereich),
|
||||||
|
gruppen: [],
|
||||||
|
totals: {},
|
||||||
|
};
|
||||||
|
bereichMap.set(names.bereich, bereich);
|
||||||
|
}
|
||||||
|
bereich.gruppen.push(gruppe);
|
||||||
|
|
||||||
|
// Roll up into the bereich totals.
|
||||||
|
for (const [year, perFlow] of yearMap.entries()) {
|
||||||
|
let bTotals = bereich.totals[year];
|
||||||
|
if (!bTotals) {
|
||||||
|
bTotals = emptyYearTotals();
|
||||||
|
bereich.totals[year] = bTotals;
|
||||||
|
}
|
||||||
|
for (const flow of ["aufwendungen", "ertraege"] as const) {
|
||||||
|
bTotals[flow].total += perFlow[flow].total;
|
||||||
|
for (const [cat, v] of Object.entries(perFlow[flow].byCategory) as Array<
|
||||||
|
[CategoryKey, number]
|
||||||
|
>) {
|
||||||
|
bTotals[flow].byCategory[cat] =
|
||||||
|
(bTotals[flow].byCategory[cat] ?? 0) + v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grand totals across all bereiche.
|
||||||
|
const grandTotals: Record<number, Record<Flow, FlowTotals>> = {};
|
||||||
|
for (const bereich of bereichMap.values()) {
|
||||||
|
for (const [yearStr, perFlow] of Object.entries(bereich.totals)) {
|
||||||
|
const year = Number(yearStr);
|
||||||
|
let g = grandTotals[year];
|
||||||
|
if (!g) {
|
||||||
|
g = emptyYearTotals();
|
||||||
|
grandTotals[year] = g;
|
||||||
|
}
|
||||||
|
for (const flow of ["aufwendungen", "ertraege"] as const) {
|
||||||
|
g[flow].total += perFlow[flow].total;
|
||||||
|
for (const [cat, v] of Object.entries(perFlow[flow].byCategory) as Array<
|
||||||
|
[CategoryKey, number]
|
||||||
|
>) {
|
||||||
|
g[flow].byCategory[cat] = (g[flow].byCategory[cat] ?? 0) + v;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const years = [...yearSet].sort((a, b) => a - b);
|
||||||
|
// Default slider position = latest year present (planning years included).
|
||||||
|
const defaultYear = years[years.length - 1] ?? new Date().getFullYear();
|
||||||
|
|
||||||
|
// Stable sort: Produktbereiche alphabetically by display name.
|
||||||
|
const produktbereiche: ProduktbereichNode[] = [...bereichMap.values()]
|
||||||
|
.map((b) => ({
|
||||||
|
name: b.name,
|
||||||
|
slug: b.slug,
|
||||||
|
produktgruppen: b.gruppen.sort((a, b) =>
|
||||||
|
a.name.localeCompare(b.name, "de")
|
||||||
|
),
|
||||||
|
totals: b.totals,
|
||||||
|
}))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name, "de"));
|
||||||
|
|
||||||
|
return {
|
||||||
|
years,
|
||||||
|
defaultYear,
|
||||||
|
produktbereiche,
|
||||||
|
totals: grandTotals,
|
||||||
|
overlaps,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,114 @@
|
|||||||
|
// CSV parsing for the four source files. Handles:
|
||||||
|
// • UTF-8 BOM
|
||||||
|
// • `;` separator
|
||||||
|
// • German number format (`.` thousands, `,` decimal)
|
||||||
|
// • Sign convention: source revenues are negative, source expenses are
|
||||||
|
// positive. We flip revenues to positive so downstream code can treat both
|
||||||
|
// flows symmetrically.
|
||||||
|
// • Header drift: `Leistunngsentgelte` (double-n) and `algemeine` (single-l)
|
||||||
|
// map to the same canonical CategoryKey.
|
||||||
|
// • The 2025 plan's extra `Globaler Minderaufwand` column.
|
||||||
|
// • Empty rows (the 2023 plan and Jahresabschluss have a stray blank line).
|
||||||
|
|
||||||
|
import { dsvFormat } from "d3-dsv";
|
||||||
|
import type {
|
||||||
|
BudgetRow,
|
||||||
|
CategoryKey,
|
||||||
|
Categories,
|
||||||
|
RowKind,
|
||||||
|
SourceFile,
|
||||||
|
} from "./types.js";
|
||||||
|
import { ERTRAG_KEYS } from "./types.js";
|
||||||
|
|
||||||
|
const semicolon = dsvFormat(";");
|
||||||
|
|
||||||
|
// Maps a verbatim source-CSV header (after BOM strip and trim) to a canonical
|
||||||
|
// CategoryKey. Includes the known typos and spelling variants observed across
|
||||||
|
// files. If a header does not appear here it is silently ignored — the first
|
||||||
|
// three columns (Produktbereich, Produktgruppe, Geschäftsjahr) are handled
|
||||||
|
// separately.
|
||||||
|
const HEADER_TO_KEY: ReadonlyMap<string, CategoryKey> = new Map([
|
||||||
|
["Steuern und ähnliche Abgaben", "steuern"],
|
||||||
|
["Zuwendungen und allgemeine Umlagen", "zuwendungenAllgemeineUmlagen"],
|
||||||
|
["Zuwendungen und algemeine Umlagen", "zuwendungenAllgemeineUmlagen"],
|
||||||
|
["Sonstige Transfererträge", "sonstigeTransfererträge"],
|
||||||
|
["Öffentlich-rechtliche Leistungsentgelte", "öffentlichRechtlicheLeistungsentgelte"],
|
||||||
|
["Öffentlich-rechtliche Leistunngsentgelte", "öffentlichRechtlicheLeistungsentgelte"],
|
||||||
|
["Privatrechtliche Leistungsentgelte", "privatrechtlicheLeistungsentgelte"],
|
||||||
|
["Kostenerstattungen und Kostenumlagen", "kostenerstattungenKostenumlagen"],
|
||||||
|
["Sonstige ordentliche Erträge", "sonstigeOrdentlicheErträge"],
|
||||||
|
["Aktivierte Eigenleistungen", "aktivierteEigenleistungen"],
|
||||||
|
["Bestandsveränderungen", "bestandsveränderungen"],
|
||||||
|
["Personalaufwendungen", "personalaufwendungen"],
|
||||||
|
["Versorgungsaufwendungen", "versorgungsaufwendungen"],
|
||||||
|
["Aufwendungen für Sach- und Dienstleistungen", "sachUndDienstleistungen"],
|
||||||
|
["Bilanzielle Abschreibungen", "bilanzielleAbschreibungen"],
|
||||||
|
["Transferaufwendungen", "transferaufwendungen"],
|
||||||
|
["Sonstige ordentliche Aufwendungen", "sonstigeOrdentlicheAufwendungen"],
|
||||||
|
["Finanzerträge", "finanzerträge"],
|
||||||
|
["Zinsen und sonstige Finanzaufwendungen", "zinsenFinanzaufwendungen"],
|
||||||
|
["Außerordentliche Erträge", "außerordentlicheErträge"],
|
||||||
|
["Außerordentliche Aufwendungen", "außerordentlicheAufwendungen"],
|
||||||
|
["Globaler Minderaufwand", "globalerMinderaufwand"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const ERTRAG_SET: ReadonlySet<CategoryKey> = new Set(ERTRAG_KEYS);
|
||||||
|
|
||||||
|
/** Parse a German-formatted decimal. Returns undefined for empty/missing. */
|
||||||
|
function parseGermanNumber(raw: string | undefined): number | undefined {
|
||||||
|
if (raw === undefined) return undefined;
|
||||||
|
const trimmed = raw.trim();
|
||||||
|
if (trimmed === "") return undefined;
|
||||||
|
// `-733.670.000,00` → `-733670000.00`
|
||||||
|
const normalized = trimmed.replace(/\./g, "").replace(",", ".");
|
||||||
|
const n = Number(normalized);
|
||||||
|
return Number.isFinite(n) ? n : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
function classifyRow(produktbereich: string, produktgruppe: string): RowKind {
|
||||||
|
if (produktbereich === "Gesamt" && produktgruppe === "Gesamt") return "grandTotal";
|
||||||
|
if (produktgruppe === "Gesamt") return "produktbereichTotal";
|
||||||
|
return "leaf";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse one CSV file's contents into BudgetRows.
|
||||||
|
*
|
||||||
|
* `text` is the file's raw UTF-8 content (BOM tolerated). `source` describes
|
||||||
|
* provenance for downstream conflict resolution.
|
||||||
|
*/
|
||||||
|
export function parseCsv(text: string, source: SourceFile): BudgetRow[] {
|
||||||
|
// Strip BOM if present; d3-dsv doesn't.
|
||||||
|
const stripped = text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
||||||
|
const rows = semicolon.parse(stripped);
|
||||||
|
|
||||||
|
const out: BudgetRow[] = [];
|
||||||
|
for (const row of rows) {
|
||||||
|
const produktbereich = (row["Produktbereich"] ?? "").trim();
|
||||||
|
const produktgruppe = (row["Produktgruppe"] ?? "").trim();
|
||||||
|
const yearRaw = (row["Geschäftsjahr"] ?? "").trim();
|
||||||
|
if (!produktbereich || !produktgruppe || !yearRaw) continue;
|
||||||
|
const year = Number.parseInt(yearRaw, 10);
|
||||||
|
if (!Number.isInteger(year)) continue;
|
||||||
|
|
||||||
|
const categories: Categories = {};
|
||||||
|
for (const [header, value] of Object.entries(row)) {
|
||||||
|
const key = HEADER_TO_KEY.get(header.trim());
|
||||||
|
if (!key) continue;
|
||||||
|
const n = parseGermanNumber(value);
|
||||||
|
if (n === undefined) continue;
|
||||||
|
// Source convention: revenues negative, expenses positive. Flip revenues.
|
||||||
|
categories[key] = ERTRAG_SET.has(key) ? -n : n;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.push({
|
||||||
|
produktbereich,
|
||||||
|
produktgruppe,
|
||||||
|
year,
|
||||||
|
kind: classifyRow(produktbereich, produktgruppe),
|
||||||
|
categories,
|
||||||
|
source,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,162 @@
|
|||||||
|
// The 19 financial line-item columns appearing in the source CSVs.
|
||||||
|
// `globalerMinderaufwand` exists only in the 2025 plan (23 columns); other
|
||||||
|
// files are 22 columns. All other categories are present in every file.
|
||||||
|
//
|
||||||
|
// Source-CSV column names are preserved verbatim where helpful, including the
|
||||||
|
// `Leistunngsentgelte` typo and `algemeine` (single-l) drift across files.
|
||||||
|
|
||||||
|
export type Flow = "aufwendungen" | "ertraege";
|
||||||
|
|
||||||
|
export type ErtragKey =
|
||||||
|
| "steuern"
|
||||||
|
| "zuwendungenAllgemeineUmlagen"
|
||||||
|
| "sonstigeTransfererträge"
|
||||||
|
| "öffentlichRechtlicheLeistungsentgelte"
|
||||||
|
| "privatrechtlicheLeistungsentgelte"
|
||||||
|
| "kostenerstattungenKostenumlagen"
|
||||||
|
| "sonstigeOrdentlicheErträge"
|
||||||
|
| "aktivierteEigenleistungen"
|
||||||
|
| "bestandsveränderungen"
|
||||||
|
| "finanzerträge"
|
||||||
|
| "außerordentlicheErträge";
|
||||||
|
|
||||||
|
export type AufwandKey =
|
||||||
|
| "personalaufwendungen"
|
||||||
|
| "versorgungsaufwendungen"
|
||||||
|
| "sachUndDienstleistungen"
|
||||||
|
| "bilanzielleAbschreibungen"
|
||||||
|
| "transferaufwendungen"
|
||||||
|
| "sonstigeOrdentlicheAufwendungen"
|
||||||
|
| "zinsenFinanzaufwendungen"
|
||||||
|
| "außerordentlicheAufwendungen"
|
||||||
|
| "globalerMinderaufwand";
|
||||||
|
|
||||||
|
export type CategoryKey = ErtragKey | AufwandKey;
|
||||||
|
|
||||||
|
export const ERTRAG_KEYS: ReadonlyArray<ErtragKey> = [
|
||||||
|
"steuern",
|
||||||
|
"zuwendungenAllgemeineUmlagen",
|
||||||
|
"sonstigeTransfererträge",
|
||||||
|
"öffentlichRechtlicheLeistungsentgelte",
|
||||||
|
"privatrechtlicheLeistungsentgelte",
|
||||||
|
"kostenerstattungenKostenumlagen",
|
||||||
|
"sonstigeOrdentlicheErträge",
|
||||||
|
"aktivierteEigenleistungen",
|
||||||
|
"bestandsveränderungen",
|
||||||
|
"finanzerträge",
|
||||||
|
"außerordentlicheErträge",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const AUFWAND_KEYS: ReadonlyArray<AufwandKey> = [
|
||||||
|
"personalaufwendungen",
|
||||||
|
"versorgungsaufwendungen",
|
||||||
|
"sachUndDienstleistungen",
|
||||||
|
"bilanzielleAbschreibungen",
|
||||||
|
"transferaufwendungen",
|
||||||
|
"sonstigeOrdentlicheAufwendungen",
|
||||||
|
"zinsenFinanzaufwendungen",
|
||||||
|
"außerordentlicheAufwendungen",
|
||||||
|
"globalerMinderaufwand",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Display labels in German (for tooltips and detail panels). Keys match
|
||||||
|
// CategoryKey; the canonical (non-typo) German names are used here.
|
||||||
|
export const CATEGORY_LABELS: Record<CategoryKey, string> = {
|
||||||
|
steuern: "Steuern und ähnliche Abgaben",
|
||||||
|
zuwendungenAllgemeineUmlagen: "Zuwendungen und allgemeine Umlagen",
|
||||||
|
sonstigeTransfererträge: "Sonstige Transfererträge",
|
||||||
|
öffentlichRechtlicheLeistungsentgelte: "Öffentlich-rechtliche Leistungsentgelte",
|
||||||
|
privatrechtlicheLeistungsentgelte: "Privatrechtliche Leistungsentgelte",
|
||||||
|
kostenerstattungenKostenumlagen: "Kostenerstattungen und Kostenumlagen",
|
||||||
|
sonstigeOrdentlicheErträge: "Sonstige ordentliche Erträge",
|
||||||
|
aktivierteEigenleistungen: "Aktivierte Eigenleistungen",
|
||||||
|
bestandsveränderungen: "Bestandsveränderungen",
|
||||||
|
finanzerträge: "Finanzerträge",
|
||||||
|
außerordentlicheErträge: "Außerordentliche Erträge",
|
||||||
|
personalaufwendungen: "Personalaufwendungen",
|
||||||
|
versorgungsaufwendungen: "Versorgungsaufwendungen",
|
||||||
|
sachUndDienstleistungen: "Aufwendungen für Sach- und Dienstleistungen",
|
||||||
|
bilanzielleAbschreibungen: "Bilanzielle Abschreibungen",
|
||||||
|
transferaufwendungen: "Transferaufwendungen",
|
||||||
|
sonstigeOrdentlicheAufwendungen: "Sonstige ordentliche Aufwendungen",
|
||||||
|
zinsenFinanzaufwendungen: "Zinsen und sonstige Finanzaufwendungen",
|
||||||
|
außerordentlicheAufwendungen: "Außerordentliche Aufwendungen",
|
||||||
|
globalerMinderaufwand: "Globaler Minderaufwand",
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Categories = Partial<Record<CategoryKey, number>>;
|
||||||
|
|
||||||
|
// `Gesamt` rows (where Produktgruppe is "Gesamt") are subtotals at the
|
||||||
|
// Produktbereich level. The single Produktbereich="Gesamt" row is the grand
|
||||||
|
// total. We retain both but mark them so they can be filtered out of leaf-level
|
||||||
|
// aggregations.
|
||||||
|
export type RowKind = "leaf" | "produktbereichTotal" | "grandTotal";
|
||||||
|
|
||||||
|
export interface BudgetRow {
|
||||||
|
produktbereich: string;
|
||||||
|
produktgruppe: string;
|
||||||
|
year: number;
|
||||||
|
kind: RowKind;
|
||||||
|
// Numeric values, normalized: revenues are now POSITIVE (sign flipped from
|
||||||
|
// source). Empty source cells become `undefined`, not 0.
|
||||||
|
categories: Categories;
|
||||||
|
// Provenance: which CSV file contributed this row. Used to resolve overlaps
|
||||||
|
// between plan years.
|
||||||
|
source: SourceFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SourceFile {
|
||||||
|
/** Filename relative to data/ (e.g., "2025/HH_Plan_Muenster_25-28.csv"). */
|
||||||
|
path: string;
|
||||||
|
/** Plan year of the file. For Jahresabschluss this is the file's max year. */
|
||||||
|
planYear: number;
|
||||||
|
/** Whether the file contains actuals (Jahresabschluss) or planning values. */
|
||||||
|
kind: "actual" | "plan";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregated per-year, per-flow totals across the leaves of one node.
|
||||||
|
export interface FlowTotals {
|
||||||
|
/** Sum across all categories belonging to the flow, in absolute euros. */
|
||||||
|
total: number;
|
||||||
|
/** Per-category breakdown. Missing categories omitted. */
|
||||||
|
byCategory: Categories;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The shape consumed by the page and treemap components.
|
||||||
|
export interface BudgetTree {
|
||||||
|
/** Sorted list of years for which we have data. */
|
||||||
|
years: number[];
|
||||||
|
/** Year of the latest plan (default slider position). */
|
||||||
|
defaultYear: number;
|
||||||
|
/** Top-level: Produktbereich → Produktgruppe → year → flow totals. */
|
||||||
|
produktbereiche: ProduktbereichNode[];
|
||||||
|
/** Across all Produktbereiche, per-year, per-flow grand totals. */
|
||||||
|
totals: Record<number, Record<Flow, FlowTotals>>;
|
||||||
|
/** Conflicts where two source files disagreed by >5% on the same cell. */
|
||||||
|
overlaps: OverlapNote[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProduktbereichNode {
|
||||||
|
name: string;
|
||||||
|
/** Stable URL slug derived from name. */
|
||||||
|
slug: string;
|
||||||
|
produktgruppen: ProduktgruppeNode[];
|
||||||
|
/** Per-year, per-flow totals aggregated from leaf produktgruppen. */
|
||||||
|
totals: Record<number, Record<Flow, FlowTotals>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProduktgruppeNode {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
/** Per-year, per-flow totals from the source row(s) for this leaf. */
|
||||||
|
totals: Record<number, Record<Flow, FlowTotals>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OverlapNote {
|
||||||
|
produktbereich: string;
|
||||||
|
produktgruppe: string;
|
||||||
|
year: number;
|
||||||
|
category: CategoryKey;
|
||||||
|
values: { source: string; value: number }[];
|
||||||
|
divergencePct: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
// Build a path-keyed lookup of per-Produktgruppe per-category
|
||||||
|
// breakdowns extracted from the source PDF Erläuterungen.
|
||||||
|
//
|
||||||
|
// Currently only PG 1601 (Allgemeine Finanzwirtschaft) has parseable
|
||||||
|
// tabular breakdowns — that's where the major taxes (Gewerbesteuer,
|
||||||
|
// Grundsteuer, Einkommensteuer, etc.) get itemised. Other PGs have
|
||||||
|
// either prose-only Erläuterungen or no breakdowns; for those the
|
||||||
|
// lookup simply has no entries.
|
||||||
|
|
||||||
|
import type { BudgetTree, CategoryKey, Flow } from "../data/types.js";
|
||||||
|
|
||||||
|
// Teilergebnisplan line numbers map to our CategoryKey + flow side
|
||||||
|
// per the standard NRW-NKF schema. (Lines 27/28 are internal
|
||||||
|
// Leistungsbeziehungen and don't map to our category list.)
|
||||||
|
const LINE_TO_CATEGORY: Record<number, { key: CategoryKey; flow: Flow }> = {
|
||||||
|
1: { key: "steuern", flow: "ertraege" },
|
||||||
|
2: { key: "zuwendungenAllgemeineUmlagen", flow: "ertraege" },
|
||||||
|
3: { key: "sonstigeTransfererträge", flow: "ertraege" },
|
||||||
|
4: { key: "öffentlichRechtlicheLeistungsentgelte", flow: "ertraege" },
|
||||||
|
5: { key: "privatrechtlicheLeistungsentgelte", flow: "ertraege" },
|
||||||
|
6: { key: "kostenerstattungenKostenumlagen", flow: "ertraege" },
|
||||||
|
7: { key: "sonstigeOrdentlicheErträge", flow: "ertraege" },
|
||||||
|
8: { key: "aktivierteEigenleistungen", flow: "ertraege" },
|
||||||
|
9: { key: "bestandsveränderungen", flow: "ertraege" },
|
||||||
|
11: { key: "personalaufwendungen", flow: "aufwendungen" },
|
||||||
|
12: { key: "versorgungsaufwendungen", flow: "aufwendungen" },
|
||||||
|
13: { key: "sachUndDienstleistungen", flow: "aufwendungen" },
|
||||||
|
14: { key: "bilanzielleAbschreibungen", flow: "aufwendungen" },
|
||||||
|
15: { key: "transferaufwendungen", flow: "aufwendungen" },
|
||||||
|
16: { key: "sonstigeOrdentlicheAufwendungen", flow: "aufwendungen" },
|
||||||
|
19: { key: "finanzerträge", flow: "ertraege" },
|
||||||
|
20: { key: "zinsenFinanzaufwendungen", flow: "aufwendungen" },
|
||||||
|
23: { key: "außerordentlicheErträge", flow: "ertraege" },
|
||||||
|
24: { key: "außerordentlicheAufwendungen", flow: "aufwendungen" },
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface BreakdownItem {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
/** € values keyed by year. Sparse — only years the source covers. */
|
||||||
|
values: Record<number, number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lookup keyed by `${flow}/${bereichSlug}/${gruppeSlug}/${categoryKey}` */
|
||||||
|
export type BreakdownsByPath = Record<string, BreakdownItem[]>;
|
||||||
|
|
||||||
|
interface PgSectionWithBreakdowns {
|
||||||
|
pgNumber: string;
|
||||||
|
name: string;
|
||||||
|
beschreibung: string | null;
|
||||||
|
erlaeuterungen: string | null;
|
||||||
|
breakdowns?: Record<string, Array<{ name: string; values: Record<string, number> }>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SLUG_REPLACEMENTS: Array<readonly [RegExp, string]> = [
|
||||||
|
[/ä/g, "ae"],
|
||||||
|
[/ö/g, "oe"],
|
||||||
|
[/ü/g, "ue"],
|
||||||
|
[/ß/g, "ss"],
|
||||||
|
];
|
||||||
|
function slugify(input: string): string {
|
||||||
|
let s = input.toLowerCase();
|
||||||
|
for (const [pat, rep] of SLUG_REPLACEMENTS) s = s.replace(pat, rep);
|
||||||
|
s = s.replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
||||||
|
return s || "n-a";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Match a PG by name against the budget tree and return its
|
||||||
|
* (bereich, gruppe) pair. Tolerates the same abbreviations as the
|
||||||
|
* pg-notes matcher. */
|
||||||
|
function matchPgInTree(
|
||||||
|
pgName: string,
|
||||||
|
tree: BudgetTree
|
||||||
|
): { bereichSlug: string; gruppeSlug: string } | null {
|
||||||
|
const target = pgName.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||||
|
let best: { dist: number; bereich: string; gruppe: string } | null = null;
|
||||||
|
for (const bereich of tree.produktbereiche) {
|
||||||
|
for (const gruppe of bereich.produktgruppen) {
|
||||||
|
const candidate = gruppe.name.toLowerCase().replace(/[^a-z0-9]/g, "");
|
||||||
|
if (candidate === target) {
|
||||||
|
return { bereichSlug: bereich.slug, gruppeSlug: gruppe.slug };
|
||||||
|
}
|
||||||
|
// Fuzzy fallback (cheap edit-distance via length symmetric diff).
|
||||||
|
let dist = Math.abs(candidate.length - target.length);
|
||||||
|
const min = Math.min(candidate.length, target.length);
|
||||||
|
for (let i = 0; i < min; i++) {
|
||||||
|
if (candidate[i] !== target[i]) dist++;
|
||||||
|
}
|
||||||
|
if (best === null || dist < best.dist) {
|
||||||
|
best = { dist, bereich: bereich.slug, gruppe: gruppe.slug };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best && best.dist / Math.max(target.length, 1) < 0.4) {
|
||||||
|
return { bereichSlug: best.bereich, gruppeSlug: best.gruppe };
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildBreakdownsByPath(
|
||||||
|
tree: BudgetTree,
|
||||||
|
pgSections: Record<string, PgSectionWithBreakdowns>
|
||||||
|
): BreakdownsByPath {
|
||||||
|
const out: BreakdownsByPath = {};
|
||||||
|
|
||||||
|
for (const pg of Object.values(pgSections)) {
|
||||||
|
if (!pg.breakdowns || Object.keys(pg.breakdowns).length === 0) continue;
|
||||||
|
const place = matchPgInTree(pg.name, tree);
|
||||||
|
if (!place) continue;
|
||||||
|
|
||||||
|
for (const [lineNumStr, items] of Object.entries(pg.breakdowns)) {
|
||||||
|
const lineNum = parseInt(lineNumStr, 10);
|
||||||
|
const map = LINE_TO_CATEGORY[lineNum];
|
||||||
|
if (!map) continue;
|
||||||
|
|
||||||
|
// Convert string year keys back to numbers; assemble the lookup
|
||||||
|
// key in the same shape consumers will use it.
|
||||||
|
const breakdownItems: BreakdownItem[] = items.map((it) => {
|
||||||
|
const values: Record<number, number> = {};
|
||||||
|
for (const [yStr, v] of Object.entries(it.values)) {
|
||||||
|
values[Number(yStr)] = v;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
name: it.name,
|
||||||
|
slug: slugify(it.name),
|
||||||
|
values,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const key = `${map.flow}/${place.bereichSlug}/${place.gruppeSlug}/${map.key}`;
|
||||||
|
out[key] = breakdownItems;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
// OKLCH-based tile color computation for the treemap.
|
||||||
|
//
|
||||||
|
// Each flow has its own hue family (set via CSS custom properties in
|
||||||
|
// global.css). Within a flow, tile lightness/chroma encodes the share-of-
|
||||||
|
// parent so larger tiles read as more saturated and smaller tiles fade
|
||||||
|
// toward the paper background. This matches the brief's two-hue decision:
|
||||||
|
// tile lightness within a flow encodes value; the hue itself signals which
|
||||||
|
// flow we're in.
|
||||||
|
|
||||||
|
import type { Flow } from "../data/types.js";
|
||||||
|
|
||||||
|
interface FlowPalette {
|
||||||
|
/** OKLCH hue, degrees. */
|
||||||
|
h: number;
|
||||||
|
/** OKLCH chroma at the darkest end of the scale. */
|
||||||
|
cMax: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must mirror the CSS custom properties in src/styles/global.css. Keep
|
||||||
|
// the two definitions in sync — the JS values drive the SVG fills, the
|
||||||
|
// CSS values drive page chrome (toggle underline, Saldo color, etc.).
|
||||||
|
const PALETTE: Record<Flow, FlowPalette> = {
|
||||||
|
aufwendungen: { h: 295, cMax: 0.17 }, // deep plum-purple
|
||||||
|
ertraege: { h: 55, cMax: 0.16 }, // warm orange / amber
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TileColorOptions {
|
||||||
|
/** Share of the parent total for this tile, in [0, 1]. */
|
||||||
|
share: number;
|
||||||
|
/** Which flow we're rendering. */
|
||||||
|
flow: Flow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute fill + label color for a treemap tile.
|
||||||
|
*
|
||||||
|
* Lightness scale is non-linear: most tiles fall in a middle band so the
|
||||||
|
* legibility of labels stays manageable, only the very largest tiles go
|
||||||
|
* really dark. We use a cube-root mapping which compresses the long tail
|
||||||
|
* of small Produktgruppen.
|
||||||
|
*/
|
||||||
|
export function tileColor({ share, flow }: TileColorOptions): {
|
||||||
|
fill: string;
|
||||||
|
label: string;
|
||||||
|
} {
|
||||||
|
const { h, cMax } = PALETTE[flow];
|
||||||
|
|
||||||
|
// Cube-root maps a long-tail distribution into a more even one.
|
||||||
|
const t = Math.cbrt(Math.max(0, Math.min(1, share)));
|
||||||
|
|
||||||
|
// Lightness from 78% (smallest) down to 38% (largest). Above 78% the
|
||||||
|
// tile vanishes against the paper; below 38% we lose label contrast.
|
||||||
|
const L = 78 - t * 40;
|
||||||
|
// Chroma scales with t too, but shallower — small tiles still feel
|
||||||
|
// tinted, not gray.
|
||||||
|
const C = cMax * (0.45 + 0.55 * t);
|
||||||
|
|
||||||
|
const fill = `oklch(${L.toFixed(1)}% ${C.toFixed(3)} ${h})`;
|
||||||
|
// Switch label color when the tile is dark enough to need light text.
|
||||||
|
const label =
|
||||||
|
L > 56
|
||||||
|
? `oklch(20% 0.02 ${h})`
|
||||||
|
: `oklch(96% 0.01 ${h})`;
|
||||||
|
return { fill, label };
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// German-locale number formatting helpers. Used everywhere a euro figure
|
||||||
|
// appears so the spelling and rounding stay consistent.
|
||||||
|
|
||||||
|
const fmtFull = new Intl.NumberFormat("de-DE", {
|
||||||
|
style: "currency",
|
||||||
|
currency: "EUR",
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fmtTwoSig = new Intl.NumberFormat("de-DE", {
|
||||||
|
minimumFractionDigits: 1,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fmtPlainInt = new Intl.NumberFormat("de-DE", {
|
||||||
|
maximumFractionDigits: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fmtPercent = new Intl.NumberFormat("de-DE", {
|
||||||
|
style: "percent",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fmtPercentSigned = new Intl.NumberFormat("de-DE", {
|
||||||
|
style: "percent",
|
||||||
|
minimumFractionDigits: 0,
|
||||||
|
maximumFractionDigits: 1,
|
||||||
|
signDisplay: "exceptZero",
|
||||||
|
});
|
||||||
|
|
||||||
|
/** Full-euro formatting: "1.234.567 €". For the detail panel. */
|
||||||
|
export function fmtEuroFull(n: number): string {
|
||||||
|
return fmtFull.format(n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compact, headline-grade euro formatting:
|
||||||
|
* < 1 Mio. → "754.300 €" (full form)
|
||||||
|
* ≥ 1 Mio. → "12,3 Mio. €"
|
||||||
|
* ≥ 1 Mrd. → "1,7 Mrd. €"
|
||||||
|
*
|
||||||
|
* The dropoff to full-form below 1 Mio. avoids "0,8 Mio. €" which reads
|
||||||
|
* worse than the precise number.
|
||||||
|
*/
|
||||||
|
export function fmtEuroCompact(n: number): string {
|
||||||
|
const abs = Math.abs(n);
|
||||||
|
const sign = n < 0 ? "−" : "";
|
||||||
|
if (abs >= 1_000_000_000) {
|
||||||
|
return `${sign}${fmtTwoSig.format(abs / 1_000_000_000)} Mrd. €`;
|
||||||
|
}
|
||||||
|
if (abs >= 1_000_000) {
|
||||||
|
return `${sign}${fmtTwoSig.format(abs / 1_000_000)} Mio. €`;
|
||||||
|
}
|
||||||
|
return `${sign}${fmtPlainInt.format(abs)} €`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Same as fmtEuroCompact but ALWAYS shows the sign — "+" for positive,
|
||||||
|
* "−" for negative, "±" for zero. Use for signed deltas like Saldo
|
||||||
|
* where the sign carries semantic meaning.
|
||||||
|
*/
|
||||||
|
export function fmtEuroCompactSigned(n: number): string {
|
||||||
|
if (n === 0) return `±${fmtEuroCompact(0)}`;
|
||||||
|
const prefix = n > 0 ? "+" : "";
|
||||||
|
return `${prefix}${fmtEuroCompact(n)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Share-of-something formatting: "24 %". */
|
||||||
|
export function fmtPercentage(fraction: number): string {
|
||||||
|
return fmtPercent.format(fraction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Signed share for delta displays: "+1,3 %", "−4,2 %". */
|
||||||
|
export function fmtDelta(fraction: number): string {
|
||||||
|
return fmtPercentSigned.format(fraction);
|
||||||
|
}
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
// Build-time icicle (partition) layout.
|
||||||
|
//
|
||||||
|
// Three levels of hierarchy:
|
||||||
|
// Produktbereich → Produktgruppe → Category (financial line item)
|
||||||
|
// Column widths are non-uniform: the third column is intentionally
|
||||||
|
// narrower (a thin "fine print" stripe) and rendered without labels.
|
||||||
|
//
|
||||||
|
// d3.partition lays this out as nested rectangles where each level
|
||||||
|
// occupies its own column (horizontal orientation: column = depth, row =
|
||||||
|
// share within parent). At any given depth, sibling rectangles stack
|
||||||
|
// along the share axis with sizes proportional to value. Coordinates
|
||||||
|
// are returned in viewBox units so the Astro component renders without
|
||||||
|
// further math.
|
||||||
|
|
||||||
|
import { hierarchy, partition as d3partition } from "d3-hierarchy";
|
||||||
|
import type { HierarchyRectangularNode } from "d3-hierarchy";
|
||||||
|
|
||||||
|
import type { BudgetTree, CategoryKey, Flow } from "../data/types.js";
|
||||||
|
import {
|
||||||
|
AUFWAND_KEYS,
|
||||||
|
CATEGORY_LABELS,
|
||||||
|
ERTRAG_KEYS,
|
||||||
|
} from "../data/types.js";
|
||||||
|
import type { BreakdownsByPath } from "./breakdowns.js";
|
||||||
|
|
||||||
|
// viewBox dimensions; the page's CSS aspect-ratio matches them so
|
||||||
|
// preserveAspectRatio="none" doesn't actually distort. The 4:5 ratio
|
||||||
|
// gives bars enough vertical room with 17 Bereiche.
|
||||||
|
export const VB_W = 1600;
|
||||||
|
export const VB_H = 2000;
|
||||||
|
|
||||||
|
/** Depth values used by Icicle.astro to switch styling per level. */
|
||||||
|
export const DEPTH_BEREICH = 1;
|
||||||
|
export const DEPTH_GRUPPE = 2;
|
||||||
|
export const DEPTH_CATEGORY = 3;
|
||||||
|
export const DEPTH_BREAKDOWN = 4;
|
||||||
|
|
||||||
|
/** Column boundaries on the depth axis (viewBox x). depth d ∈ {1..4}
|
||||||
|
* occupies x in [COL_X[d-1], COL_X[d]]. At the OVERVIEW state the
|
||||||
|
* depth-4 (Breakdown) column collapses to zero width so the
|
||||||
|
* fine-print stays hidden — depth-4 only becomes visible once the
|
||||||
|
* user zooms in (see ZOOM_COL_BOUNDS in Icicle.astro for the
|
||||||
|
* per-zoom-state widths). */
|
||||||
|
export const COL_X = [0, 770, 1540, VB_W, VB_W];
|
||||||
|
|
||||||
|
export interface IcicleNode {
|
||||||
|
/** 1 = Bereich, 2 = Gruppe, 3 = Category, 4 = Breakdown item. */
|
||||||
|
depth: number;
|
||||||
|
/** Display name. */
|
||||||
|
name: string;
|
||||||
|
/** Stable URL slug. */
|
||||||
|
slug: string;
|
||||||
|
/** Path of slugs from root, joined by "/", suitable for URLs and keys. */
|
||||||
|
path: string;
|
||||||
|
value: number;
|
||||||
|
/** Share of the parent total, in [0, 1]. */
|
||||||
|
shareOfParent: number;
|
||||||
|
/** Share of the grand total, in [0, 1]. */
|
||||||
|
shareOfGrand: number;
|
||||||
|
/** Rectangle in viewBox coordinates (horizontal orientation:
|
||||||
|
* x is the depth axis, y is the share axis). */
|
||||||
|
x0: number;
|
||||||
|
y0: number;
|
||||||
|
x1: number;
|
||||||
|
y1: number;
|
||||||
|
/** Parent slug path; empty for top-level nodes. */
|
||||||
|
parentPath: string;
|
||||||
|
/** True when this node has no children in the rendered tree. Used
|
||||||
|
* by the component to decide click-to-zoom eligibility — a depth-3
|
||||||
|
* Category is a leaf only when it has no extracted breakdowns. */
|
||||||
|
isLeaf: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IcicleLayout {
|
||||||
|
flow: Flow;
|
||||||
|
year: number;
|
||||||
|
total: number;
|
||||||
|
nodes: IcicleNode[];
|
||||||
|
/** How many depth levels the layout actually contains (max 3). */
|
||||||
|
maxDepth: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface InputDatum {
|
||||||
|
name: string;
|
||||||
|
slug: string;
|
||||||
|
value?: number;
|
||||||
|
children?: InputDatum[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoriesFor(flow: Flow): readonly CategoryKey[] {
|
||||||
|
return flow === "aufwendungen" ? AUFWAND_KEYS : ERTRAG_KEYS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the input hierarchy and run d3.partition.
|
||||||
|
*
|
||||||
|
* Three or four levels:
|
||||||
|
* Bereich → Gruppe → Category (default)
|
||||||
|
* Bereich → Gruppe → Category → Breakdown (when extracted breakdown
|
||||||
|
* data exists for that flow/bereich/gruppe/category cell AND the
|
||||||
|
* selected year has at least one non-zero breakdown value)
|
||||||
|
*
|
||||||
|
* Empty branches are pruned. Only leaves carry numeric values; sums
|
||||||
|
* propagate upward via .sum().
|
||||||
|
*/
|
||||||
|
export function icicleLayout(
|
||||||
|
tree: BudgetTree,
|
||||||
|
flow: Flow,
|
||||||
|
year: number,
|
||||||
|
breakdowns: BreakdownsByPath = {}
|
||||||
|
): IcicleLayout {
|
||||||
|
const flowCategories = categoriesFor(flow);
|
||||||
|
|
||||||
|
const inputBereiche: InputDatum[] = [];
|
||||||
|
for (const bereich of tree.produktbereiche) {
|
||||||
|
const gruppen: InputDatum[] = [];
|
||||||
|
for (const g of bereich.produktgruppen) {
|
||||||
|
const flowTotals = g.totals[year]?.[flow];
|
||||||
|
if (!flowTotals || flowTotals.total <= 0) continue;
|
||||||
|
const categoryChildren: InputDatum[] = [];
|
||||||
|
for (const catKey of flowCategories) {
|
||||||
|
const v = flowTotals.byCategory[catKey] ?? 0;
|
||||||
|
if (v <= 0) continue;
|
||||||
|
// If breakdown data exists for this cell AND covers the year
|
||||||
|
// with non-zero values, attach the breakdown items as the
|
||||||
|
// category's children. Otherwise the category stays a leaf
|
||||||
|
// with its CSV-derived value.
|
||||||
|
const breakdownKey = `${flow}/${bereich.slug}/${g.slug}/${catKey}`;
|
||||||
|
const items = breakdowns[breakdownKey];
|
||||||
|
const breakdownChildren: InputDatum[] = [];
|
||||||
|
if (items && items.length > 0) {
|
||||||
|
for (const item of items) {
|
||||||
|
const iv = item.values[year] ?? 0;
|
||||||
|
if (iv > 0) {
|
||||||
|
breakdownChildren.push({
|
||||||
|
name: item.name,
|
||||||
|
slug: item.slug,
|
||||||
|
value: iv,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (breakdownChildren.length > 0) {
|
||||||
|
categoryChildren.push({
|
||||||
|
name: CATEGORY_LABELS[catKey],
|
||||||
|
slug: catKey,
|
||||||
|
children: breakdownChildren,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
categoryChildren.push({
|
||||||
|
name: CATEGORY_LABELS[catKey],
|
||||||
|
slug: catKey,
|
||||||
|
value: v,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (categoryChildren.length === 0) continue;
|
||||||
|
gruppen.push({
|
||||||
|
name: g.name,
|
||||||
|
slug: g.slug,
|
||||||
|
children: categoryChildren,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (gruppen.length === 0) continue;
|
||||||
|
inputBereiche.push({
|
||||||
|
name: bereich.name,
|
||||||
|
slug: bereich.slug,
|
||||||
|
children: gruppen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = hierarchy<InputDatum>({
|
||||||
|
name: "Gesamt",
|
||||||
|
slug: "",
|
||||||
|
children: inputBereiche,
|
||||||
|
})
|
||||||
|
.sum((d) => d.value ?? 0)
|
||||||
|
.sort((a, b) => (b.value ?? 0) - (a.value ?? 0));
|
||||||
|
|
||||||
|
const grandTotal = root.value ?? 0;
|
||||||
|
|
||||||
|
// partition.size([width, height]) — d3.partition computes x in [0, dx]
|
||||||
|
// for the SHARE axis and divides y in [0, dy] equally across depth+1
|
||||||
|
// levels. We pass dx = VB_H so the share axis fits our viewBox height,
|
||||||
|
// then in the output we override the depth-axis coordinates ourselves
|
||||||
|
// (otherwise the synthetic root steals one column of width).
|
||||||
|
const layoutRoot = d3partition<InputDatum>()
|
||||||
|
.size([VB_H, VB_W])
|
||||||
|
.padding(0)(root) as HierarchyRectangularNode<InputDatum>;
|
||||||
|
|
||||||
|
const nodes: IcicleNode[] = [];
|
||||||
|
let maxDepth = 0;
|
||||||
|
|
||||||
|
for (const node of layoutRoot.descendants()) {
|
||||||
|
if (node.depth === 0) continue; // synthetic root
|
||||||
|
if (node.depth > maxDepth) maxDepth = node.depth;
|
||||||
|
|
||||||
|
const value = node.value ?? 0;
|
||||||
|
const parentValue = node.parent?.value ?? grandTotal;
|
||||||
|
const ancestorSlugs = node
|
||||||
|
.ancestors()
|
||||||
|
.reverse()
|
||||||
|
.slice(1) // drop root
|
||||||
|
.map((a) => a.data.slug);
|
||||||
|
const path = ancestorSlugs.join("/");
|
||||||
|
const parentPath = ancestorSlugs.slice(0, -1).join("/");
|
||||||
|
|
||||||
|
// x (depth axis) — explicit per-depth column boundaries (Bereich and
|
||||||
|
// Gruppe wide, Category narrow).
|
||||||
|
// y (share axis) — partition's x0/x1 output (we passed size with
|
||||||
|
// dx = VB_H, so share already runs [0, VB_H]).
|
||||||
|
const xCol0 = COL_X[node.depth - 1] ?? 0;
|
||||||
|
const xCol1 = COL_X[node.depth] ?? VB_W;
|
||||||
|
|
||||||
|
nodes.push({
|
||||||
|
depth: node.depth,
|
||||||
|
name: node.data.name,
|
||||||
|
slug: node.data.slug,
|
||||||
|
path,
|
||||||
|
value,
|
||||||
|
shareOfParent: parentValue > 0 ? value / parentValue : 0,
|
||||||
|
shareOfGrand: grandTotal > 0 ? value / grandTotal : 0,
|
||||||
|
x0: xCol0,
|
||||||
|
y0: node.x0,
|
||||||
|
x1: xCol1,
|
||||||
|
y1: node.x1,
|
||||||
|
parentPath,
|
||||||
|
isLeaf: !node.children || node.children.length === 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flow,
|
||||||
|
year,
|
||||||
|
total: grandTotal,
|
||||||
|
nodes,
|
||||||
|
maxDepth,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Multi-year ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/** Per-path per-year arrays. Indexed positionally by years[i]. */
|
||||||
|
export interface PathSeries {
|
||||||
|
/** Numeric value (positive €), 0 if path didn't exist that year. */
|
||||||
|
v: number[];
|
||||||
|
/** Layout y0 in viewBox units, 0 if missing. */
|
||||||
|
y0: number[];
|
||||||
|
/** Layout y1 in viewBox units, 0 if missing (collapsed bar). */
|
||||||
|
y1: number[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MultiYearLayout {
|
||||||
|
flow: Flow;
|
||||||
|
years: number[];
|
||||||
|
defaultYear: number;
|
||||||
|
/** Year with the largest total budget (defines the canvas height). */
|
||||||
|
maxYear: number;
|
||||||
|
/** Largest year's total (positive €). */
|
||||||
|
maxTotal: number;
|
||||||
|
/** Per-year totals, indexed positionally by years[i]. */
|
||||||
|
totals: number[];
|
||||||
|
/** Per-path metadata + per-year series (only paths that exist in
|
||||||
|
* some year). */
|
||||||
|
paths: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
parentPath: string;
|
||||||
|
x0: number;
|
||||||
|
x1: number;
|
||||||
|
series: PathSeries;
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
/** The default-year layout, used for the initial server-rendered
|
||||||
|
* SVG (so the page is fully visible without JS). */
|
||||||
|
initial: IcicleLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute icicle layouts for every year in the tree, plus a per-path
|
||||||
|
* time series. Used by the year-slider to morph bars between years.
|
||||||
|
*/
|
||||||
|
export function multiYearLayout(
|
||||||
|
tree: BudgetTree,
|
||||||
|
flow: Flow,
|
||||||
|
defaultYear: number,
|
||||||
|
breakdowns: BreakdownsByPath = {}
|
||||||
|
): MultiYearLayout {
|
||||||
|
const years = [...tree.years].sort((a, b) => a - b);
|
||||||
|
|
||||||
|
// Build per-year layouts and index them by path for fast lookup.
|
||||||
|
const layoutsByYear = new Map<number, IcicleLayout>();
|
||||||
|
type IcicleNodeLite = IcicleLayout["nodes"][number];
|
||||||
|
const idxByYear = new Map<number, Map<string, IcicleNodeLite>>();
|
||||||
|
|
||||||
|
for (const year of years) {
|
||||||
|
const layout = icicleLayout(tree, flow, year, breakdowns);
|
||||||
|
layoutsByYear.set(year, layout);
|
||||||
|
const idx = new Map<string, IcicleNodeLite>();
|
||||||
|
for (const node of layout.nodes) idx.set(node.path, node);
|
||||||
|
idxByYear.set(year, idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totals = years.map((y) => layoutsByYear.get(y)?.total ?? 0);
|
||||||
|
let maxTotal = 0;
|
||||||
|
let maxYear = years[0] ?? defaultYear;
|
||||||
|
for (let i = 0; i < years.length; i++) {
|
||||||
|
const t = totals[i] ?? 0;
|
||||||
|
if (t > maxTotal) {
|
||||||
|
maxTotal = t;
|
||||||
|
maxYear = years[i]!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union of all paths across all years.
|
||||||
|
const allPaths = new Set<string>();
|
||||||
|
for (const layout of layoutsByYear.values()) {
|
||||||
|
for (const node of layout.nodes) allPaths.add(node.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build per-path series. For years where a path didn't exist, fall
|
||||||
|
// back to {v: 0, y0: 0, y1: 0} so the bar renders collapsed (zero
|
||||||
|
// height) and animates back when the path reappears.
|
||||||
|
const paths: MultiYearLayout["paths"] = {};
|
||||||
|
for (const path of allPaths) {
|
||||||
|
let meta: {
|
||||||
|
name: string;
|
||||||
|
depth: number;
|
||||||
|
parentPath: string;
|
||||||
|
x0: number;
|
||||||
|
x1: number;
|
||||||
|
} | null = null;
|
||||||
|
|
||||||
|
const v: number[] = [];
|
||||||
|
const y0: number[] = [];
|
||||||
|
const y1: number[] = [];
|
||||||
|
|
||||||
|
for (const year of years) {
|
||||||
|
const node = idxByYear.get(year)?.get(path);
|
||||||
|
if (node) {
|
||||||
|
if (!meta) {
|
||||||
|
meta = {
|
||||||
|
name: node.name,
|
||||||
|
depth: node.depth,
|
||||||
|
parentPath: node.parentPath,
|
||||||
|
x0: node.x0,
|
||||||
|
x1: node.x1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
v.push(node.value);
|
||||||
|
y0.push(node.y0);
|
||||||
|
y1.push(node.y1);
|
||||||
|
} else {
|
||||||
|
v.push(0);
|
||||||
|
y0.push(0);
|
||||||
|
y1.push(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!meta) continue;
|
||||||
|
paths[path] = { ...meta, series: { v, y0, y1 } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
flow,
|
||||||
|
years,
|
||||||
|
defaultYear,
|
||||||
|
maxYear,
|
||||||
|
maxTotal,
|
||||||
|
totals,
|
||||||
|
paths,
|
||||||
|
initial: layoutsByYear.get(defaultYear) ?? layoutsByYear.get(years[0]!)!,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Both flows' multi-year layouts, combined for the icicle component
|
||||||
|
* to render side-by-side and toggle between at runtime. */
|
||||||
|
export interface BothFlowsLayout {
|
||||||
|
years: number[];
|
||||||
|
defaultYear: number;
|
||||||
|
defaultFlow: Flow;
|
||||||
|
byFlow: Record<Flow, MultiYearLayout>;
|
||||||
|
/** Max total across BOTH flows × all years; the canvas reserves
|
||||||
|
* this height as the global max-budget reference. */
|
||||||
|
maxTotalAcrossBothFlows: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function multiYearLayoutBothFlows(
|
||||||
|
tree: BudgetTree,
|
||||||
|
defaultYear: number,
|
||||||
|
defaultFlow: Flow = "aufwendungen",
|
||||||
|
breakdowns: BreakdownsByPath = {}
|
||||||
|
): BothFlowsLayout {
|
||||||
|
const aufw = multiYearLayout(tree, "aufwendungen", defaultYear, breakdowns);
|
||||||
|
const ertr = multiYearLayout(tree, "ertraege", defaultYear, breakdowns);
|
||||||
|
return {
|
||||||
|
years: aufw.years,
|
||||||
|
defaultYear,
|
||||||
|
defaultFlow,
|
||||||
|
byFlow: { aufwendungen: aufw, ertraege: ertr },
|
||||||
|
maxTotalAcrossBothFlows: Math.max(aufw.maxTotal, ertr.maxTotal),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
// Match Produktgruppe names from the open-data CSV (which uses
|
||||||
|
// abbreviations like "Erzieh.u.wirtschaftl.Hilfen für Familien")
|
||||||
|
// against the names from the extracted PDF sections (which use the
|
||||||
|
// full forms like "Erzieherische und wirtschaftliche Hilfen für
|
||||||
|
// Familien"), and produce a path-keyed map of {beschreibung,
|
||||||
|
// erlaeuterungen} for the sidebar to consume.
|
||||||
|
|
||||||
|
import type { BudgetTree } from "../data/types.js";
|
||||||
|
|
||||||
|
export interface PgSection {
|
||||||
|
pgNumber: string;
|
||||||
|
name: string;
|
||||||
|
beschreibung: string | null;
|
||||||
|
erlaeuterungen: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PgSections = Record<string, PgSection>;
|
||||||
|
|
||||||
|
export interface NotesByPath {
|
||||||
|
[path: string]: {
|
||||||
|
beschreibung: string | null;
|
||||||
|
erlaeuterungen: string | null;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Lowercase + strip German diacritics + drop non-alphanumeric chars. */
|
||||||
|
function normalize(s: string): string {
|
||||||
|
const lower = s.toLowerCase();
|
||||||
|
const transliterated = lower
|
||||||
|
.replace(/ä/g, "ae")
|
||||||
|
.replace(/ö/g, "oe")
|
||||||
|
.replace(/ü/g, "ue")
|
||||||
|
.replace(/ß/g, "ss");
|
||||||
|
return transliterated.replace(/[^a-z0-9]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Plain Levenshtein distance, iterative DP. Small inputs; we don't
|
||||||
|
* bother optimizing. */
|
||||||
|
function levenshtein(a: string, b: string): number {
|
||||||
|
if (a === b) return 0;
|
||||||
|
if (!a.length) return b.length;
|
||||||
|
if (!b.length) return a.length;
|
||||||
|
let prev = new Array(b.length + 1);
|
||||||
|
let curr = new Array(b.length + 1);
|
||||||
|
for (let j = 0; j <= b.length; j++) prev[j] = j;
|
||||||
|
for (let i = 1; i <= a.length; i++) {
|
||||||
|
curr[0] = i;
|
||||||
|
for (let j = 1; j <= b.length; j++) {
|
||||||
|
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
||||||
|
curr[j] = Math.min(
|
||||||
|
curr[j - 1] + 1,
|
||||||
|
prev[j] + 1,
|
||||||
|
prev[j - 1] + cost
|
||||||
|
);
|
||||||
|
}
|
||||||
|
[prev, curr] = [curr, prev];
|
||||||
|
}
|
||||||
|
return prev[b.length] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given the budget tree and the extracted PG sections JSON, build a
|
||||||
|
* path-keyed lookup that the icicle component can embed in the page.
|
||||||
|
*
|
||||||
|
* Matching strategy:
|
||||||
|
* 1. Exact normalized name match (most CSV/PDF pairs match this way).
|
||||||
|
* 2. Fallback to Levenshtein with a length-normalized threshold,
|
||||||
|
* picking the closest unique candidate per name.
|
||||||
|
*
|
||||||
|
* Anything still unmatched is left out — the sidebar simply won't show
|
||||||
|
* a Beschreibung/Erläuterungen for those paths.
|
||||||
|
*/
|
||||||
|
export function buildNotesByPath(
|
||||||
|
tree: BudgetTree,
|
||||||
|
pgSections: PgSections
|
||||||
|
): NotesByPath {
|
||||||
|
const candidates = Object.values(pgSections).map((e) => ({
|
||||||
|
pg: e.pgNumber,
|
||||||
|
name: e.name,
|
||||||
|
norm: normalize(e.name),
|
||||||
|
section: e,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const out: NotesByPath = {};
|
||||||
|
|
||||||
|
for (const bereich of tree.produktbereiche) {
|
||||||
|
for (const gruppe of bereich.produktgruppen) {
|
||||||
|
const target = normalize(gruppe.name);
|
||||||
|
let chosen: (typeof candidates)[number] | null = null;
|
||||||
|
|
||||||
|
// 1. Exact normalized match.
|
||||||
|
for (const c of candidates) {
|
||||||
|
if (c.norm === target) {
|
||||||
|
chosen = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Fuzzy fallback: pick the closest by Levenshtein, accept if
|
||||||
|
// the relative edit distance is under 0.4 (i.e., >60% similar).
|
||||||
|
if (!chosen) {
|
||||||
|
let best: { c: (typeof candidates)[number]; ratio: number } | null =
|
||||||
|
null;
|
||||||
|
for (const c of candidates) {
|
||||||
|
const dist = levenshtein(target, c.norm);
|
||||||
|
const ratio = dist / Math.max(target.length, c.norm.length, 1);
|
||||||
|
if (best === null || ratio < best.ratio) {
|
||||||
|
best = { c, ratio };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (best && best.ratio < 0.4) chosen = best.c;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!chosen) continue;
|
||||||
|
const path = `${bereich.slug}/${gruppe.slug}`;
|
||||||
|
out[path] = {
|
||||||
|
beschreibung: chosen.section.beschreibung,
|
||||||
|
erlaeuterungen: chosen.section.erlaeuterungen,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,197 @@
|
|||||||
|
/* ──────────────────────────────────────────────────────────────────────────
|
||||||
|
ms-haushalt · global tokens, reset, and base typography.
|
||||||
|
See .impeccable.md for the design context this expresses.
|
||||||
|
────────────────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
|
@import "@fontsource-variable/geist";
|
||||||
|
@import "@fontsource-variable/geist-mono";
|
||||||
|
|
||||||
|
/* Figtree Variable — display + body face. SIL OFL, served from
|
||||||
|
public/fonts. Roman + italic axes, weight 300–900. */
|
||||||
|
@font-face {
|
||||||
|
font-family: "Figtree Variable";
|
||||||
|
src: url("/fonts/Figtree-VariableFont_wght.woff2") format("woff2-variations");
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Figtree Variable";
|
||||||
|
src: url("/fonts/Figtree-Italic-VariableFont_wght.woff2") format("woff2-variations");
|
||||||
|
font-weight: 300 900;
|
||||||
|
font-style: italic;
|
||||||
|
font-display: swap;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
/* ── Color (OKLCH, paper-feeling, two-hue flow palette) ───────────── */
|
||||||
|
/* Background: warm uncoated stock, never pure white. Tinted toward the
|
||||||
|
Aufwendungen hue family by 0.005-0.01 chroma per the impeccable
|
||||||
|
"tint neutrals toward brand hue" principle. */
|
||||||
|
--paper: oklch(98.4% 0.006 60);
|
||||||
|
--paper-deep: oklch(96.0% 0.008 60);
|
||||||
|
|
||||||
|
/* Ink: warmed near-black. Same principle inverted. */
|
||||||
|
--ink: oklch(20% 0.02 50);
|
||||||
|
--ink-soft: oklch(38% 0.02 55);
|
||||||
|
--ink-mute: oklch(58% 0.015 60);
|
||||||
|
|
||||||
|
/* Hairlines & faint surfaces. */
|
||||||
|
--rule: oklch(86% 0.012 60);
|
||||||
|
--rule-soft: oklch(92% 0.008 60);
|
||||||
|
|
||||||
|
/* Two-hue flow palette per design brief §9.5: deep purple for
|
||||||
|
Aufwendungen (money leaving), orange for Erträge (money coming in).
|
||||||
|
Far enough apart on the OKLCH wheel to read as a clear pivot; both
|
||||||
|
hold their own against the warm-paper background. NOT red/green. */
|
||||||
|
--flow-aufwand-h: 295; /* deep plum-purple */
|
||||||
|
--flow-aufwand-c: 0.17;
|
||||||
|
--flow-ertrag-h: 55; /* warm orange / amber */
|
||||||
|
--flow-ertrag-c: 0.16;
|
||||||
|
|
||||||
|
/* Solid ink-on-paper renders of each flow accent (used for active toggle
|
||||||
|
state, breadcrumb separators, slider handle, etc.). */
|
||||||
|
--flow-aufwand: oklch(40% var(--flow-aufwand-c) var(--flow-aufwand-h));
|
||||||
|
--flow-ertrag: oklch(58% var(--flow-ertrag-c) var(--flow-ertrag-h));
|
||||||
|
|
||||||
|
/* ── Spacing (4pt scale, semantic names) ──────────────────────────── */
|
||||||
|
--space-2xs: 4px;
|
||||||
|
--space-xs: 8px;
|
||||||
|
--space-sm: 12px;
|
||||||
|
--space-md: 16px;
|
||||||
|
--space-lg: 24px;
|
||||||
|
--space-xl: 32px;
|
||||||
|
--space-2xl: 48px;
|
||||||
|
--space-3xl: 64px;
|
||||||
|
--space-4xl: 96px;
|
||||||
|
|
||||||
|
/* ── Type scale ───────────────────────────────────────────────────── */
|
||||||
|
/* Display sizes are fluid (editorial, not app-chrome), per the
|
||||||
|
typography rule. Body and UI sizes are fixed rem. The display
|
||||||
|
range is intentionally tighter than the previous version — the
|
||||||
|
hero needs to stay above the fold on a typical 13" laptop. */
|
||||||
|
--size-display-1: clamp(2.625rem, 5.5vw + 1rem, 5.25rem); /* hero stack */
|
||||||
|
--size-display-2: clamp(1.75rem, 2.5vw + 0.5rem, 2.5rem); /* sidebar heading */
|
||||||
|
--size-display-3: 1.375rem; /* secondary */
|
||||||
|
|
||||||
|
--size-body: 1rem; /* 16px */
|
||||||
|
--size-body-lg: 1.0625rem;/* 17px lede */
|
||||||
|
--size-caption: 0.8125rem;/* 13px */
|
||||||
|
--size-eyebrow: 0.6875rem;/* 11px small caps */
|
||||||
|
--size-micro: 0.75rem; /* 12px sidebar dt labels */
|
||||||
|
|
||||||
|
/* ── Faces ────────────────────────────────────────────────────────── */
|
||||||
|
/* Display + body: Figtree (variable, SIL OFL, served from
|
||||||
|
public/fonts). Numbers: Geist Mono. The `--face-serif` /
|
||||||
|
`--face-sans` token names are legacy aliases — both now point to
|
||||||
|
Figtree since it carries display and body alike. */
|
||||||
|
--face-display: "Figtree Variable", -apple-system, BlinkMacSystemFont,
|
||||||
|
"Segoe UI", system-ui, sans-serif;
|
||||||
|
--face-serif: var(--face-display);
|
||||||
|
--face-sans: var(--face-display);
|
||||||
|
--face-mono: "Geist Mono Variable", "Geist Variable", ui-monospace,
|
||||||
|
"SF Mono", "Menlo", monospace;
|
||||||
|
|
||||||
|
/* ── Layout rhythm ────────────────────────────────────────────────── */
|
||||||
|
--measure: 65ch;
|
||||||
|
--page-max: 1280px;
|
||||||
|
--page-pad-x: clamp(1rem, 4vw, 3rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Reset ──────────────────────────────────────────────────────────── */
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
text-size-adjust: 100%;
|
||||||
|
font-feature-settings:
|
||||||
|
"kern" 1,
|
||||||
|
"liga" 1,
|
||||||
|
"calt" 1,
|
||||||
|
"ss01" 1;
|
||||||
|
/* German-specific OpenType: lining figures for tabular data, oldstyle
|
||||||
|
elsewhere. Enabled per-element where useful. */
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
background: var(--paper);
|
||||||
|
color: var(--ink);
|
||||||
|
font-family: var(--face-serif);
|
||||||
|
font-size: var(--size-body);
|
||||||
|
line-height: 1.55;
|
||||||
|
font-feature-settings:
|
||||||
|
"kern" 1,
|
||||||
|
"liga" 1,
|
||||||
|
"calt" 1;
|
||||||
|
/* Slight optical correction for warmly-tinted paper background. */
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 var(--space-md);
|
||||||
|
max-width: var(--measure);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--face-serif);
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 0.95;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
font-variant-ligatures: discretionary-ligatures;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration-thickness: 1px;
|
||||||
|
text-underline-offset: 0.18em;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
background: none;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── Utility patterns we use repeatedly ─────────────────────────────── */
|
||||||
|
|
||||||
|
.eyebrow {
|
||||||
|
font-family: var(--face-sans);
|
||||||
|
font-size: var(--size-eyebrow);
|
||||||
|
font-weight: 500;
|
||||||
|
letter-spacing: 0.14em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: var(--ink-mute);
|
||||||
|
}
|
||||||
|
|
||||||
|
.caption {
|
||||||
|
font-family: var(--face-sans);
|
||||||
|
font-size: var(--size-caption);
|
||||||
|
color: var(--ink-soft);
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabular {
|
||||||
|
font-family: var(--face-mono);
|
||||||
|
font-variant-numeric: tabular-nums lining-nums;
|
||||||
|
font-feature-settings: "tnum" 1, "lnum" 1;
|
||||||
|
letter-spacing: -0.01em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
animation-duration: 0.001ms !important;
|
||||||
|
transition-duration: 0.001ms !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "astro/tsconfigs/strict",
|
||||||
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
|
"exclude": ["dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user