#!/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()