#!/usr/bin/env python3 """ CSV-Validator für Lotto 6aus49 und Eurojackpot Validiert CSV-Dateien auf: - Korrekte Spaltenstruktur - Datumformat und -konsistenz - Zahlenbereich und Eindeutigkeit - Wochentag (nur Mi/Sa für Lotto, nur Di/Fr für Eurojackpot) - Chronologische Sortierung - Duplikate - Fehlende Werte - Zukunftsdaten """ import pandas as pd from datetime import datetime import sys import os from typing import List, Dict, Tuple class LottoCSVValidator: """Validator für Lotto 6aus49 CSV-Dateien.""" def __init__(self, csv_file: str): self.csv_file = csv_file self.df = None self.errors = [] self.warnings = [] self.lottery_type = self._detect_lottery_type() def _detect_lottery_type(self) -> str: """Erkennt ob Lotto oder Eurojackpot basierend auf Dateiname.""" basename = os.path.basename(self.csv_file).lower() if 'eurojackpot' in basename: return 'eurojackpot' elif 'lotto' in basename: return 'lotto' else: # Versuche anhand der Spalten zu erkennen return 'unknown' def load_csv(self) -> bool: """Lädt 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 = pd.read_csv(self.csv_file, sep=';') # Auto-detect wenn noch unknown if self.lottery_type == 'unknown': if 'SZ2' in self.df.columns: self.lottery_type = 'eurojackpot' elif 'SZ' in self.df.columns: self.lottery_type = 'lotto' else: self.errors.append("❌ Kann Lottery-Typ nicht erkennen") return False return True except Exception as e: self.errors.append(f"❌ Fehler beim Laden: {e}") return False def validate_structure(self) -> bool: """Validiert Spaltenstruktur.""" if self.lottery_type == 'lotto': expected_cols = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ'] elif self.lottery_type == 'eurojackpot': expected_cols = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2'] else: self.errors.append("❌ Unbekannter Lottery-Typ") return False actual_cols = list(self.df.columns) if actual_cols != expected_cols: self.errors.append(f"❌ Spaltenstruktur falsch") self.errors.append(f" Erwartet: {expected_cols}") self.errors.append(f" Gefunden: {actual_cols}") return False return True def validate_dates(self) -> bool: """Validiert Datumsspalte.""" valid = True # Prüfe auf leere Daten empty_dates = self.df[self.df['datum'].isna() | (self.df['datum'] == '')] if len(empty_dates) > 0: self.errors.append(f"❌ {len(empty_dates)} Zeile(n) mit fehlendem Datum:") for idx in empty_dates.index[:5]: # Zeige max 5 self.errors.append(f" Zeile {idx + 2}") if len(empty_dates) > 5: self.errors.append(f" ... und {len(empty_dates) - 5} weitere") valid = False # Konvertiere Datum try: self.df['datum'] = pd.to_datetime(self.df['datum'], format='%Y-%m-%d', errors='coerce') except Exception as e: self.errors.append(f"❌ Fehler beim Parsen der Datumsangaben: {e}") return False # Prüfe auf ungültige Datumsangaben invalid_dates = self.df[self.df['datum'].isna()] if len(invalid_dates) > 0: self.errors.append(f"❌ {len(invalid_dates)} ungültige Datumsangaben") valid = False # Prüfe auf Zukunftsdaten today = datetime.now() future_dates = self.df[self.df['datum'] > today] if len(future_dates) > 0: self.errors.append(f"❌ {len(future_dates)} Datum/Daten in der Zukunft:") for idx, row in future_dates.iterrows(): self.errors.append(f" Zeile {idx + 2}: {row['datum'].strftime('%Y-%m-%d')}") valid = False # Prüfe Wochentage if self.lottery_type == 'lotto': # Historisch: # 1955-1965: Sonntags (und manchmal Montags bei Feiertagen) # 1965-2000: Samstags # Ab 2000: Mittwoch + Samstag weekday_names = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'} else: # eurojackpot # Eurojackpot: Dienstag + Freitag weekday_names = {0: 'Mo', 1: 'Di', 2: 'Mi', 3: 'Do', 4: 'Fr', 5: 'Sa', 6: 'So'} wrong_weekdays = [] for idx, row in self.df.iterrows(): if pd.notna(row['datum']): weekday = row['datum'].weekday() date = row['datum'] if self.lottery_type == 'lotto': # Lotto: Historische Regeln if date < datetime(1965, 9, 1): # Bis Aug 1965: Sonntag (und Montag bei Feiertagen) if weekday not in [0, 6]: # Mo, So wrong_weekdays.append((idx, date)) elif date < datetime(2000, 12, 1): # Sep 1965 - Nov 2000: Nur Samstag if weekday != 5: # Sa wrong_weekdays.append((idx, date)) else: # Ab Dez 2000: Mittwoch + Samstag if weekday not in [2, 5]: # Mi, Sa wrong_weekdays.append((idx, date)) else: # Eurojackpot: Dienstag + Freitag if weekday not in [1, 4]: # Di, Fr wrong_weekdays.append((idx, date)) if len(wrong_weekdays) > 0: # Filtere mögliche Feiertags-Sonderziehungen (< 5 pro Zeitraum = OK) if len(wrong_weekdays) <= 5: self.warnings.append(f"⚠️ {len(wrong_weekdays)} Ziehung(en) am ungewöhnlichen Wochentag (möglicherweise Feiertage):") for idx, date in wrong_weekdays: weekday_name = weekday_names[date.weekday()] self.warnings.append(f" Zeile {idx + 2}: {date.strftime('%Y-%m-%d')} ({weekday_name})") else: self.errors.append(f"❌ {len(wrong_weekdays)} Ziehung(en) am falschen Wochentag:") for idx, date in wrong_weekdays[:5]: weekday_name = weekday_names[date.weekday()] self.errors.append(f" Zeile {idx + 2}: {date.strftime('%Y-%m-%d')} ({weekday_name})") if len(wrong_weekdays) > 5: self.errors.append(f" ... und {len(wrong_weekdays) - 5} weitere") valid = False return valid def validate_numbers(self) -> bool: """Validiert Zahlenwerte.""" valid = True if self.lottery_type == 'lotto': main_cols = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6'] main_range = (1, 49) sz_cols = ['SZ'] sz_range = (0, 9) else: # eurojackpot main_cols = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5'] main_range = (1, 50) sz_cols = ['SZ1', 'SZ2'] sz_range = (1, 12) # Prüfe Hauptzahlen for col in main_cols: # Fehlende Werte missing = self.df[self.df[col].isna()] if len(missing) > 0: self.errors.append(f"❌ {len(missing)} fehlende Werte in Spalte {col}") valid = False # Zahlenbereich out_of_range = self.df[ (self.df[col] < main_range[0]) | (self.df[col] > main_range[1]) ] if len(out_of_range) > 0: self.errors.append(f"❌ {len(out_of_range)} Werte außerhalb {main_range} in {col}") for idx, row in out_of_range.head(3).iterrows(): self.errors.append(f" Zeile {idx + 2}: {row[col]}") valid = False # Prüfe Superzahl(en) for col in sz_cols: missing = self.df[self.df[col].isna()] if len(missing) > 0: self.warnings.append(f"⚠️ {len(missing)} fehlende Werte in Spalte {col}") out_of_range = self.df[ (self.df[col] < sz_range[0]) | (self.df[col] > sz_range[1]) ] if len(out_of_range) > 0: self.errors.append(f"❌ {len(out_of_range)} Werte außerhalb {sz_range} in {col}") valid = False # Prüfe Eindeutigkeit der Hauptzahlen pro Zeile duplicate_numbers = [] for idx, row in self.df.iterrows(): main_numbers = [row[col] for col in main_cols if pd.notna(row[col])] if len(main_numbers) != len(set(main_numbers)): duplicate_numbers.append((idx, main_numbers)) if len(duplicate_numbers) > 0: self.errors.append(f"❌ {len(duplicate_numbers)} Zeile(n) mit doppelten Hauptzahlen:") for idx, numbers in duplicate_numbers[:5]: self.errors.append(f" Zeile {idx + 2}: {numbers}") if len(duplicate_numbers) > 5: self.errors.append(f" ... und {len(duplicate_numbers) - 5} weitere") valid = False # Prüfe Sortierung der Hauptzahlen pro Zeile unsorted_rows = [] for idx, row in self.df.iterrows(): main_numbers = [row[col] for col in main_cols if pd.notna(row[col])] if main_numbers != sorted(main_numbers): unsorted_rows.append((idx, main_numbers)) if len(unsorted_rows) > 0: self.warnings.append(f"⚠️ {len(unsorted_rows)} Zeile(n) mit unsortierten Zahlen:") for idx, numbers in unsorted_rows[:3]: self.warnings.append(f" Zeile {idx + 2}: {numbers}") # Prüfe Sortierung der Eurozahlen (nur Eurojackpot) if self.lottery_type == 'eurojackpot': unsorted_euro = [] for idx, row in self.df.iterrows(): if pd.notna(row['SZ1']) and pd.notna(row['SZ2']): if row['SZ1'] > row['SZ2']: unsorted_euro.append((idx, row['SZ1'], row['SZ2'])) if len(unsorted_euro) > 0: self.warnings.append(f"⚠️ {len(unsorted_euro)} Zeile(n) mit unsortierten Eurozahlen") return valid def validate_duplicates(self) -> bool: """Prüft auf doppelte Datumssätze.""" duplicates = self.df[self.df.duplicated(subset=['datum'], keep=False)] if len(duplicates) > 0: self.errors.append(f"❌ {len(duplicates)} doppelte Datumseinträge gefunden:") for idx, row in duplicates.head(5).iterrows(): self.errors.append(f" Zeile {idx + 2}: {row['datum'].strftime('%Y-%m-%d')}") return False return True def validate_chronology(self) -> bool: """Prüft chronologische Sortierung.""" if len(self.df) < 2: return True unsorted = [] for i in range(len(self.df) - 1): if self.df.iloc[i]['datum'] > self.df.iloc[i + 1]['datum']: unsorted.append((i, self.df.iloc[i]['datum'], self.df.iloc[i + 1]['datum'])) if len(unsorted) > 0: self.warnings.append(f"⚠️ {len(unsorted)} Stelle(n) mit falscher chronologischer Reihenfolge:") for idx, date1, date2 in unsorted[:3]: self.warnings.append(f" Zeile {idx + 2}: {date1.strftime('%Y-%m-%d')} > {date2.strftime('%Y-%m-%d')}") return len(unsorted) == 0 def validate_completeness(self) -> bool: """Prüft auf Lücken in den Ziehungen.""" if len(self.df) < 2: return True if self.lottery_type == 'lotto': # Lotto: 2x pro Woche (Mi, Sa) expected_days = 3.5 # Durchschnitt else: # Eurojackpot: 2x pro Woche (Di, Fr) expected_days = 3.5 gaps = [] for i in range(len(self.df) - 1): date1 = self.df.iloc[i]['datum'] date2 = self.df.iloc[i + 1]['datum'] diff = (date2 - date1).days # Wenn Lücke größer als 7 Tage, könnte Ziehung fehlen if diff > 7: gaps.append((i, date1, date2, diff)) if len(gaps) > 0: self.warnings.append(f"⚠️ {len(gaps)} mögliche Lücke(n) in den Ziehungen (>7 Tage):") for idx, date1, date2, diff in gaps[:5]: self.warnings.append(f" Zeile {idx + 2}: {date1.strftime('%Y-%m-%d')} → {date2.strftime('%Y-%m-%d')} ({diff} Tage)") if len(gaps) > 5: self.warnings.append(f" ... und {len(gaps) - 5} weitere") return True def get_statistics(self) -> Dict: """Erstellt Statistiken.""" if self.df is None or len(self.df) == 0: return {} stats = { 'total_draws': len(self.df), 'date_range': ( self.df['datum'].min().strftime('%Y-%m-%d'), self.df['datum'].max().strftime('%Y-%m-%d') ), 'years_covered': (self.df['datum'].max().year - self.df['datum'].min().year) + 1, } return stats def validate_all(self) -> bool: """Führt alle Validierungen durch.""" print(f"\n{'='*70}") print(f" CSV VALIDATOR") print(f" Typ: {self.lottery_type.upper()}") print(f"{'='*70}") print(f"\n📁 Datei: {os.path.basename(self.csv_file)}") if not self.load_csv(): return False print(f"✅ Datei geladen: {len(self.df)} Zeilen") # Alle Validierungen checks = [ ("Spaltenstruktur", self.validate_structure), ("Datumsangaben", self.validate_dates), ("Zahlenwerte", self.validate_numbers), ("Duplikate", self.validate_duplicates), ("Chronologie", self.validate_chronology), ("Vollständigkeit", self.validate_completeness), ] print(f"\n🔍 VALIDIERUNG") print("="*70) all_valid = True for check_name, check_func in checks: try: result = check_func() status = "✅" if result else "❌" print(f"{status} {check_name}") if not result: all_valid = False except Exception as e: print(f"❌ {check_name} - Fehler: {e}") all_valid = False # Statistiken stats = self.get_statistics() if stats: print(f"\n📊 STATISTIKEN") print("="*70) print(f"Ziehungen gesamt: {stats['total_draws']}") print(f"Zeitraum: {stats['date_range'][0]} bis {stats['date_range'][1]}") print(f"Jahre: {stats['years_covered']}") # Fehler ausgeben if self.errors: print(f"\n❌ FEHLER ({len(self.errors)})") print("="*70) for error in self.errors: print(error) # Warnungen ausgeben if self.warnings: print(f"\n⚠️ WARNUNGEN ({len(self.warnings)})") print("="*70) for warning in self.warnings: print(warning) # Zusammenfassung print(f"\n{'='*70}") if all_valid and not self.errors: print("✅ VALIDIERUNG ERFOLGREICH - Keine Fehler gefunden!") elif not self.errors and self.warnings: print("✅ VALIDIERUNG OK - Nur Warnungen (keine kritischen Fehler)") else: print("❌ VALIDIERUNG FEHLGESCHLAGEN") print("="*70) return all_valid and len(self.errors) == 0 def main(): """Hauptfunktion.""" import sys # Standard-Dateien default_files = { 'lotto': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv', 'eurojackpot': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv' } if len(sys.argv) > 1: # Benutzerdefinierte Datei csv_file = sys.argv[1] validator = LottoCSVValidator(csv_file) success = validator.validate_all() sys.exit(0 if success else 1) else: # Validiere beide Standard-Dateien print("\n🎲 Validiere beide Lottery-Dateien...\n") results = {} for lottery_type, csv_file in default_files.items(): if os.path.exists(csv_file): validator = LottoCSVValidator(csv_file) results[lottery_type] = validator.validate_all() else: print(f"\n⚠️ {lottery_type.upper()}: Datei nicht gefunden: {csv_file}") results[lottery_type] = False # Gesamtergebnis print("\n" + "="*70) print(" GESAMTERGEBNIS") print("="*70) for lottery_type, success in results.items(): status = "✅" if success else "❌" print(f"{status} {lottery_type.upper()}") print("="*70) all_success = all(results.values()) sys.exit(0 if all_success else 1) if __name__ == "__main__": main()