Initial commit: Lotto number generator project

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>
This commit is contained in:
cbazzaandClaude Sonnet 4.5 committed 2025-12-16 14:47:59 +01:00
commit f6106b8333
48 files changed
+25168

No files matched your search

+241
View File
@@ -0,0 +1,241 @@
#!/usr/bin/env python3
"""
Model Evaluation & Reporting Module
Für umfassende ML-Model-Evaluation
"""
import numpy as np
import json
import os
from datetime import datetime
from collections import defaultdict
try:
from sklearn.model_selection import cross_val_score
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
SKLEARN_AVAILABLE = True
except ImportError:
SKLEARN_AVAILABLE = False
class ModelEvaluator:
"""Evaluiert und dokumentiert ML-Modelle."""
def __init__(self, cache_path=None):
self.cache_path = cache_path
self.evaluation_results = {}
self.report_file = os.path.join(cache_path, 'model_evaluation.json') if cache_path else None
def evaluate_model(self, model, X_train, X_test, y_train, y_test, model_name, number):
"""
Umfassende Model-Evaluation.
Returns:
dict with metrics
"""
if not SKLEARN_AVAILABLE:
return {'error': 'scikit-learn not available'}
results = {
'model_name': model_name,
'number': number,
'timestamp': datetime.now().isoformat(),
'data_size': {
'train': len(X_train),
'test': len(X_test)
}
}
try:
# 1. Training Score
y_train_pred = model.predict(X_train)
results['train_r2'] = r2_score(y_train, y_train_pred)
results['train_mae'] = mean_absolute_error(y_train, y_train_pred)
results['train_rmse'] = np.sqrt(mean_squared_error(y_train, y_train_pred))
# 2. Test Score
y_test_pred = model.predict(X_test)
results['test_r2'] = r2_score(y_test, y_test_pred)
results['test_mae'] = mean_absolute_error(y_test, y_test_pred)
results['test_rmse'] = np.sqrt(mean_squared_error(y_test, y_test_pred))
# 3. Overfit Detection
results['overfitting'] = results['train_r2'] - results['test_r2']
results['is_overfit'] = results['overfitting'] > 0.2
# 4. Cross-Validation (3-fold for speed)
try:
cv_scores = cross_val_score(model, X_train, y_train, cv=3, scoring='r2')
results['cv_mean'] = float(np.mean(cv_scores))
results['cv_std'] = float(np.std(cv_scores))
results['cv_scores'] = [float(s) for s in cv_scores]
except Exception as e:
results['cv_error'] = str(e)
# 5. Feature Importance (if available)
if hasattr(model, 'feature_importances_'):
importances = model.feature_importances_
results['top_features'] = {
f'feature_{i}': float(imp)
for i, imp in enumerate(importances[:10]) # Top 10
}
results['feature_importance_sum'] = float(np.sum(importances))
# 6. Prediction Distribution
results['pred_distribution'] = {
'min': float(np.min(y_test_pred)),
'max': float(np.max(y_test_pred)),
'mean': float(np.mean(y_test_pred)),
'std': float(np.std(y_test_pred))
}
# 7. Quality Rating
test_r2 = results['test_r2']
if test_r2 > 0.7:
results['quality'] = 'Excellent'
elif test_r2 > 0.5:
results['quality'] = 'Good'
elif test_r2 > 0.3:
results['quality'] = 'Fair'
elif test_r2 > 0.1:
results['quality'] = 'Poor'
else:
results['quality'] = 'Very Poor'
except Exception as e:
results['error'] = str(e)
return results
def add_evaluation(self, number, model_name, results):
"""Fügt Evaluation-Result hinzu."""
key = f"{number}_{model_name}"
self.evaluation_results[key] = results
def generate_summary_report(self):
"""Generiert Zusammenfassungs-Report."""
if not self.evaluation_results:
return "Keine Evaluation-Daten vorhanden"
report = []
report.append("=" * 80)
report.append("MODEL EVALUATION SUMMARY")
report.append("=" * 80)
report.append(f"Timestamp: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
report.append(f"Total Evaluations: {len(self.evaluation_results)}")
report.append("")
# Aggregate stats
all_test_r2 = []
all_cv_mean = []
overfit_count = 0
quality_dist = defaultdict(int)
for key, results in self.evaluation_results.items():
if 'test_r2' in results:
all_test_r2.append(results['test_r2'])
if 'cv_mean' in results:
all_cv_mean.append(results['cv_mean'])
if results.get('is_overfit'):
overfit_count += 1
if 'quality' in results:
quality_dist[results['quality']] += 1
# Overall Stats
report.append("OVERALL STATISTICS")
report.append("-" * 80)
if all_test_r2:
report.append(f"Test R² Score:")
report.append(f" Mean: {np.mean(all_test_r2):.4f}")
report.append(f" Median: {np.median(all_test_r2):.4f}")
report.append(f" Std: {np.std(all_test_r2):.4f}")
report.append(f" Min: {np.min(all_test_r2):.4f}")
report.append(f" Max: {np.max(all_test_r2):.4f}")
report.append("")
if all_cv_mean:
report.append(f"Cross-Validation R² Score:")
report.append(f" Mean: {np.mean(all_cv_mean):.4f}")
report.append(f" Std: {np.std(all_cv_mean):.4f}")
report.append("")
report.append(f"Overfitting Detection:")
report.append(f" Overfit Models: {overfit_count}/{len(self.evaluation_results)}")
report.append("")
report.append(f"Quality Distribution:")
for quality in ['Excellent', 'Good', 'Fair', 'Poor', 'Very Poor']:
count = quality_dist.get(quality, 0)
pct = (count / len(self.evaluation_results)) * 100 if self.evaluation_results else 0
report.append(f" {quality:12}: {count:3} ({pct:5.1f}%)")
report.append("")
# Top 10 Best Models
sorted_results = sorted(
[(k, v) for k, v in self.evaluation_results.items() if 'test_r2' in v],
key=lambda x: x[1]['test_r2'],
reverse=True
)[:10]
report.append("TOP 10 MODELS (by Test R²)")
report.append("-" * 80)
report.append(f"{'Number':<10} {'Model':<20} {'Test R²':<12} {'CV Mean':<12} {'Quality':<15}")
report.append("-" * 80)
for key, results in sorted_results:
number = results.get('number', 'N/A')
model = results.get('model_name', 'N/A')
test_r2 = results.get('test_r2', 0)
cv_mean = results.get('cv_mean', 0)
quality = results.get('quality', 'N/A')
report.append(f"{number:<10} {model:<20} {test_r2:<12.4f} {cv_mean:<12.4f} {quality:<15}")
report.append("")
report.append("=" * 80)
return "\n".join(report)
def save_evaluation(self):
"""Speichert Evaluation persistent."""
if not self.report_file:
return
try:
os.makedirs(os.path.dirname(self.report_file), exist_ok=True)
data = {
'timestamp': datetime.now().isoformat(),
'total_evaluations': len(self.evaluation_results),
'results': self.evaluation_results
}
with open(self.report_file, 'w') as f:
json.dump(data, f, indent=2)
print(f" 💾 Evaluation saved: {self.report_file}")
except Exception as e:
print(f" ⚠️ Could not save evaluation: {e}")
def load_evaluation(self):
"""Lädt gespeicherte Evaluation."""
if not self.report_file or not os.path.exists(self.report_file):
return
try:
with open(self.report_file, 'r') as f:
data = json.load(f)
self.evaluation_results = data.get('results', {})
timestamp = data.get('timestamp', 'Unknown')
print(f" 📂 Evaluation loaded: {len(self.evaluation_results)} results (from {timestamp[:10]})")
except Exception as e:
print(f" ⚠️ Could not load evaluation: {e}")
def print_summary(self):
"""Druckt Summary auf Console."""
summary = self.generate_summary_report()
print("\n" + summary)
+340
View File
@@ -0,0 +1,340 @@
#!/usr/bin/env python3
"""
Lotto 6aus49 Benachrichtigungs-System
Unterstützt:
- Telegram Bot Notifications
- Email Notifications (optional)
Konfiguration über config/notifications.json
"""
import os
import json
import requests
from typing import List, Dict, Any
class LottoNotifier:
"""Benachrichtigungs-System für Lotto 6aus49."""
def __init__(self, config_path: str = None):
"""
Initialisiert Notifier.
Args:
config_path: Pfad zur Konfigurationsdatei
"""
if config_path is None:
# Default: config/notifications.json im Projekt-Root
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(os.path.dirname(script_dir))
config_path = os.path.join(project_root, 'config', 'notifications.json')
self.config_path = config_path
self.config = self._load_config()
def _load_config(self) -> dict:
"""Lädt Konfiguration."""
if not os.path.exists(self.config_path):
print(f"⚠️ Konfigurationsdatei nicht gefunden: {self.config_path}")
return {
"telegram": {"enabled": False},
"email": {"enabled": False}
}
try:
with open(self.config_path, 'r') as f:
config = json.load(f)
return config
except Exception as e:
print(f"⚠️ Fehler beim Laden der Konfiguration: {e}")
return {
"telegram": {"enabled": False},
"email": {"enabled": False}
}
def send_tips_generated(self, tips: List[Dict], timestamp: str, best_tip: Dict):
"""
Sendet Benachrichtigung über generierte Tipps.
Args:
tips: Liste aller generierten Tipps
timestamp: Timestamp der Generierung
best_tip: Bester Tipp (höchste Confidence)
"""
if self.config['telegram']['enabled']:
self._send_telegram_tips(tips, timestamp, best_tip)
if self.config['email']['enabled']:
self._send_email_tips(tips, timestamp, best_tip)
def _send_telegram_tips(self, tips: List[Dict], timestamp: str, best_tip: Dict):
"""Sendet Telegram-Nachricht."""
try:
bot_token = self.config['telegram']['bot_token']
chat_id = self.config['telegram']['chat_id']
# Formatiere Nachricht
message = self._format_telegram_message(tips, timestamp, best_tip)
# Sende über Telegram Bot API
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
data = {
"chat_id": chat_id,
"text": message,
"parse_mode": "Markdown"
}
response = requests.post(url, data=data, timeout=10)
if response.status_code == 200:
print("✅ Telegram-Benachrichtigung gesendet")
else:
print(f"⚠️ Telegram-Fehler: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"⚠️ Telegram-Benachrichtigung fehlgeschlagen: {e}")
def _format_telegram_message(self, tips: List[Dict], timestamp: str, best_tip: Dict) -> str:
"""Formatiert Telegram-Nachricht."""
# Header
message = "🎲 *LOTTO 6AUS49 - NEUE TIPPS GENERIERT*\n"
message += "=" * 40 + "\n\n"
# Timestamp
message += f"📅 *Generiert:* {timestamp}\n"
message += f"📊 *Anzahl Tipps:* {len(tips)}\n\n"
# Bester Tipp
message += "⭐ *BESTER TIPP:*\n"
numbers_str = ' - '.join([f"{n:02d}" for n in best_tip['numbers']])
message += f"🎯 Zahlen: `{numbers_str}`\n"
message += f"🌟 Superzahl: `{best_tip['superzahl']}`\n"
message += f"📈 Confidence: `{best_tip['confidence']:.4f}`\n"
message += f"🎨 Strategie: `{best_tip['strategy']}`\n\n"
# Statistiken
avg_conf = sum(t['confidence'] for t in tips) / len(tips)
avg_qual = sum(t['quality'] for t in tips) / len(tips)
message += "📊 *STATISTIKEN:*\n"
message += f"🎯 Ø Confidence: `{avg_conf:.4f}`\n"
message += f"💎 Ø Quality: `{avg_qual:.4f}`\n\n"
# Top 3 Tipps
message += "🏆 *TOP 3 TIPPS:*\n"
sorted_tips = sorted(tips, key=lambda t: t['confidence'], reverse=True)[:3]
for i, tip in enumerate(sorted_tips, 1):
nums = ' - '.join([f"{n:02d}" for n in tip['numbers']])
message += f"{i}. `{nums}` + SZ `{tip['superzahl']}` "
message += f"({tip['confidence']:.3f})\n"
message += "\n🍀 *Viel Glück!*"
return message
def _send_email_tips(self, tips: List[Dict], timestamp: str, best_tip: Dict):
"""Sendet Email-Benachrichtigung."""
# TODO: Email-Versand implementieren wenn gewünscht
print("⚠️ Email-Benachrichtigung noch nicht implementiert")
def send_draw_results(self, draw: Dict, evaluation_summary: Dict, best_match: Dict):
"""
Sendet Benachrichtigung über Ziehungs-Ergebnisse und Tipp-Evaluation.
Args:
draw: Dict mit Ziehungsdaten (date, Z1-Z6, SZ)
evaluation_summary: Dict mit avg_main, sz_rate
best_match: Dict mit main_matches, sz_match
"""
if self.config['telegram']['enabled']:
self._send_telegram_draw_results(draw, evaluation_summary, best_match)
if self.config['email']['enabled']:
self._send_email_draw_results(draw, evaluation_summary, best_match)
def _send_telegram_draw_results(self, draw: Dict, evaluation_summary: Dict, best_match: Dict):
"""Sendet Telegram-Nachricht mit Ziehungsergebnissen."""
try:
bot_token = self.config['telegram']['bot_token']
chat_id = self.config['telegram']['chat_id']
# Formatiere Nachricht
message = self._format_draw_results_message(draw, evaluation_summary, best_match)
# Sende über Telegram Bot API
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
data = {
"chat_id": chat_id,
"text": message,
"parse_mode": "Markdown"
}
response = requests.post(url, data=data, timeout=10)
if response.status_code == 200:
print("✅ Telegram-Benachrichtigung gesendet")
else:
print(f"⚠️ Telegram-Fehler: {response.status_code}")
print(f" Response: {response.text}")
except Exception as e:
print(f"⚠️ Telegram-Benachrichtigung fehlgeschlagen: {e}")
def _format_draw_results_message(self, draw: Dict, evaluation_summary: Dict, best_match: Dict) -> str:
"""Formatiert Telegram-Nachricht für Ziehungsergebnisse."""
# Header
message = "🎲 *LOTTO 6AUS49 - ZIEHUNGSERGEBNISSE*\n"
message += "=" * 40 + "\n\n"
# Ziehungsdatum
draw_date = draw['date']
if hasattr(draw_date, 'strftime'):
date_str = draw_date.strftime('%d.%m.%Y')
else:
date_str = str(draw_date)
message += f"📅 *Ziehung vom:* {date_str}\n\n"
# Gezogene Zahlen
drawn_numbers = [draw.get(f'Z{i}') for i in range(1, 7)]
numbers_str = ' - '.join([f"{n:02d}" for n in drawn_numbers if n is not None])
message += f"🎯 *Gewinnzahlen:* `{numbers_str}`\n"
if draw.get('SZ') is not None:
message += f"🌟 *Superzahl:* `{draw['SZ']}`\n\n"
else:
message += "\n"
# Evaluation Summary
message += "📊 *TIPP-EVALUATION:*\n"
if evaluation_summary:
avg_main = evaluation_summary.get('avg_main', 0)
sz_rate = evaluation_summary.get('sz_rate', 0)
message += f"🎯 Ø Treffer Hauptzahlen: `{avg_main:.2f}`\n"
message += f"🌟 Superzahl-Rate: `{sz_rate*100:.1f}%`\n\n"
# Bester Tipp
message += "🏆 *BESTER TIPP:*\n"
if best_match:
main_matches = best_match.get('main_matches', 0)
sz_match = best_match.get('sz_match', False)
# Rating emoji
if main_matches == 6 and sz_match:
rating = "🏆 JACKPOT!"
elif main_matches == 6:
rating = "💰 Klasse 2"
elif main_matches == 5 and sz_match:
rating = "💰 Klasse 3"
elif main_matches == 5:
rating = "💰 Klasse 4"
elif main_matches == 4:
rating = "💵 Klasse 6"
elif main_matches == 3:
rating = "✅ Klasse 8"
elif main_matches >= 2:
rating = "👍 OK"
else:
rating = "⚪ Niedrig"
sz_indicator = "✅" if sz_match else "⚪"
message += f"🎯 Treffer Hauptzahlen: `{main_matches}/6`\n"
message += f"🌟 Superzahl: {sz_indicator}\n"
message += f"📈 Bewertung: {rating}\n"
message += "\n🔄 *System wurde aktualisiert und trainiert!*"
return message
def _send_email_draw_results(self, draw: Dict, evaluation_summary: Dict, best_match: Dict):
"""Sendet Email-Benachrichtigung."""
# TODO: Email-Versand implementieren wenn gewünscht
print("⚠️ Email-Benachrichtigung noch nicht implementiert")
def send_test_notification(self):
"""Sendet Test-Benachrichtigung."""
if self.config['telegram']['enabled']:
try:
bot_token = self.config['telegram']['bot_token']
chat_id = self.config['telegram']['chat_id']
message = "🧪 *LOTTO 6AUS49 TEST*\n\n"
message += "✅ Benachrichtigungs-System funktioniert!\n"
message += f"📅 {os.popen('date').read().strip()}"
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
data = {
"chat_id": chat_id,
"text": message,
"parse_mode": "Markdown"
}
response = requests.post(url, data=data, timeout=10)
if response.status_code == 200:
print("✅ Test-Benachrichtigung erfolgreich gesendet")
return True
else:
print(f"❌ Fehler: {response.status_code}")
print(f" Response: {response.text}")
return False
except Exception as e:
print(f"❌ Test fehlgeschlagen: {e}")
return False
else:
print("⚠️ Telegram nicht aktiviert in config/notifications.json")
return False
def main():
"""Test-Funktion."""
import argparse
parser = argparse.ArgumentParser(description="Lotto 6aus49 Notifier Test")
parser.add_argument(
'--test',
action='store_true',
help='Sende Test-Benachrichtigung'
)
parser.add_argument(
'--config',
type=str,
help='Pfad zur Config-Datei'
)
args = parser.parse_args()
notifier = LottoNotifier(config_path=args.config)
if args.test:
notifier.send_test_notification()
else:
# Beispiel-Tipps
example_tips = [
{
'numbers': [7, 14, 21, 28, 35, 42],
'superzahl': 3,
'confidence': 0.7234,
'quality': 0.6891,
'strategy': 'PURE-AI'
},
{
'numbers': [2, 11, 19, 27, 36, 45],
'superzahl': 7,
'confidence': 0.6978,
'quality': 0.6543,
'strategy': 'HYBRID-OPT'
}
]
best = example_tips[0]
notifier.send_tips_generated(example_tips, "2024-11-27 16:00", best)
if __name__ == "__main__":
main()
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""
Einfacher Lotto 6aus49 Data Updater
Manuelle Eingabe oder CSV-Import von neuen Ziehungen.
Perfekt als Fallback wenn Web-Scraping oder APIs nicht funktionieren.
"""
import pandas as pd
from datetime import datetime
import shutil
import os
def load_existing_data(data_file):
"""Lädt existierende Daten."""
print("📂 Lade existierende Daten...")
if not os.path.exists(data_file):
print(" ⚠️ Datei existiert nicht - erstelle neue")
return pd.DataFrame(columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ'])
df = pd.read_csv(data_file, sep=';')
df['datum'] = pd.to_datetime(df['datum'], format='%Y-%m-%d', errors='coerce')
print(f" ✅ {len(df)} Ziehungen geladen")
if len(df) > 0:
print(f" 📅 Neueste: {df['datum'].max().strftime('%Y-%m-%d')}")
return df
def create_backup(data_file):
"""Erstellt Backup."""
if not os.path.exists(data_file):
return
print("\n💾 Erstelle Backup...")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
# Backup im Lotto/data/backups Verzeichnis
parent_dir = os.path.dirname(data_file)
lotto_dir = os.path.join(parent_dir, "Lotto")
backup_dir = os.path.join(lotto_dir, "data", "backups")
os.makedirs(backup_dir, exist_ok=True)
backup_file = os.path.join(backup_dir, f"{os.path.basename(data_file)}.backup_{timestamp}")
shutil.copy2(data_file, backup_file)
print(f" ✅ Backup: {os.path.basename(backup_file)}")
def manual_input_mode():
"""Manuelle Eingabe von Ziehungen."""
print("\n⌨️ MANUELLE EINGABE")
print("=" * 70)
print("Format: YYYY-MM-DD Z1 Z2 Z3 Z4 Z5 Z6 SZ")
print("Beispiel: 2025-01-22 7 14 21 28 35 42 3")
print("Leer lassen zum Beenden.\n")
draws = []
while True:
user_input = input(f"Ziehung {len(draws) + 1}: ").strip()
if not user_input:
break
try:
parts = user_input.split()
if len(parts) != 8:
print(" ❌ Ungültiges Format. Bitte 8 Werte eingeben.")
continue
date_obj = datetime.strptime(parts[0], '%Y-%m-%d')
numbers = [int(parts[i]) for i in range(1, 7)]
superzahl = int(parts[7])
# Validierung
if not all(1 <= n <= 49 for n in numbers):
print(" ❌ Hauptzahlen müssen zwischen 1 und 49 liegen.")
continue
if not 0 <= superzahl <= 9:
print(" ❌ Superzahl muss zwischen 0 und 9 liegen.")
continue
if len(set(numbers)) != 6:
print(" ❌ Hauptzahlen müssen eindeutig sein.")
continue
# Sortiere Zahlen
numbers_sorted = sorted(numbers)
draws.append({
'datum': date_obj,
'Z1': numbers_sorted[0],
'Z2': numbers_sorted[1],
'Z3': numbers_sorted[2],
'Z4': numbers_sorted[3],
'Z5': numbers_sorted[4],
'Z6': numbers_sorted[5],
'SZ': superzahl
})
print(f" ✅ Hinzugefügt: {date_obj.strftime('%Y-%m-%d')} | "
f"{'-'.join(map(str, numbers_sorted))} | SZ: {superzahl}")
except ValueError as e:
print(f" ❌ Fehler: {e}")
continue
return draws
def csv_import_mode():
"""CSV-Import von Ziehungen."""
print("\n📁 CSV-IMPORT")
print("=" * 70)
print("CSV-Format: datum;Z1;Z2;Z3;Z4;Z5;Z6;SZ")
print("Beispiel: 2025-01-22;7;14;21;28;35;42;3\n")
csv_file = input("Pfad zur CSV-Datei: ").strip()
if not os.path.exists(csv_file):
print(f" ❌ Datei nicht gefunden: {csv_file}")
return []
try:
df_import = pd.read_csv(csv_file, sep=';')
df_import['datum'] = pd.to_datetime(df_import['datum'], format='%Y-%m-%d', errors='coerce')
draws = df_import.to_dict('records')
print(f" ✅ {len(draws)} Ziehungen aus CSV geladen")
# Zeige erste 3
for i, draw in enumerate(draws[:3]):
nums = f"{draw['Z1']}-{draw['Z2']}-{draw['Z3']}-{draw['Z4']}-{draw['Z5']}-{draw['Z6']}"
print(f" {i+1}. {draw['datum'].strftime('%Y-%m-%d')} | {nums} | SZ: {draw['SZ']}")
if len(draws) > 3:
print(f" ... und {len(draws) - 3} weitere")
return draws
except Exception as e:
print(f" ❌ Fehler beim Laden: {e}")
return []
def merge_and_save(df_existing, new_draws, data_file):
"""Merged und speichert Daten."""
if not new_draws:
print("\n⚠️ Keine neuen Ziehungen zum Hinzufügen")
return False
print(f"\n🔀 Merge {len(new_draws)} neue Ziehung(en)...")
df_new = pd.DataFrame(new_draws)
df_combined = pd.concat([df_existing, df_new], ignore_index=True)
# Duplikate entfernen
before = len(df_combined)
df_combined = df_combined.drop_duplicates(subset=['datum'], keep='first')
after = len(df_combined)
if before - after > 0:
print(f" 🗑️ {before - after} Duplikat(e) entfernt")
# Sortieren
df_combined = df_combined.sort_values('datum', ascending=True).reset_index(drop=True)
# Speichern
print(f"\n💾 Speichere {len(df_combined)} Ziehungen...")
df_export = df_combined.copy()
df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d')
df_export.to_csv(data_file, sep=';', index=False)
print(f" ✅ Gespeichert: {data_file}")
print(f" 📊 Vorher: {len(df_existing)} | Neu: {len(df_combined) - len(df_existing)} | Gesamt: {len(df_combined)}")
return True
def main():
"""Hauptfunktion."""
print()
print("=" * 70)
print(" LOTTO 6AUS49 SIMPLE UPDATER")
print(" Manuelle Eingabe oder CSV-Import")
print("=" * 70)
print()
# Datei-Pfad
default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv"
print(f"Standard-Datei: {default_file}")
use_default = input("\nStandard verwenden? (j/n): ").strip().lower()
if use_default in ['j', 'ja', 'y', 'yes', '']:
data_file = default_file
else:
data_file = input("Pfad zur Datei: ").strip()
print()
# Lade existierende Daten
df_existing = load_existing_data(data_file)
# Backup
create_backup(data_file)
# Eingabe-Modus wählen
print("\n📝 EINGABE-MODUS")
print("=" * 70)
print("1. Manuelle Eingabe (einzelne Ziehungen)")
print("2. CSV-Import (mehrere Ziehungen)")
print()
mode = input("Wahl (1/2): ").strip()
if mode == '2':
new_draws = csv_import_mode()
else:
new_draws = manual_input_mode()
# Merge und speichern
success = merge_and_save(df_existing, new_draws, data_file)
print()
print("=" * 70)
if success:
print("✅ UPDATE ERFOLGREICH")
else:
print("⚠️ KEINE ÄNDERUNGEN")
print("=" * 70)
if __name__ == "__main__":
main()
+496
View File
@@ -0,0 +1,496 @@
#!/usr/bin/env python3
"""
Lotto 6aus49 API Data Updater
Lädt Lotto-Ziehungen von verschiedenen APIs:
1. lottoAPI (https://lottoapi.herokuapp.com)
2. Lotto.de API
3. Fallback Quellen
Features:
- Automatisches Laden von APIs
- Fallback auf andere APIs wenn primäre fehlschlägt
- Duplikate-Vermeidung
- Automatisches Backup
"""
import requests
import pandas as pd
from datetime import datetime
import shutil
import os
from typing import List, Dict, Optional
import time
class LottoAPIUpdater:
"""Updater für Lotto 6aus49 über APIs."""
def __init__(self, data_file: str):
self.data_file = data_file
self.backup_file = None
self.df_existing = None
# API-Endpoints
self.apis = {
'github': {
'url': 'https://johannesfriedrich.github.io/LottoNumberArchive/Lottonumbers_tidy_complete.json',
'name': 'GitHub Lotto Archive',
'parser': self._parse_github_archive
},
'lottoapi': {
'url': 'https://lottoapi.herokuapp.com/lotto/6aus49/100',
'name': 'lottoAPI (Backup)',
'parser': self._parse_lottoapi
}
}
print("🔄 LOTTO 6AUS49 API UPDATER")
print("=" * 70)
print(f"📁 Datei: {os.path.basename(data_file)}")
print("=" * 70)
def load_existing_data(self) -> bool:
"""Lädt existierende Daten."""
print("\n📂 Lade existierende Daten...")
try:
if not os.path.exists(self.data_file):
print(f" ⚠️ Datei existiert nicht - erstelle neue")
self.df_existing = pd.DataFrame(
columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
)
return True
self.df_existing = pd.read_csv(self.data_file, sep=';')
# Konvertiere Datum
if 'datum' in self.df_existing.columns:
self.df_existing['datum'] = pd.to_datetime(
self.df_existing['datum'],
format='%Y-%m-%d',
errors='coerce'
)
print(f" ✅ {len(self.df_existing)} Ziehungen geladen")
if len(self.df_existing) > 0:
latest = self.df_existing['datum'].max()
oldest = self.df_existing['datum'].min()
print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")
return True
except Exception as e:
print(f" ❌ Fehler: {e}")
return False
def create_backup(self) -> bool:
"""Erstellt Backup."""
if not os.path.exists(self.data_file):
return True
try:
print("\n💾 Erstelle Backup...")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_dir = os.path.join(os.path.dirname(self.data_file), "data", "backups")
os.makedirs(backup_dir, exist_ok=True)
filename = os.path.basename(self.data_file)
self.backup_file = os.path.join(backup_dir, f"{filename}.backup_{timestamp}")
shutil.copy2(self.data_file, self.backup_file)
print(f" ✅ Backup: {os.path.basename(self.backup_file)}")
return True
except Exception as e:
print(f" ⚠️ Backup-Fehler: {e}")
return False
def fetch_from_api(self, api_name: str) -> List[Dict]:
"""
Holt Daten von spezifischer API.
Args:
api_name: Name der API ('lottoapi')
Returns:
Liste von Ziehungen
"""
if api_name not in self.apis:
print(f" ❌ Unbekannte API: {api_name}")
return []
api = self.apis[api_name]
print(f"\n🌐 Versuche {api['name']}...")
print(f" URL: {api['url']}")
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
response = requests.get(api['url'], headers=headers, timeout=15)
response.raise_for_status()
print(f" ✅ Antwort erhalten ({len(response.content)} bytes)")
# Parse mit spezifischem Parser
draws = api['parser'](response.json())
if draws:
print(f" ✅ {len(draws)} Ziehungen extrahiert")
else:
print(f" ⚠️ Keine Ziehungen extrahiert")
return draws
except requests.RequestException as e:
print(f" ❌ Netzwerkfehler: {e}")
return []
except Exception as e:
print(f" ❌ Fehler: {e}")
return []
def _parse_github_archive(self, data: List[Dict]) -> List[Dict]:
"""
Parst GitHub Lotto Archive Response.
Expected format:
[
{
"id": 4963,
"date": "26.11.2025",
"variable": "Lottozahl",
"value": 2
},
...
]
"""
draws = []
try:
# Gruppiere nach ID (jede ID = eine Ziehung)
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 unserem Format
for draw_id, draw_data in draws_by_id.items():
try:
# Parse Datum (Format: DD.MM.YYYY)
date_str = draw_data['date']
day, month, year = date_str.split('.')
date_obj = datetime(int(year), int(month), int(day))
# Sortiere Zahlen
numbers = sorted(draw_data['numbers'])
# Validierung
if len(numbers) != 6:
continue
superzahl = draw_data['superzahl']
if superzahl is None:
continue
draw = {
'datum': date_obj,
'Z1': numbers[0],
'Z2': numbers[1],
'Z3': numbers[2],
'Z4': numbers[3],
'Z5': numbers[4],
'Z6': numbers[5],
'SZ': superzahl
}
if self._validate_draw(draw):
draws.append(draw)
except Exception as e:
continue
except Exception as e:
print(f" ⚠️ Parse-Fehler: {e}")
return draws
def _parse_lottoapi(self, data: List[Dict]) -> List[Dict]:
"""
Parst lottoAPI Response.
Expected format:
[
{
"date": "2025-01-22",
"numbers": [3, 9, 12, 24, 39, 45],
"superzahl": 7
},
...
]
"""
draws = []
try:
for item in data:
# Datum
date_str = item.get('date')
if not date_str:
continue
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
# Hauptzahlen (6 Zahlen)
numbers = sorted(item.get('numbers', []))
if len(numbers) != 6:
continue
# Superzahl
superzahl = item.get('superzahl')
if superzahl is None:
continue
draw = {
'datum': date_obj,
'Z1': numbers[0],
'Z2': numbers[1],
'Z3': numbers[2],
'Z4': numbers[3],
'Z5': numbers[4],
'Z6': numbers[5],
'SZ': superzahl
}
if self._validate_draw(draw):
draws.append(draw)
except Exception as e:
print(f" ⚠️ Parse-Fehler: {e}")
return draws
def _validate_draw(self, draw: Dict) -> bool:
"""Validiert eine Ziehung."""
try:
required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
if not all(field in draw for field in required_fields):
return False
# Hauptzahlen (1-49)
main_numbers = [draw[f'Z{i}'] for i in range(1, 7)]
if not all(isinstance(n, int) and 1 <= n <= 49 for n in main_numbers):
return False
if len(set(main_numbers)) != 6:
return False
# Superzahl (0-9)
if not isinstance(draw['SZ'], int) or not 0 <= draw['SZ'] <= 9:
return False
if not isinstance(draw['datum'], datetime):
return False
return True
except Exception:
return False
def fetch_from_all_apis(self) -> List[Dict]:
"""
Versucht alle APIs nacheinander.
Returns:
Liste von Ziehungen
"""
print("\n🔍 SUCHE NACH DATEN VON APIs")
print("=" * 70)
all_draws = []
# Versuche alle APIs (GitHub zuerst, da zuverlässig)
for api_name in ['github', 'lottoapi']:
draws = self.fetch_from_api(api_name)
if draws:
all_draws.extend(draws)
print(f" ✅ {len(draws)} Ziehungen von {self.apis[api_name]['name']}")
break # Erste erfolgreiche API nutzen
else:
print(f" ⏭️ Weiter zur nächsten API...")
time.sleep(1) # Pause zwischen APIs
if not all_draws:
print("\n ❌ Keine Daten von APIs erhalten")
print(" 💡 Tipp: Verwende simple_update.py für manuelle Eingabe")
return all_draws
def merge_with_existing(self, new_draws: List[Dict]) -> pd.DataFrame:
"""Merged neue Ziehungen mit existierenden Daten."""
print(f"\n🔀 Merge mit existierenden Daten...")
if not new_draws:
print(" ⚠️ Keine neuen Ziehungen zum Mergen")
return self.df_existing
df_new = pd.DataFrame(new_draws)
if self.df_existing is None or len(self.df_existing) == 0:
df_combined = df_new
print(f" ✅ Neue Datei erstellt mit {len(df_combined)} Ziehungen")
else:
df_combined = pd.concat([self.df_existing, df_new], ignore_index=True)
before_dedup = len(df_combined)
df_combined = df_combined.drop_duplicates(subset=['datum'], keep='first')
after_dedup = len(df_combined)
duplicates = before_dedup - after_dedup
if duplicates > 0:
print(f" 🗑️ {duplicates} Duplikat(e) entfernt")
df_combined = df_combined.sort_values('datum', ascending=True)
df_combined = df_combined.reset_index(drop=True)
new_entries = len(df_combined) - (len(self.df_existing) if self.df_existing is not None else 0)
print(f" ✅ Merge abgeschlossen:")
print(f" Vorher: {len(self.df_existing) if self.df_existing is not None else 0} Ziehungen")
print(f" Neue: {new_entries} Ziehungen")
print(f" Gesamt: {len(df_combined)} Ziehungen")
return df_combined
def save_data(self, df: pd.DataFrame) -> bool:
"""Speichert Daten."""
try:
print(f"\n💾 Speichere Daten...")
df_export = df.copy()
df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d')
column_order = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
df_export = df_export[column_order]
df_export.to_csv(self.data_file, sep=';', index=False)
print(f" ✅ Gespeichert: {self.data_file}")
print(f" 📊 {len(df)} Ziehungen total")
if len(df) > 0:
latest = df['datum'].max()
oldest = df['datum'].min()
print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")
return True
except Exception as e:
print(f" ❌ Speicherfehler: {e}")
if self.backup_file and os.path.exists(self.backup_file):
print(" 🔄 Stelle Backup wieder her...")
shutil.copy2(self.backup_file, self.data_file)
print(" ✅ Backup wiederhergestellt")
return False
def update(self, api_name: str = 'all', create_backup: bool = True) -> bool:
"""
Führt komplettes Update durch.
Args:
api_name: 'all' oder 'lottoapi'
create_backup: Backup erstellen
Returns:
True bei Erfolg
"""
print("\n🚀 STARTE UPDATE")
print("=" * 70)
if not self.load_existing_data():
return False
if create_backup:
self.create_backup()
if api_name == 'all':
new_draws = self.fetch_from_all_apis()
else:
new_draws = self.fetch_from_api(api_name)
if not new_draws:
print("\n❌ Keine Daten von APIs geladen")
print("💡 Tipp: Verwende simple_update.py für manuelle Eingabe")
return False
df_updated = self.merge_with_existing(new_draws)
success = self.save_data(df_updated)
print("\n" + "=" * 70)
if success:
print("✅ UPDATE ERFOLGREICH ABGESCHLOSSEN")
else:
print("❌ UPDATE FEHLGESCHLAGEN")
print("=" * 70)
return success
def main():
"""Hauptfunktion."""
import sys
print()
print("=" * 70)
print(" LOTTO 6AUS49 API UPDATER")
print(" Unterstützt: GitHub Lotto Archive, lottoAPI")
print("=" * 70)
print()
default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv"
if len(sys.argv) > 1:
data_file = sys.argv[1]
api_name = sys.argv[2] if len(sys.argv) > 2 else 'all'
else:
print(f"Standard-Datei: {default_file}")
use_default = input("\nStandard verwenden? (j/n): ").strip().lower()
if use_default in ['j', 'ja', 'y', 'yes', '']:
data_file = default_file
else:
data_file = input("Pfad zur Datei: ").strip()
api_name = 'all'
print()
updater = LottoAPIUpdater(data_file)
success = updater.update(api_name=api_name)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+499
View File
@@ -0,0 +1,499 @@
#!/usr/bin/env python3
"""
Lotto 6aus49 Data Updater für lotto.de
Web-Scraper für https://www.lotto.de/lotto-6aus49/lottozahlen
Features:
- Automatisches Scraping aller historischen Ziehungen
- Duplikate-Vermeidung
- Automatisches Backup vor Update
- Validierung der Daten
- Fortschrittsanzeige
"""
import pandas as pd
import requests
from bs4 import BeautifulSoup
from datetime import datetime
import shutil
import os
from typing import List, Dict, Optional
import time
class LottoWebUpdater:
"""Updater für lotto.de Web-Scraping"""
def __init__(self, data_file: str):
self.data_file = data_file
self.url = "https://www.lotto.de/lotto-6aus49/lottozahlen"
self.backup_file = None
self.df_existing = None
print("🔄 LOTTO 6AUS49 WEB UPDATER")
print("=" * 70)
print(f"📁 Datei: {os.path.basename(data_file)}")
print(f"🌐 Quelle: {self.url}")
print("=" * 70)
def load_existing_data(self) -> bool:
"""Lädt existierende Daten."""
print("\n📂 Lade existierende Daten...")
try:
if not os.path.exists(self.data_file):
print(f" ⚠️ Datei existiert nicht - erstelle neue")
self.df_existing = pd.DataFrame(
columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
)
return True
self.df_existing = pd.read_csv(self.data_file, sep=';')
# Konvertiere Datum
if 'datum' in self.df_existing.columns:
self.df_existing['datum'] = pd.to_datetime(
self.df_existing['datum'],
format='%Y-%m-%d',
errors='coerce'
)
print(f" ✅ {len(self.df_existing)} Ziehungen geladen")
if len(self.df_existing) > 0:
latest = self.df_existing['datum'].max()
oldest = self.df_existing['datum'].min()
print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")
return True
except Exception as e:
print(f" ❌ Fehler: {e}")
return False
def create_backup(self) -> bool:
"""Erstellt Backup."""
if not os.path.exists(self.data_file):
return True
try:
print("\n💾 Erstelle Backup...")
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
parent_dir = os.path.dirname(self.data_file)
lotto_dir = os.path.join(parent_dir, "Lotto")
backup_dir = os.path.join(lotto_dir, "data", "backups")
os.makedirs(backup_dir, exist_ok=True)
filename = os.path.basename(self.data_file)
self.backup_file = os.path.join(backup_dir, f"{filename}.backup_{timestamp}")
shutil.copy2(self.data_file, self.backup_file)
print(f" ✅ Backup: {os.path.basename(self.backup_file)}")
return True
except Exception as e:
print(f" ⚠️ Backup-Fehler: {e}")
return False
def fetch_draws_from_web(self, max_pages: int = 5) -> List[Dict]:
"""
Scrapt Ziehungen von lotto.de
Args:
max_pages: Maximale Anzahl Seiten (jede Seite ~20 Ziehungen)
Returns:
Liste von Ziehungen
"""
print(f"\n🌐 Lade Daten von {self.url}...")
print(f" 📄 Lade bis zu {max_pages} Seiten...")
all_draws = []
try:
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
}
for page in range(1, max_pages + 1):
url = f"{self.url}?page={page}" if page > 1 else self.url
print(f"\n 📄 Seite {page}/{max_pages}...")
response = requests.get(url, headers=headers, timeout=15)
response.raise_for_status()
soup = BeautifulSoup(response.content, 'html.parser')
# Finde Ziehungen auf dieser Seite
page_draws = self._extract_draws_from_page(soup, page)
if not page_draws:
print(f" ⚠️ Keine Ziehungen gefunden - Abbruch")
break
all_draws.extend(page_draws)
print(f" ✅ {len(page_draws)} Ziehungen extrahiert")
# Rate limiting
if page < max_pages:
time.sleep(1)
print(f"\n ✅ Gesamt: {len(all_draws)} Ziehungen von {page} Seite(n)")
return all_draws
except requests.RequestException as e:
print(f" ❌ Netzwerkfehler: {e}")
return all_draws # Return was bisher geladen wurde
except Exception as e:
print(f" ❌ Fehler: {e}")
return all_draws
def _extract_draws_from_page(self, soup: BeautifulSoup, page_num: int) -> List[Dict]:
"""
Extrahiert Ziehungen von einer Seite.
Args:
soup: BeautifulSoup Objekt
page_num: Seitennummer
Returns:
Liste von Ziehungen
"""
draws = []
try:
# Verschiedene Selektoren versuchen
# 1. Versuche mit data-attribute
result_divs = soup.find_all('div', class_=lambda x: x and 'result' in x.lower())
if not result_divs:
# 2. Versuche table
result_divs = soup.find_all('tr', class_=lambda x: x and ('draw' in x.lower() or 'result' in x.lower()))
if not result_divs:
# 3. Versuche generische Container
result_divs = soup.find_all('div', class_=lambda x: x and 'drawing' in x.lower())
print(f" 🔍 {len(result_divs)} potentielle Ergebnis-Container gefunden")
for i, elem in enumerate(result_divs):
try:
# Extrahiere Datum
date_elem = elem.find('time') or elem.find(class_=lambda x: x and 'date' in x.lower())
if not date_elem:
# Versuche direkten Text
date_text = self._extract_date_text(elem.get_text())
else:
date_text = date_elem.get_text().strip()
date_obj = self._parse_date(date_text)
# Extrahiere Zahlen - verschiedene Ansätze
numbers = self._extract_numbers(elem)
if len(numbers) < 7: # Mindestens 6 Hauptzahlen + 1 Superzahl
continue
main_numbers = sorted(numbers[:6])
superzahl = numbers[6]
draw = {
'datum': date_obj,
'Z1': main_numbers[0],
'Z2': main_numbers[1],
'Z3': main_numbers[2],
'Z4': main_numbers[3],
'Z5': main_numbers[4],
'Z6': main_numbers[5],
'SZ': superzahl
}
if self._validate_draw(draw):
draws.append(draw)
else:
print(f" ⚠️ Element {i+1}: Validierung fehlgeschlagen")
except Exception as e:
continue
except Exception as e:
print(f" ⚠️ Fehler beim Extrahieren: {e}")
return draws
def _extract_date_text(self, text: str) -> str:
"""Extrahiert Datum aus Text."""
import re
# Suche nach Datum im Format DD.MM.YYYY oder YYYY-MM-DD
patterns = [
r'(\d{2}\.\d{2}\.\d{4})',
r'(\d{4}-\d{2}-\d{2})',
r'(\d{1,2}\.\s*\w+\s*\d{4})' # z.B. "22. Januar 2025"
]
for pattern in patterns:
match = re.search(pattern, text)
if match:
return match.group(1)
return text.strip()
def _extract_numbers(self, elem) -> List[int]:
"""Extrahiert Zahlen aus Element."""
numbers = []
# 1. Versuche mit span/div mit Klassen wie 'ball', 'number', etc.
number_elems = elem.find_all(class_=lambda x: x and any(c in x.lower() for c in ['ball', 'number', 'zahl']))
if number_elems:
for num_elem in number_elems:
try:
text = num_elem.get_text().strip()
# Extrahiere nur Zahlen
import re
nums = re.findall(r'\d+', text)
if nums:
numbers.append(int(nums[0]))
except:
continue
# 2. Fallback: Alle Zahlen aus Text extrahieren
if len(numbers) < 7:
import re
text = elem.get_text()
# Finde alle Zahlen
all_nums = re.findall(r'\b(\d+)\b', text)
# Filtere plausible Lotto-Zahlen (1-49 für Hauptzahlen, 0-9 für Superzahl)
plausible = []
for n in all_nums:
num = int(n)
if 1 <= num <= 49:
plausible.append(num)
elif 0 <= num <= 9 and len(plausible) >= 6:
# Könnte Superzahl sein
plausible.append(num)
if len(plausible) >= 7:
numbers = plausible[:7]
return numbers
def _parse_date(self, date_str: str) -> datetime:
"""Parst Datum."""
import re
# Clean up
date_str = date_str.strip()
# Format 1: DD.MM.YYYY
try:
return datetime.strptime(date_str, '%d.%m.%Y')
except ValueError:
pass
# Format 2: YYYY-MM-DD
try:
return datetime.strptime(date_str, '%Y-%m-%d')
except ValueError:
pass
# Format 3: DD. MONAT YYYY (z.B. "22. Januar 2025")
months_de = {
'januar': 1, 'februar': 2, 'märz': 3, 'april': 4,
'mai': 5, 'juni': 6, 'juli': 7, 'august': 8,
'september': 9, 'oktober': 10, 'november': 11, 'dezember': 12
}
match = re.search(r'(\d{1,2})\.\s*(\w+)\s*(\d{4})', date_str.lower())
if match:
day = int(match.group(1))
month_str = match.group(2)
year = int(match.group(3))
if month_str in months_de:
return datetime(year, months_de[month_str], day)
# Fallback: Aktuelles Datum
print(f" ⚠️ Konnte Datum nicht parsen: {date_str}")
return datetime.now()
def _validate_draw(self, draw: Dict) -> bool:
"""Validiert eine Ziehung."""
try:
required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
if not all(field in draw for field in required_fields):
return False
# Hauptzahlen (1-49)
main_numbers = [draw[f'Z{i}'] for i in range(1, 7)]
if not all(isinstance(n, int) and 1 <= n <= 49 for n in main_numbers):
return False
if len(set(main_numbers)) != 6:
return False
# Superzahl (0-9)
if not isinstance(draw['SZ'], int) or not 0 <= draw['SZ'] <= 9:
return False
if not isinstance(draw['datum'], datetime):
return False
return True
except Exception:
return False
def merge_with_existing(self, new_draws: List[Dict]) -> pd.DataFrame:
"""Merged neue Ziehungen mit existierenden Daten."""
print(f"\n🔀 Merge mit existierenden Daten...")
if not new_draws:
print(" ⚠️ Keine neuen Ziehungen zum Mergen")
return self.df_existing
df_new = pd.DataFrame(new_draws)
if self.df_existing is None or len(self.df_existing) == 0:
df_combined = df_new
print(f" ✅ Neue Datei erstellt mit {len(df_combined)} Ziehungen")
else:
df_combined = pd.concat([self.df_existing, df_new], ignore_index=True)
before_dedup = len(df_combined)
df_combined = df_combined.drop_duplicates(subset=['datum'], keep='first')
after_dedup = len(df_combined)
duplicates = before_dedup - after_dedup
if duplicates > 0:
print(f" 🗑️ {duplicates} Duplikat(e) entfernt")
df_combined = df_combined.sort_values('datum', ascending=True)
df_combined = df_combined.reset_index(drop=True)
new_entries = len(df_combined) - (len(self.df_existing) if self.df_existing is not None else 0)
print(f" ✅ Merge abgeschlossen:")
print(f" Vorher: {len(self.df_existing) if self.df_existing is not None else 0} Ziehungen")
print(f" Neue: {new_entries} Ziehungen")
print(f" Gesamt: {len(df_combined)} Ziehungen")
return df_combined
def save_data(self, df: pd.DataFrame) -> bool:
"""Speichert Daten."""
try:
print(f"\n💾 Speichere Daten...")
df_export = df.copy()
df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d')
column_order = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'Z6', 'SZ']
df_export = df_export[column_order]
df_export.to_csv(self.data_file, sep=';', index=False)
print(f" ✅ Gespeichert: {self.data_file}")
print(f" 📊 {len(df)} Ziehungen total")
if len(df) > 0:
latest = df['datum'].max()
oldest = df['datum'].min()
print(f" 📅 Zeitraum: {oldest.strftime('%Y-%m-%d')} bis {latest.strftime('%Y-%m-%d')}")
return True
except Exception as e:
print(f" ❌ Speicherfehler: {e}")
if self.backup_file and os.path.exists(self.backup_file):
print(" 🔄 Stelle Backup wieder her...")
shutil.copy2(self.backup_file, self.data_file)
print(" ✅ Backup wiederhergestellt")
return False
def update(self, max_pages: int = 5, create_backup: bool = True) -> bool:
"""
Führt komplettes Update durch.
Args:
max_pages: Maximale Anzahl Seiten zum Scrapen
create_backup: Backup vor Update erstellen
Returns:
True bei Erfolg
"""
print("\n🚀 STARTE UPDATE")
print("=" * 70)
if not self.load_existing_data():
return False
if create_backup:
self.create_backup()
new_draws = self.fetch_draws_from_web(max_pages=max_pages)
if not new_draws:
print("\n❌ Keine Daten von Website geladen")
print("💡 Tipp: Verwende simple_update.py für manuelle Eingabe")
return False
df_updated = self.merge_with_existing(new_draws)
success = self.save_data(df_updated)
print("\n" + "=" * 70)
if success:
print("✅ UPDATE ERFOLGREICH ABGESCHLOSSEN")
else:
print("❌ UPDATE FEHLGESCHLAGEN")
print("=" * 70)
return success
def main():
"""Hauptfunktion."""
import sys
print()
print("=" * 70)
print(" LOTTO 6AUS49 WEB UPDATER")
print(" Quelle: lotto.de")
print("=" * 70)
print()
default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data/AlleLottozahlen.csv"
if len(sys.argv) > 1:
data_file = sys.argv[1]
max_pages = int(sys.argv[2]) if len(sys.argv) > 2 else 5
else:
print(f"Standard-Datei: {default_file}")
use_default = input("\nStandard verwenden? (j/n): ").strip().lower()
if use_default in ['j', 'ja', 'y', 'yes', '']:
data_file = default_file
else:
data_file = input("Pfad zur Datei: ").strip()
pages_input = input("\nAnzahl Seiten laden (1-10, default=5): ").strip()
max_pages = int(pages_input) if pages_input else 5
print()
updater = LottoWebUpdater(data_file)
success = updater.update(max_pages=max_pages)
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+460
View File
@@ -0,0 +1,460 @@
#!/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()
+401
View File
@@ -0,0 +1,401 @@
#!/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()