Überwacht die Zusatzbeiträge der gesetzlichen Krankenkassen und warnt abonnierte Nutzer, solange ihr Sonderkündigungsrecht noch gilt (Ende des Monats der Erhöhung). Hintergrund: Seit dem GKV-Beitragssatzstabilisierungsgesetz (10.07.2026) sind die Kassen nicht mehr verpflichtet, ihre Mitglieder über eine Erhöhung des Zusatzbeitrags zu informieren.
| Schicht | Technologie |
|---|---|
| Dev-Umgebung | DDEV (PHP 8.4, nginx-fpm) |
| Backend | Symfony 7.4 + API Platform 4, JWT (lexik) |
| Datenbank | MariaDB 11.8 |
| Frontend | React 19 + Vite + TypeScript, TanStack Query |
Entkoppelt: api/ (JSON-API) und frontend/ (SPA). In der Entwicklung
proxyt Vite /api an nginx im selben Container — kein CORS im Browser.
Code-Konvention: englische Bezeichner. Domäne übersetzt:
HealthInsurer(Krankenkasse),ContributionRate(Zusatzbeitrag-Historie), FeldcurrentRate; Routen/api/insurers,/api/rates. Sichtbare UI-Texte und E-Mails bleiben deutsch.
ddev start # Container hoch (einmalig sudo für /etc/hosts)| Zweck | URL |
|---|---|
| Frontend (Vite) | https://kk-sentinel.ddev.site:5173 |
| API-Entrypoint | https://kk-sentinel.ddev.site/api |
| API-Doku (OpenAPI) | https://kk-sentinel.ddev.site/api/docs |
| Mailpit | https://kk-sentinel.ddev.site:8026 |
Der Vite-Dev-Server läuft als DDEV-Daemon (.ddev/config.vite.yaml) und
startet automatisch mit ddev start.
vendor/, node_modules/ und die JWT-Keys sind bewusst nicht im Repo.
Die JWT-Passphrase liegt in .env, daher lassen sich die Keys reproduzierbar
neu erzeugen:
ddev start
ddev exec 'cd api && composer install'
ddev exec 'cd api && php bin/console lexik:jwt:generate-keypair'
ddev exec 'cd api && php bin/console doctrine:migrations:migrate --no-interaction'
ddev exec 'cd api && php bin/console app:crawl:gkv' # 93 Kassen + Beiträge laden
ddev exec 'cd api && php bin/console app:create-admin admin@kk-sentinel.ddev.site "ChangeMe123!"'
ddev exec 'cd frontend && npm install'# Backend (im Container)
ddev exec 'cd api && php bin/console make:migration'
ddev exec 'cd api && php bin/console doctrine:migrations:migrate'
ddev exec 'cd api && php bin/console app:crawl:gkv' # GKV-Zentralliste crawlen
ddev exec 'cd api && php bin/console app:alerts:dispatch' # Warn-Mails versenden (idempotent)
ddev exec 'cd api && php bin/console app:create-admin <mail> <pw>'
ddev exec 'cd api && php bin/console app:seed-insurers' # nur Offline-Fallback (Platzhalter)
# Messenger-Worker (async Crawl-Jobs + täglicher Scheduler).
# Läuft in DDEV automatisch als Daemon (.ddev/config.vite.yaml); manuell:
ddev exec 'cd api && php bin/console messenger:consume async scheduler_main -v'
# Frontend
ddev exec 'cd frontend && npm run build'- Crawler (
app:crawl:gkv) lädt die GKV-Spitzenverband-Zentralliste (alle 93 Kassen, § 242 Abs. 5 SGB V), gleicht sie per Slug ab und legt bei jeder Beitragsänderung eine neueContributionRatean. Ersatzkassen (BARMER/TK/DAK/KKH/HEK/hkk) werden als TypEKerkannt; die SVLFG erhebt keinen Zusatzbeitrag (currentRate= null). - Der Scheduler (
MainSchedule) stößt den Crawl täglich an. Sowohl der Scheduler als auch der Admin-Button „Jetzt crawlen" dispatchenRunGkvCrawlan den async-Transport (Doctrine); der Messenger-Worker führt Crawl +AlertDispatcheraus (HTTP-Request blockiert nicht). - Der AlertDispatcher (
app:alerts:dispatch) schickt für jede alertfähige Erhöhung eine Warn-Mail an aktive Abonnenten inkl. Sonderkündigungs-Frist — idempotent (uniqueNotificationje Abo+Rate, keine Warnung für Abos, die nach der Änderung entstanden).
Manuelle Rate vs. Crawler: Der Crawler überschreibt einen manuell gesetzten Beitrag beim nächsten Lauf (GKV ist maßgeblich). Alerts feuern nur bei echten GKV→GKV-Anstiegen (
ContributionRate.alertable); ein manueller Vorwert oder ein manueller Wert selbst löst nie eine Warnung aus.
- Kassen-Liste mit Kennzahlen (Anzahl, Ø, günstigste/teuerste), Name-Suche, Bundesland-Filter, „nur allgemein wählbare"-Toggle (blendet betriebs-/ branchenbezogene BKKs aus), Typ-Filter und Sortierung (inkl. „zuletzt geändert").
- Beitrag als Farbbalken (grün→rot) mit Ø-Abweichung; Trend-Badge (↑/↓ + Datum) bei erkannten Änderungen; aufklappbare Detailzeile (Historie-Sparkline, Regionen, Website).
- „beobachten" übernimmt eine Kasse ins Abo-Formular; dort Mehrfachauswahl per Suche + Chips (eine Bestätigungsmail für alle).
| Methode | Pfad | Zweck |
|---|---|---|
| GET | /api/insurers |
Alle Kassen: Beitrag, Typ, Regionen, letzte Änderung |
| GET | /api/rates?insurer=/api/insurers/{id} |
Beitrags-Historie |
| POST | /api/subscribe |
Abo anlegen ({email, insurers:[…]}) → Double-Opt-In |
| GET | /api/subscribe/confirm/{token} |
Abo bestätigen |
| GET | /api/subscribe/unsubscribe/{token} |
Abmelden |
| POST | /api/login_check |
Admin-JWT |
Geschützter Bereich unter /admin in der React-App (JWT im
Authorization-Header, Login unter /admin/login):
- Dashboard — Kennzahlen, letzter Crawl, letzte Erhöhungen
- Crawl-Läufe — Verlauf + „Jetzt crawlen" (async; löst danach Alerts aus)
- Kassen — bearbeiten, aktiv/inaktiv, manuelle Beitragskorrektur, Historie
- Abonnenten — Filter (Status, Kasse) + E-Mail-Suche; einzeln/Bulk
an-/abmelden (mit E-Mail-Benachrichtigung), löschen (DSGVO, mit
Bestätigungs- und Lösch-Mail), einzelnes Kassen-Abo per × entfernen; Versand-Log
- Resend. Löschungen sind rückfrage-bestätigt.
API dahinter unter /api/admin/* (ROLE_ADMIN).
- Publikum: E-Mail Double-Opt-In (Phase 4) — keine Passwörter.
- Admin: JWT via
POST /api/login_check({ "email", "password" }). Dev-Admin:admin@kk-sentinel.ddev.site/ChangeMe123!(bitte ändern).
- Rate-Limiting auf
POST /api/subscribe: 5/15 min pro IP, 3/h pro Ziel-Adresse (Anti-Mail-Bombing) →429mitRetry-After. - Admin-JWT im
localStorage: bewusster SPA-Tradeoff (XSS-exponiert). Gemildert durch kurze Token-Laufzeit (1 h), Auto-Logout bei401und die kleine Admin-Angriffsfläche. Sauberer Fix (später): httpOnly-Cookie-Auth inkl. CSRF — bewusst zurückgestellt, da kein Kleinkram.
- Phase 1 DDEV-Gerüst
- Phase 2 Symfony-API-Basis (API Platform, JWT-Admin, MariaDB)
- Phase 3 React-19-Frontend (Kassen-Liste, Abo-Formular, Admin-Login)
- Phase 4 Domäne: Beitrags-Historie, GKV-Crawler + Scheduler, Double-Opt-In-Abos, E-Mail-Warnungen, Such-/Überblick-Ansicht
- Admin-Bereich (
/admin): Dashboard, Crawl-Läufe + async Trigger, Kassen-Pflege, Abonnenten-Verwaltung (Filter, Bulk, An-/Abmelden mit Mail, DSGVO-Löschmail), Versand-Log + Resend - Öffentliche Ansicht ausgebaut: Filter, Trend, Farbbalken, „beobachten", Detailzeile, Mehrfach-Abo
- Feinschliff: Ersatzkassen-Typ (EK), SVLFG (93 Kassen), Rate-Limiting, async Crawl-Worker, einheitliches Button-/Select-Styling
- Phase 4 (Ausbau, optional) Einzel-Crawler je Kasse (frühere Vorwarnung)
- Phase 5 Hetzner-Hosting (Ubuntu 24.04, nginx + PHP-FPM 8.4 + MariaDB, Scheduler-Worker als systemd-Service) — bewusst ganz am Ende