Files
Lotto-Tip-Generator/scripts/automation/auto_update_and_learn.py
T
cbazzaandClaude Sonnet 5 ec7290ece5 Wire the web-scraper as a third update fallback, add regression tests
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 (commit 8c2089d) that these tests would
actually have caught the sum-extremity bug fixed in e05f1f1 - this
had previously only been caught by manual spot-checking.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 10:08:45 +02:00

488 lines
16 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
Automatischer Lotto 6aus49 Update & Learning Workflow
Nach jeder Ziehung:
1. Aktualisiert Daten von API
2. Evaluiert letzte generierte Tipps
3. Real-Time Learning Update
4. Performance-Report
Verwendung:
python auto_update_and_learn.py
Oder als Cronjob (nach Ziehung):
0 21 * * 3,0 cd /path/to/Lotto && python scripts/automation/auto_update_and_learn.py
"""
import sys
import os
from datetime import datetime, timedelta
import json
import pandas as pd
# Path Setup
script_dir = os.path.dirname(os.path.abspath(__file__))
project_dir = os.path.dirname(os.path.dirname(script_dir))
sys.path.insert(0, project_dir)
from scripts.utils.update_from_api import LottoAPIUpdater
from scripts.utils.update_from_web import LottoWebUpdater
from scripts.generators.ultimate_ai_ml_hybrid_generator import UltimateAIMLHybridGenerator
from scripts.utils.notifier import LottoNotifier
class AutoUpdateAndLearn:
"""Automatisches Update und Learning System."""
def __init__(self, data_dir: str):
self.data_dir = data_dir
# CSV file is in Lotto/data folder
# data_dir = .../Lotto/data
# We need .../Lotto/data/AlleLottozahlen.csv
self.data_file = os.path.join(data_dir, "AlleLottozahlen.csv")
self.tips_dir = os.path.join(data_dir, "generated_tips")
self.reports_dir = os.path.join(data_dir, "performance_reports")
self.learning_log = os.path.join(data_dir, "learning_log.json")
os.makedirs(self.reports_dir, exist_ok=True)
# Initialisiere Notifier
self.notifier = LottoNotifier()
print("🤖 AUTOMATISCHES UPDATE & LEARNING SYSTEM - LOTTO 6AUS49")
print("=" * 70)
def load_learning_log(self) -> dict:
"""Lädt Learning Log."""
if os.path.exists(self.learning_log):
with open(self.learning_log, 'r') as f:
return json.load(f)
return {"updates": []}
def save_learning_log(self, log: dict):
"""Speichert Learning Log."""
with open(self.learning_log, 'w') as f:
json.dump(log, f, indent=2)
def check_for_new_draw(self) -> dict:
"""
Prüft ob neue Ziehung verfügbar ist.
Returns:
Dict mit neuer Ziehung oder None
"""
print("\n🔍 PRÜFE AUF NEUE ZIEHUNG")
print("=" * 70)
log = self.load_learning_log()
# Lade aktuelle Daten
try:
df = pd.read_csv(self.data_file, sep=';')
df['datum'] = pd.to_datetime(df['datum'], format='%Y-%m-%d')
# Sortiere nach Datum absteigend
df = df.sort_values('datum', ascending=False)
latest_draw = df.iloc[0] # Neueste Ziehung
latest_date = latest_draw['datum']
print(f" 📅 Neueste Ziehung in Daten: {latest_date.strftime('%Y-%m-%d')}")
# Prüfe ob schon verarbeitet
if log["updates"]:
last_processed = log["updates"][-1].get("draw_date")
if last_processed == latest_date.strftime('%Y-%m-%d'):
print(f" ⏭️ Bereits verarbeitet")
return None
print(f" ✅ Neue Ziehung gefunden!")
return {
'date': latest_date,
'Z1': int(latest_draw['Z1']),
'Z2': int(latest_draw['Z2']),
'Z3': int(latest_draw['Z3']),
'Z4': int(latest_draw['Z4']),
'Z5': int(latest_draw['Z5']),
'Z6': int(latest_draw['Z6']),
'SZ': int(latest_draw['SZ']) if pd.notna(latest_draw['SZ']) else None
}
except Exception as e:
print(f" ❌ Fehler beim Prüfen: {e}")
return None
def update_data(self) -> bool:
"""
Aktualisiert Daten von API.
Returns:
True bei Erfolg
"""
print("\n📥 AKTUALISIERE DATEN VON API")
print("=" * 70)
try:
updater = LottoAPIUpdater(self.data_file)
# 'all': Lottoland zuerst (liefert die neueste Ziehung meist noch am
# Ziehungstag), GitHub-Archiv als Quelle für die Historie/Fallback -
# das GitHub-Archiv allein hinkt wiederholt tagelang hinterher.
success = updater.update(api_name='all', create_backup=True)
if success:
print(" ✅ Daten erfolgreich aktualisiert")
else:
print(" ⚠️ API-Update ohne neue Daten, versuche Web-Scraper...")
try:
scraper = LottoWebUpdater(self.data_file)
success = scraper.update(create_backup=False)
if success:
print(" ✅ Web-Scraper erfolgreich")
else:
print(" ⚠️ Web-Scraper ohne neue Daten")
except Exception as scrape_err:
print(f" ❌ Web-Scraper Fehler: {scrape_err}")
return success
except Exception as e:
print(f" ❌ Fehler beim Update: {e}")
return False
def evaluate_tips(self, new_draw: dict) -> dict:
"""
Evaluiert letzte generierte Tipps gegen neue Ziehung.
Args:
new_draw: Dict mit neuer Ziehung
Returns:
Evaluierungs-Ergebnisse
"""
print("\n🎯 EVALUIERE LETZTE TIPPS")
print("=" * 70)
# Finde Tipps-Datei die VOR der Ziehung generiert wurde
if not os.path.exists(self.tips_dir):
print(" ⚠️ Keine Tipps zum Evaluieren")
return {}
tip_files = sorted(
[f for f in os.listdir(self.tips_dir) if f.endswith('.csv')],
reverse=True
)
if not tip_files:
print(" ⚠️ Keine Tipps-Dateien gefunden")
return {}
# Finde Tip-Datei die vor der Ziehung erstellt wurde
draw_date = new_draw['date']
selected_tip_file = None
for tip_file in tip_files:
# Parse Timestamp aus Dateiname: weekly_lotto_tips_YYYYMMDD_HHMMSS.csv
try:
parts = tip_file.replace('.csv', '').split('_')
tip_date_str = parts[-2] # YYYYMMDD
tip_date = pd.to_datetime(tip_date_str, format='%Y%m%d')
# Nehme erste Datei die vor der Ziehung war
if tip_date < draw_date:
selected_tip_file = tip_file
break
except:
continue
if not selected_tip_file:
# Fallback: nehme älteste Datei
selected_tip_file = tip_files[-1]
latest_tips_file = os.path.join(self.tips_dir, selected_tip_file)
print(f" 📁 Evaluiere: {selected_tip_file}")
try:
tips_df = pd.read_csv(latest_tips_file)
# Extrahiere gezogene Zahlen
drawn_main = [new_draw[f'Z{i}'] for i in range(1, 7)]
drawn_sz = new_draw.get('SZ')
print(f" 🎲 Gezogene Zahlen: {drawn_main} + SZ: {drawn_sz}")
print()
results = []
best_matches = {'main': 0, 'sz': False, 'tip': None}
for _, tip in tips_df.iterrows():
# Parse Hauptzahlen (Column name is 'Numbers' in Lotto)
main_str = tip['Numbers'] if 'Numbers' in tip else tip.get('Main_Numbers', '')
# Handle both formats: "[1, 2, 3]" and "1-2-3"
if main_str.startswith('['):
# Parse Python list format
import ast
tip_main = ast.literal_eval(main_str)
else:
# Parse dash-separated format
tip_main = [int(n) for n in main_str.split('-')]
# Parse Superzahl
tip_sz = int(tip['Superzahl'])
# Zähle Treffer
main_matches = len(set(tip_main) & set(drawn_main))
sz_match = (tip_sz == drawn_sz) if drawn_sz is not None else False
results.append({
'tip_number': tip['Tip_Number'],
'strategy': tip['Strategy'],
'main_matches': main_matches,
'sz_match': sz_match,
'total_score': main_matches + (1 if sz_match else 0)
})
# Track best
total_score = main_matches + (1 if sz_match else 0)
best_total = best_matches['main'] + (1 if best_matches['sz'] else 0)
if total_score > best_total:
best_matches = {
'main': main_matches,
'sz': sz_match,
'tip': tip['Tip_Number']
}
# Ausgabe
print(f" {'Tip':<5} {'Strategie':<15} {'Main':<6} {'SZ':<5} {'Score':<7} {'Bewertung'}")
print(" " + "-" * 60)
for r in results:
rating = self._get_match_rating(r['main_matches'], r['sz_match'])
sz_indicator = "✅" if r['sz_match'] else "⚪"
print(f" #{r['tip_number']:<4} {r['strategy']:<15} "
f"{r['main_matches']:<6} {sz_indicator:<5} "
f"{r['total_score']:<7} {rating}")
print()
print(f" 🏆 Bester Tipp: #{best_matches['tip']} "
f"({best_matches['main']} Main" +
(f" + SZ" if best_matches['sz'] else "") + ")")
return {
'file': tip_files[0],
'results': results,
'best': best_matches,
'avg_main_matches': sum(r['main_matches'] for r in results) / len(results),
'sz_match_rate': sum(1 for r in results if r['sz_match']) / len(results)
}
except Exception as e:
print(f" ❌ Fehler bei Evaluation: {e}")
import traceback
traceback.print_exc()
return {}
def _get_match_rating(self, main: int, sz_match: bool) -> str:
"""Bewertung der Treffer."""
if main == 6 and sz_match:
return "🏆 JACKPOT!"
elif main == 6:
return "💰 Klasse 2"
elif main == 5 and sz_match:
return "💰 Klasse 3"
elif main == 5:
return "💰 Klasse 4"
elif main == 4 and sz_match:
return "💵 Klasse 5"
elif main == 4:
return "💵 Klasse 6"
elif main == 3 and sz_match:
return "✅ Klasse 7"
elif main == 3:
return "✅ Klasse 8"
elif main == 2 and sz_match:
return "👍 Klasse 9"
elif main >= 2:
return "👍 OK"
else:
return "⚪ Niedrig"
def perform_learning_update(self, new_draw: dict) -> bool:
"""
Führt Real-Time Learning Update durch.
Args:
new_draw: Dict mit neuer Ziehung
Returns:
True bei Erfolg
"""
print("\n🧠 REAL-TIME LEARNING UPDATE")
print("=" * 70)
try:
# Initialisiere Generator
generator = UltimateAIMLHybridGenerator(
self.data_file,
fast_mode=True
)
# Learning Update (falls vorhanden)
if hasattr(generator, 'real_time_learner'):
generator.real_time_learner.learn_from_result(new_draw)
print(" ✅ Learning Update durchgeführt")
# Performance Tracking (falls vorhanden)
if hasattr(generator, 'performance_tracker'):
generator.performance_tracker.evaluate_predictions(new_draw)
print(" 📊 Performance getrackt")
return True
except Exception as e:
print(f" ⚠️ Learning Update nicht verfügbar: {e}")
print(" ℹ️ Generator funktioniert weiterhin normal")
return True
def generate_report(self, new_draw: dict, evaluation: dict):
"""Generiert Performance-Report."""
print("\n📊 PERFORMANCE-REPORT")
print("=" * 70)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
report_file = os.path.join(
self.reports_dir,
f"report_{timestamp}.txt"
)
report = []
report.append("=" * 70)
report.append("LOTTO 6AUS49 PERFORMANCE REPORT")
report.append("=" * 70)
report.append(f"Erstellt: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report.append("")
report.append(f"ZIEHUNG VOM {new_draw['date'].strftime('%Y-%m-%d')}")
report.append("-" * 70)
report.append(f"Hauptzahlen: {[new_draw[f'Z{i}'] for i in range(1, 7)]}")
report.append(f"Superzahl: {new_draw.get('SZ', 'N/A')}")
report.append("")
if evaluation:
report.append("EVALUATION DER TIPPS")
report.append("-" * 70)
report.append(f"Datei: {evaluation['file']}")
report.append(f"Tipps evaluiert: {len(evaluation['results'])}")
report.append(f"Avg Main Matches: {evaluation['avg_main_matches']:.2f}")
report.append(f"SZ Match Rate: {evaluation['sz_match_rate']*100:.1f}%")
report.append(f"Bester Tipp: #{evaluation['best']['tip']}")
report.append(f" - Main Treffer: {evaluation['best']['main']}")
report.append(f" - SZ Match: {'Ja' if evaluation['best']['sz'] else 'Nein'}")
report.append("")
report.append("=" * 70)
# Speichern
with open(report_file, 'w') as f:
f.write('\n'.join(report))
# Ausgabe
for line in report:
print(line)
print(f"\n📁 Report gespeichert: {os.path.basename(report_file)}")
def run(self):
"""Führt kompletten Workflow aus."""
print()
print("=" * 70)
print("START: AUTOMATISCHER UPDATE & LEARNING WORKFLOW")
print("=" * 70)
# 1. Update Daten
print("\n[SCHRITT 1/4] Daten aktualisieren")
self.update_data()
# 2. Prüfe auf neue Ziehung
print("\n[SCHRITT 2/4] Neue Ziehung prüfen")
new_draw = self.check_for_new_draw()
if not new_draw:
print("\n⏭️ Keine neue Ziehung - Workflow beendet")
return True
# 3. Evaluiere Tipps
print("\n[SCHRITT 3/4] Tipps evaluieren")
evaluation = self.evaluate_tips(new_draw)
# 4. Learning Update
print("\n[SCHRITT 4/4] Learning Update")
self.perform_learning_update(new_draw)
# Report
self.generate_report(new_draw, evaluation)
# Log Update
log = self.load_learning_log()
log["updates"].append({
"timestamp": datetime.now().isoformat(),
"draw_date": new_draw['date'].strftime('%Y-%m-%d'),
"evaluation": evaluation.get('best', {}),
"avg_matches": {
'main': evaluation.get('avg_main_matches', 0),
'sz_rate': evaluation.get('sz_match_rate', 0)
}
})
self.save_learning_log(log)
# Sende Benachrichtigung
try:
best_match = {
'main_matches': evaluation.get('best', {}).get('main', 0),
'sz_match': evaluation.get('best', {}).get('sz', False)
}
evaluation_summary = {
'avg_main': evaluation.get('avg_main_matches', 0),
'sz_rate': evaluation.get('sz_match_rate', 0)
}
self.notifier.send_draw_results(new_draw, evaluation_summary, best_match)
except Exception as e:
print(f"⚠️ Benachrichtigung fehlgeschlagen: {e}")
print("\n" + "=" * 70)
print("✅ WORKFLOW ERFOLGREICH ABGESCHLOSSEN")
print("=" * 70)
return True
def main():
"""Hauptfunktion."""
import argparse
parser = argparse.ArgumentParser(
description="Automatisches Update & Learning nach Ziehung"
)
parser.add_argument(
'--data-dir',
type=str,
default="/Users/sebastianfrohlich/Projekte/Lotto/data",
help='Daten-Verzeichnis'
)
args = parser.parse_args()
# Run Workflow
workflow = AutoUpdateAndLearn(args.data_dir)
success = workflow.run()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()