AutoUpdateAndLearn.update_data() now falls back to LottoWebUpdater (lotto.de scraping) when the API chain (Lottoland/GitHub/lottoAPI) returns no new data, matching the pattern already in production on the Eurojackpot sibling project. Required installing beautifulsoup4 into the venv - its absence would have crashed the entire update script the moment the fallback import was added, not just the fallback path itself, so this needed verifying before wiring it in. Also adds tests/test_popularity_score.py (pytest): regression coverage for the popularity/EV score against known overplayed combinations (1-2-3-4-5-6, 5-10-15-20-25-30) and the Superzahl EV ordering. Verified against the pre-fix code (commit8c2089d) that these tests would actually have caught the sum-extremity bug fixed ine05f1f1- this had previously only been caught by manual spot-checking. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
170 lines
9.2 KiB
Markdown
170 lines
9.2 KiB
Markdown
# Architektur
|
||
|
||
Dieses Dokument beschreibt den aktiven Datenfluss, die Kernkomponenten und die
|
||
wichtigsten Design-Entscheidungen des Lotto-6aus49-Systems. Es wurde mit Hilfe
|
||
einer Code-Graph-Analyse (`graphify-out/`, siehe unten) erstellt und sollte bei
|
||
größeren strukturellen Änderungen aktualisiert werden.
|
||
|
||
## 1. Aktiver Datenfluss
|
||
|
||
```mermaid
|
||
flowchart LR
|
||
subgraph Ingestion["1. Daten-Ingestion"]
|
||
A[GitHub Lotto Archive] -->|LottoAPIUpdater| B[AlleLottozahlen.csv]
|
||
end
|
||
|
||
subgraph Training["2. Training"]
|
||
B --> C[FeatureEngineer<br/>20 Features]
|
||
C --> D[AIMLEngine<br/>RandomForest, 40 Zahlen]
|
||
C --> E[DeepLearningEngine<br/>LSTM, PyTorch]
|
||
D --> F[Hybrid Predictor<br/>RF 40% + LSTM 60%]
|
||
E --> F
|
||
end
|
||
|
||
subgraph Generation["3. Tipp-Generierung"]
|
||
F --> G[UltimateAIMLHybridGenerator]
|
||
G --> H1[HYBRID-OPT]
|
||
G --> H2[BALANCED-SPREAD]
|
||
G --> H3[HIGH-EV]
|
||
G --> H4[SOFT-CONTRARIAN]
|
||
H1 & H2 & H3 & H4 --> I[Quality/Popularity Score]
|
||
I --> J[10 Tipps als CSV]
|
||
end
|
||
|
||
subgraph Learning["4. Learning-Loop"]
|
||
B -->|neue Ziehung| K[AutoUpdateAndLearn]
|
||
K -->|evaluiert| J
|
||
K -->|retrained| D
|
||
K -->|retrained| E
|
||
K --> L[learning_log.json /<br/>strategy_weights]
|
||
end
|
||
|
||
J --> M[LottoNotifier<br/>Telegram]
|
||
K --> M
|
||
|
||
L -.->|beeinflusst nächsten Lauf| G
|
||
```
|
||
|
||
**Zwei unabhängige Cron-Zyklen** (siehe Abschnitt 4):
|
||
|
||
| Schritt | Entrypoint | Ausführt |
|
||
| --- | --- | --- |
|
||
| Ingestion + Training + Learning | `run_update_and_learn.sh` → `scripts/automation/auto_update_and_learn.py` | Mi + Sa, 20:00 Uhr |
|
||
| Tipp-Generierung | `run_tip_generator.sh` → `scripts/automation/weekly_tip_generator.py` | Di + Fr, 21:00 Uhr |
|
||
|
||
Wichtig: Die Tipp-Generierung läuft **vor** dem nächsten Update-Lauf. Das
|
||
System hinkt daher strukturell immer bis zu einem Zyklus hinter neu
|
||
verfügbaren Ziehungen hinterher, falls die externe Datenquelle verspätet
|
||
aktualisiert (siehe Abschnitt 5).
|
||
|
||
## 2. Kernkomponenten (aktive Pipeline)
|
||
|
||
Alle Pfade relativ zum Projekt-Root.
|
||
|
||
| Komponente | Datei | Rolle |
|
||
| --- | --- | --- |
|
||
| `LottoAPIUpdater` | `scripts/utils/update_from_api.py` | Holt neue Ziehungen über `fetch_from_all_apis()`: Lottoland zuerst (liefert die neueste Ziehung meist noch am Ziehungstag), GitHub-Archiv für die Historie/als Fallback (kann tagelang hinterherhinken). `auto_update_and_learn.py` nutzt seit August 2026 `api_name='all'`, davor fest `api_name='github'` — daher wiederholt verspätete Ziehungen in der Vergangenheit. Liefert die API-Kette nichts Neues, greift automatisch `LottoWebUpdater` (lotto.de-Scraping) als dritter Fallback. |
|
||
| `AutoUpdateAndLearn` | `scripts/automation/auto_update_and_learn.py` | Orchestriert den 4-Schritte-Workflow: Daten aktualisieren → neue Ziehung prüfen → letzte Tipps evaluieren → Learning-Update (Retraining). |
|
||
| `FeatureEngineer` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Baut 20 Features (Frequenzen, Gaps, Momentum, Trends, Beziehungen, Zyklen) aus den Rohdaten. |
|
||
| `AIMLEngine` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | RandomForest-Modelle, ein Modell pro Zahl (1–49). Cached unter `data/ultimate_ml_models/`. |
|
||
| `DeepLearningEngine` | `scripts/utils/deep_learning_engine_pytorch.py` | LSTM (PyTorch) über Sequenzen der letzten 20 Ziehungen. Wird mit RF zu 40/60 kombiniert (`Hybrid Predictor`). |
|
||
| `UltimateAIMLHybridGenerator` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | **God Node der Pipeline** (35 Kanten im Code-Graph) — verbindet Training, die 4 Tipp-Strategien und den Quality/Popularity-Score. Zentraler Einstiegspunkt für `generate_ultimate_tips()`. |
|
||
| `PatternEngine` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Historische Verteilungsmuster (N/M/H-Zonen etc.), fließt als `pattern_weight` in die Tipp-Bewertung ein. |
|
||
| `HybridOptimizer` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Kandidaten-Generierung für die HYBRID-OPT-Strategie. |
|
||
| `RealTimeLearner` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Persistiert Learning-State (`learning_state.json`), passt `strategy_weights` nach jedem Zyklus an (`_update_strategy_weights`). |
|
||
| `PerformanceTracker` | `scripts/generators/ultimate_ai_ml_hybrid_generator.py` | Loggt Trefferauswertungen nach `data/learning_log.json`. |
|
||
| `WeeklyTipGenerator` | `scripts/automation/weekly_tip_generator.py` | Ruft `generate_ultimate_tips()`, exportiert CSV nach `data/generated_tips/`, aktualisiert `generation_history.json`. |
|
||
| `LottoNotifier` | `scripts/utils/notifier.py` | Telegram-Benachrichtigungen für neue Tipps und Ziehungsergebnisse. |
|
||
|
||
### Die 4 Tipp-Strategien
|
||
|
||
Pro Lauf werden 10 Tipps über 4 Strategien verteilt (Gewichtung passt sich
|
||
über `RealTimeLearner` dynamisch an, Startwerte: HYBRID-OPT 40%,
|
||
BALANCED-SPREAD 30%, HIGH-EV 20%, SOFT-CONTRARIAN 10%):
|
||
|
||
- **HYBRID-OPT** — AI-Score + Pattern-Gewicht kombiniert (`HybridOptimizer`)
|
||
- **BALANCED-SPREAD** — erzwingt Verteilung über N/M/H-Zonen, Summenbereich 127–171
|
||
- **HIGH-EV** — 2–3 Zahlen >31, meidet empirisch belegte populäre Einzelzahlen (siehe Abschnitt 3)
|
||
- **SOFT-CONTRARIAN** — bevorzugt in den letzten 30 Ziehungen unterrepräsentierte Zahlen
|
||
|
||
## 3. Design-Entscheidung: Quality-Score = EV-Optimierung, nicht Trefferprognose
|
||
|
||
Lotto-6aus49-Ziehungen sind mechanisch geprüfte, unabhängige Zufallsereignisse
|
||
(i.i.d.). Kein Modell — auch kein LSTM — kann daraus einen Vorteil gegenüber
|
||
reinem Zufall bei der **Trefferwahrscheinlichkeit** ableiten. Das bestätigen
|
||
auch die eigenen `learning_log.json`-Daten: die durchschnittlichen
|
||
Haupttreffer schwanken um den Erwartungswert von Zufallstipps (~0.73 Treffer
|
||
pro 6er-Tipp), ohne erkennbaren Aufwärtstrend trotz kontinuierlichem Retraining.
|
||
|
||
Der `_calculate_quality_score()` in `ultimate_ai_ml_hybrid_generator.py`
|
||
optimiert deshalb bewusst nicht auf Trefferwahrscheinlichkeit, sondern auf
|
||
**Expected Value im Gewinnfall**: unpopuläre Zahlenkombinationen haben bei
|
||
einem Treffer weniger Mitgewinner und damit eine höhere Auszahlung.
|
||
`_calculate_popularity_score()` bewertet dafür (Gewicht 50% der Quality-Formel):
|
||
|
||
1. Geburtstags-Range (>31 bevorzugt)
|
||
2. Empirisch belegte populäre Einzelzahlen (`POPULAR_PLAYER_PICKS`)
|
||
3. Zahlenfolgen/arithmetische Muster (nicht nur direkte Nachbarn)
|
||
4. Odd/Even-Split-Extremität (Menschen bevorzugen "ausgeglichen aussehende" 3-3-Splits)
|
||
|
||
Die Superzahl-Auswahl (`_get_smart_superzahl`) folgt derselben Logik: eine
|
||
feste EV-Rangfolge unpopulärer Ziffern statt (bedeutungsloser) historischer
|
||
Ziehungshäufigkeit, da die Superzahl pro Ziehung unabhängig gleichverteilt ist.
|
||
|
||
AI-Score, Pattern-Gewicht und Recency fließen weiterhin mit reduziertem
|
||
Gewicht in die Quality-Formel ein — nicht weil sie die Trefferchance erhöhen,
|
||
sondern weil eine gewisse Portfolio-Diversität über die 10 Wochentipps
|
||
gewünscht ist.
|
||
|
||
## 4. Automatisierung
|
||
|
||
Die Automatisierung läuft über **launchd** (`~/Library/LaunchAgents/`), nicht
|
||
über `crontab` — die Kommentare in `crontab -l` sind veraltete Doku-Reste und
|
||
spiegeln nicht den tatsächlichen Zeitplan wider:
|
||
|
||
| launchd Job | Plist | Zeitplan (tatsächlich) |
|
||
| --- | --- | --- |
|
||
| `com.lotto.update` | `scripts/automation/com.lotto.update.plist` | Mi + Sa, 20:00 Uhr |
|
||
| `com.lotto.weekly` | `scripts/automation/com.lotto.weekly.plist` | Di + Fr, 21:00 Uhr |
|
||
| `com.lotto.watchdog` | `scripts/automation/com.lotto.watchdog.plist` | täglich, 21:30 Uhr |
|
||
|
||
Logs: `logs/update_stdout.log` (Update+Learning), `logs/stdout.log`
|
||
(Tipp-Generierung), `logs/watchdog_stdout.log` (Watchdog).
|
||
|
||
`scripts/utils/watchdog.py` prüft, ob die letzte Ziehung in den Daten älter
|
||
als 5 Tage ist bzw. die Modelle hinter der neuesten Ziehung zurückliegen, und
|
||
schickt bei einem Treffer einen Telegram-Alarm über `LottoNotifier.send_alert()`.
|
||
Grund: `StartCalendarInterval`-Jobs, die launchd wegen Schlafmodus verpasst,
|
||
werden nicht automatisch nachgeholt — ohne diesen Check fiel das erst bei der
|
||
nächsten manuellen Nachfrage auf (so geschehen am 15.08.2026).
|
||
|
||
Backups (`data/backups/`) werden bei jedem Update automatisch erstellt und
|
||
auf die letzten 30 rotiert (`LottoAPIUpdater._prune_old_backups`).
|
||
|
||
## 5. Bekannte Schwachstellen / offene Punkte
|
||
|
||
- **Utility-Skripte** in `scripts/utils/` (`health_check.py`, `validate_csv.py`,
|
||
`verify_draws.py`, `model_evaluator.py`) existieren, sind aber nicht in die
|
||
Cron-Automatisierung eingebunden; manuelle Ausführung bei Bedarf.
|
||
- Kein automatisiertes Retry/Catch-up für komplett verpasste launchd-Läufe
|
||
(nur Alarmierung via Watchdog) — Nachholen bleibt manuell.
|
||
|
||
## 6. Code-Graph
|
||
|
||
Eine navigierbare Graph-Ansicht aller Module, Klassen und ihrer Beziehungen
|
||
liegt unter `graphify-out/`:
|
||
|
||
- `graphify-out/graph.html` — interaktive Visualisierung (im Browser öffnen)
|
||
- `graphify-out/GRAPH_REPORT.md` — God Nodes, Communities, auffällige Verbindungen
|
||
- `graphify-out/graph.json` — Rohdaten (GraphRAG-fähig)
|
||
|
||
Bei größeren strukturellen Änderungen: `/graphify --update` zum
|
||
inkrementellen Neuaufbau.
|
||
|
||
## 7. Tests
|
||
|
||
`tests/test_popularity_score.py` (pytest) prüft den Popularity/EV-Score
|
||
gegen bekannte Vielspieler-Kombinationen und deckt Regressionen wie den
|
||
Sum-Extremity-Bug (Abschnitt 3) sowie die volle Superzahl-Abdeckung über
|
||
einen 10er-Batch ab. Ausführen mit `pytest tests/`.
|