Files
Lotto-Tip-Generator/scripts/utils/validate_csv.py
T

461 lines
17 KiB
Python
Raw Normal View History

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