Live: https://rueckspiegel.net
A small webapp that visualises prices increases for German gas stations. Currently relevant because of the federal price-transparency rule: one daily price increase at noon (Europe/Berlin).
This project is not meant to accuse anyone of wrongdoing, just a visualization of data
┌──────────────────┐ ┌────────────────────────────┐ ┌─────────────────────┐
│ tankerkoenig CSV │ ───▶ │ ingestion/ (Bun + TS) │ ───▶ │ Supabase (Postgres) │
│ (daily snapshot) │ │ load-stations, load-prices │ │ + PostGIS + RLS │
└──────────────────┘ └────────────────────────────┘ └──────────┬──────────┘
│ anon key
▼
┌─────────────────────┐
│ frontend/ (Vite) │
│ MapLibre + Supabase │
└─────────────────────┘
db/— SQL schema, mock data, and migrations (markdown with embedded SQL blocks; paste into the Supabase SQL editor).ingestion/— Bun + TypeScript scripts that pull yesterday's tankerkoenig CSVs over HTTPS Basic auth and upsert into Supabase using the service-role key. Designed to run from cron at ~03:00 Europe/Berlin.frontend/— Vite + TypeScript + MapLibre GL. Loads stations viewport-first via astations_in_bboxPostgres RPC; the hamburger list view lazy-loads the full set on first open.
Prereqs: Bun and a Supabase project.
In the Supabase SQL editor, run in order:
CREATE EXTENSION IF NOT EXISTS postgis;db/gas_station_schema.mddb/migration_001_price_changes_unique.mddb/migration_002_daily_compliance_rollup.mddb/migration_003_stations_in_bbox.md- (Optional)
db/mock_data.mdfor three fictional stations + a day of price events. - Enable RLS on all three tables and add
selectpolicies foranon, authenticated using (true). No write policies — ingestion uses the service-role key server-side.
cd frontend
cp .env.example .env # fill VITE_SUPABASE_URL and VITE_SUPABASE_ANON_KEY (publishable key, sb_publishable_…)
bun install
bun run devYou'll need credentials for the tankerkoenig data portal (free, request access).
cd ingestion
cp .env.example .env # fill TK_USER, TK_PASS, SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY
bun install
bun run daily # refreshes stations, then ingests yesterday's pricesbun run load-prices -- --date YYYY-MM-DD backfills any past day. See ingestion/README.md for details.
- Frontend: Vite, TypeScript, MapLibre GL, OpenFreeMap Positron tiles,
@supabase/supabase-js. Bun as package manager. - Backend: Supabase (Postgres + PostGIS + PostgREST + anon key model).
- Ingestion: Bun, TypeScript, streaming CSV via
csv-parse.
- Fuel-price data: Tankerkönig — CC BY 4.0, attribution required.
- Map data: © OpenStreetMap contributors — ODbL, attribution required.
- Map tiles & style: OpenFreeMap (Positron, MIT-licensed).
Attribution for all three is shown in-app (corner control + the "Hinweis zu den Daten" section of the info modal).
MIT — see LICENSE.
CLAUDE.md in the repo root is the long-form architecture doc: data model, the relaxed compliance rule and why, frontend architecture, ingestion pipeline, gotchas. Worth reading before opening a non-trivial PR.