This project includes multiple AI/ML-based lottery number generators for German Lotto 6aus49, including pattern analysis, weighted predictions, and hybrid approaches. Features automated weekly tip generation, performance tracking, and Telegram bot integration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
402 lines
13 KiB
Python
Executable File
402 lines
13 KiB
Python
Executable File
#!/usr/bin/env python3
|
||
"""
|
||
Ziehungs-Verifizierer für Lotto 6aus49 und Eurojackpot
|
||
|
||
Verifiziert Ziehungen gegen offizielle Datenquellen:
|
||
1. GitHub Lotto Archive (für Lotto 6aus49)
|
||
2. Eurojackpot-zahlen.eu (für Eurojackpot)
|
||
|
||
Prüft:
|
||
- Vollständigkeit (fehlende Ziehungen)
|
||
- Korrektheit (falsche Zahlen)
|
||
- Duplikate
|
||
"""
|
||
|
||
import pandas as pd
|
||
import requests
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict, List, Tuple, Optional
|
||
import sys
|
||
import os
|
||
|
||
|
||
class DrawVerifier:
|
||
"""Verifiziert Ziehungen gegen offizielle Quellen."""
|
||
|
||
def __init__(self, csv_file: str, lottery_type: str):
|
||
self.csv_file = csv_file
|
||
self.lottery_type = lottery_type
|
||
self.df_local = None
|
||
self.df_official = None
|
||
self.errors = []
|
||
self.warnings = []
|
||
self.info = []
|
||
|
||
def load_local_data(self) -> bool:
|
||
"""Lädt lokale CSV-Datei."""
|
||
try:
|
||
if not os.path.exists(self.csv_file):
|
||
self.errors.append(f"❌ Datei existiert nicht: {self.csv_file}")
|
||
return False
|
||
|
||
self.df_local = pd.read_csv(self.csv_file, sep=';')
|
||
self.df_local['datum'] = pd.to_datetime(
|
||
self.df_local['datum'],
|
||
format='%Y-%m-%d',
|
||
errors='coerce'
|
||
)
|
||
|
||
self.info.append(f"✅ Lokale Daten: {len(self.df_local)} Ziehungen")
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.errors.append(f"❌ Fehler beim Laden: {e}")
|
||
return False
|
||
|
||
def fetch_official_lotto_data(self) -> bool:
|
||
"""Holt offizielle Lotto 6aus49 Daten von GitHub Archive."""
|
||
try:
|
||
url = 'https://johannesfriedrich.github.io/LottoNumberArchive/Lottonumbers_tidy_complete.json'
|
||
|
||
print("🌐 Lade offizielle Lotto-Daten von GitHub Archive...")
|
||
|
||
headers = {
|
||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
|
||
}
|
||
|
||
response = requests.get(url, headers=headers, timeout=30)
|
||
response.raise_for_status()
|
||
|
||
data = response.json()
|
||
|
||
# Parse gruppierte Daten
|
||
draws_by_id = {}
|
||
for entry in data:
|
||
draw_id = entry.get('id')
|
||
if draw_id not in draws_by_id:
|
||
draws_by_id[draw_id] = {
|
||
'date': entry.get('date'),
|
||
'numbers': [],
|
||
'superzahl': None
|
||
}
|
||
|
||
variable = entry.get('variable')
|
||
value = entry.get('value')
|
||
|
||
if variable == 'Lottozahl':
|
||
draws_by_id[draw_id]['numbers'].append(value)
|
||
elif variable == 'Superzahl':
|
||
draws_by_id[draw_id]['superzahl'] = value
|
||
|
||
# Konvertiere zu DataFrame
|
||
official_draws = []
|
||
for draw_id, draw_data in draws_by_id.items():
|
||
try:
|
||
date_str = draw_data['date']
|
||
day, month, year = date_str.split('.')
|
||
date_obj = datetime(int(year), int(month), int(day))
|
||
|
||
numbers = sorted(draw_data['numbers'])
|
||
|
||
if len(numbers) == 6:
|
||
official_draws.append({
|
||
'datum': date_obj,
|
||
'Z1': numbers[0],
|
||
'Z2': numbers[1],
|
||
'Z3': numbers[2],
|
||
'Z4': numbers[3],
|
||
'Z5': numbers[4],
|
||
'Z6': numbers[5],
|
||
'SZ': draw_data['superzahl']
|
||
})
|
||
except Exception:
|
||
continue
|
||
|
||
self.df_official = pd.DataFrame(official_draws)
|
||
self.info.append(f"✅ Offizielle Daten: {len(self.df_official)} Ziehungen")
|
||
|
||
return True
|
||
|
||
except Exception as e:
|
||
self.errors.append(f"❌ Fehler beim Laden offizieller Daten: {e}")
|
||
return False
|
||
|
||
def fetch_official_eurojackpot_data(self) -> bool:
|
||
"""
|
||
Holt offizielle Eurojackpot Daten.
|
||
|
||
Note: Da es keine vollständige öffentliche API gibt,
|
||
beschränken wir uns auf Plausibilitätsprüfungen.
|
||
"""
|
||
self.warnings.append("⚠️ Keine vollständige offizielle Eurojackpot-API verfügbar")
|
||
self.warnings.append(" Nur Plausibilitätsprüfungen möglich")
|
||
|
||
# Erstelle minimale "offizielle" Daten basierend auf erwarteten Ziehungsterminen
|
||
# Dies ist nur für Vollständigkeitsprüfung
|
||
|
||
if len(self.df_local) > 0:
|
||
start_date = self.df_local['datum'].min()
|
||
end_date = datetime.now()
|
||
|
||
expected_dates = []
|
||
current = start_date
|
||
|
||
while current <= end_date:
|
||
# Eurojackpot: Dienstag (1) und Freitag (4)
|
||
if current.weekday() in [1, 4]:
|
||
expected_dates.append(current)
|
||
current += timedelta(days=1)
|
||
|
||
self.df_official = pd.DataFrame({'datum': expected_dates})
|
||
self.info.append(f"ℹ️ Erwartete Ziehungstermine: {len(expected_dates)}")
|
||
|
||
return True
|
||
|
||
def compare_completeness(self) -> List[datetime]:
|
||
"""Prüft auf fehlende Ziehungen."""
|
||
if self.df_official is None or self.df_local is None:
|
||
return []
|
||
|
||
official_dates = set(self.df_official['datum'].dt.date)
|
||
local_dates = set(self.df_local['datum'].dt.date)
|
||
|
||
missing = official_dates - local_dates
|
||
extra = local_dates - official_dates
|
||
|
||
if missing:
|
||
self.errors.append(f"❌ {len(missing)} fehlende Ziehung(en):")
|
||
for date in sorted(missing)[:10]:
|
||
self.errors.append(f" {date.strftime('%Y-%m-%d')}")
|
||
if len(missing) > 10:
|
||
self.errors.append(f" ... und {len(missing) - 10} weitere")
|
||
|
||
if extra:
|
||
self.warnings.append(f"⚠️ {len(extra)} zusätzliche Ziehung(en) (nicht in offiziellen Daten):")
|
||
for date in sorted(extra)[:5]:
|
||
self.warnings.append(f" {date.strftime('%Y-%m-%d')}")
|
||
if len(extra) > 5:
|
||
self.warnings.append(f" ... und {len(extra) - 5} weitere")
|
||
|
||
return list(missing)
|
||
|
||
def compare_accuracy(self) -> int:
|
||
"""Vergleicht Zahlenwerte mit offiziellen Daten."""
|
||
if self.df_official is None or self.df_local is None:
|
||
return 0
|
||
|
||
if self.lottery_type == 'eurojackpot':
|
||
# Keine detaillierten offiziellen Daten verfügbar
|
||
return 0
|
||
|
||
mismatches = 0
|
||
|
||
# Merge auf Datum
|
||
merged = pd.merge(
|
||
self.df_local,
|
||
self.df_official,
|
||
on='datum',
|
||
suffixes=('_local', '_official'),
|
||
how='inner'
|
||
)
|
||
|
||
if self.lottery_type == 'lotto':
|
||
cols_to_check = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
|
||
else: # eurojackpot
|
||
cols_to_check = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2']
|
||
|
||
for idx, row in merged.iterrows():
|
||
mismatch_cols = []
|
||
|
||
for col in cols_to_check:
|
||
local_col = f"{col}_local"
|
||
official_col = f"{col}_official"
|
||
|
||
if local_col in row and official_col in row:
|
||
# Vergleiche nur wenn beide Werte vorhanden
|
||
if pd.notna(row[local_col]) and pd.notna(row[official_col]):
|
||
if row[local_col] != row[official_col]:
|
||
mismatch_cols.append(
|
||
f"{col}: {int(row[local_col])} ≠ {int(row[official_col])}"
|
||
)
|
||
|
||
if mismatch_cols:
|
||
mismatches += 1
|
||
if mismatches == 1:
|
||
self.errors.append("❌ Zahlen-Abweichungen gefunden:")
|
||
|
||
if mismatches <= 10:
|
||
date_str = row['datum'].strftime('%Y-%m-%d')
|
||
self.errors.append(f" {date_str}: {', '.join(mismatch_cols)}")
|
||
|
||
if mismatches > 10:
|
||
self.errors.append(f" ... und {mismatches - 10} weitere Abweichungen")
|
||
|
||
return mismatches
|
||
|
||
def check_recent_draws(self, days: int = 30) -> None:
|
||
"""Prüft besonders die letzten N Tage."""
|
||
if self.df_local is None:
|
||
return
|
||
|
||
cutoff = datetime.now() - timedelta(days=days)
|
||
recent = self.df_local[self.df_local['datum'] >= cutoff]
|
||
|
||
self.info.append(f"ℹ️ Letzte {days} Tage: {len(recent)} Ziehungen")
|
||
|
||
if len(recent) == 0:
|
||
self.warnings.append(f"⚠️ Keine Ziehungen in den letzten {days} Tagen!")
|
||
|
||
# Erwartete Anzahl berechnen
|
||
if self.lottery_type == 'lotto':
|
||
# 2x pro Woche
|
||
expected = (days / 7) * 2
|
||
else: # eurojackpot
|
||
# 2x pro Woche
|
||
expected = (days / 7) * 2
|
||
|
||
if len(recent) < expected * 0.8: # Toleranz 20%
|
||
self.warnings.append(
|
||
f"⚠️ Weniger Ziehungen als erwartet: {len(recent)} vs. ~{int(expected)}"
|
||
)
|
||
|
||
def verify_all(self) -> bool:
|
||
"""Führt komplette Verifikation durch."""
|
||
print(f"\n{'='*70}")
|
||
print(f" ZIEHUNGS-VERIFIZIERER")
|
||
print(f" Typ: {self.lottery_type.upper()}")
|
||
print(f"{'='*70}")
|
||
print(f"\n📁 Datei: {os.path.basename(self.csv_file)}")
|
||
|
||
# Lade lokale Daten
|
||
if not self.load_local_data():
|
||
return False
|
||
|
||
# Lade offizielle Daten
|
||
print()
|
||
if self.lottery_type == 'lotto':
|
||
if not self.fetch_official_lotto_data():
|
||
return False
|
||
else: # eurojackpot
|
||
if not self.fetch_official_eurojackpot_data():
|
||
return False
|
||
|
||
print(f"\n🔍 VERIFIKATION")
|
||
print("="*70)
|
||
|
||
# Prüfungen
|
||
print("Prüfe Vollständigkeit...")
|
||
missing = self.compare_completeness()
|
||
|
||
if self.lottery_type == 'lotto':
|
||
print("Prüfe Zahlenwerte...")
|
||
mismatches = self.compare_accuracy()
|
||
|
||
print("Prüfe aktuelle Ziehungen...")
|
||
self.check_recent_draws(30)
|
||
|
||
# Statistiken
|
||
print(f"\n📊 STATISTIKEN")
|
||
print("="*70)
|
||
for info in self.info:
|
||
print(info)
|
||
|
||
# Zusammenfassung
|
||
if self.df_local is not None and self.df_official is not None:
|
||
if self.lottery_type == 'lotto':
|
||
overlap = len(pd.merge(
|
||
self.df_local,
|
||
self.df_official,
|
||
on='datum',
|
||
how='inner'
|
||
))
|
||
|
||
if overlap > 0:
|
||
print(f"\n✅ {overlap} Ziehungen in beiden Quellen")
|
||
|
||
# Genauigkeit
|
||
if self.lottery_type == 'lotto':
|
||
accuracy = ((overlap - (mismatches if 'mismatches' in locals() else 0)) / overlap * 100)
|
||
print(f"✅ Genauigkeit: {accuracy:.1f}%")
|
||
|
||
# Fehler
|
||
if self.errors:
|
||
print(f"\n❌ FEHLER ({len(self.errors)})")
|
||
print("="*70)
|
||
for error in self.errors:
|
||
print(error)
|
||
|
||
# Warnungen
|
||
if self.warnings:
|
||
print(f"\n⚠️ WARNUNGEN ({len(self.warnings)})")
|
||
print("="*70)
|
||
for warning in self.warnings:
|
||
print(warning)
|
||
|
||
# Ergebnis
|
||
print(f"\n{'='*70}")
|
||
if not self.errors:
|
||
if self.warnings:
|
||
print("✅ VERIFIKATION OK - Nur Warnungen")
|
||
else:
|
||
print("✅ VERIFIKATION ERFOLGREICH - Alle Ziehungen korrekt!")
|
||
else:
|
||
print("❌ VERIFIKATION FEHLGESCHLAGEN - Fehler gefunden")
|
||
print("="*70)
|
||
|
||
return len(self.errors) == 0
|
||
|
||
|
||
def main():
|
||
"""Hauptfunktion."""
|
||
|
||
# Standard-Dateien
|
||
files = {
|
||
'lotto': {
|
||
'path': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv',
|
||
'type': 'lotto'
|
||
},
|
||
'eurojackpot': {
|
||
'path': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv',
|
||
'type': 'eurojackpot'
|
||
}
|
||
}
|
||
|
||
if len(sys.argv) > 1:
|
||
# Einzelne Datei
|
||
csv_file = sys.argv[1]
|
||
lottery_type = sys.argv[2] if len(sys.argv) > 2 else 'lotto'
|
||
|
||
verifier = DrawVerifier(csv_file, lottery_type)
|
||
success = verifier.verify_all()
|
||
sys.exit(0 if success else 1)
|
||
|
||
else:
|
||
# Beide Dateien
|
||
print("\n🎲 Verifiziere beide Lottery-Dateien...\n")
|
||
|
||
results = {}
|
||
|
||
for name, config in files.items():
|
||
if os.path.exists(config['path']):
|
||
verifier = DrawVerifier(config['path'], config['type'])
|
||
results[name] = verifier.verify_all()
|
||
else:
|
||
print(f"\n⚠️ {name.upper()}: Datei nicht gefunden")
|
||
results[name] = False
|
||
|
||
# Gesamtergebnis
|
||
print("\n" + "="*70)
|
||
print(" GESAMTERGEBNIS")
|
||
print("="*70)
|
||
for name, success in results.items():
|
||
status = "✅" if success else "❌"
|
||
print(f"{status} {name.upper()}")
|
||
print("="*70)
|
||
|
||
all_success = all(results.values())
|
||
sys.exit(0 if all_success else 1)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|