Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
b43b72d
Use Zenodo curated wind dataset instead of MaStR API scraping
MaykThewessen Mar 4, 2026
1f7e5bd
Add incremental MaStR API fetch + historical capacity snapshots per r…
MaykThewessen Mar 4, 2026
210c479
Add export_wind_csv.py for wind-only CSV export from parsed MaStR data
MaykThewessen May 1, 2026
7adfb78
Expand README with pipeline diagrams, schema tables, and usage details
MaykThewessen May 1, 2026
d45260a
Add interactive marimo notebooks, RTD-style docs site, pipeline diagrams
MaykThewessen May 16, 2026
b8e424b
Re-render sample SVGs + notebooks against real MaStR data
MaykThewessen May 17, 2026
3cef76c
Improve PV sample: top 50k plants by capacity instead of first-page s…
MaykThewessen May 17, 2026
ac4801e
Add wind animation, per-Bundesland chart, pixi.toml
MaykThewessen May 17, 2026
318974d
Add top-Kreise table, scale-picker, CI weekly refresh
MaykThewessen May 17, 2026
cca0c0f
Add top-operators + offshore-wind charts; OG meta tags + SVG favicon
MaykThewessen May 17, 2026
54e4d64
Add PV animation GIF; generalise GIF renderer; widen PV scrape to 200k
MaykThewessen May 17, 2026
f62fbc9
Add energy-mix + YoY-additions charts, bulk downloads, parser tests, …
MaykThewessen May 17, 2026
8844769
Add per-Bundesland stacked-area ramp + PV-by-installation-type chart
MaykThewessen May 18, 2026
69f9e97
Add density choropleths, largest-plants table, README badges, .github…
MaykThewessen May 18, 2026
06b6b81
Add PV orientation + wind-age charts, CHANGELOG, CONTRIBUTING
MaykThewessen May 18, 2026
1420649
Extend animation GIFs to May 2026 (YTD)
MaykThewessen May 19, 2026
3a006da
Bump snapshot date from 2025-01-01 to 2026-05-01
MaykThewessen May 19, 2026
6d57b50
Fix title-GW underreport, active_at_snap consistency, offshore-anonym…
MaykThewessen May 19, 2026
b808b39
Fix duplicate date in GIF frame titles
MaykThewessen May 19, 2026
8495636
Move snapshot date to the first row of every chart title
MaykThewessen May 19, 2026
2ca5c88
Bump GIF resolution from 770×900 to 1027×1200
MaykThewessen May 19, 2026
6ff60ad
Bump GIF resolution to 1233×1440 (was 1027×1200)
MaykThewessen May 19, 2026
a92ab28
Switch intermediate frames to WebP; emit MP4 alongside the GIF
MaykThewessen May 19, 2026
04e97f8
Add BESS (battery + electricity storage) coverage
MaykThewessen May 19, 2026
d395864
Add HSS / CSS / LSS three-sector BESS split (battery-charts.de conven…
MaykThewessen May 19, 2026
35118c4
Add per-unit size-bin breakdown for Wind / PV / BESS
MaykThewessen May 19, 2026
f7fb452
Split pumped-hydro out of BESS; wind size-bin aggregates per project
MaykThewessen May 19, 2026
5919f2f
Add MaStR Zenodo full-dump processor with wind/solar/storage parquet …
MaykThewessen May 19, 2026
ee39d35
Regenerate storage.parquet after script update
MaykThewessen May 19, 2026
82bc274
Add open-MaStR Zenodo bulk loader; PV jumps 200k → 4.86M plants
MaykThewessen May 19, 2026
038c5f8
Add BESS GWh trajectory chart; fix bulk-vs-API data source mismatch
MaykThewessen May 19, 2026
1610d73
Add storage to incremental fetcher; add merge_storage_gap.py
MaykThewessen May 19, 2026
482117f
Merge branch 'claude/nervous-mclaren-bc7d24'
MaykThewessen May 19, 2026
4115100
Add BKG Kreise source, refactor delta merge, regenerate samples
MaykThewessen May 19, 2026
f67521e
Add Germany country outline to all choropleth SVG maps
MaykThewessen May 19, 2026
6dd8b76
Merge country outline + BKG Kreise source + delta merge refactor
MaykThewessen May 19, 2026
ebd39da
Close PV/Wind/BESS gap to today; Kreise GPKG → BKG VG2500 (99.93% match)
MaykThewessen May 19, 2026
cca4034
Add PV orientation polar heatmap and snapshot rose
MaykThewessen May 19, 2026
e2ca057
Merge fork/main (gap-close update) into local main
MaykThewessen May 19, 2026
1a50aa6
Refactor render_samples.py
MaykThewessen May 19, 2026
d718d36
Add gradient choropleth variant alongside Jenks color-bands
MaykThewessen May 19, 2026
871c92d
Add planned pipeline dotted line to PV/wind growth charts; fix chart …
MaykThewessen May 19, 2026
e22f62f
Pin building_other and balkonkraftwerk to bottom of PV installation t…
MaykThewessen May 19, 2026
d424400
Add per-page/resumable scrape to fetch_mastr; fix build_full_bess path
MaykThewessen May 19, 2026
a3447ab
Remove stale 'top-50k / >=200 kW' labels now that full bulk registry …
MaykThewessen May 19, 2026
6ca4bc3
Merge fix/pv-installation-type-ordering into main
MaykThewessen May 19, 2026
ca58d66
Make plot_choropleth column + legend configurable; add BESS energy GI…
MaykThewessen May 19, 2026
5df8543
Update BESS animation assets for May 2026
MaykThewessen May 19, 2026
4b0f4cf
Regenerate BESS animation GIF/MP4 (2005-May2026)
MaykThewessen May 19, 2026
f89b380
Regenerate all sample SVGs with BESS/PSH charts and country outlines
MaykThewessen May 19, 2026
cbbe9b9
Add name-based Kreis fallback for anonymised BESS coordinates; add BE…
MaykThewessen May 19, 2026
83f0f75
Fix 4 audit findings: duplicate Kreis names, null-date incremental, p…
MaykThewessen May 19, 2026
8a37e39
PV growth chart: full registry, all sizes, x-axis capped at 2030
MaykThewessen May 19, 2026
12ca4d3
Merge remote-tracking branch 'fork/main' into claude/nervous-mclaren-…
MaykThewessen May 19, 2026
7a81b1a
Re-render PV growth chart after merge (all sizes, 2030 cap)
MaykThewessen May 19, 2026
fbf0f1a
Re-render sample SVGs and BESS animations with current pipeline
MaykThewessen May 19, 2026
864b2b3
BESS sector duration: combine three panels into one overlaid histogram
MaykThewessen May 19, 2026
664a73a
Parametrize chart year ranges on SNAP.year; misc pipeline fixes
MaykThewessen May 19, 2026
2c64da4
Merge fork/main: BESS sector duration overlaid histogram
MaykThewessen May 19, 2026
7a1e08f
Fix audit pass 2 findings: data loss in BESS aggregation, stale dates…
MaykThewessen May 19, 2026
384abd8
Update README PV stats to include all sizes; re-render sample SVGs
MaykThewessen May 19, 2026
71365c0
Re-render sample SVGs; pin pixi.lock; ignore .claude/
MaykThewessen May 19, 2026
bb19992
Add BESS energy choropleth animation; drop _gradient SVG duplicates
MaykThewessen May 19, 2026
6ae847d
Re-render state-ramp + pv-by-type SVGs (follow-up)
MaykThewessen May 19, 2026
338d2ff
Re-render SVGs after pass-3 fixes (offshore Nordsee/Ostsee split, lab…
MaykThewessen May 19, 2026
9068054
Re-render fig/ sample SVGs after pass-3 fixes
MaykThewessen May 19, 2026
eab8a7f
Re-render sample SVGs with country outline
MaykThewessen May 19, 2026
88f1ac8
Reduce sample SVG size via Kreis geometry simplification
MaykThewessen May 19, 2026
61f957c
Re-render PV animation from full bulk dataset (all plant sizes)
MaykThewessen May 19, 2026
2d468cf
Revert SVG simplification: restore full-resolution Kreis maps
MaykThewessen May 19, 2026
1c2711d
Label PV capacity as GWp throughout; re-render all sample SVGs
MaykThewessen May 19, 2026
f07d64d
Move open-mastr DB into repo at data/mastr/
MaykThewessen May 19, 2026
3158604
Disambiguate Kreis names via AGS5; re-render samples + animations
MaykThewessen May 19, 2026
b7b96f7
Clean up stale _gradient SVG duplicates; add open-mastr plan doc
MaykThewessen May 19, 2026
a44eedd
Phase 1+5: open-mastr SQLite pipeline adapter + parity tests
MaykThewessen May 19, 2026
fb8b5f9
Phase 2: wire open-mastr SQLite into load_from_bulk via source= param
MaykThewessen May 19, 2026
629f9d8
Phase 3+4: rebuild build_full_bess.py, wire notebooks via load_records
MaykThewessen May 19, 2026
b0a19f9
Add db-mastr-storage-units task; include in db-mastr-core
MaykThewessen May 19, 2026
bb070c8
Phase 6: docs + CI refresh for open-mastr SQLite source
MaykThewessen May 19, 2026
c98ab76
Re-render samples against SQLite; drop stale prefer_bulk=False for BESS
MaykThewessen May 19, 2026
bcfb7f3
Re-render samples with full 2.5M BESS slice via SQLite
MaykThewessen May 20, 2026
dd79403
Resolve open risks: PSH energy backfill, GWh trajectory, cadence option
MaykThewessen May 20, 2026
3dfc390
Render wind animation at monthly cadence (262 frames, 2005→May 2026)
MaykThewessen May 20, 2026
6342ec8
BESS monthly cadence GIF/MP4; update docs path 2017→2018
MaykThewessen May 20, 2026
83c9f06
Fix snapshot loop missing 2026 Jan-Apr; BESS animation now starts 2020
MaykThewessen May 20, 2026
9a3643f
Re-render all 3 monthly animations with 2026 Jan-Apr included
MaykThewessen May 20, 2026
2fdf059
Fix municipality_key NaN→'nan' string trap in AGS5 override
MaykThewessen May 20, 2026
9ed7a12
FINAL_FRAME tracks date.today() so renders always close on real data
MaykThewessen May 20, 2026
bf834dc
Add BESS income-by-Bundesland snapshot script
claude May 23, 2026
45c254e
Vectorise normalise_kreis_name via unique-map (~20x faster)
MaykThewessen May 24, 2026
62f9303
Re-render all 3 animations on June 2026 MaStR snapshot
MaykThewessen Jun 14, 2026
22de5ff
Merge BESS income-by-Bundesland snapshot script
MaykThewessen Jun 14, 2026
30bc963
Improve README: 3-gif marquee, At-a-glance table, retrieval-mode data…
MaykThewessen Jun 14, 2026
e37a869
Remove legacy Zenodo CSV converters superseded by open-mastr DB
MaykThewessen Jun 16, 2026
358558e
Load wind from open-mastr DB instead of frozen Zenodo CSV
MaykThewessen Jun 16, 2026
b0cbed9
Show MB/s next to ETA in MaStR bulk download
MaykThewessen Jun 16, 2026
13879c2
Show In-Planung pipeline through 2028 in capacity animations
MaykThewessen Jun 16, 2026
fd9e365
mastr_download: fix combustion table aborting on multi-code catalog v…
MaykThewessen Jun 16, 2026
165afa1
Store MaStR bulk data as zstd parquet instead of SQLite
MaykThewessen Jul 30, 2026
447e6e0
Rewrite CLAUDE.md for the current codebase
MaykThewessen Jul 30, 2026
f099680
Upgrade open-mastr to 0.17.4 and drop the redundant katalogwerte patch
MaykThewessen Jul 30, 2026
7a86f5c
Document the parquet store's home-directory fallback
MaykThewessen Jul 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
name: Bug report
about: Something rendered wrong, a parser enum is off, or the scrape broke
title: '[bug] '
labels: bug
---

## What happened

<!-- One sentence: what did you observe? -->

## What you expected

<!-- One sentence: what should have happened? -->

## How to reproduce

1.
2.
3.

## Environment

- OS:
- Python version:
- `pixi --version`:
- Commit / branch:

## Relevant logs / screenshots

```
<paste here>
```

## Optional

- If a specific MaStR enum code is missing, paste the row's JSON.
- If a Kreis renders wrong, give its `name` from `germany_kreise.gpkg`.
23 changes: 23 additions & 0 deletions .github/ISSUE_TEMPLATE/feature_request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
---
name: Feature request
about: Suggest a new chart, dataset slice, or notebook control
title: '[feature] '
labels: enhancement
---

## What would you like to see?

<!-- One sentence: the chart, control, or output you want. -->

## Why

<!-- Who would use this and for what question? -->

## Suggested approach

<!-- Optional: rough sketch of how it could be built. -->

## Related

- Links to similar charts elsewhere?
- Specific MaStR fields needed?
23 changes: 23 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!--
Thanks for the PR. Keep this template short — fill what's relevant, delete
what's not.
-->

## What this changes

<!-- One-paragraph summary. -->

## Why

<!-- Motivation: what was wrong or missing? -->

## Test plan

- [ ] `pixi run test` — parser tests pass
- [ ] `pixi run render-samples` — sample SVGs regenerate without warnings
- [ ] `pixi run docs-build` — `docs/pv.html` + `docs/wind.html` re-export cleanly
- [ ] Spot-checked at least one before/after image

## Notes for the reviewer

<!-- Anything subtle: scope decisions, follow-ups intentionally deferred, etc. -->
150 changes: 150 additions & 0 deletions .github/workflows/refresh-docs.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: Refresh docs

# Weekly re-scrape of the MaStR registry, re-render of the marimo notebooks,
# regeneration of all sample SVGs + the wind animation GIF, then commit the
# updated artifacts back to main so GitHub Pages picks them up.

on:
schedule:
# Sundays at 03:17 UTC. Picked off-the-hour to dodge cron stampedes.
- cron: "17 3 * * 0"
workflow_dispatch:
inputs:
pages_to_scrape_pv:
description: "Number of 25k-row pages of top-sorted PV plants to fetch"
default: "8"
pages_to_scrape_wind:
description: "Number of 25k-row pages of non-PV plants to fetch"
default: "7"

permissions:
contents: write

concurrency:
group: refresh-docs
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
timeout-minutes: 60

steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install pixi
uses: prefix-dev/setup-pixi@v0.8.10
with:
pixi-version: latest
cache: true

- name: Resolve env
run: pixi install

- name: Refresh open-mastr SQLite snapshot (wind, solar, storage, storage_units, market)
# ~6 GB DB + 2 GB ZIP fit comfortably on the runner; drop the ZIP after
# parse to keep disk pressure off the subsequent render steps.
# SQLITE_DATABASE_PATH (read by open-mastr/helpers.py:50) directs the
# write to the repo path so mastr_db.DB_PATH resolves to _REPO_DB.
env:
SQLITE_DATABASE_PATH: ${{ github.workspace }}/data/mastr/open-mastr.db
run: |
mkdir -p data/mastr
pixi run db-mastr-core
rm -f ~/.open-MaStR/data/xml_download/*.zip || true

- name: Fetch Kreis polygons
run: pixi run fetch-kreise

- name: Scrape MaStR (non-PV — wind, hydro, biomass…)
env:
PAGES_NON_PV: ${{ github.event.inputs.pages_to_scrape_wind || '7' }}
run: |
mkdir -p data
for p in $(seq 1 "${PAGES_NON_PV}"); do
curl --get \
'https://www.marktstammdatenregister.de/MaStR/Einheit/EinheitJson/GetErweiterteOeffentlicheEinheitStromerzeugung' \
--data-urlencode 'sort=' \
--data-urlencode "page=${p}" \
--data-urlencode 'pageSize=25000' \
--data-urlencode 'group=' \
--data-urlencode "filter=Energieträger~neq~'2495'~and~Energieträger~neq~'2496'" \
--data-urlencode 'forExport=true' \
-o "data/data-${p}.json" &
done
wait

- name: Scrape MaStR (BESS top-200k)
run: |
mkdir -p data-bess
for p in $(seq 1 8); do
curl --get \
'https://www.marktstammdatenregister.de/MaStR/Einheit/EinheitJson/GetErweiterteOeffentlicheEinheitStromerzeugung' \
--data-urlencode 'sort=Bruttoleistung-desc' \
--data-urlencode "page=${p}" \
--data-urlencode 'pageSize=25000' \
--data-urlencode 'group=' \
--data-urlencode "filter=Energieträger~eq~'2496'" \
--data-urlencode 'forExport=true' \
-o "data-bess/data-${p}.json" &
done
wait

- name: Scrape MaStR (PV top-200k)
env:
PAGES_PV: ${{ github.event.inputs.pages_to_scrape_pv || '8' }}
run: |
mkdir -p data-pv
for p in $(seq 1 "${PAGES_PV}"); do
curl --get \
'https://www.marktstammdatenregister.de/MaStR/Einheit/EinheitJson/GetErweiterteOeffentlicheEinheitStromerzeugung' \
--data-urlencode 'sort=Bruttoleistung-desc' \
--data-urlencode "page=${p}" \
--data-urlencode 'pageSize=25000' \
--data-urlencode 'group=' \
--data-urlencode "filter=Energieträger~eq~'2495'" \
--data-urlencode 'forExport=true' \
-o "data-pv/data-${p}.json" &
done
wait

- name: Re-render sample SVGs + Bundesland chart
run: pixi run python scripts/render_samples.py

- name: Re-render wind + PV + BESS animation GIFs + MP4s
run: pixi run python scripts/render_wind_gif.py all

- name: Rebuild per-Kreis JSON
run: pixi run python scripts/build_kreise_json.py

- name: Build bulk-download artefacts
run: pixi run python scripts/build_downloads.py

- name: Run unit tests
run: pixi run test

- name: Re-export marimo HTML
run: pixi run docs-build

- name: Commit refreshed docs
run: |
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add fig/sample-*.svg \
fig/wind-*.gif fig/pv-*.gif fig/bess-*.gif \
fig/wind-*.mp4 fig/pv-*.mp4 fig/bess-*.mp4 \
docs/assets/sample-*.svg \
docs/assets/wind-*.gif docs/assets/pv-*.gif docs/assets/bess-*.gif \
docs/assets/wind-*.mp4 docs/assets/pv-*.mp4 docs/assets/bess-*.mp4 \
docs/assets/kreise.json \
docs/data/*.parquet docs/data/*.csv.gz \
docs/pv.html docs/wind.html || true
if git diff --cached --quiet; then
echo "No changes to commit."
else
git commit -m "Weekly refresh: re-scrape MaStR + re-render docs ($(date -u +%Y-%m-%d))"
git push
fi
59 changes: 59 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# macOS
.DS_Store

# Python
__pycache__/
*.py[cod]
*.egg-info/
.pytest_cache/
.ipynb_checkpoints/

# Marimo
__marimo__/

# Per-frame raster dumps (regenerable from notebooks)
fig/
fig/frames/
fig/frames-pv/
fig/frames-bess/

# Scrape outputs
*.gif
*.png
*.csv
*.json
data-bess/
data-delta-pv/
data-delta-bess/
data-delta-wind/

# open-MaStR Zenodo bulk dump (regenerable from Zenodo record 14783581)
BNetzA_MaStR/
non-pv-data/

# OSM artifacts
*.o5m
*.osm
*.osm.pbf
germany-latest.*
germany_admin_levels_*.osm
*.gpkg

# Virtual envs
.venv/
.env

# Claude Code local config
.claude/

# open-mastr snapshots, both regenerable via `pixi run db-mastr-core`:
# data/mastr/parquet/ 1.3 GB zstd parquet store (the default path)
# data/mastr/open-mastr.db 11 GB legacy SQLite, only written with --sqlite.
# Both are symlinked from ~/.open-MaStR/data/{parquet,sqlite} so open-mastr's
# own writer refreshes the in-repo copy, and so git worktrees (whose own
# data/mastr/ is empty) resolve the store via mastr_parquet's HOME_DIR fallback
# instead of silently falling back to demo data.
data/mastr/

# geoBoundaries cache (refetched on demand by bess_income_by_bundesland.py).
data/geo/
Loading