From c9da3841f96047e8d8801228960d57acdb2dd90c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Fr=C3=B6hlich?= Date: Sat, 12 Sep 2026 10:08:59 +0200 Subject: [PATCH] Add regression tests for the popularity/EV score Ports tests/test_popularity_score.py from the Lotto sibling project (same purpose: pin down known overplayed combinations scoring lower than spread ones), adapted for 5 main numbers and adds coverage for the Euro-number determinism bug fixed in 0285a1c (tips #1-3 used to always return the identical pair). Also documents beautifulsoup4 as a required dependency in the README, which was missing despite update_from_eurojackpot_zahlen_eu.py needing it. Co-Authored-By: Claude Sonnet 5 --- README.md | 11 ++++- documentation/ARCHITECTURE.md | 7 +++ tests/test_popularity_score.py | 79 ++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 tests/test_popularity_score.py diff --git a/README.md b/README.md index f471ac3..1dab317 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ Eurojackpot/ ```bash python3 -m venv venv source venv/bin/activate -pip install pandas numpy scikit-learn requests torch +pip install pandas numpy scikit-learn requests torch beautifulsoup4 ``` ### 2. Telegram-Bot einrichten @@ -139,6 +139,15 @@ Tipp, Ø Confidence/Quality, Trefferauswertung). Alle Generierungen werden in `data/generated_tips/generation_history.json` protokolliert, alle Ziehungsauswertungen in `data/learning_log.json`. +## 🧪 Tests + +Regressionstests für den Popularity/EV-Score (`tests/test_popularity_score.py`): + +```bash +pip install pytest +pytest tests/ +``` + ## 🗂️ Datenformat `data/AlleEurojackpotzahlen.csv` (Semikolon-getrennt): diff --git a/documentation/ARCHITECTURE.md b/documentation/ARCHITECTURE.md index 9108039..95d8b17 100644 --- a/documentation/ARCHITECTURE.md +++ b/documentation/ARCHITECTURE.md @@ -159,3 +159,10 @@ Navigierbare Graph-Ansicht unter `graphify-out/`: Bei 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 (portiert von Lotto) und deckt +außerdem den Euro-Zahlen-Determinismus-Bug ab (Tipps #1-3 lieferten früher +immer dasselbe Paar). Ausführen mit `pytest tests/`. diff --git a/tests/test_popularity_score.py b/tests/test_popularity_score.py new file mode 100644 index 0000000..1fd843a --- /dev/null +++ b/tests/test_popularity_score.py @@ -0,0 +1,79 @@ +""" +Regressionstests für den Popularity/EV-Score. + +Port der Lotto-Tests (siehe dortiges tests/test_popularity_score.py), an das +Eurojackpot-Format angepasst: 5 Hauptzahlen statt 6, Split-Mittelpunkt 2.5 +statt 3. Stellt sicher, dass bekannte Vielspieler-Kombinationen konsistent +NIEDRIGER bewertet werden als gestreute - genau das hatte der beim +Lotto-Projekt gefundene Sum-Extremity-Bug (Commit e05f1f1 dort) verletzt, +bevor der Fix hierher portiert wurde. +""" +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "scripts", "generators")) + +from ultimate_ai_ml_eurojackpot_generator import UltimateAIMLEurojackpotGenerator as Generator + +OVERPLAYED_COMBOS = [ + [1, 2, 3, 4, 5], + [5, 10, 15, 20, 25], +] + +SPREAD_COMBOS = [ + [5, 26, 28, 30, 40], # echte Ziehung vom 14.08.2026 + [2, 17, 29, 33, 48], +] + + +def test_overplayed_combos_score_low(): + for combo in OVERPLAYED_COMBOS: + score = Generator._calculate_popularity_score(combo) + assert score < 0.3, f"{combo} sollte als populär (niedriger Score) gelten, war {score:.3f}" + + +def test_spread_combos_score_high(): + for combo in SPREAD_COMBOS: + score = Generator._calculate_popularity_score(combo) + assert score > 0.5, f"{combo} sollte als unpopulär (hoher Score) gelten, war {score:.3f}" + + +def test_overplayed_never_outscores_spread(): + """Regressionsschutz für den (portierten) Sum-Extremity-Bug: eine + bekannte Vielspieler-Kombination darf NIE höher bewerten als eine + gestreute.""" + worst_overplayed = max(Generator._calculate_popularity_score(c) for c in OVERPLAYED_COMBOS) + best_spread = min(Generator._calculate_popularity_score(c) for c in SPREAD_COMBOS) + assert worst_overplayed < best_spread + + +def test_arithmetic_progression_penalized_regardless_of_step(): + step_one = Generator._calculate_popularity_score([10, 11, 12, 13, 14]) + step_seven = Generator._calculate_popularity_score([2, 9, 16, 23, 30]) + spread = Generator._calculate_popularity_score([2, 14, 23, 31, 40]) + assert step_one < spread + assert step_seven < spread + + +def test_balanced_odd_even_split_scores_lower_than_extreme_split(): + """Bei 5 Zahlen ist 2-3/3-2 der ausgeglichenste Split (Mittelpunkt 2.5) - + ein extremer Split (hier 5 gerade) sollte höher bewerten.""" + balanced = Generator._calculate_popularity_score([2, 3, 34, 35, 46]) # 2 gerade, 3 ungerade + extreme = Generator._calculate_popularity_score([2, 4, 34, 36, 46]) # 5 gerade + assert extreme > balanced + + +def test_score_bounded_between_zero_and_one(): + for combo in OVERPLAYED_COMBOS + SPREAD_COMBOS: + score = Generator._calculate_popularity_score(combo) + assert 0.0 <= score <= 1.0 + + +def test_euro_numbers_not_deterministic_across_first_three_tips(): + """Regressionsschutz für den Bug, bei dem Tipps #1-3 IMMER exakt + dasselbe Euro-Zahlenpaar bekamen (starre Top-2-AI-Prediction ohne + Zufall) - siehe Commit 0285a1c.""" + gen = Generator.__new__(Generator) # Instanz ohne teuren __init__ + euro_preds = {n: 0.3 for n in range(1, 13)} + pairs = [tuple(gen._get_smart_euro_numbers(tip, euro_preds)) for tip in (1, 2, 3)] + assert len(set(pairs)) > 1, f"Tipps 1-3 lieferten alle dasselbe Euro-Paar: {pairs}"