402 lines
13 KiB
Python
402 lines
13 KiB
Python
#!/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()
|