Initial commit: Eurojackpot analysis and prediction system
This repository contains a comprehensive Eurojackpot lottery analysis and prediction system including: - Historical data analysis and processing - ML-based prediction models - Automated weekly tip generation - Position and range analysis tools - Notification system for results 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
commit
70e0638dee
67 files changed
+8491869
No files matched your search
@@ -0,0 +1,188 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Bereichskombinationen-Analyse
|
||||
|
||||
Analysiert die Kombinationen von Zahlenbereichen in den gezogenen 5er-Kombinationen.
|
||||
Focus auf Z1-Z4 wie gewünscht.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from collections import Counter, defaultdict
|
||||
|
||||
def get_range_for_number(number):
|
||||
"""Bestimmt den Bereich für eine gegebene Zahl."""
|
||||
if 1 <= number <= 10:
|
||||
return 'A(1-10)'
|
||||
elif 11 <= number <= 20:
|
||||
return 'B(11-20)'
|
||||
elif 21 <= number <= 30:
|
||||
return 'C(21-30)'
|
||||
elif 31 <= number <= 40:
|
||||
return 'D(31-40)'
|
||||
elif 41 <= number <= 50:
|
||||
return 'E(41-50)'
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
def analyze_range_combinations():
|
||||
"""Analysiert Bereichskombinationen in Eurojackpot-Ziehungen."""
|
||||
|
||||
# Daten laden
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
print(f"🎲 EUROJACKPOT BEREICHSKOMBINATIONEN-ANALYSE")
|
||||
print(f"Anzahl analysierte Ziehungen: {len(df)}")
|
||||
print("="*60)
|
||||
|
||||
# Analyse für Z1-Z4 (wie gewünscht)
|
||||
z_columns_z1_z4 = ['Z1', 'Z2', 'Z3', 'Z4']
|
||||
|
||||
# Alle Kombinationen sammeln
|
||||
combinations_z1_z4 = []
|
||||
range_patterns_z1_z4 = []
|
||||
|
||||
for _, row in df.iterrows():
|
||||
# Bereiche für Z1-Z4 bestimmen
|
||||
ranges = [get_range_for_number(row[col]) for col in z_columns_z1_z4]
|
||||
range_pattern = '|'.join(sorted(ranges)) # Sortiert für einheitliche Muster
|
||||
|
||||
combinations_z1_z4.append(tuple(ranges))
|
||||
range_patterns_z1_z4.append(range_pattern)
|
||||
|
||||
# Auch vollständige Z1-Z5 Analyse
|
||||
z_columns_all = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']
|
||||
combinations_all = []
|
||||
range_patterns_all = []
|
||||
|
||||
for _, row in df.iterrows():
|
||||
ranges = [get_range_for_number(row[col]) for col in z_columns_all]
|
||||
range_pattern = '|'.join(sorted(ranges))
|
||||
|
||||
combinations_all.append(tuple(ranges))
|
||||
range_patterns_all.append(range_pattern)
|
||||
|
||||
# Häufigkeitsanalyse
|
||||
print("\n📊 ANALYSE Z1-Z4 (4 Zahlen):")
|
||||
print("="*40)
|
||||
|
||||
pattern_counts_z1_z4 = Counter(range_patterns_z1_z4)
|
||||
|
||||
print(f"Häufigste Bereichskombinationen (Z1-Z4):")
|
||||
for i, (pattern, count) in enumerate(pattern_counts_z1_z4.most_common(15), 1):
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{i:2}. {pattern:30} {count:3}x ({percentage:4.1f}%)")
|
||||
|
||||
print(f"\n📊 VERGLEICH: ANALYSE Z1-Z5 (alle 5 Zahlen):")
|
||||
print("="*40)
|
||||
|
||||
pattern_counts_all = Counter(range_patterns_all)
|
||||
|
||||
print(f"Häufigste Bereichskombinationen (Z1-Z5):")
|
||||
for i, (pattern, count) in enumerate(pattern_counts_all.most_common(15), 1):
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{i:2}. {pattern:35} {count:3}x ({percentage:4.1f}%)")
|
||||
|
||||
# Analyse der Bereichsverteilung in Kombinationen
|
||||
print(f"\n🎯 BEREICHSVERTEILUNG IN KOMBINATIONEN:")
|
||||
print("="*50)
|
||||
|
||||
# Wie oft kommt jeder Bereich in Z1-Z4 vor?
|
||||
range_in_combination_counts = defaultdict(int)
|
||||
|
||||
for combination in combinations_z1_z4:
|
||||
for range_name in set(combination): # set() um Duplikate zu vermeiden
|
||||
range_in_combination_counts[range_name] += 1
|
||||
|
||||
print("Häufigkeit der Bereiche in Z1-Z4 Kombinationen:")
|
||||
for range_name in ['A(1-10)', 'B(11-20)', 'C(21-30)', 'D(31-40)', 'E(41-50)']:
|
||||
count = range_in_combination_counts[range_name]
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{range_name}: {count:3} Kombinationen ({percentage:4.1f}%)")
|
||||
|
||||
# Analyse: Wie viele verschiedene Bereiche pro Kombination?
|
||||
print(f"\n📈 BEREICHSVIELFALT PRO KOMBINATION (Z1-Z4):")
|
||||
print("="*45)
|
||||
|
||||
diversity_counts = defaultdict(int)
|
||||
|
||||
for combination in combinations_z1_z4:
|
||||
unique_ranges = len(set(combination))
|
||||
diversity_counts[unique_ranges] += 1
|
||||
|
||||
for num_ranges in sorted(diversity_counts.keys()):
|
||||
count = diversity_counts[num_ranges]
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{num_ranges} verschiedene Bereiche: {count:3} Kombinationen ({percentage:4.1f}%)")
|
||||
|
||||
# Spezielle Muster
|
||||
print(f"\n🔍 SPEZIELLE MUSTER (Z1-Z4):")
|
||||
print("="*35)
|
||||
|
||||
# Alle aus dem gleichen Bereich
|
||||
same_range_count = sum(1 for combo in combinations_z1_z4 if len(set(combo)) == 1)
|
||||
print(f"Alle 4 Zahlen aus gleichem Bereich: {same_range_count} ({(same_range_count/len(df)*100):.1f}%)")
|
||||
|
||||
# Alle aus verschiedenen Bereichen (4 verschiedene)
|
||||
all_different_count = sum(1 for combo in combinations_z1_z4 if len(set(combo)) == 4)
|
||||
print(f"Alle 4 Zahlen aus verschiedenen Bereichen: {all_different_count} ({(all_different_count/len(df)*100):.1f}%)")
|
||||
|
||||
# Benachbarte Bereiche-Analyse
|
||||
print(f"\n🏠 BENACHBARTE BEREICHE-ANALYSE (Z1-Z4):")
|
||||
print("="*40)
|
||||
|
||||
adjacent_patterns = {
|
||||
'A+B': 0, # 1-10 + 11-20
|
||||
'B+C': 0, # 11-20 + 21-30
|
||||
'C+D': 0, # 21-30 + 31-40
|
||||
'D+E': 0, # 31-40 + 41-50
|
||||
}
|
||||
|
||||
for combination in combinations_z1_z4:
|
||||
ranges_set = set(combination)
|
||||
if 'A(1-10)' in ranges_set and 'B(11-20)' in ranges_set:
|
||||
adjacent_patterns['A+B'] += 1
|
||||
if 'B(11-20)' in ranges_set and 'C(21-30)' in ranges_set:
|
||||
adjacent_patterns['B+C'] += 1
|
||||
if 'C(21-30)' in ranges_set and 'D(31-40)' in ranges_set:
|
||||
adjacent_patterns['C+D'] += 1
|
||||
if 'D(31-40)' in ranges_set and 'E(41-50)' in ranges_set:
|
||||
adjacent_patterns['D+E'] += 1
|
||||
|
||||
for pattern, count in adjacent_patterns.items():
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"Benachbarte Bereiche {pattern}: {count:3} Kombinationen ({percentage:4.1f}%)")
|
||||
|
||||
# Export der Ergebnisse
|
||||
print(f"\n💾 EXPORT DER ERGEBNISSE:")
|
||||
print("="*30)
|
||||
|
||||
# DataFrame für Z1-Z4 Kombinationen erstellen
|
||||
results_data = []
|
||||
for i, row in df.iterrows():
|
||||
ranges_z1_z4 = [get_range_for_number(row[col]) for col in z_columns_z1_z4]
|
||||
pattern = '|'.join(sorted(ranges_z1_z4))
|
||||
diversity = len(set(ranges_z1_z4))
|
||||
|
||||
results_data.append({
|
||||
'datum': row['datum'],
|
||||
'Z1': row['Z1'],
|
||||
'Z2': row['Z2'],
|
||||
'Z3': row['Z3'],
|
||||
'Z4': row['Z4'],
|
||||
'Z1_bereich': ranges_z1_z4[0],
|
||||
'Z2_bereich': ranges_z1_z4[1],
|
||||
'Z3_bereich': ranges_z1_z4[2],
|
||||
'Z4_bereich': ranges_z1_z4[3],
|
||||
'bereichsmuster': pattern,
|
||||
'anzahl_bereiche': diversity
|
||||
})
|
||||
|
||||
results_df = pd.DataFrame(results_data)
|
||||
output_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/bereichskombinationen_z1_z4.csv"
|
||||
results_df.to_csv(output_file, sep=';', index=False)
|
||||
print(f"✅ Detailergebnisse gespeichert: bereichskombinationen_z1_z4.csv")
|
||||
|
||||
return pattern_counts_z1_z4, pattern_counts_all
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_range_combinations()
|
||||
@@ -0,0 +1,217 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Bereichsanalyse
|
||||
|
||||
Analysiert, in welchen Zahlenbereichen am häufigsten Zahlen gezogen wurden.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import matplotlib.pyplot as plt
|
||||
import seaborn as sns
|
||||
import numpy as np
|
||||
from collections import Counter
|
||||
|
||||
def load_drawn_numbers(filepath):
|
||||
"""Lädt die gezogenen Eurojackpot-Zahlen."""
|
||||
try:
|
||||
df = pd.read_csv(filepath, sep=';')
|
||||
print(f"Gezogene Zahlen geladen: {len(df)} Ziehungen")
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden: {e}")
|
||||
return None
|
||||
|
||||
def analyze_number_ranges(df):
|
||||
"""Analysiert die Häufigkeit von Zahlen in verschiedenen Bereichen."""
|
||||
|
||||
# Alle gezogenen Zahlen sammeln (Z1-Z5)
|
||||
all_numbers = []
|
||||
for col in ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']:
|
||||
all_numbers.extend(df[col].tolist())
|
||||
|
||||
print(f"Gesamtanzahl gezogene Zahlen: {len(all_numbers)}")
|
||||
|
||||
# Häufigkeit jeder Zahl
|
||||
number_counts = Counter(all_numbers)
|
||||
|
||||
# Bereiche definieren
|
||||
ranges = {
|
||||
'1-10': (1, 10),
|
||||
'11-20': (11, 20),
|
||||
'21-30': (21, 30),
|
||||
'31-40': (31, 40),
|
||||
'41-50': (41, 50)
|
||||
}
|
||||
|
||||
# Analyse pro Bereich
|
||||
range_analysis = {}
|
||||
|
||||
for range_name, (start, end) in ranges.items():
|
||||
numbers_in_range = [num for num in all_numbers if start <= num <= end]
|
||||
|
||||
range_analysis[range_name] = {
|
||||
'anzahl_ziehungen': len(numbers_in_range),
|
||||
'prozent': (len(numbers_in_range) / len(all_numbers)) * 100,
|
||||
'haeufigste_zahl': max(number_counts.items(),
|
||||
key=lambda x: x[1] if start <= x[0] <= end else 0),
|
||||
'durchschnitt': np.mean(numbers_in_range) if numbers_in_range else 0,
|
||||
'zahlen_im_bereich': sorted(set(numbers_in_range))
|
||||
}
|
||||
|
||||
return range_analysis, number_counts
|
||||
|
||||
def create_visualizations(range_analysis, number_counts, output_dir):
|
||||
"""Erstellt Visualisierungen der Analyse."""
|
||||
|
||||
# 1. Balkendiagramm: Häufigkeit pro Bereich
|
||||
plt.figure(figsize=(12, 8))
|
||||
|
||||
ranges = list(range_analysis.keys())
|
||||
counts = [range_analysis[r]['anzahl_ziehungen'] for r in ranges]
|
||||
percentages = [range_analysis[r]['prozent'] for r in ranges]
|
||||
|
||||
plt.subplot(2, 2, 1)
|
||||
bars = plt.bar(ranges, counts, color=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57'])
|
||||
plt.title('Anzahl gezogener Zahlen pro Bereich', fontsize=14, fontweight='bold')
|
||||
plt.ylabel('Anzahl Ziehungen')
|
||||
plt.xticks(rotation=45)
|
||||
|
||||
# Prozente auf Balken anzeigen
|
||||
for bar, pct in zip(bars, percentages):
|
||||
plt.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 5,
|
||||
f'{pct:.1f}%', ha='center', va='bottom', fontweight='bold')
|
||||
|
||||
# 2. Heatmap: Häufigkeit einzelner Zahlen
|
||||
plt.subplot(2, 2, 2)
|
||||
numbers = list(range(1, 51))
|
||||
frequencies = [number_counts.get(num, 0) for num in numbers]
|
||||
|
||||
# Als 5x10 Matrix darstellen
|
||||
freq_matrix = np.array(frequencies).reshape(5, 10)
|
||||
|
||||
sns.heatmap(freq_matrix, annot=True, fmt='d', cmap='YlOrRd',
|
||||
xticklabels=list(range(1, 11)),
|
||||
yticklabels=[f'{i*10+1}-{(i+1)*10}' for i in range(5)])
|
||||
plt.title('Häufigkeit einzelner Zahlen', fontsize=14, fontweight='bold')
|
||||
|
||||
# 3. Liniendiagramm: Häufigkeit aller Zahlen
|
||||
plt.subplot(2, 2, 3)
|
||||
plt.plot(numbers, frequencies, marker='o', linewidth=2, markersize=4)
|
||||
plt.title('Häufigkeitsverteilung aller Zahlen (1-50)', fontsize=14, fontweight='bold')
|
||||
plt.xlabel('Zahl')
|
||||
plt.ylabel('Häufigkeit')
|
||||
plt.grid(True, alpha=0.3)
|
||||
|
||||
# Bereiche farblich markieren
|
||||
colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57']
|
||||
for i, (range_name, color) in enumerate(zip(range_analysis.keys(), colors)):
|
||||
start = i * 10 + 1
|
||||
end = (i + 1) * 10
|
||||
plt.axvspan(start, end, alpha=0.2, color=color, label=range_name)
|
||||
|
||||
plt.legend()
|
||||
|
||||
# 4. Pie Chart: Prozentuale Verteilung
|
||||
plt.subplot(2, 2, 4)
|
||||
plt.pie(percentages, labels=ranges, autopct='%1.1f%%',
|
||||
colors=['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FECA57'],
|
||||
startangle=90)
|
||||
plt.title('Prozentuale Verteilung nach Bereichen', fontsize=14, fontweight='bold')
|
||||
|
||||
plt.tight_layout()
|
||||
plt.savefig(f'{output_dir}/eurojackpot_bereichsanalyse.png', dpi=300, bbox_inches='tight')
|
||||
plt.show()
|
||||
|
||||
def print_detailed_analysis(range_analysis, number_counts):
|
||||
"""Gibt detaillierte Analyseergebnisse aus."""
|
||||
|
||||
print("\n" + "="*60)
|
||||
print("🎯 EUROJACKPOT BEREICHSANALYSE")
|
||||
print("="*60)
|
||||
|
||||
total_numbers = sum(analysis['anzahl_ziehungen'] for analysis in range_analysis.values())
|
||||
|
||||
for range_name, analysis in range_analysis.items():
|
||||
print(f"\n📊 BEREICH {range_name}:")
|
||||
print(f" Anzahl Ziehungen: {analysis['anzahl_ziehungen']:,}")
|
||||
print(f" Prozentanteil: {analysis['prozent']:.2f}%")
|
||||
print(f" Durchschnittswert: {analysis['durchschnitt']:.1f}")
|
||||
|
||||
# Top 3 Zahlen in diesem Bereich
|
||||
start, end = map(int, range_name.split('-'))
|
||||
range_numbers = [(num, count) for num, count in number_counts.items()
|
||||
if start <= num <= end]
|
||||
range_numbers.sort(key=lambda x: x[1], reverse=True)
|
||||
|
||||
print(f" Top 3 Zahlen: ", end="")
|
||||
for i, (num, count) in enumerate(range_numbers[:3]):
|
||||
print(f"{num} ({count}x)", end="")
|
||||
if i < 2 and i < len(range_numbers) - 1:
|
||||
print(", ", end="")
|
||||
print()
|
||||
|
||||
# Allgemeine Statistiken
|
||||
print(f"\n📈 ALLGEMEINE STATISTIKEN:")
|
||||
print(f" Gesamte gezogene Zahlen: {total_numbers:,}")
|
||||
print(f" Durchschnitt pro Bereich: {total_numbers/5:.1f}")
|
||||
|
||||
# Häufigste und seltenste Zahlen insgesamt
|
||||
most_common = number_counts.most_common(5)
|
||||
least_common = number_counts.most_common()[-5:]
|
||||
|
||||
print(f"\n🔥 HÄUFIGSTE ZAHLEN GESAMT:")
|
||||
for i, (num, count) in enumerate(most_common, 1):
|
||||
print(f" {i}. Zahl {num}: {count} mal gezogen")
|
||||
|
||||
print(f"\n❄️ SELTENSTE ZAHLEN GESAMT:")
|
||||
for i, (num, count) in enumerate(reversed(least_common), 1):
|
||||
print(f" {i}. Zahl {num}: {count} mal gezogen")
|
||||
|
||||
# Empfehlungen
|
||||
print(f"\n💡 ERKENNTNISSE:")
|
||||
best_range = max(range_analysis.keys(), key=lambda x: range_analysis[x]['prozent'])
|
||||
worst_range = min(range_analysis.keys(), key=lambda x: range_analysis[x]['prozent'])
|
||||
|
||||
print(f" • Bester Bereich: {best_range} ({range_analysis[best_range]['prozent']:.1f}%)")
|
||||
print(f" • Schwächster Bereich: {worst_range} ({range_analysis[worst_range]['prozent']:.1f}%)")
|
||||
print(f" • Unterschied: {range_analysis[best_range]['prozent'] - range_analysis[worst_range]['prozent']:.1f} Prozentpunkte")
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion."""
|
||||
|
||||
# Pfade
|
||||
input_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
output_dir = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot"
|
||||
|
||||
# Daten laden
|
||||
print("🔄 Lade Eurojackpot-Daten...")
|
||||
df = load_drawn_numbers(input_file)
|
||||
|
||||
if df is None:
|
||||
print("❌ Fehler beim Laden der Daten!")
|
||||
return
|
||||
|
||||
# Analyse durchführen
|
||||
print("\n🔍 Führe Bereichsanalyse durch...")
|
||||
range_analysis, number_counts = analyze_number_ranges(df)
|
||||
|
||||
# Ergebnisse ausgeben
|
||||
print_detailed_analysis(range_analysis, number_counts)
|
||||
|
||||
# Visualisierungen erstellen
|
||||
print(f"\n📊 Erstelle Visualisierungen...")
|
||||
try:
|
||||
create_visualizations(range_analysis, number_counts, output_dir)
|
||||
print(f"✅ Diagramm gespeichert: {output_dir}/eurojackpot_bereichsanalyse.png")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Visualisierung konnte nicht erstellt werden: {e}")
|
||||
print("💡 Installieren Sie matplotlib und seaborn: pip install matplotlib seaborn")
|
||||
|
||||
# CSV-Export der Analyse
|
||||
results_df = pd.DataFrame.from_dict(range_analysis, orient='index')
|
||||
results_df.to_csv(f'{output_dir}/bereichsanalyse_ergebnisse.csv', sep=';')
|
||||
print(f"✅ Ergebnisse gespeichert: {output_dir}/bereichsanalyse_ergebnisse.csv")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,208 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Positionsanalyse
|
||||
|
||||
Analysiert, welche Bereiche an welchen Positionen (Z1, Z2, Z3, Z4, Z5) am häufigsten stehen.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from collections import defaultdict
|
||||
import numpy as np
|
||||
|
||||
def get_range_for_number(number):
|
||||
"""Bestimmt den Bereich für eine gegebene Zahl."""
|
||||
if 1 <= number <= 10:
|
||||
return 'A(1-10)'
|
||||
elif 11 <= number <= 20:
|
||||
return 'B(11-20)'
|
||||
elif 21 <= number <= 30:
|
||||
return 'C(21-30)'
|
||||
elif 31 <= number <= 40:
|
||||
return 'D(31-40)'
|
||||
elif 41 <= number <= 50:
|
||||
return 'E(41-50)'
|
||||
else:
|
||||
return 'Unknown'
|
||||
|
||||
def analyze_positions():
|
||||
"""Führt eine detaillierte Positionsanalyse durch."""
|
||||
|
||||
# Daten laden
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
print(f"🎯 EUROJACKPOT POSITIONSANALYSE")
|
||||
print(f"Anzahl analysierte Ziehungen: {len(df)}")
|
||||
print("="*60)
|
||||
|
||||
# Positionen definieren
|
||||
positions = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']
|
||||
ranges = ['A(1-10)', 'B(11-20)', 'C(21-30)', 'D(31-40)', 'E(41-50)']
|
||||
|
||||
# Matrix für Positions-Bereich-Kombinationen
|
||||
position_range_matrix = defaultdict(lambda: defaultdict(int))
|
||||
|
||||
# Daten sammeln
|
||||
for _, row in df.iterrows():
|
||||
for pos in positions:
|
||||
range_name = get_range_for_number(row[pos])
|
||||
position_range_matrix[pos][range_name] += 1
|
||||
|
||||
# 1. Übersichtstabelle erstellen
|
||||
print(f"\n📊 POSITIONS-BEREICH-MATRIX:")
|
||||
print("="*60)
|
||||
print(f"{'Position':<8} {'A(1-10)':<12} {'B(11-20)':<12} {'C(21-30)':<12} {'D(31-40)':<12} {'E(41-50)':<12}")
|
||||
print("-" * 60)
|
||||
|
||||
for pos in positions:
|
||||
line = f"{pos:<8} "
|
||||
for range_name in ranges:
|
||||
count = position_range_matrix[pos][range_name]
|
||||
percentage = (count / len(df)) * 100
|
||||
line += f"{count:3}({percentage:4.1f}%) "
|
||||
print(line)
|
||||
|
||||
# 2. Beste Bereiche pro Position
|
||||
print(f"\n🏆 BESTE BEREICHE PRO POSITION:")
|
||||
print("="*40)
|
||||
|
||||
for pos in positions:
|
||||
best_range = max(ranges, key=lambda r: position_range_matrix[pos][r])
|
||||
best_count = position_range_matrix[pos][best_range]
|
||||
best_percentage = (best_count / len(df)) * 100
|
||||
|
||||
# Alle Bereiche für diese Position sortiert
|
||||
sorted_ranges = sorted(ranges, key=lambda r: position_range_matrix[pos][r], reverse=True)
|
||||
|
||||
print(f"\n{pos}: {best_range} führt mit {best_count} ({best_percentage:.1f}%)")
|
||||
print(f" Ranking: ", end="")
|
||||
for i, range_name in enumerate(sorted_ranges, 1):
|
||||
count = position_range_matrix[pos][range_name]
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{i}.{range_name}({percentage:.1f}%)", end="")
|
||||
if i < len(sorted_ranges):
|
||||
print(" > ", end="")
|
||||
print()
|
||||
|
||||
# 3. Beste Positionen pro Bereich
|
||||
print(f"\n🎯 BESTE POSITIONEN PRO BEREICH:")
|
||||
print("="*40)
|
||||
|
||||
for range_name in ranges:
|
||||
best_position = max(positions, key=lambda p: position_range_matrix[p][range_name])
|
||||
best_count = position_range_matrix[best_position][range_name]
|
||||
best_percentage = (best_count / len(df)) * 100
|
||||
|
||||
# Alle Positionen für diesen Bereich sortiert
|
||||
sorted_positions = sorted(positions, key=lambda p: position_range_matrix[p][range_name], reverse=True)
|
||||
|
||||
print(f"\n{range_name}: Position {best_position} führt mit {best_count} ({best_percentage:.1f}%)")
|
||||
print(f" Ranking: ", end="")
|
||||
for i, pos in enumerate(sorted_positions, 1):
|
||||
count = position_range_matrix[pos][range_name]
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{i}.{pos}({percentage:.1f}%)", end="")
|
||||
if i < len(sorted_positions):
|
||||
print(" > ", end="")
|
||||
print()
|
||||
|
||||
# 4. Spezielle Analysen
|
||||
print(f"\n📈 SPEZIELLE POSITIONSANALYSEN:")
|
||||
print("="*45)
|
||||
|
||||
# Niedrige vs. hohe Bereiche pro Position
|
||||
for pos in positions:
|
||||
low_ranges = position_range_matrix[pos]['A(1-10)'] + position_range_matrix[pos]['B(11-20)']
|
||||
high_ranges = position_range_matrix[pos]['D(31-40)'] + position_range_matrix[pos]['E(41-50)']
|
||||
middle_range = position_range_matrix[pos]['C(21-30)']
|
||||
|
||||
low_pct = (low_ranges / len(df)) * 100
|
||||
high_pct = (high_ranges / len(df)) * 100
|
||||
middle_pct = (middle_range / len(df)) * 100
|
||||
|
||||
print(f"{pos}: Niedrig(A+B)={low_pct:4.1f}% | Mitte(C)={middle_pct:4.1f}% | Hoch(D+E)={high_pct:4.1f}%")
|
||||
|
||||
# 5. Gleichverteilungs-Analyse
|
||||
print(f"\n⚖️ GLEICHVERTEILUNGS-ANALYSE:")
|
||||
print("="*35)
|
||||
|
||||
expected_per_range = len(df) / 5 # Erwartete Gleichverteilung
|
||||
|
||||
print(f"Erwartete Gleichverteilung pro Bereich und Position: {expected_per_range:.1f}")
|
||||
print(f"\nAbweichungen von der Gleichverteilung:")
|
||||
|
||||
for pos in positions:
|
||||
print(f"\n{pos}:")
|
||||
for range_name in ranges:
|
||||
actual = position_range_matrix[pos][range_name]
|
||||
deviation = actual - expected_per_range
|
||||
deviation_pct = (deviation / expected_per_range) * 100
|
||||
|
||||
symbol = "📈" if deviation > 0 else "📉" if deviation < 0 else "⚖️"
|
||||
print(f" {range_name}: {actual:3} ({deviation:+4.1f}, {deviation_pct:+5.1f}%) {symbol}")
|
||||
|
||||
# 6. Heatmap-Daten für Export
|
||||
print(f"\n💾 DATENEXPORT:")
|
||||
print("="*20)
|
||||
|
||||
# Erstelle eine Matrix für bessere Visualisierung
|
||||
matrix_data = []
|
||||
for pos in positions:
|
||||
row_data = {'Position': pos}
|
||||
for range_name in ranges:
|
||||
count = position_range_matrix[pos][range_name]
|
||||
percentage = (count / len(df)) * 100
|
||||
row_data[range_name] = count
|
||||
row_data[f"{range_name}_Prozent"] = percentage
|
||||
matrix_data.append(row_data)
|
||||
|
||||
matrix_df = pd.DataFrame(matrix_data)
|
||||
|
||||
# Zusätzliche Statistiken hinzufügen
|
||||
stats_data = []
|
||||
for _, row in df.iterrows():
|
||||
row_data = {'datum': row['datum']}
|
||||
for pos in positions:
|
||||
row_data[f"{pos}_Zahl"] = row[pos]
|
||||
row_data[f"{pos}_Bereich"] = get_range_for_number(row[pos])
|
||||
stats_data.append(row_data)
|
||||
|
||||
stats_df = pd.DataFrame(stats_data)
|
||||
|
||||
# Export
|
||||
matrix_output = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/positions_matrix.csv"
|
||||
stats_output = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/positions_details.csv"
|
||||
|
||||
matrix_df.to_csv(matrix_output, sep=';', index=False)
|
||||
stats_df.to_csv(stats_output, sep=';', index=False)
|
||||
|
||||
print(f"✅ Positionsmatrix gespeichert: positions_matrix.csv")
|
||||
print(f"✅ Detaildaten gespeichert: positions_details.csv")
|
||||
|
||||
# 7. Strategische Empfehlungen
|
||||
print(f"\n💡 STRATEGISCHE EMPFEHLUNGEN:")
|
||||
print("="*35)
|
||||
|
||||
# Finde die besten Kombinationen pro Position
|
||||
recommendations = {}
|
||||
for pos in positions:
|
||||
best_range = max(ranges, key=lambda r: position_range_matrix[pos][r])
|
||||
recommendations[pos] = best_range
|
||||
|
||||
print(f"Optimale Bereichsauswahl pro Position:")
|
||||
for pos, best_range in recommendations.items():
|
||||
count = position_range_matrix[pos][best_range]
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f" {pos}: {best_range} ({percentage:.1f}%)")
|
||||
|
||||
# Berechne theoretische Erfolgswahrscheinlichkeit
|
||||
theoretical_success = 1.0
|
||||
for pos, best_range in recommendations.items():
|
||||
prob = position_range_matrix[pos][best_range] / len(df)
|
||||
theoretical_success *= prob
|
||||
|
||||
print(f"\nTheoretische Erfolgswahrscheinlichkeit dieser Kombination: {theoretical_success*100:.4f}%")
|
||||
|
||||
return position_range_matrix, matrix_df
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_positions()
|
||||
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Einfache Eurojackpot Bereichsanalyse
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from collections import Counter
|
||||
|
||||
def analyze_ranges():
|
||||
"""Analysiert Zahlenbereiche in Eurojackpot-Daten."""
|
||||
|
||||
# Daten laden
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
print(f"Anzahl Ziehungen: {len(df)}")
|
||||
|
||||
# Alle Zahlen sammeln
|
||||
all_numbers = []
|
||||
for col in ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']:
|
||||
all_numbers.extend(df[col].tolist())
|
||||
|
||||
# Bereiche definieren
|
||||
ranges = {
|
||||
'1-10': list(range(1, 11)),
|
||||
'11-20': list(range(11, 21)),
|
||||
'21-30': list(range(21, 31)),
|
||||
'31-40': list(range(31, 41)),
|
||||
'41-50': list(range(41, 51))
|
||||
}
|
||||
|
||||
print(f"\nGesamt gezogene Zahlen: {len(all_numbers)}")
|
||||
print("="*50)
|
||||
|
||||
# Analyse pro Bereich
|
||||
for range_name, range_numbers in ranges.items():
|
||||
count = sum(1 for num in all_numbers if num in range_numbers)
|
||||
percentage = (count / len(all_numbers)) * 100
|
||||
|
||||
print(f"{range_name:6}: {count:4} Zahlen ({percentage:5.1f}%)")
|
||||
|
||||
print("="*50)
|
||||
|
||||
# Häufigste Zahlen
|
||||
number_counts = Counter(all_numbers)
|
||||
print("\nHäufigste 10 Zahlen:")
|
||||
for num, count in number_counts.most_common(10):
|
||||
print(f"Zahl {num:2}: {count:3} mal")
|
||||
|
||||
print("\nSeltenste 10 Zahlen:")
|
||||
for num, count in number_counts.most_common()[-10:]:
|
||||
print(f"Zahl {num:2}: {count:3} mal")
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_ranges()
|
||||
@@ -0,0 +1,270 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Treffer-Analyse (umschlüsselte Werte)
|
||||
|
||||
Analysiert, wo die größten Treffer-Wahrscheinlichkeiten bei den umschlüsselten Werten liegen.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
from collections import Counter, defaultdict
|
||||
import numpy as np
|
||||
|
||||
def analyze_hit_probabilities():
|
||||
"""Analysiert die Treffer-Wahrscheinlichkeiten der umschlüsselten Werte."""
|
||||
|
||||
# Umschlüsselte Daten laden
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/AlleEurojackpotzahlen_umschluesselt.csv", sep=';')
|
||||
|
||||
print("🎯 TREFFER-ANALYSE UMSCHLÜSSELTE WERTE (1-10)")
|
||||
print("="*60)
|
||||
print(f"Analysierte Ziehungen: {len(df)}")
|
||||
|
||||
# 1. Einzelne Gruppen-Wahrscheinlichkeiten pro Position
|
||||
print(f"\n🏆 HÖCHSTE TREFFER-WAHRSCHEINLICHKEITEN PRO POSITION:")
|
||||
print("="*55)
|
||||
|
||||
positions = ['z1', 'z2', 'z3', 'z4', 'z5']
|
||||
position_stats = {}
|
||||
|
||||
for pos in positions:
|
||||
group_counts = Counter(df[pos])
|
||||
total = len(df)
|
||||
|
||||
# Beste Gruppe für diese Position
|
||||
best_group = max(group_counts, key=group_counts.get)
|
||||
best_count = group_counts[best_group]
|
||||
best_probability = (best_count / total) * 100
|
||||
|
||||
position_stats[pos] = {
|
||||
'best_group': best_group,
|
||||
'probability': best_probability,
|
||||
'count': best_count,
|
||||
'all_groups': group_counts
|
||||
}
|
||||
|
||||
print(f"\n{pos.upper()}: Gruppe {best_group} führt mit {best_probability:.1f}% ({best_count}/{total})")
|
||||
|
||||
# Top 3 für diese Position
|
||||
top_3 = group_counts.most_common(3)
|
||||
print(f" Top 3: ", end="")
|
||||
for i, (group, count) in enumerate(top_3):
|
||||
prob = (count / total) * 100
|
||||
print(f"{i+1}.Gruppe {group}({prob:.1f}%)", end="")
|
||||
if i < 2:
|
||||
print(" > ", end="")
|
||||
print()
|
||||
|
||||
# 2. Beste Gesamtkombination
|
||||
print(f"\n🔥 OPTIMAL-KOMBINATION (höchste Einzelwahrscheinlichkeiten):")
|
||||
print("="*60)
|
||||
|
||||
optimal_combination = []
|
||||
total_probability = 1.0
|
||||
|
||||
for pos in positions:
|
||||
best_group = position_stats[pos]['best_group']
|
||||
probability = position_stats[pos]['probability'] / 100
|
||||
optimal_combination.append(best_group)
|
||||
total_probability *= probability
|
||||
|
||||
print(f"{pos}: Gruppe {best_group} ({position_stats[pos]['probability']:.1f}%)")
|
||||
|
||||
optimal_string = '-'.join(map(str, optimal_combination))
|
||||
print(f"\nOptimal-Kombination: {optimal_string}")
|
||||
print(f"Theoretische Wahrscheinlichkeit: {total_probability*100:.6f}%")
|
||||
print(f"Das entspricht etwa 1 in {1/total_probability:,.0f} Ziehungen")
|
||||
|
||||
# 3. Tatsächlich aufgetretene häufigste Kombinationen
|
||||
print(f"\n📊 REAL AUFGETRETENE HÄUFIGSTE KOMBINATIONEN:")
|
||||
print("="*50)
|
||||
|
||||
combination_counts = Counter(df['kombination_umschluesselt'])
|
||||
|
||||
print(f"Top 20 real aufgetretene Kombinationen:")
|
||||
for i, (combination, count) in enumerate(combination_counts.most_common(20), 1):
|
||||
probability = (count / len(df)) * 100
|
||||
print(f"{i:2}. {combination:15} {count}x ({probability:.2f}%)")
|
||||
|
||||
# 4. Bereichs-Kombinationen mit höchster Wahrscheinlichkeit
|
||||
print(f"\n🎲 BEREICHS-KOMBINATIONEN MIT HÖCHSTER WAHRSCHEINLICHKEIT:")
|
||||
print("="*60)
|
||||
|
||||
# Niedrig (1-3), Mittel (4-7), Hoch (8-10) Kombinationen
|
||||
range_combinations = defaultdict(int)
|
||||
|
||||
for _, row in df.iterrows():
|
||||
ranges = []
|
||||
for pos in positions:
|
||||
val = row[pos]
|
||||
if 1 <= val <= 3:
|
||||
ranges.append('N') # Niedrig
|
||||
elif 4 <= val <= 7:
|
||||
ranges.append('M') # Mittel
|
||||
else:
|
||||
ranges.append('H') # Hoch
|
||||
|
||||
range_pattern = ''.join(ranges)
|
||||
range_combinations[range_pattern] += 1
|
||||
|
||||
print(f"Häufigste Bereichsmuster (N=Niedrig1-3, M=Mittel4-7, H=Hoch8-10):")
|
||||
sorted_patterns = sorted(range_combinations.items(), key=lambda x: x[1], reverse=True)
|
||||
|
||||
for i, (pattern, count) in enumerate(sorted_patterns[:15], 1):
|
||||
probability = (count / len(df)) * 100
|
||||
pattern_readable = pattern.replace('N', 'Niedrig').replace('M', 'Mittel').replace('H', 'Hoch')
|
||||
print(f"{i:2}. {pattern:5} ({pattern_readable:25}) {count:3}x ({probability:5.1f}%)")
|
||||
|
||||
# 5. Positions-spezifische Empfehlungen
|
||||
print(f"\n💡 POSITIONS-SPEZIFISCHE EMPFEHLUNGEN:")
|
||||
print("="*45)
|
||||
|
||||
recommendations = {}
|
||||
|
||||
for pos in positions:
|
||||
group_counts = position_stats[pos]['all_groups']
|
||||
total = len(df)
|
||||
|
||||
# Top 3 Gruppen für maximale Abdeckung
|
||||
top_groups = [group for group, count in group_counts.most_common(3)]
|
||||
top_coverage = sum(group_counts[group] for group in top_groups)
|
||||
coverage_percentage = (top_coverage / total) * 100
|
||||
|
||||
recommendations[pos] = {
|
||||
'top_groups': top_groups,
|
||||
'coverage': coverage_percentage
|
||||
}
|
||||
|
||||
print(f"\n{pos.upper()}: Empfohlene Gruppen {top_groups}")
|
||||
print(f" Abdeckung: {coverage_percentage:.1f}% aller Ziehungen")
|
||||
|
||||
# Wahrscheinlichkeitsverteilung
|
||||
print(f" Verteilung: ", end="")
|
||||
for group in top_groups:
|
||||
prob = (group_counts[group] / total) * 100
|
||||
print(f"Gruppe {group}({prob:.1f}%)", end="")
|
||||
if group != top_groups[-1]:
|
||||
print(", ", end="")
|
||||
print()
|
||||
|
||||
# 6. Strategische Kombinationen
|
||||
print(f"\n🎯 STRATEGISCHE KOMBINATIONEN FÜR MAXIMALE TREFFER:")
|
||||
print("="*55)
|
||||
|
||||
# Berechne verschiedene Strategien
|
||||
strategies = {
|
||||
'Konservativ': {
|
||||
'z1': [1, 2], # Top 2 der Position z1
|
||||
'z2': [3, 4], # Top 2 der Position z2
|
||||
'z3': [5, 6], # Top 2 der Position z3
|
||||
'z4': [7, 8], # Top 2 der Position z4
|
||||
'z5': [9, 10] # Top 2 der Position z5
|
||||
},
|
||||
'Ausgewogen': {
|
||||
'z1': [1, 2, 3], # Top 3 jeder Position
|
||||
'z2': [3, 4, 5],
|
||||
'z3': [4, 5, 6, 7],
|
||||
'z4': [6, 7, 8],
|
||||
'z5': [8, 9, 10]
|
||||
},
|
||||
'Optimal': {} # Wird basierend auf tatsächlichen Daten gefüllt
|
||||
}
|
||||
|
||||
# Optimal-Strategie basierend auf echten Top-3 pro Position
|
||||
for pos in positions:
|
||||
top_3_groups = recommendations[pos]['top_groups']
|
||||
strategies['Optimal'][pos] = top_3_groups
|
||||
|
||||
for strategy_name, strategy in strategies.items():
|
||||
if strategy: # Nur wenn Strategie gefüllt ist
|
||||
print(f"\n{strategy_name}-Strategie:")
|
||||
total_combinations = 1
|
||||
coverage_per_position = []
|
||||
|
||||
for pos in positions:
|
||||
recommended_groups = strategy[pos]
|
||||
pos_stats = position_stats[pos]['all_groups']
|
||||
total_pos = len(df)
|
||||
|
||||
# Abdeckung dieser Gruppen
|
||||
coverage = sum(pos_stats.get(group, 0) for group in recommended_groups)
|
||||
coverage_pct = (coverage / total_pos) * 100
|
||||
coverage_per_position.append(coverage_pct)
|
||||
|
||||
total_combinations *= len(recommended_groups)
|
||||
|
||||
print(f" {pos}: Gruppen {recommended_groups} ({coverage_pct:.1f}% Abdeckung)")
|
||||
|
||||
avg_coverage = np.mean(coverage_per_position)
|
||||
print(f" Durchschnittliche Abdeckung: {avg_coverage:.1f}%")
|
||||
print(f" Mögliche Kombinationen: {total_combinations:,}")
|
||||
|
||||
# 7. Heiße und kalte Zahlen
|
||||
print(f"\n🔥❄️ HEISSE UND KALTE GRUPPEN:")
|
||||
print("="*35)
|
||||
|
||||
# Alle Gruppen über alle Positionen sammeln
|
||||
all_groups = []
|
||||
for pos in positions:
|
||||
all_groups.extend(df[pos].tolist())
|
||||
|
||||
group_total_counts = Counter(all_groups)
|
||||
total_appearances = len(all_groups)
|
||||
expected_per_group = total_appearances / 10 # 10 Gruppen
|
||||
|
||||
print(f"Erwartete Häufigkeit pro Gruppe: {expected_per_group:.1f}")
|
||||
print(f"\nHeisse Gruppen (über Erwartung):")
|
||||
hot_groups = []
|
||||
for group in range(1, 11):
|
||||
actual = group_total_counts.get(group, 0)
|
||||
deviation = actual - expected_per_group
|
||||
if deviation > 0:
|
||||
hot_groups.append((group, actual, deviation))
|
||||
|
||||
hot_groups.sort(key=lambda x: x[2], reverse=True)
|
||||
for group, count, deviation in hot_groups:
|
||||
percentage = (count / total_appearances) * 100
|
||||
print(f" Gruppe {group}: {count} (+{deviation:.1f}, {percentage:.1f}%)")
|
||||
|
||||
print(f"\nKalte Gruppen (unter Erwartung):")
|
||||
cold_groups = []
|
||||
for group in range(1, 11):
|
||||
actual = group_total_counts.get(group, 0)
|
||||
deviation = actual - expected_per_group
|
||||
if deviation < 0:
|
||||
cold_groups.append((group, actual, deviation))
|
||||
|
||||
cold_groups.sort(key=lambda x: x[2])
|
||||
for group, count, deviation in cold_groups:
|
||||
percentage = (count / total_appearances) * 100
|
||||
print(f" Gruppe {group}: {count} ({deviation:.1f}, {percentage:.1f}%)")
|
||||
|
||||
# 8. Export der Empfehlungen
|
||||
print(f"\n💾 EMPFEHLUNGEN EXPORT:")
|
||||
print("="*25)
|
||||
|
||||
# Erstelle Empfehlungs-DataFrame
|
||||
recommendation_data = []
|
||||
|
||||
# Für jede Position die besten Empfehlungen
|
||||
for pos in positions:
|
||||
pos_recommendations = recommendations[pos]
|
||||
for group in pos_recommendations['top_groups']:
|
||||
prob = (position_stats[pos]['all_groups'][group] / len(df)) * 100
|
||||
recommendation_data.append({
|
||||
'position': pos,
|
||||
'gruppe': group,
|
||||
'wahrscheinlichkeit_prozent': prob,
|
||||
'anzahl_auftreten': position_stats[pos]['all_groups'][group],
|
||||
'empfehlung_rang': pos_recommendations['top_groups'].index(group) + 1
|
||||
})
|
||||
|
||||
rec_df = pd.DataFrame(recommendation_data)
|
||||
output_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/treffer_empfehlungen_umschluesselt.csv"
|
||||
rec_df.to_csv(output_file, sep=';', index=False)
|
||||
|
||||
print(f"✅ Empfehlungen gespeichert: treffer_empfehlungen_umschluesselt.csv")
|
||||
|
||||
return position_stats, recommendations
|
||||
|
||||
if __name__ == "__main__":
|
||||
analyze_hit_probabilities()
|
||||
@@ -0,0 +1,358 @@
|
||||
# Eurojackpot Automation System
|
||||
|
||||
Vollautomatisches System für wöchentliche Tipp-Generierung und Learning nach Ziehungen.
|
||||
|
||||
## 📋 Überblick
|
||||
|
||||
Das System besteht aus 2 Hauptkomponenten:
|
||||
|
||||
1. **Weekly Tip Generator** - Generiert automatisch Tipps vor Ziehungen
|
||||
2. **Auto Update & Learn** - Aktualisiert Daten und lernt nach Ziehungen
|
||||
|
||||
## 🚀 Installation
|
||||
|
||||
### 1. Virtuelle Umgebung aktivieren
|
||||
|
||||
```bash
|
||||
cd "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot"
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
### 2. Scripts ausführbar machen
|
||||
|
||||
```bash
|
||||
chmod +x scripts/automation/*.py
|
||||
```
|
||||
|
||||
## 🎯 Manuelle Nutzung
|
||||
|
||||
### Tipps generieren
|
||||
|
||||
```bash
|
||||
# Normale Generierung (prüft ob nötig)
|
||||
python scripts/automation/weekly_tip_generator.py
|
||||
|
||||
# Erzwinge Generierung
|
||||
python scripts/automation/weekly_tip_generator.py --force
|
||||
|
||||
# Generiere 20 Tipps statt 10
|
||||
python scripts/automation/weekly_tip_generator.py --num-tips 20
|
||||
|
||||
# Zeige nur Historie
|
||||
python scripts/automation/weekly_tip_generator.py --history
|
||||
```
|
||||
|
||||
### Nach Ziehung aktualisieren
|
||||
|
||||
```bash
|
||||
# Kompletter Workflow: Update + Evaluation + Learning
|
||||
python scripts/automation/auto_update_and_learn.py
|
||||
```
|
||||
|
||||
## ⏰ Automatisierung mit Cron
|
||||
|
||||
### Setup
|
||||
|
||||
1. Öffne Crontab:
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
2. Füge folgende Zeilen hinzu:
|
||||
|
||||
```bash
|
||||
# Pfad zur virtuellen Umgebung
|
||||
EUROJACKPOT_PATH="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot"
|
||||
|
||||
# Montag & Donnerstag 09:00 Uhr: Tipps generieren (vor Ziehung)
|
||||
0 9 * * 1,4 cd "$EUROJACKPOT_PATH" && source venv/bin/activate && python scripts/automation/weekly_tip_generator.py >> logs/tips_generation.log 2>&1
|
||||
|
||||
# Dienstag & Freitag 21:00 Uhr: Update nach Ziehung
|
||||
0 21 * * 2,5 cd "$EUROJACKPOT_PATH" && source venv/bin/activate && python scripts/automation/auto_update_and_learn.py >> logs/auto_update.log 2>&1
|
||||
```
|
||||
|
||||
3. Speichern (ESC, :wq in vim)
|
||||
|
||||
### Cron Zeitplan
|
||||
|
||||
```
|
||||
Montag 09:00 → Tipps für Dienstag-Ziehung generieren
|
||||
Dienstag 20:00 → Ziehung findet statt
|
||||
Dienstag 21:00 → Auto-Update & Learning
|
||||
|
||||
Donnerstag 09:00 → Tipps für Freitag-Ziehung generieren
|
||||
Freitag 20:00 → Ziehung findet statt
|
||||
Freitag 21:00 → Auto-Update & Learning
|
||||
```
|
||||
|
||||
### Logs prüfen
|
||||
|
||||
```bash
|
||||
# Erstelle Log-Verzeichnis
|
||||
mkdir -p logs
|
||||
|
||||
# Zeige letzte Tipps-Generierung
|
||||
tail -50 logs/tips_generation.log
|
||||
|
||||
# Zeige letzte Updates
|
||||
tail -50 logs/auto_update.log
|
||||
```
|
||||
|
||||
## 📊 Dateien & Verzeichnisse
|
||||
|
||||
```
|
||||
data/
|
||||
├── generated_tips/ # Generierte Tipps
|
||||
│ ├── weekly_tips_*.csv # Tipp-Dateien
|
||||
│ └── generation_history.json # Historie
|
||||
│
|
||||
├── performance_reports/ # Performance-Reports
|
||||
│ └── report_*.txt # Reports nach Ziehungen
|
||||
│
|
||||
├── learning_log.json # Learning-Historie
|
||||
└── backups/ # Automatische Backups
|
||||
|
||||
logs/
|
||||
├── tips_generation.log # Cron Log für Generierung
|
||||
└── auto_update.log # Cron Log für Updates
|
||||
```
|
||||
|
||||
## 🔍 Features im Detail
|
||||
|
||||
### Weekly Tip Generator
|
||||
|
||||
**Intelligente Prüfung:**
|
||||
- Generiert nur wenn nötig (> 3 Tage seit letzter Generierung)
|
||||
- Berücksichtigt Wochentage (Montag/Donnerstag vor Ziehungen)
|
||||
- Vermeidet unnötige Generierungen
|
||||
|
||||
**Historie-Tracking:**
|
||||
- Speichert alle Generierungen
|
||||
- Trackt Confidence & Quality Scores
|
||||
- Ermöglicht Performance-Analyse über Zeit
|
||||
|
||||
**Ausgabe:**
|
||||
```
|
||||
📊 GENERIERUNGS-HISTORIE
|
||||
======================================================================
|
||||
Nr Datum Tips Confidence Quality Datei
|
||||
----------------------------------------------------------------------
|
||||
1 2025-01-20 09:00 10 0.5823 0.6734 weekly_tips_20250120_090000.csv
|
||||
2 2025-01-16 09:00 10 0.5891 0.6812 weekly_tips_20250116_090000.csv
|
||||
```
|
||||
|
||||
### Auto Update & Learn
|
||||
|
||||
**4-Schritt-Workflow:**
|
||||
|
||||
1. **Daten aktualisieren**
|
||||
- Lädt neueste Ziehung von API
|
||||
- Erstellt automatisches Backup
|
||||
- Validiert Daten
|
||||
|
||||
2. **Neue Ziehung prüfen**
|
||||
- Erkennt automatisch neue Ziehungen
|
||||
- Verhindert Doppel-Verarbeitung
|
||||
- Extrahiert Ziehungsdaten
|
||||
|
||||
3. **Tipps evaluieren**
|
||||
- Vergleicht letzte Tipps mit Ziehung
|
||||
- Zählt Treffer (Main + Euro)
|
||||
- Ermittelt beste Strategien
|
||||
|
||||
4. **Learning Update**
|
||||
- Passt ML-Modelle an
|
||||
- Aktualisiert Predictions
|
||||
- Speichert Performance-Metriken
|
||||
|
||||
**Evaluation Output:**
|
||||
```
|
||||
🎯 EVALUIERE LETZTE TIPPS
|
||||
======================================================================
|
||||
Tip Strategie Main Euro Gesamt Bewertung
|
||||
------------------------------------------------------------
|
||||
#1 PURE-AI 2 1 3 ✅ Gut
|
||||
#2 HYBRID-OPT 3 0 3 ✅ Gut
|
||||
#3 PURE-PATTERN 1 1 2 👍 OK
|
||||
...
|
||||
|
||||
🏆 Bester Tipp: #2 (3 Main + 0 Euro)
|
||||
```
|
||||
|
||||
## 📈 Performance-Reports
|
||||
|
||||
Reports werden automatisch nach jeder Ziehung erstellt:
|
||||
|
||||
```
|
||||
======================================================================
|
||||
EUROJACKPOT PERFORMANCE REPORT
|
||||
======================================================================
|
||||
Erstellt: 2025-01-17 21:05:23
|
||||
|
||||
ZIEHUNG VOM 2025-01-17
|
||||
----------------------------------------------------------------------
|
||||
Hauptzahlen: [3, 9, 12, 24, 39]
|
||||
Eurozahlen: [5, 10]
|
||||
|
||||
EVALUATION DER TIPPS
|
||||
----------------------------------------------------------------------
|
||||
Datei: weekly_tips_20250115_090000.csv
|
||||
Tipps evaluiert: 10
|
||||
Avg Main Matches: 1.80
|
||||
Avg Euro Matches: 0.60
|
||||
Bester Tipp: #5
|
||||
- Main Treffer: 3
|
||||
- Euro Treffer: 1
|
||||
======================================================================
|
||||
```
|
||||
|
||||
## 🛠️ Troubleshooting
|
||||
|
||||
### "No module named 'requests'"
|
||||
|
||||
```bash
|
||||
cd "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot"
|
||||
source venv/bin/activate
|
||||
pip install requests beautifulsoup4 pandas numpy scikit-learn
|
||||
```
|
||||
|
||||
### "Permission denied"
|
||||
|
||||
```bash
|
||||
chmod +x scripts/automation/*.py
|
||||
```
|
||||
|
||||
### Cron läuft nicht
|
||||
|
||||
```bash
|
||||
# Prüfe Cron-Service
|
||||
launchctl list | grep cron
|
||||
|
||||
# Prüfe System-Logs
|
||||
tail -f /var/log/system.log | grep cron
|
||||
|
||||
# Test manuell
|
||||
cd "/path/to/eurojackpot"
|
||||
source venv/bin/activate
|
||||
python scripts/automation/weekly_tip_generator.py
|
||||
```
|
||||
|
||||
### Logs sind leer
|
||||
|
||||
Stelle sicher dass das logs-Verzeichnis existiert:
|
||||
```bash
|
||||
mkdir -p logs
|
||||
```
|
||||
|
||||
## 🎯 Best Practices
|
||||
|
||||
### 1. Teste manuell vor Cron
|
||||
|
||||
```bash
|
||||
# Test 1: Tipps generieren
|
||||
python scripts/automation/weekly_tip_generator.py --force
|
||||
|
||||
# Test 2: Update & Learn
|
||||
python scripts/automation/auto_update_and_learn.py
|
||||
```
|
||||
|
||||
### 2. Überwache Logs regelmäßig
|
||||
|
||||
```bash
|
||||
# Wöchentlich prüfen
|
||||
tail -100 logs/tips_generation.log
|
||||
tail -100 logs/auto_update.log
|
||||
```
|
||||
|
||||
### 3. Backup-Strategie
|
||||
|
||||
Backups werden automatisch erstellt, aber zusätzlich:
|
||||
|
||||
```bash
|
||||
# Manuelles Backup
|
||||
cp AlleEurojackpotzahlen.csv "AlleEurojackpotzahlen_$(date +%Y%m%d).csv"
|
||||
```
|
||||
|
||||
### 4. Performance-Analyse
|
||||
|
||||
```bash
|
||||
# Zeige Generierungs-Historie
|
||||
python scripts/automation/weekly_tip_generator.py --history
|
||||
|
||||
# Analysiere Reports
|
||||
cat data/performance_reports/report_*.txt | grep "Avg Main Matches"
|
||||
```
|
||||
|
||||
## 📱 Benachrichtigungen (Optional)
|
||||
|
||||
Füge Benachrichtigungen zu Cron hinzu:
|
||||
|
||||
### macOS Notification
|
||||
|
||||
```bash
|
||||
# In Cron-Zeile hinzufügen:
|
||||
&& osascript -e 'display notification "Eurojackpot Tipps generiert" with title "Automation"'
|
||||
```
|
||||
|
||||
### Email (wenn konfiguriert)
|
||||
|
||||
```bash
|
||||
# In Cron-Zeile:
|
||||
&& echo "Tipps generiert" | mail -s "Eurojackpot Update" your@email.com
|
||||
```
|
||||
|
||||
## 🔄 Workflow-Diagramm
|
||||
|
||||
```
|
||||
WOCHE
|
||||
│
|
||||
├─ Montag 09:00
|
||||
│ └─ 🎲 Generiere Tipps für Dienstag
|
||||
│ └─ weekly_tips_TIMESTAMP.csv
|
||||
│
|
||||
├─ Dienstag 20:00
|
||||
│ └─ 🎰 Ziehung findet statt
|
||||
│
|
||||
├─ Dienstag 21:00
|
||||
│ └─ 🔄 Auto-Update & Learning
|
||||
│ ├─ Daten aktualisieren
|
||||
│ ├─ Tipps evaluieren
|
||||
│ ├─ Learning Update
|
||||
│ └─ Report erstellen
|
||||
│
|
||||
├─ Donnerstag 09:00
|
||||
│ └─ 🎲 Generiere Tipps für Freitag
|
||||
│ └─ weekly_tips_TIMESTAMP.csv
|
||||
│
|
||||
├─ Freitag 20:00
|
||||
│ └─ 🎰 Ziehung findet statt
|
||||
│
|
||||
└─ Freitag 21:00
|
||||
└─ 🔄 Auto-Update & Learning
|
||||
├─ Daten aktualisieren
|
||||
├─ Tipps evaluieren
|
||||
├─ Learning Update
|
||||
└─ Report erstellen
|
||||
```
|
||||
|
||||
## 💡 Tipps
|
||||
|
||||
1. **Erste Woche:** Nutze `--force` um initiale Tipps zu generieren
|
||||
2. **Strategie-Mix:** Das System nutzt automatisch den optimalen Mix
|
||||
3. **Learning:** Nach 4-5 Ziehungen beginnt das System besser zu werden
|
||||
4. **Reports:** Analysiere Reports monatlich für Trend-Analyse
|
||||
5. **Backup:** Die automatischen Backups werden im backups/ Ordner gespeichert
|
||||
|
||||
## 🆘 Support
|
||||
|
||||
Bei Problemen:
|
||||
1. Prüfe Logs: `tail -100 logs/*.log`
|
||||
2. Teste manuell: `python script.py`
|
||||
3. Prüfe venv: `source venv/bin/activate && pip list`
|
||||
4. Checke Cronjobs: `crontab -l`
|
||||
|
||||
---
|
||||
|
||||
**Version:** 1.0
|
||||
**Erstellt:** 2025-01-25
|
||||
**Kompatibel mit:** Ultimate AI-ML Generator V2.1
|
||||
@@ -0,0 +1,445 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Automatischer Eurojackpot Update & Learning Workflow
|
||||
|
||||
Nach jeder Ziehung:
|
||||
1. Aktualisiert Daten von API
|
||||
2. Evaluiert letzte generierte Tipps
|
||||
3. Real-Time Learning Update
|
||||
4. Performance-Report
|
||||
|
||||
Verwendung:
|
||||
python auto_update_and_learn.py
|
||||
|
||||
Oder als Cronjob (nach Ziehung):
|
||||
0 21 * * 2,5 cd /path/to/eurojackpot && source venv/bin/activate && python scripts/automation/auto_update_and_learn.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
import pandas as pd
|
||||
|
||||
# Path Setup
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_dir = os.path.dirname(os.path.dirname(script_dir))
|
||||
sys.path.insert(0, project_dir)
|
||||
|
||||
from scripts.utils.update_from_api import EurojackpotAPIUpdater
|
||||
from scripts.generators.ultimate_ai_ml_eurojackpot_generator import UltimateAIMLEurojackpotGenerator
|
||||
from scripts.utils.notifier import EurojackpotNotifier
|
||||
|
||||
|
||||
class AutoUpdateAndLearn:
|
||||
"""Automatisches Update und Learning System."""
|
||||
|
||||
def __init__(self, data_dir: str):
|
||||
self.data_dir = data_dir
|
||||
# CSV file is in Eurojackpot/data folder
|
||||
# data_dir = .../Eurojackpot/data
|
||||
# We need .../Eurojackpot/data/AlleEurojackpotzahlen.csv
|
||||
self.data_file = os.path.join(data_dir, "AlleEurojackpotzahlen.csv")
|
||||
self.tips_dir = os.path.join(data_dir, "generated_tips")
|
||||
self.reports_dir = os.path.join(data_dir, "performance_reports")
|
||||
self.learning_log = os.path.join(data_dir, "learning_log.json")
|
||||
|
||||
os.makedirs(self.reports_dir, exist_ok=True)
|
||||
|
||||
# Initialisiere Notifier
|
||||
self.notifier = EurojackpotNotifier()
|
||||
|
||||
print("🤖 AUTOMATISCHES UPDATE & LEARNING SYSTEM")
|
||||
print("=" * 70)
|
||||
|
||||
def load_learning_log(self) -> dict:
|
||||
"""Lädt Learning Log."""
|
||||
if os.path.exists(self.learning_log):
|
||||
with open(self.learning_log, 'r') as f:
|
||||
return json.load(f)
|
||||
return {"updates": []}
|
||||
|
||||
def save_learning_log(self, log: dict):
|
||||
"""Speichert Learning Log."""
|
||||
with open(self.learning_log, 'w') as f:
|
||||
json.dump(log, f, indent=2)
|
||||
|
||||
def check_for_new_draw(self) -> dict:
|
||||
"""
|
||||
Prüft ob neue Ziehung verfügbar ist.
|
||||
|
||||
Returns:
|
||||
Dict mit neuer Ziehung oder None
|
||||
"""
|
||||
print("\n🔍 PRÜFE AUF NEUE ZIEHUNG")
|
||||
print("=" * 70)
|
||||
|
||||
log = self.load_learning_log()
|
||||
|
||||
# Lade aktuelle Daten
|
||||
try:
|
||||
df = pd.read_csv(self.data_file, sep=';')
|
||||
df['datum'] = pd.to_datetime(df['datum'], format='%Y-%m-%d')
|
||||
|
||||
latest_draw = df.iloc[0] # Neueste Ziehung (sortiert absteigend)
|
||||
latest_date = latest_draw['datum']
|
||||
|
||||
print(f" 📅 Neueste Ziehung in Daten: {latest_date.strftime('%Y-%m-%d')}")
|
||||
|
||||
# Prüfe ob schon verarbeitet
|
||||
if log["updates"]:
|
||||
last_processed = log["updates"][-1].get("draw_date")
|
||||
if last_processed == latest_date.strftime('%Y-%m-%d'):
|
||||
print(f" ⏭️ Bereits verarbeitet")
|
||||
return None
|
||||
|
||||
print(f" ✅ Neue Ziehung gefunden!")
|
||||
|
||||
return {
|
||||
'date': latest_date,
|
||||
'Z1': int(latest_draw['Z1']),
|
||||
'Z2': int(latest_draw['Z2']),
|
||||
'Z3': int(latest_draw['Z3']),
|
||||
'Z4': int(latest_draw['Z4']),
|
||||
'Z5': int(latest_draw['Z5']),
|
||||
'SZ1': int(latest_draw['SZ1']),
|
||||
'SZ2': int(latest_draw['SZ2'])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler beim Prüfen: {e}")
|
||||
return None
|
||||
|
||||
def update_data(self) -> bool:
|
||||
"""
|
||||
Aktualisiert Daten von API.
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
print("\n📥 AKTUALISIERE DATEN VON API")
|
||||
print("=" * 70)
|
||||
|
||||
try:
|
||||
updater = EurojackpotAPIUpdater(self.data_file)
|
||||
success = updater.update(api_name='all', create_backup=True)
|
||||
|
||||
if success:
|
||||
print(" ✅ Daten erfolgreich aktualisiert")
|
||||
else:
|
||||
print(" ⚠️ Update ohne neue Daten")
|
||||
|
||||
return success
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler beim Update: {e}")
|
||||
return False
|
||||
|
||||
def evaluate_tips(self, new_draw: dict) -> dict:
|
||||
"""
|
||||
Evaluiert letzte generierte Tipps gegen neue Ziehung.
|
||||
|
||||
Args:
|
||||
new_draw: Dict mit neuer Ziehung
|
||||
|
||||
Returns:
|
||||
Evaluierungs-Ergebnisse
|
||||
"""
|
||||
print("\n🎯 EVALUIERE LETZTE TIPPS")
|
||||
print("=" * 70)
|
||||
|
||||
# Finde neueste Tipps-Datei
|
||||
if not os.path.exists(self.tips_dir):
|
||||
print(" ⚠️ Keine Tipps zum Evaluieren")
|
||||
return {}
|
||||
|
||||
tip_files = sorted(
|
||||
[f for f in os.listdir(self.tips_dir) if f.endswith('.csv')],
|
||||
reverse=True
|
||||
)
|
||||
|
||||
if not tip_files:
|
||||
print(" ⚠️ Keine Tipps-Dateien gefunden")
|
||||
return {}
|
||||
|
||||
latest_tips_file = os.path.join(self.tips_dir, tip_files[0])
|
||||
print(f" 📁 Evaluiere: {tip_files[0]}")
|
||||
|
||||
try:
|
||||
tips_df = pd.read_csv(latest_tips_file)
|
||||
|
||||
# Extrahiere gezogene Zahlen
|
||||
drawn_main = [new_draw[f'Z{i}'] for i in range(1, 6)]
|
||||
drawn_euro = [new_draw['SZ1'], new_draw['SZ2']]
|
||||
|
||||
print(f" 🎲 Gezogene Zahlen: {drawn_main} + Euro {drawn_euro}")
|
||||
print()
|
||||
|
||||
results = []
|
||||
best_matches = {'main': 0, 'euro': 0, 'tip': None}
|
||||
|
||||
for _, tip in tips_df.iterrows():
|
||||
# Parse Hauptzahlen
|
||||
main_str = tip['Main_Numbers']
|
||||
|
||||
# Handle both formats: "[1, 2, 3]" and "1-2-3"
|
||||
if main_str.startswith('['):
|
||||
# Parse Python list format
|
||||
import ast
|
||||
tip_main = ast.literal_eval(main_str)
|
||||
else:
|
||||
# Parse dash-separated format
|
||||
tip_main = [int(n) for n in main_str.split('-')]
|
||||
|
||||
# Parse Eurozahlen
|
||||
euro_str = tip['Euro_Numbers']
|
||||
if euro_str.startswith('['):
|
||||
import ast
|
||||
tip_euro = ast.literal_eval(euro_str)
|
||||
else:
|
||||
tip_euro = [int(n) for n in euro_str.split('-')]
|
||||
|
||||
# Zähle Treffer
|
||||
main_matches = len(set(tip_main) & set(drawn_main))
|
||||
euro_matches = len(set(tip_euro) & set(drawn_euro))
|
||||
|
||||
total_matches = main_matches + euro_matches
|
||||
|
||||
results.append({
|
||||
'tip_number': tip['Tip_Number'],
|
||||
'strategy': tip['Strategy'],
|
||||
'main_matches': main_matches,
|
||||
'euro_matches': euro_matches,
|
||||
'total_matches': total_matches
|
||||
})
|
||||
|
||||
# Track best
|
||||
if total_matches > (best_matches['main'] + best_matches['euro']):
|
||||
best_matches = {
|
||||
'main': main_matches,
|
||||
'euro': euro_matches,
|
||||
'tip': tip['Tip_Number']
|
||||
}
|
||||
|
||||
# Ausgabe
|
||||
print(f" {'Tip':<5} {'Strategie':<15} {'Main':<6} {'Euro':<6} {'Gesamt':<8} {'Bewertung'}")
|
||||
print(" " + "-" * 60)
|
||||
|
||||
for r in results:
|
||||
rating = self._get_match_rating(r['main_matches'], r['euro_matches'])
|
||||
print(f" #{r['tip_number']:<4} {r['strategy']:<15} "
|
||||
f"{r['main_matches']:<6} {r['euro_matches']:<6} "
|
||||
f"{r['total_matches']:<8} {rating}")
|
||||
|
||||
print()
|
||||
print(f" 🏆 Bester Tipp: #{best_matches['tip']} "
|
||||
f"({best_matches['main']} Main + {best_matches['euro']} Euro)")
|
||||
|
||||
return {
|
||||
'file': tip_files[0],
|
||||
'results': results,
|
||||
'best': best_matches,
|
||||
'avg_main_matches': sum(r['main_matches'] for r in results) / len(results),
|
||||
'avg_euro_matches': sum(r['euro_matches'] for r in results) / len(results)
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler bei Evaluation: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return {}
|
||||
|
||||
def _get_match_rating(self, main: int, euro: int) -> str:
|
||||
"""Bewertung der Treffer."""
|
||||
total = main + euro
|
||||
|
||||
if main == 5 and euro == 2:
|
||||
return "🏆 JACKPOT!"
|
||||
elif main == 5 and euro == 1:
|
||||
return "💰 Klasse 2"
|
||||
elif main == 5 and euro == 0:
|
||||
return "💰 Klasse 3"
|
||||
elif main == 4 and euro == 2:
|
||||
return "💰 Klasse 4"
|
||||
elif main == 4 and euro == 1:
|
||||
return "💵 Klasse 5"
|
||||
elif total >= 3:
|
||||
return "✅ Gut"
|
||||
elif total >= 2:
|
||||
return "👍 OK"
|
||||
else:
|
||||
return "⚪ Niedrig"
|
||||
|
||||
def perform_learning_update(self, new_draw: dict) -> bool:
|
||||
"""
|
||||
Führt Real-Time Learning Update durch.
|
||||
|
||||
Args:
|
||||
new_draw: Dict mit neuer Ziehung
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
print("\n🧠 REAL-TIME LEARNING UPDATE")
|
||||
print("=" * 70)
|
||||
|
||||
try:
|
||||
# Initialisiere Generator
|
||||
generator = UltimateAIMLEurojackpotGenerator(
|
||||
self.data_file,
|
||||
fast_mode=True
|
||||
)
|
||||
|
||||
# Learning Update
|
||||
generator.real_time_learner.learn_from_result(new_draw)
|
||||
|
||||
# Performance Tracking
|
||||
generator.performance_tracker.evaluate_predictions(new_draw)
|
||||
|
||||
print(" ✅ Learning Update durchgeführt")
|
||||
print(" 📊 Models wurden angepasst")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler beim Learning: {e}")
|
||||
return False
|
||||
|
||||
def generate_report(self, new_draw: dict, evaluation: dict):
|
||||
"""Generiert Performance-Report."""
|
||||
print("\n📊 PERFORMANCE-REPORT")
|
||||
print("=" * 70)
|
||||
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
report_file = os.path.join(
|
||||
self.reports_dir,
|
||||
f"report_{timestamp}.txt"
|
||||
)
|
||||
|
||||
report = []
|
||||
report.append("=" * 70)
|
||||
report.append("EUROJACKPOT PERFORMANCE REPORT")
|
||||
report.append("=" * 70)
|
||||
report.append(f"Erstellt: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
report.append("")
|
||||
report.append(f"ZIEHUNG VOM {new_draw['date'].strftime('%Y-%m-%d')}")
|
||||
report.append("-" * 70)
|
||||
report.append(f"Hauptzahlen: {[new_draw[f'Z{i}'] for i in range(1, 6)]}")
|
||||
report.append(f"Eurozahlen: [{new_draw['SZ1']}, {new_draw['SZ2']}]")
|
||||
report.append("")
|
||||
|
||||
if evaluation:
|
||||
report.append("EVALUATION DER TIPPS")
|
||||
report.append("-" * 70)
|
||||
report.append(f"Datei: {evaluation['file']}")
|
||||
report.append(f"Tipps evaluiert: {len(evaluation['results'])}")
|
||||
report.append(f"Avg Main Matches: {evaluation['avg_main_matches']:.2f}")
|
||||
report.append(f"Avg Euro Matches: {evaluation['avg_euro_matches']:.2f}")
|
||||
report.append(f"Bester Tipp: #{evaluation['best']['tip']}")
|
||||
report.append(f" - Main Treffer: {evaluation['best']['main']}")
|
||||
report.append(f" - Euro Treffer: {evaluation['best']['euro']}")
|
||||
|
||||
report.append("")
|
||||
report.append("=" * 70)
|
||||
|
||||
# Speichern
|
||||
with open(report_file, 'w') as f:
|
||||
f.write('\n'.join(report))
|
||||
|
||||
# Ausgabe
|
||||
for line in report:
|
||||
print(line)
|
||||
|
||||
print(f"\n📁 Report gespeichert: {os.path.basename(report_file)}")
|
||||
|
||||
def run(self):
|
||||
"""Führt kompletten Workflow aus."""
|
||||
print()
|
||||
print("=" * 70)
|
||||
print("START: AUTOMATISCHER UPDATE & LEARNING WORKFLOW")
|
||||
print("=" * 70)
|
||||
|
||||
# 1. Update Daten
|
||||
print("\n[SCHRITT 1/4] Daten aktualisieren")
|
||||
self.update_data()
|
||||
|
||||
# 2. Prüfe auf neue Ziehung
|
||||
print("\n[SCHRITT 2/4] Neue Ziehung prüfen")
|
||||
new_draw = self.check_for_new_draw()
|
||||
|
||||
if not new_draw:
|
||||
print("\n⏭️ Keine neue Ziehung - Workflow beendet")
|
||||
return True
|
||||
|
||||
# 3. Evaluiere Tipps
|
||||
print("\n[SCHRITT 3/4] Tipps evaluieren")
|
||||
evaluation = self.evaluate_tips(new_draw)
|
||||
|
||||
# 4. Learning Update
|
||||
print("\n[SCHRITT 4/4] Learning Update")
|
||||
self.perform_learning_update(new_draw)
|
||||
|
||||
# Report
|
||||
self.generate_report(new_draw, evaluation)
|
||||
|
||||
# Log Update
|
||||
log = self.load_learning_log()
|
||||
log["updates"].append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"draw_date": new_draw['date'].strftime('%Y-%m-%d'),
|
||||
"evaluation": evaluation.get('best', {}),
|
||||
"avg_matches": {
|
||||
'main': evaluation.get('avg_main_matches', 0),
|
||||
'euro': evaluation.get('avg_euro_matches', 0)
|
||||
}
|
||||
})
|
||||
self.save_learning_log(log)
|
||||
|
||||
# Sende Benachrichtigung
|
||||
try:
|
||||
best_match = {
|
||||
'main_matches': evaluation.get('best', {}).get('main', 0),
|
||||
'euro_matches': evaluation.get('best', {}).get('euro', 0)
|
||||
}
|
||||
|
||||
evaluation_summary = {
|
||||
'avg_main': evaluation.get('avg_main_matches', 0),
|
||||
'avg_euro': evaluation.get('avg_euro_matches', 0)
|
||||
}
|
||||
|
||||
self.notifier.send_draw_results(new_draw, evaluation_summary, best_match)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Benachrichtigung fehlgeschlagen: {e}")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ WORKFLOW ERFOLGREICH ABGESCHLOSSEN")
|
||||
print("=" * 70)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Automatisches Update & Learning nach Ziehung"
|
||||
)
|
||||
parser.add_argument(
|
||||
'--data-dir',
|
||||
type=str,
|
||||
default="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data",
|
||||
help='Daten-Verzeichnis'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Run Workflow
|
||||
workflow = AutoUpdateAndLearn(args.data_dir)
|
||||
success = workflow.run()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,278 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Wöchentlicher Eurojackpot Tipp-Generator
|
||||
|
||||
Automatisiert:
|
||||
1. Prüft ob neue Tipps nötig sind (basierend auf letzter Generierung)
|
||||
2. Generiert 10 Ultimate Tipps
|
||||
3. Speichert mit Timestamp
|
||||
4. Trackt Generierungs-Historie
|
||||
|
||||
Verwendung:
|
||||
python weekly_tip_generator.py
|
||||
|
||||
Oder als Cronjob:
|
||||
0 9 * * 2,5 cd /path/to/eurojackpot && source venv/bin/activate && python scripts/automation/weekly_tip_generator.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
|
||||
# Füge Parent-Verzeichnisse zum Path hinzu
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_dir = os.path.dirname(os.path.dirname(script_dir))
|
||||
generators_dir = os.path.join(project_dir, 'scripts', 'generators')
|
||||
sys.path.insert(0, project_dir)
|
||||
sys.path.insert(0, generators_dir)
|
||||
|
||||
# Import des Ultimate Generators
|
||||
from scripts.generators.ultimate_ai_ml_eurojackpot_generator import UltimateAIMLEurojackpotGenerator
|
||||
from scripts.utils.notifier import EurojackpotNotifier
|
||||
|
||||
|
||||
class WeeklyTipGenerator:
|
||||
"""Automatischer wöchentlicher Tipp-Generator."""
|
||||
|
||||
def __init__(self, data_dir: str):
|
||||
self.data_dir = data_dir
|
||||
# CSV file is in Eurojackpot/data folder
|
||||
# data_dir = .../Eurojackpot/data
|
||||
# We need .../Eurojackpot/data/AlleEurojackpotzahlen.csv
|
||||
self.data_file = os.path.join(data_dir, "AlleEurojackpotzahlen.csv")
|
||||
self.tips_dir = os.path.join(data_dir, "generated_tips")
|
||||
self.history_file = os.path.join(self.tips_dir, "generation_history.json")
|
||||
|
||||
os.makedirs(self.tips_dir, exist_ok=True)
|
||||
|
||||
# Initialisiere Notifier
|
||||
self.notifier = EurojackpotNotifier()
|
||||
|
||||
print("🤖 AUTOMATISCHER WÖCHENTLICHER TIPP-GENERATOR")
|
||||
print("=" * 70)
|
||||
|
||||
def load_history(self) -> dict:
|
||||
"""Lädt Generierungs-Historie."""
|
||||
if os.path.exists(self.history_file):
|
||||
with open(self.history_file, 'r') as f:
|
||||
return json.load(f)
|
||||
return {"generations": []}
|
||||
|
||||
def save_history(self, history: dict):
|
||||
"""Speichert Historie."""
|
||||
with open(self.history_file, 'w') as f:
|
||||
json.dump(history, f, indent=2)
|
||||
|
||||
def needs_new_tips(self) -> bool:
|
||||
"""
|
||||
Prüft ob neue Tipps nötig sind.
|
||||
|
||||
Logik:
|
||||
- Eurojackpot: Dienstag & Freitag Ziehungen
|
||||
- Generiere Tipps wenn:
|
||||
a) Noch nie generiert
|
||||
b) Letzte Generierung > 3 Tage her
|
||||
c) Es ist Montag oder Donnerstag (vor Ziehung)
|
||||
"""
|
||||
history = self.load_history()
|
||||
|
||||
if not history["generations"]:
|
||||
print(" ℹ️ Noch nie Tipps generiert")
|
||||
return True
|
||||
|
||||
last_gen = history["generations"][-1]
|
||||
last_date = datetime.fromisoformat(last_gen["timestamp"])
|
||||
days_since = (datetime.now() - last_date).days
|
||||
|
||||
print(f" 📅 Letzte Generierung: {last_date.strftime('%Y-%m-%d %H:%M')}")
|
||||
print(f" ⏱️ Vor {days_since} Tagen")
|
||||
|
||||
# Wenn > 3 Tage her
|
||||
if days_since > 3:
|
||||
print(" ✅ Mehr als 3 Tage her - neue Tipps nötig")
|
||||
return True
|
||||
|
||||
# Prüfe Wochentag (0=Montag, 4=Freitag)
|
||||
today = datetime.now().weekday()
|
||||
|
||||
# Montag (vor Dienstag-Ziehung)
|
||||
if today == 0 and days_since >= 1:
|
||||
print(" ✅ Montag - generiere für Dienstag-Ziehung")
|
||||
return True
|
||||
|
||||
# Donnerstag (vor Freitag-Ziehung)
|
||||
if today == 3 and days_since >= 1:
|
||||
print(" ✅ Donnerstag - generiere für Freitag-Ziehung")
|
||||
return True
|
||||
|
||||
print(" ⏭️ Keine neuen Tipps nötig")
|
||||
return False
|
||||
|
||||
def generate_tips(self, num_tips: int = 10, force: bool = False) -> bool:
|
||||
"""
|
||||
Generiert neue Tipps.
|
||||
|
||||
Args:
|
||||
num_tips: Anzahl Tipps
|
||||
force: Ignoriere needs_new_tips Check
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
print("\n🎯 TIPP-GENERIERUNG")
|
||||
print("=" * 70)
|
||||
|
||||
# Check ob nötig
|
||||
if not force and not self.needs_new_tips():
|
||||
print("\n⏭️ Keine Generierung nötig")
|
||||
return True
|
||||
|
||||
print(f"\n🚀 Generiere {num_tips} Ultimate Tipps...")
|
||||
print("-" * 70)
|
||||
|
||||
try:
|
||||
# Initialisiere Generator
|
||||
generator = UltimateAIMLEurojackpotGenerator(
|
||||
self.data_file,
|
||||
fast_mode=True
|
||||
)
|
||||
|
||||
# Generiere Tipps
|
||||
tips = generator.generate_ultimate_tips(num_tips=num_tips)
|
||||
|
||||
if not tips:
|
||||
print("\n❌ Keine Tipps generiert")
|
||||
return False
|
||||
|
||||
# Speichere Tipps
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = os.path.join(
|
||||
self.tips_dir,
|
||||
f"weekly_tips_{timestamp}.csv"
|
||||
)
|
||||
|
||||
generator.export_tips_to_csv(tips, output_file)
|
||||
|
||||
# Update Historie
|
||||
history = self.load_history()
|
||||
history["generations"].append({
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"num_tips": len(tips),
|
||||
"file": os.path.basename(output_file),
|
||||
"avg_confidence": sum(t['confidence'] for t in tips) / len(tips),
|
||||
"avg_quality": sum(t['quality'] for t in tips) / len(tips)
|
||||
})
|
||||
self.save_history(history)
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ TIPPS ERFOLGREICH GENERIERT")
|
||||
print(f"📁 Datei: {os.path.basename(output_file)}")
|
||||
print(f"📊 Anzahl: {len(tips)}")
|
||||
print(f"🎯 Avg Confidence: {history['generations'][-1]['avg_confidence']:.4f}")
|
||||
print(f"💎 Avg Quality: {history['generations'][-1]['avg_quality']:.4f}")
|
||||
print("=" * 70)
|
||||
|
||||
# Sende Benachrichtigung
|
||||
try:
|
||||
# Finde besten Tipp (höchste Confidence)
|
||||
best_tip = max(tips, key=lambda t: t.get('confidence', 0))
|
||||
|
||||
# Formatiere für Notification
|
||||
best_tip_formatted = {
|
||||
'main_numbers': best_tip.get('main_numbers', '?'),
|
||||
'euro_numbers': best_tip.get('euro_numbers', '?'),
|
||||
'confidence': best_tip.get('confidence', 0),
|
||||
'strategy': best_tip.get('strategy', 'UNKNOWN')
|
||||
}
|
||||
|
||||
timestamp_formatted = datetime.now().strftime('%Y-%m-%d %H:%M')
|
||||
self.notifier.send_tips_generated(tips, timestamp_formatted, best_tip_formatted)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Benachrichtigung fehlgeschlagen: {e}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n❌ Fehler bei Generierung: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return False
|
||||
|
||||
def show_history(self):
|
||||
"""Zeigt Generierungs-Historie."""
|
||||
history = self.load_history()
|
||||
|
||||
if not history["generations"]:
|
||||
print("\n ℹ️ Noch keine Generierungen")
|
||||
return
|
||||
|
||||
print("\n📊 GENERIERUNGS-HISTORIE")
|
||||
print("=" * 70)
|
||||
print(f"{'Nr':<4} {'Datum':<20} {'Tips':<6} {'Confidence':<12} {'Quality':<12} {'Datei'}")
|
||||
print("-" * 70)
|
||||
|
||||
for i, gen in enumerate(reversed(history["generations"][-10:]), 1):
|
||||
timestamp = datetime.fromisoformat(gen["timestamp"])
|
||||
print(f"{i:<4} {timestamp.strftime('%Y-%m-%d %H:%M'):<20} "
|
||||
f"{gen['num_tips']:<6} {gen['avg_confidence']:<12.4f} "
|
||||
f"{gen['avg_quality']:<12.4f} {gen['file']}")
|
||||
|
||||
print("-" * 70)
|
||||
print(f"Total: {len(history['generations'])} Generierungen")
|
||||
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Wöchentlicher Eurojackpot Tipp-Generator"
|
||||
)
|
||||
parser.add_argument(
|
||||
'--force',
|
||||
action='store_true',
|
||||
help='Generiere Tipps auch wenn nicht nötig'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--num-tips',
|
||||
type=int,
|
||||
default=10,
|
||||
help='Anzahl Tipps zu generieren (default: 10)'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--history',
|
||||
action='store_true',
|
||||
help='Zeige nur Historie'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--data-dir',
|
||||
type=str,
|
||||
default="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data",
|
||||
help='Daten-Verzeichnis'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
# Initialisiere
|
||||
generator = WeeklyTipGenerator(args.data_dir)
|
||||
|
||||
# Zeige Historie wenn gewünscht
|
||||
if args.history:
|
||||
generator.show_history()
|
||||
return
|
||||
|
||||
# Generiere Tipps
|
||||
success = generator.generate_tips(
|
||||
num_tips=args.num_tips,
|
||||
force=args.force
|
||||
)
|
||||
|
||||
# Zeige Historie
|
||||
generator.show_history()
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,141 @@
|
||||
import csv
|
||||
from itertools import combinations
|
||||
import time
|
||||
|
||||
print("=== EUROJACKPOT KOMBINATIONS-GENERATOR ===")
|
||||
print("Erstellt alle möglichen Kombinationen mit Status (gezogen/nicht gezogen)")
|
||||
print()
|
||||
|
||||
# Pfade
|
||||
eurojackpot_file = '/Users/sebastianfrohlich/Downloads/EJ_ab_2018.csv'
|
||||
output_file = '/Users/sebastianfrohlich/Desktop/Alle_Eurojackpot_Kombinationen_mit_Status.csv'
|
||||
missing_file = '/Users/sebastianfrohlich/Desktop/Fehlende_Eurojackpot_Kombinationen.csv'
|
||||
|
||||
# Schritt 1: Einlesen der gezogenen Kombinationen
|
||||
print("Schritt 1: Lade gezogene Eurojackpot-Kombinationen...")
|
||||
existing_combinations = set()
|
||||
|
||||
try:
|
||||
with open(eurojackpot_file, 'r', encoding='utf-8') as file:
|
||||
lines = file.readlines()
|
||||
|
||||
for line in lines[1:]: # Header überspringen
|
||||
parts = line.strip().split(';')
|
||||
if len(parts) >= 6: # Mindestens Datum + 5 Zahlen
|
||||
try:
|
||||
numbers = []
|
||||
for i in range(1, 6): # Spalten 1-5 (nach Datum)
|
||||
if i < len(parts) and parts[i].strip().isdigit():
|
||||
numbers.append(int(parts[i].strip()))
|
||||
|
||||
if len(numbers) == 5: # Vollständige Kombination
|
||||
combo = frozenset(numbers)
|
||||
existing_combinations.add(combo)
|
||||
|
||||
except (ValueError, IndexError):
|
||||
continue
|
||||
|
||||
print(f" ✓ {len(existing_combinations)} gezogene Kombinationen geladen")
|
||||
|
||||
# Beispiele anzeigen
|
||||
if existing_combinations:
|
||||
print(" Beispiele:")
|
||||
for i, combo in enumerate(list(existing_combinations)[:3]):
|
||||
sorted_combo = sorted(list(combo))
|
||||
print(f" {sorted_combo}")
|
||||
if len(existing_combinations) > 3:
|
||||
print(f" ... und {len(existing_combinations)-3} weitere")
|
||||
|
||||
except FileNotFoundError:
|
||||
print(f" ⚠️ Datei nicht gefunden: {eurojackpot_file}")
|
||||
existing_combinations = set()
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Fehler beim Lesen: {e}")
|
||||
existing_combinations = set()
|
||||
|
||||
print()
|
||||
|
||||
# Schritt 2: Generierung aller möglichen Kombinationen
|
||||
print("Schritt 2: Generiere alle möglichen Kombinationen (5 aus 50)...")
|
||||
total_combinations = 2118760 # C(50,5)
|
||||
print(f" Gesamtanzahl zu verarbeitender Kombinationen: {total_combinations:,}")
|
||||
|
||||
all_combinations = []
|
||||
count = 0
|
||||
start_time = time.time()
|
||||
|
||||
for combo in combinations(range(1, 51), 5):
|
||||
count += 1
|
||||
|
||||
# Fortschritt anzeigen
|
||||
if count % 100000 == 0:
|
||||
elapsed = time.time() - start_time
|
||||
rate = count / elapsed if elapsed > 0 else 0
|
||||
remaining = (total_combinations - count) / rate if rate > 0 else 0
|
||||
print(f" Fortschritt: {count:,} / {total_combinations:,} "
|
||||
f"({count/total_combinations*100:.1f}%) "
|
||||
f"- {rate:,.0f}/s - noch ~{remaining/60:.1f} min")
|
||||
|
||||
# Prüfen ob Kombination bereits gezogen wurde
|
||||
sorted_combo = sorted(combo)
|
||||
is_drawn = 1 if frozenset(combo) in existing_combinations else 0
|
||||
|
||||
# Zur Liste hinzufügen (Z1, Z2, Z3, Z4, Z5, gezogen)
|
||||
combination_row = sorted_combo + [is_drawn]
|
||||
all_combinations.append(combination_row)
|
||||
|
||||
print(f" ✓ Alle {len(all_combinations):,} Kombinationen generiert")
|
||||
print()
|
||||
|
||||
# Schritt 3: Statistiken berechnen
|
||||
print("Schritt 3: Berechne Statistiken...")
|
||||
drawn_combinations = sum(1 for combo in all_combinations if combo[5] == 1)
|
||||
undrawn_combinations = len(all_combinations) - drawn_combinations
|
||||
|
||||
print(f" Gesamt mögliche Kombinationen: {len(all_combinations):,}")
|
||||
print(f" Bereits gezogene Kombinationen: {drawn_combinations:,}")
|
||||
print(f" Noch nicht gezogene Kombinationen: {undrawn_combinations:,}")
|
||||
if len(all_combinations) > 0:
|
||||
percentage = drawn_combinations / len(all_combinations) * 100
|
||||
print(f" Anteil gezogener Kombinationen: {percentage:.6f}%")
|
||||
print()
|
||||
|
||||
# Schritt 4: Hauptdatei speichern
|
||||
print("Schritt 4: Speichere Hauptdatei...")
|
||||
try:
|
||||
with open(output_file, 'w', newline='', encoding='utf-8') as outfile:
|
||||
writer = csv.writer(outfile, delimiter=';')
|
||||
writer.writerow(['Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'gezogen']) # Header
|
||||
for combo in all_combinations:
|
||||
writer.writerow(combo)
|
||||
print(f" ✓ Hauptdatei gespeichert: {output_file}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Fehler beim Speichern der Hauptdatei: {e}")
|
||||
|
||||
# Schritt 5: Datei mit fehlenden Kombinationen speichern
|
||||
print("Schritt 5: Speichere Datei mit fehlenden Kombinationen...")
|
||||
try:
|
||||
missing_combinations = [combo[:5] for combo in all_combinations if combo[5] == 0]
|
||||
with open(missing_file, 'w', newline='', encoding='utf-8') as outfile:
|
||||
writer = csv.writer(outfile, delimiter=';')
|
||||
writer.writerow(['Z1', 'Z2', 'Z3', 'Z4', 'Z5']) # Header
|
||||
for combo in missing_combinations:
|
||||
writer.writerow(combo)
|
||||
print(f" ✓ Datei mit fehlenden Kombinationen gespeichert: {missing_file}")
|
||||
print(f" ✓ {len(missing_combinations):,} fehlende Kombinationen")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Fehler beim Speichern der fehlenden Kombinationen: {e}")
|
||||
|
||||
print()
|
||||
print("=== FERTIG ===")
|
||||
print("Erstellte Dateien:")
|
||||
print(f"1. {output_file}")
|
||||
print(f" - Alle {len(all_combinations):,} Kombinationen mit Status")
|
||||
print(f"2. {missing_file}")
|
||||
if 'missing_combinations' in locals():
|
||||
print(f" - {len(missing_combinations):,} noch nicht gezogene Kombinationen")
|
||||
print()
|
||||
print("Format der Hauptdatei:")
|
||||
print("Z1;Z2;Z3;Z4;Z5;gezogen")
|
||||
print("1;2;3;4;5;0")
|
||||
print("...")
|
||||
@@ -0,0 +1,806 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Ultimativer Eurojackpot Tipp-Generator mit Multi-Ziehungs-Trend-Analyse
|
||||
|
||||
Kombiniert multiple Optimierungsstrategien:
|
||||
- Historische Häufigkeitsanalyse
|
||||
- Positionsbasierte Gewichtung
|
||||
- Multi-Ziehungs-Trend-Analyse (NEU!)
|
||||
- Zahlen-Momentum-Tracking
|
||||
- Sequenzielle Abhängigkeiten
|
||||
- Zyklische Muster-Erkennung
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import random
|
||||
import numpy as np
|
||||
from itertools import combinations
|
||||
from collections import Counter, defaultdict, deque
|
||||
import datetime
|
||||
|
||||
class UltimativerEurojackpotGenerator:
|
||||
def __init__(self, data_path="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv"):
|
||||
self.data_path = data_path
|
||||
self.df = None
|
||||
self.drawn_combinations = set()
|
||||
|
||||
# Basis-Analyse (Original)
|
||||
self.number_frequencies = Counter()
|
||||
self.position_frequencies = defaultdict(Counter)
|
||||
self.pattern_frequencies = Counter()
|
||||
self.supernumber_frequencies = {'sz1': Counter(), 'sz2': Counter()}
|
||||
self.number_distances = []
|
||||
|
||||
# Multi-Ziehungs-Trend-Analyse (NEU!)
|
||||
self.number_sequences = defaultdict(list)
|
||||
self.momentum_scores = {}
|
||||
self.trend_predictions = {}
|
||||
self.sequential_dependencies = defaultdict(lambda: defaultdict(int))
|
||||
self.cycle_patterns = {}
|
||||
self.hot_numbers = []
|
||||
self.warm_numbers = []
|
||||
self.cold_numbers = []
|
||||
|
||||
# Initialisierung
|
||||
self.load_and_analyze_all_data()
|
||||
|
||||
def load_and_analyze_all_data(self):
|
||||
"""Lädt Daten und führt alle Analysen durch."""
|
||||
try:
|
||||
self.df = pd.read_csv(self.data_path, sep=';')
|
||||
|
||||
# Chronologische Sortierung für Trend-Analyse
|
||||
if 'Datum' in self.df.columns:
|
||||
self.df['Datum'] = pd.to_datetime(self.df['Datum'], format='%d.%m.%Y')
|
||||
self.df = self.df.sort_values('Datum')
|
||||
|
||||
print(f"🚀 ULTIMATIVER EUROJACKPOT GENERATOR")
|
||||
print("=" * 60)
|
||||
print(f"📊 Analysiere {len(self.df)} Ziehungen mit Multi-Trend-Analyse...")
|
||||
|
||||
# Basis-Analysen durchführen
|
||||
self._perform_basic_analysis()
|
||||
|
||||
# Multi-Ziehungs-Trend-Analysen durchführen (NEU!)
|
||||
self._perform_momentum_analysis()
|
||||
self._perform_sequential_analysis()
|
||||
self._perform_cycle_analysis()
|
||||
self._generate_trend_predictions()
|
||||
|
||||
print(f"✅ Komplette Analyse abgeschlossen!")
|
||||
self._print_ultimate_analysis_summary()
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler beim Laden der Daten: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _perform_basic_analysis(self):
|
||||
"""Führt die ursprünglichen Basis-Analysen durch."""
|
||||
for _, row in self.df.iterrows():
|
||||
# Gezogene Kombinationen
|
||||
combo = tuple(sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']]))
|
||||
self.drawn_combinations.add(combo)
|
||||
|
||||
# Zahlenfrequenzen
|
||||
numbers = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']]
|
||||
for num in numbers:
|
||||
self.number_frequencies[num] += 1
|
||||
|
||||
# Positionsfrequenzen
|
||||
sorted_numbers = sorted(numbers)
|
||||
for i, num in enumerate(sorted_numbers):
|
||||
self.position_frequencies[f'pos_{i+1}'][num] += 1
|
||||
|
||||
# Muster analysieren
|
||||
pattern = self._get_pattern(sorted_numbers)
|
||||
self.pattern_frequencies[pattern] += 1
|
||||
|
||||
# Superzahlen
|
||||
self.supernumber_frequencies['sz1'][row['SZ1']] += 1
|
||||
self.supernumber_frequencies['sz2'][row['SZ2']] += 1
|
||||
|
||||
# Zahlenabstände
|
||||
distances = [sorted_numbers[i+1] - sorted_numbers[i] for i in range(4)]
|
||||
self.number_distances.extend(distances)
|
||||
|
||||
def _perform_momentum_analysis(self, window_size=15):
|
||||
"""Führt Multi-Ziehungs-Momentum-Analyse durch (NEU!)"""
|
||||
print(f"\n🔥 MOMENTUM-ANALYSE (Fenster: {window_size})")
|
||||
|
||||
# Zahlensequenzen über Zeit aufbauen
|
||||
for number in range(1, 51):
|
||||
sequence = []
|
||||
for _, row in self.df.iterrows():
|
||||
drawn_numbers = [row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']]
|
||||
sequence.append(1 if number in drawn_numbers else 0)
|
||||
self.number_sequences[number] = sequence
|
||||
|
||||
# Momentum-Scores berechnen
|
||||
momentum_results = {}
|
||||
for number in range(1, 51):
|
||||
recent_sequence = self.number_sequences[number][-window_size:]
|
||||
|
||||
hit_rate = sum(recent_sequence) / len(recent_sequence)
|
||||
trend_score = self._calculate_trend_score(recent_sequence)
|
||||
recency_score = self._calculate_recency_score(recent_sequence)
|
||||
|
||||
# Kombinierter Momentum-Score mit Gewichtung
|
||||
momentum_score = (hit_rate * 0.4) + (trend_score * 0.4) + (recency_score * 0.2)
|
||||
|
||||
momentum_results[number] = {
|
||||
'hit_rate': hit_rate,
|
||||
'trend_score': trend_score,
|
||||
'recency_score': recency_score,
|
||||
'momentum_score': momentum_score,
|
||||
'status': self._get_momentum_status(momentum_score)
|
||||
}
|
||||
|
||||
self.momentum_scores = momentum_results
|
||||
|
||||
# Zahlen in Kategorien einteilen
|
||||
sorted_momentum = sorted(momentum_results.items(),
|
||||
key=lambda x: x[1]['momentum_score'], reverse=True)
|
||||
|
||||
self.hot_numbers = [num for num, data in sorted_momentum[:20]
|
||||
if data['momentum_score'] > 0.35]
|
||||
self.warm_numbers = [num for num, data in sorted_momentum[20:35]
|
||||
if 0.2 <= data['momentum_score'] <= 0.35]
|
||||
self.cold_numbers = [num for num, data in sorted_momentum[35:]
|
||||
if data['momentum_score'] < 0.2][:15]
|
||||
|
||||
print(f"🔥 {len(self.hot_numbers)} heiße Zahlen identifiziert")
|
||||
print(f"🌡️ {len(self.warm_numbers)} warme Zahlen identifiziert")
|
||||
print(f"🧊 {len(self.cold_numbers)} kalte Zahlen identifiziert")
|
||||
|
||||
def _perform_sequential_analysis(self, look_back=3):
|
||||
"""Analysiert sequenzielle Abhängigkeiten zwischen Ziehungen (NEU!)"""
|
||||
print(f"\n🔗 SEQUENZIELLE ABHÄNGIGKEITEN (Look-back: {look_back})")
|
||||
|
||||
for i in range(look_back, len(self.df)):
|
||||
current_numbers = set([self.df.iloc[i]['Z1'], self.df.iloc[i]['Z2'],
|
||||
self.df.iloc[i]['Z3'], self.df.iloc[i]['Z4'],
|
||||
self.df.iloc[i]['Z5']])
|
||||
|
||||
for j in range(1, look_back + 1):
|
||||
prev_numbers = set([self.df.iloc[i-j]['Z1'], self.df.iloc[i-j]['Z2'],
|
||||
self.df.iloc[i-j]['Z3'], self.df.iloc[i-j]['Z4'],
|
||||
self.df.iloc[i-j]['Z5']])
|
||||
|
||||
for prev_num in prev_numbers:
|
||||
for curr_num in current_numbers:
|
||||
self.sequential_dependencies[f"lag_{j}"][f"{prev_num}_{curr_num}"] += 1
|
||||
|
||||
print(f"🔗 Sequenzielle Muster für {look_back} Ziehungen analysiert")
|
||||
|
||||
def _perform_cycle_analysis(self, max_cycle_length=15):
|
||||
"""Analysiert zyklische Muster (NEU!)"""
|
||||
print(f"\n🔄 ZYKLUS-ANALYSE")
|
||||
|
||||
pattern_sequence = []
|
||||
for _, row in self.df.iterrows():
|
||||
numbers = sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']])
|
||||
pattern = self._get_pattern(numbers)
|
||||
pattern_sequence.append(pattern)
|
||||
|
||||
# Zyklen erkennen
|
||||
self.cycle_patterns = {}
|
||||
for cycle_length in range(3, max_cycle_length + 1):
|
||||
cycles = self._find_pattern_cycles(pattern_sequence, cycle_length)
|
||||
if cycles:
|
||||
self.cycle_patterns[cycle_length] = cycles
|
||||
|
||||
cycle_count = sum(len(cycles) for cycles in self.cycle_patterns.values())
|
||||
print(f"🔄 {cycle_count} zyklische Muster erkannt")
|
||||
|
||||
def _generate_trend_predictions(self):
|
||||
"""Generiert Trend-basierte Vorhersagen (NEU!)"""
|
||||
print(f"\n🎯 TREND-VORHERSAGEN GENERIEREN")
|
||||
|
||||
for number in range(1, 51):
|
||||
if number in self.momentum_scores:
|
||||
momentum_data = self.momentum_scores[number]
|
||||
|
||||
# Multi-Faktor-Vorhersage-Score
|
||||
momentum_weight = momentum_data['momentum_score'] * 0.4
|
||||
frequency_weight = (self.number_frequencies[number] / len(self.df)) * 0.3
|
||||
trend_weight = max(0, momentum_data['trend_score']) * 0.3
|
||||
|
||||
prediction_score = momentum_weight + frequency_weight + trend_weight
|
||||
|
||||
self.trend_predictions[number] = {
|
||||
'prediction_score': prediction_score,
|
||||
'recommendation': self._get_prediction_recommendation(prediction_score),
|
||||
'confidence': self._get_confidence_level(prediction_score)
|
||||
}
|
||||
|
||||
print(f"🎯 Trend-Vorhersagen für alle 50 Zahlen generiert")
|
||||
|
||||
def _calculate_trend_score(self, sequence):
|
||||
"""Berechnet Trend-Score für eine Zahlensequenz."""
|
||||
if len(sequence) < 2:
|
||||
return 0
|
||||
|
||||
x = np.arange(len(sequence))
|
||||
y = np.array(sequence)
|
||||
weights = np.exp(x / len(x)) # Neuere Ziehungen wichtiger
|
||||
|
||||
try:
|
||||
coeffs = np.polyfit(x, y, 1, w=weights)
|
||||
return coeffs[0] # Steigung = Trend
|
||||
except:
|
||||
return 0
|
||||
|
||||
def _calculate_recency_score(self, sequence):
|
||||
"""Berechnet Recency-Score."""
|
||||
try:
|
||||
last_hit_index = len(sequence) - 1 - sequence[::-1].index(1)
|
||||
recency = 1 - (len(sequence) - 1 - last_hit_index) / len(sequence)
|
||||
return recency
|
||||
except ValueError:
|
||||
return 0
|
||||
|
||||
def _get_momentum_status(self, score):
|
||||
"""Klassifiziert Momentum-Status."""
|
||||
if score > 0.5:
|
||||
return "🔥 SEHR HEISS"
|
||||
elif score > 0.35:
|
||||
return "🌡️ HEISS"
|
||||
elif score > 0.2:
|
||||
return "😐 WARM"
|
||||
elif score > 0.1:
|
||||
return "🧊 KÜHL"
|
||||
else:
|
||||
return "❄️ EISKALT"
|
||||
|
||||
def _get_prediction_recommendation(self, score):
|
||||
"""Empfehlung basierend auf Vorhersage-Score."""
|
||||
if score > 0.4:
|
||||
return "SEHR EMPFOHLEN"
|
||||
elif score > 0.25:
|
||||
return "EMPFOHLEN"
|
||||
elif score > 0.15:
|
||||
return "NEUTRAL"
|
||||
else:
|
||||
return "VERMEIDEN"
|
||||
|
||||
def _get_confidence_level(self, score):
|
||||
"""Konfidenz-Level für Vorhersagen."""
|
||||
if score > 0.4:
|
||||
return "HOCH"
|
||||
elif score > 0.25:
|
||||
return "MITTEL"
|
||||
else:
|
||||
return "NIEDRIG"
|
||||
|
||||
def _find_pattern_cycles(self, sequence, cycle_length):
|
||||
"""Findet zyklische Muster."""
|
||||
cycle_patterns = defaultdict(list)
|
||||
|
||||
for i in range(len(sequence) - cycle_length):
|
||||
pattern = ''.join(sequence[i:i+cycle_length])
|
||||
cycle_patterns[pattern].append(i)
|
||||
|
||||
return {pattern: positions for pattern, positions in cycle_patterns.items()
|
||||
if len(positions) >= 2}
|
||||
|
||||
def _get_pattern(self, numbers):
|
||||
"""Bestimmt N/M/H-Muster."""
|
||||
pattern = []
|
||||
for num in numbers:
|
||||
if 1 <= num <= 15:
|
||||
pattern.append('N')
|
||||
elif 16 <= num <= 35:
|
||||
pattern.append('M')
|
||||
else:
|
||||
pattern.append('H')
|
||||
return ''.join(pattern)
|
||||
|
||||
def _print_ultimate_analysis_summary(self):
|
||||
"""Gibt detaillierte Analyse-Zusammenfassung aus."""
|
||||
print(f"\n📈 ULTIMATE ANALYSE-ZUSAMMENFASSUNG:")
|
||||
print("=" * 55)
|
||||
|
||||
# Top Trend-Empfehlungen
|
||||
print("\n🎯 TOP 10 TREND-EMPFEHLUNGEN:")
|
||||
top_predictions = sorted(self.trend_predictions.items(),
|
||||
key=lambda x: x[1]['prediction_score'], reverse=True)[:10]
|
||||
for i, (number, data) in enumerate(top_predictions):
|
||||
momentum_status = self.momentum_scores[number]['status']
|
||||
print(f"{i+1:2}. Zahl {number:2}: Score {data['prediction_score']:.3f} "
|
||||
f"({data['recommendation']}) {momentum_status}")
|
||||
|
||||
# Top Muster mit Trend-Integration
|
||||
print(f"\n🎨 TOP MUSTER (kombiniert mit Trends):")
|
||||
for pattern, count in self.pattern_frequencies.most_common(5):
|
||||
percentage = (count / len(self.drawn_combinations)) * 100
|
||||
print(f" {pattern}: {count}x ({percentage:.1f}%)")
|
||||
|
||||
# Zyklische Erkenntnisse
|
||||
if self.cycle_patterns:
|
||||
print(f"\n🔄 ERKANNTE ZYKLEN:")
|
||||
total_cycles = sum(len(cycles) for cycles in self.cycle_patterns.values())
|
||||
print(f" Insgesamt {total_cycles} zyklische Muster erkannt")
|
||||
|
||||
def generate_ultimate_combination(self):
|
||||
"""Generiert ultimativ optimierte Kombination mit allen Methoden."""
|
||||
max_attempts = 1000
|
||||
|
||||
for attempt in range(max_attempts):
|
||||
numbers = []
|
||||
|
||||
# Strategie-Mix basierend auf Trends:
|
||||
# 40% Top-Trend-Zahlen, 30% Heiße Zahlen, 20% Position-optimiert, 10% Balance
|
||||
|
||||
# 2 Zahlen aus Top-Trend-Empfehlungen (40%)
|
||||
top_trend_numbers = [num for num, data in sorted(self.trend_predictions.items(),
|
||||
key=lambda x: x[1]['prediction_score'], reverse=True)[:15]
|
||||
if data['recommendation'] in ['SEHR EMPFOHLEN', 'EMPFOHLEN']]
|
||||
|
||||
if len(top_trend_numbers) >= 2:
|
||||
trend_picks = random.sample(top_trend_numbers[:10], 2)
|
||||
numbers.extend(trend_picks)
|
||||
|
||||
# 2 Zahlen aus heißen Zahlen (30%)
|
||||
if len(self.hot_numbers) >= 2:
|
||||
remaining_hot = [n for n in self.hot_numbers if n not in numbers]
|
||||
if len(remaining_hot) >= 2:
|
||||
hot_picks = random.sample(remaining_hot[:8], min(2, len(remaining_hot)))
|
||||
numbers.extend(hot_picks)
|
||||
|
||||
# 1 Zahl positions-optimiert oder warm (30%)
|
||||
remaining_slots = 5 - len(numbers)
|
||||
if remaining_slots > 0:
|
||||
if self.warm_numbers:
|
||||
remaining_warm = [n for n in self.warm_numbers if n not in numbers]
|
||||
if remaining_warm:
|
||||
warm_pick = random.choice(remaining_warm[:5])
|
||||
numbers.append(warm_pick)
|
||||
|
||||
# Auffüllen bis 5 Zahlen
|
||||
while len(numbers) < 5:
|
||||
available_numbers = [n for n in range(1, 51) if n not in numbers]
|
||||
|
||||
# Gewichtete Auswahl basierend auf Trend-Scores
|
||||
weights = [self.trend_predictions[n]['prediction_score'] for n in available_numbers]
|
||||
if sum(weights) > 0:
|
||||
additional_number = random.choices(available_numbers, weights=weights)[0]
|
||||
else:
|
||||
additional_number = random.choice(available_numbers)
|
||||
|
||||
numbers.append(additional_number)
|
||||
|
||||
# Sortieren und validieren
|
||||
numbers = sorted(numbers[:5])
|
||||
|
||||
if self._validate_ultimate_combination(numbers):
|
||||
return numbers
|
||||
|
||||
# Fallback
|
||||
return self._generate_fallback_combination()
|
||||
|
||||
def _validate_ultimate_combination(self, numbers):
|
||||
"""Erweiterte Validierung mit Trend-Kriterien."""
|
||||
# Basis-Validierung
|
||||
if tuple(numbers) in self.drawn_combinations:
|
||||
return False
|
||||
|
||||
if len(set(numbers)) != 5:
|
||||
return False
|
||||
|
||||
# Trend-Validierung
|
||||
hot_count = sum(1 for n in numbers if n in self.hot_numbers)
|
||||
high_trend_count = sum(1 for n in numbers
|
||||
if self.trend_predictions[n]['recommendation'] == 'SEHR EMPFOHLEN')
|
||||
|
||||
# Mindestens 1 heiße oder 1 sehr empfohlene Zahl
|
||||
if hot_count == 0 and high_trend_count == 0:
|
||||
return False
|
||||
|
||||
# Standard-Validierungen
|
||||
distances = [numbers[i+1] - numbers[i] for i in range(4)]
|
||||
if min(distances) < 2 or max(distances) > 18:
|
||||
return False
|
||||
|
||||
even_count = sum(1 for n in numbers if n % 2 == 0)
|
||||
if even_count == 0 or even_count == 5:
|
||||
return False
|
||||
|
||||
total = sum(numbers)
|
||||
if total < 80 or total > 170:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _generate_fallback_combination(self):
|
||||
"""Fallback mit Trend-Integration."""
|
||||
numbers = []
|
||||
|
||||
# Basis: NMMHH mit Trend-Gewichtung
|
||||
ranges = {
|
||||
'N': [n for n in range(1, 16) if n in self.hot_numbers + self.warm_numbers] or list(range(1, 16)),
|
||||
'M': [n for n in range(16, 36) if n in self.hot_numbers + self.warm_numbers] or list(range(16, 36)),
|
||||
'H': [n for n in range(36, 51) if n in self.hot_numbers + self.warm_numbers] or list(range(36, 51))
|
||||
}
|
||||
|
||||
numbers.extend(random.sample(ranges['N'][:10], 1))
|
||||
numbers.extend(random.sample(ranges['M'][:15], 2))
|
||||
numbers.extend(random.sample(ranges['H'][:10], 2))
|
||||
|
||||
return sorted(numbers)
|
||||
|
||||
def get_optimized_supernumbers(self):
|
||||
"""Trend-optimierte Superzahlen-Auswahl."""
|
||||
# Integration von Trend-Daten für Superzahlen
|
||||
sz1_trends = {}
|
||||
sz2_trends = {}
|
||||
|
||||
# Letzte 10 Ziehungen analysieren
|
||||
recent_df = self.df.tail(10)
|
||||
|
||||
for sz1 in range(1, 13): # Eurojackpot SZ1: 1-12
|
||||
recent_count = (recent_df['SZ1'] == sz1).sum()
|
||||
total_count = self.supernumber_frequencies['sz1'][sz1]
|
||||
trend_score = (recent_count / 10) * 0.6 + (total_count / len(self.df)) * 0.4
|
||||
sz1_trends[sz1] = trend_score
|
||||
|
||||
for sz2 in range(1, 11): # Eurojackpot SZ2: 1-10
|
||||
recent_count = (recent_df['SZ2'] == sz2).sum()
|
||||
total_count = self.supernumber_frequencies['sz2'][sz2]
|
||||
trend_score = (recent_count / 10) * 0.6 + (total_count / len(self.df)) * 0.4
|
||||
sz2_trends[sz2] = trend_score
|
||||
|
||||
# Gewichtete Auswahl
|
||||
sz1_candidates = list(sz1_trends.keys())
|
||||
sz1_weights = list(sz1_trends.values())
|
||||
sz1 = random.choices(sz1_candidates, weights=sz1_weights)[0]
|
||||
|
||||
sz2_candidates = list(sz2_trends.keys())
|
||||
sz2_weights = list(sz2_trends.values())
|
||||
sz2 = random.choices(sz2_candidates, weights=sz2_weights)[0]
|
||||
|
||||
return sz1, sz2
|
||||
|
||||
def generate_ultimate_tips(self, num_tips=10):
|
||||
"""Generiert ultimative Tipps mit kompletter Multi-Trend-Integration."""
|
||||
print(f"\n🚀 ULTIMATE TIPP-GENERIERUNG")
|
||||
print("=" * 50)
|
||||
print(f"🎯 Kombiniert ALLE Optimierungsstrategien:")
|
||||
print(f" ✅ Historische Häufigkeitsanalyse")
|
||||
print(f" ✅ Positionsbasierte Gewichtung")
|
||||
print(f" ✅ Multi-Ziehungs-Momentum-Analyse")
|
||||
print(f" ✅ Sequenzielle Abhängigkeiten")
|
||||
print(f" ✅ Zyklische Muster-Erkennung")
|
||||
print(f" ✅ Trend-Vorhersage-Algorithmus")
|
||||
|
||||
generated_tips = []
|
||||
strategy_distribution = Counter()
|
||||
|
||||
print(f"\n🎲 GENERIERE {num_tips} ULTIMATE TIPPS:")
|
||||
print("=" * 60)
|
||||
print(f"{'Nr':<3} {'Zahlen':<20} {'Muster':<7} {'🔥':<3} {'🎯':<3} {'Status'}")
|
||||
print("-" * 60)
|
||||
|
||||
attempts = 0
|
||||
max_attempts = num_tips * 50
|
||||
|
||||
while len(generated_tips) < num_tips and attempts < max_attempts:
|
||||
attempts += 1
|
||||
|
||||
combination = self.generate_ultimate_combination()
|
||||
|
||||
if combination and tuple(combination) not in [tuple(tip['zahlen']) for tip in generated_tips]:
|
||||
pattern = self._get_pattern(combination)
|
||||
|
||||
# Trend-Analyse des Tipps
|
||||
hot_count = sum(1 for n in combination if n in self.hot_numbers)
|
||||
trend_count = sum(1 for n in combination
|
||||
if self.trend_predictions[n]['recommendation'] in ['SEHR EMPFOHLEN', 'EMPFOHLEN'])
|
||||
|
||||
# Superzahlen
|
||||
sz1, sz2 = self.get_optimized_supernumbers()
|
||||
|
||||
# Strategie-Klassifikation
|
||||
if hot_count >= 3:
|
||||
strategy = "🔥 MOMENTUM"
|
||||
elif trend_count >= 3:
|
||||
strategy = "🎯 TREND"
|
||||
elif pattern in ['NMMHH', 'MNMHH', 'NHMHM']:
|
||||
strategy = "🎨 MUSTER"
|
||||
else:
|
||||
strategy = "⚖️ BALANCE"
|
||||
|
||||
strategy_distribution[strategy] += 1
|
||||
|
||||
tip = {
|
||||
'tipp_nr': len(generated_tips) + 1,
|
||||
'zahlen': combination,
|
||||
'z1': combination[0],
|
||||
'z2': combination[1],
|
||||
'z3': combination[2],
|
||||
'z4': combination[3],
|
||||
'z5': combination[4],
|
||||
'sz1': sz1,
|
||||
'sz2': sz2,
|
||||
'muster': pattern,
|
||||
'summe': sum(combination),
|
||||
'hot_count': hot_count,
|
||||
'trend_count': trend_count,
|
||||
'strategy': strategy
|
||||
}
|
||||
|
||||
generated_tips.append(tip)
|
||||
|
||||
# Status ausgeben
|
||||
zahlen_str = f"{combination[0]:2}-{combination[1]:2}-{combination[2]:2}-{combination[3]:2}-{combination[4]:2}"
|
||||
print(f"{len(generated_tips):2}. {zahlen_str:<20} {pattern:<7} {hot_count:<3} {trend_count:<3} {strategy}")
|
||||
|
||||
# Ultimate Zusammenfassung
|
||||
print(f"\n🏆 ULTIMATE OPTIMIERUNGS-ZUSAMMENFASSUNG:")
|
||||
print("=" * 50)
|
||||
print(f"✅ {len(generated_tips)} Ultimate Tipps generiert")
|
||||
print(f"🎯 Erfolgsrate: {(len(generated_tips)/attempts)*100:.1f}%")
|
||||
print(f"📊 Durchschnitt {sum(tip['hot_count'] for tip in generated_tips)/len(generated_tips):.1f} heiße Zahlen pro Tipp")
|
||||
print(f"🔮 Durchschnitt {sum(tip['trend_count'] for tip in generated_tips)/len(generated_tips):.1f} Trend-Zahlen pro Tipp")
|
||||
|
||||
# Strategie-Verteilung
|
||||
print(f"\n📈 STRATEGIE-VERTEILUNG:")
|
||||
for strategy, count in strategy_distribution.most_common():
|
||||
print(f" {strategy}: {count} Tipps")
|
||||
|
||||
# Komplette Tipp-Ausgabe
|
||||
print(f"\n🎯 ULTIMATE TIPP-EMPFEHLUNGEN:")
|
||||
print("=" * 70)
|
||||
print(f"{'Nr':<3} {'Hauptzahlen':<20} {'SZ1':<4} {'SZ2':<4} {'Muster':<7} {'Strategie':<12} {'Score'}")
|
||||
print("-" * 70)
|
||||
|
||||
for tip in generated_tips:
|
||||
zahlen_str = f"{tip['z1']:2}-{tip['z2']:2}-{tip['z3']:2}-{tip['z4']:2}-{tip['z5']:2}"
|
||||
|
||||
# Ultimate Score berechnen
|
||||
ultimate_score = (tip['hot_count'] * 0.3 + tip['trend_count'] * 0.4 +
|
||||
(5 if tip['muster'] in ['NMMHH', 'MNMHH'] else 3) * 0.3)
|
||||
|
||||
print(f"{tip['tipp_nr']:2}. {zahlen_str:<20} {tip['sz1']:<4} {tip['sz2']:<4} "
|
||||
f"{tip['muster']:<7} {tip['strategy']:<12} {ultimate_score:.1f}")
|
||||
|
||||
# Export mit Ultimate Features
|
||||
self._export_ultimate_tips(generated_tips)
|
||||
|
||||
return generated_tips
|
||||
|
||||
def _export_ultimate_tips(self, tips):
|
||||
"""Exportiert Ultimate Tipps mit erweiterten Daten."""
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = f"/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/ultimate_tipps_{timestamp}.csv"
|
||||
|
||||
# Erweiterte Daten für Export
|
||||
export_data = []
|
||||
for tip in tips:
|
||||
tip_data = tip.copy()
|
||||
|
||||
# Zusätzliche Trend-Daten hinzufügen
|
||||
tip_data['trend_scores'] = [self.trend_predictions[n]['prediction_score']
|
||||
for n in tip['zahlen']]
|
||||
tip_data['avg_trend_score'] = np.mean(tip_data['trend_scores'])
|
||||
tip_data['momentum_ratings'] = [self.momentum_scores[n]['status']
|
||||
for n in tip['zahlen']]
|
||||
|
||||
export_data.append(tip_data)
|
||||
|
||||
tips_df = pd.DataFrame(export_data)
|
||||
tips_df.to_csv(output_file, sep=';', index=False)
|
||||
|
||||
print(f"\n💾 ULTIMATE EXPORT:")
|
||||
print("=" * 25)
|
||||
print(f"✅ Ultimate Tipps gespeichert: ultimate_tipps_{timestamp}.csv")
|
||||
print(f"🚀 Multi-Trend-Analyse integriert")
|
||||
print(f"🎯 Höchste Optimierungsstufe erreicht")
|
||||
print(f"📊 Erweiterte Trend-Daten enthalten")
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion für Ultimate Generator."""
|
||||
print("🚀 ULTIMATE EUROJACKPOT GENERATOR")
|
||||
print("🎯 Mit Multi-Ziehungs-Trend-Integration")
|
||||
print("=" * 50)
|
||||
|
||||
# Generator initialisieren (lädt und analysiert automatisch alle Daten)
|
||||
generator = UltimativerEurojackpotGenerator()
|
||||
|
||||
# Ultimate Tipps generieren
|
||||
tips = generator.generate_ultimate_tips(10)
|
||||
|
||||
print(f"\n🏆 ULTIMATE OPTIMIERUNG ABGESCHLOSSEN!")
|
||||
print("=" * 45)
|
||||
print(f"🚀 10 Ultimate Tipps mit Multi-Trend-Analyse generiert")
|
||||
print(f"📈 Maximale Trefferwahrscheinlichkeit durch:")
|
||||
print(f" • Momentum-Analyse über 15 Ziehungen")
|
||||
print(f" • Sequenzielle Abhängigkeiten")
|
||||
print(f" • Zyklische Muster-Erkennung")
|
||||
print(f" • Positionsbasierte Optimierung")
|
||||
print(f" • Trend-Vorhersage-Algorithmus")
|
||||
print(f"🍀 Viel Erfolg bei der nächsten Ziehung!")
|
||||
|
||||
# Zusätzliche Ultimate Insights
|
||||
print(f"\n💡 ULTIMATE INSIGHTS:")
|
||||
print("=" * 30)
|
||||
|
||||
# Top 5 Trend-Zahlen für nächste Ziehung
|
||||
top_trend_numbers = sorted(generator.trend_predictions.items(),
|
||||
key=lambda x: x[1]['prediction_score'], reverse=True)[:5]
|
||||
print(f"🎯 TOP 5 TREND-ZAHLEN für nächste Ziehung:")
|
||||
for i, (number, data) in enumerate(top_trend_numbers):
|
||||
momentum_status = generator.momentum_scores[number]['status']
|
||||
print(f" {i+1}. Zahl {number:2}: {data['recommendation']} {momentum_status}")
|
||||
|
||||
# Empfohlene Muster basierend auf Zyklen
|
||||
if generator.cycle_patterns:
|
||||
print(f"\n🔄 ZYKLUS-EMPFEHLUNG:")
|
||||
# Finde das wahrscheinlichste nächste Muster basierend auf Zyklen
|
||||
recent_patterns = []
|
||||
for _, row in generator.df.tail(5).iterrows():
|
||||
numbers = sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']])
|
||||
pattern = generator._get_pattern(numbers)
|
||||
recent_patterns.append(pattern)
|
||||
|
||||
print(f" Letzte 5 Muster: {' → '.join(recent_patterns)}")
|
||||
|
||||
# Empfehlung basierend auf häufigsten Mustern
|
||||
top_pattern = generator.pattern_frequencies.most_common(1)[0]
|
||||
print(f" Empfohlenes Muster: {top_pattern[0]} ({(top_pattern[1]/len(generator.df))*100:.1f}% Erfolgsrate)")
|
||||
|
||||
# Momentum-Warnung
|
||||
very_hot = [n for n in generator.hot_numbers
|
||||
if generator.momentum_scores[n]['momentum_score'] > 0.5]
|
||||
if very_hot:
|
||||
print(f"\n🔥 MOMENTUM-ALERT:")
|
||||
print(f" SEHR HEIßE Zahlen: {very_hot[:5]}")
|
||||
print(f" → Mindestens 1-2 dieser Zahlen in Tipps verwenden!")
|
||||
|
||||
return tips
|
||||
|
||||
# Zusätzliche Utility-Funktionen für erweiterte Analyse
|
||||
|
||||
def analyze_tip_quality(generator, tip_numbers):
|
||||
"""Analysiert die Qualität eines einzelnen Tipps."""
|
||||
quality_score = 0
|
||||
analysis = {}
|
||||
|
||||
# Momentum-Analyse
|
||||
hot_count = sum(1 for n in tip_numbers if n in generator.hot_numbers)
|
||||
analysis['hot_numbers'] = hot_count
|
||||
quality_score += hot_count * 0.2
|
||||
|
||||
# Trend-Analyse
|
||||
trend_scores = [generator.trend_predictions[n]['prediction_score'] for n in tip_numbers]
|
||||
avg_trend = np.mean(trend_scores)
|
||||
analysis['avg_trend_score'] = avg_trend
|
||||
quality_score += avg_trend * 0.3
|
||||
|
||||
# Positions-Analyse
|
||||
position_quality = 0
|
||||
for i, num in enumerate(sorted(tip_numbers)):
|
||||
pos_freq = generator.position_frequencies[f'pos_{i+1}'][num]
|
||||
if pos_freq > 0:
|
||||
position_quality += pos_freq
|
||||
analysis['position_quality'] = position_quality
|
||||
quality_score += (position_quality / len(generator.df)) * 0.2
|
||||
|
||||
# Muster-Analyse
|
||||
pattern = generator._get_pattern(sorted(tip_numbers))
|
||||
pattern_freq = generator.pattern_frequencies[pattern]
|
||||
pattern_score = pattern_freq / len(generator.df)
|
||||
analysis['pattern'] = pattern
|
||||
analysis['pattern_score'] = pattern_score
|
||||
quality_score += pattern_score * 0.3
|
||||
|
||||
analysis['total_quality_score'] = quality_score
|
||||
analysis['quality_rating'] = get_quality_rating(quality_score)
|
||||
|
||||
return analysis
|
||||
|
||||
def get_quality_rating(score):
|
||||
"""Konvertiert Quality-Score in Rating."""
|
||||
if score > 0.8:
|
||||
return "🏆 PREMIUM"
|
||||
elif score > 0.6:
|
||||
return "🥇 SEHR GUT"
|
||||
elif score > 0.4:
|
||||
return "🥈 GUT"
|
||||
elif score > 0.2:
|
||||
return "🥉 DURCHSCHNITT"
|
||||
else:
|
||||
return "⚠️ SCHWACH"
|
||||
|
||||
def predict_jackpot_probability(generator, tip_numbers):
|
||||
"""Schätzt Jackpot-Wahrscheinlichkeit basierend auf Trends."""
|
||||
base_probability = 1 / 139838160 # Mathematische Grundwahrscheinlichkeit
|
||||
|
||||
# Trend-Multiplikator berechnen
|
||||
trend_multiplier = 1.0
|
||||
|
||||
for number in tip_numbers:
|
||||
momentum_score = generator.momentum_scores[number]['momentum_score']
|
||||
trend_score = generator.trend_predictions[number]['prediction_score']
|
||||
|
||||
# Zahlen mit hohem Momentum/Trend erhöhen die relative Wahrscheinlichkeit
|
||||
number_multiplier = 1 + (momentum_score * 0.1) + (trend_score * 0.15)
|
||||
trend_multiplier *= number_multiplier
|
||||
|
||||
# Pattern-Bonus
|
||||
pattern = generator._get_pattern(sorted(tip_numbers))
|
||||
pattern_frequency = generator.pattern_frequencies[pattern] / len(generator.df)
|
||||
pattern_multiplier = 1 + (pattern_frequency * 0.2)
|
||||
|
||||
estimated_probability = base_probability * trend_multiplier * pattern_multiplier
|
||||
|
||||
return {
|
||||
'base_probability': base_probability,
|
||||
'trend_multiplier': trend_multiplier,
|
||||
'pattern_multiplier': pattern_multiplier,
|
||||
'estimated_probability': estimated_probability,
|
||||
'improvement_factor': (estimated_probability / base_probability)
|
||||
}
|
||||
|
||||
def export_detailed_analysis(generator, tips, filename_suffix="detailed"):
|
||||
"""Exportiert detaillierte Analyse aller Tipps."""
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
output_file = f"/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/analysis_{filename_suffix}_{timestamp}.csv"
|
||||
|
||||
detailed_data = []
|
||||
|
||||
for tip in tips:
|
||||
tip_analysis = analyze_tip_quality(generator, tip['zahlen'])
|
||||
probability_analysis = predict_jackpot_probability(generator, tip['zahlen'])
|
||||
|
||||
detailed_entry = {
|
||||
**tip,
|
||||
**tip_analysis,
|
||||
**probability_analysis,
|
||||
'individual_momentum_scores': [generator.momentum_scores[n]['momentum_score'] for n in tip['zahlen']],
|
||||
'individual_trend_scores': [generator.trend_predictions[n]['prediction_score'] for n in tip['zahlen']],
|
||||
'number_frequencies': [generator.number_frequencies[n] for n in tip['zahlen']]
|
||||
}
|
||||
|
||||
detailed_data.append(detailed_entry)
|
||||
|
||||
# DataFrame erstellen und exportieren
|
||||
df_detailed = pd.DataFrame(detailed_data)
|
||||
df_detailed.to_csv(output_file, sep=';', index=False)
|
||||
|
||||
print(f"\n📊 DETAILLIERTE ANALYSE EXPORTIERT:")
|
||||
print(f" 📁 Datei: analysis_{filename_suffix}_{timestamp}.csv")
|
||||
print(f" 📈 Enthält Quality-Scores, Trend-Analysen und Wahrscheinlichkeits-Schätzungen")
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Reproduzierbarer Zufallsseed
|
||||
random.seed(42)
|
||||
np.random.seed(42)
|
||||
|
||||
# Ultimate Generator starten
|
||||
tips = main()
|
||||
|
||||
# Optional: Detaillierte Analyse exportieren
|
||||
if tips:
|
||||
print(f"\n📊 ERWEITERTE ANALYSE VERFÜGBAR:")
|
||||
print("=" * 40)
|
||||
|
||||
# Beispiel: Analysiere ersten Tipp detailliert
|
||||
if len(tips) > 0:
|
||||
generator = UltimativerEurojackpotGenerator()
|
||||
|
||||
sample_tip = tips[0]['zahlen']
|
||||
quality_analysis = analyze_tip_quality(generator, sample_tip)
|
||||
probability_analysis = predict_jackpot_probability(generator, sample_tip)
|
||||
|
||||
print(f"\n🔍 BEISPIEL-ANALYSE für Tipp 1 ({sample_tip}):")
|
||||
print(f" 🏆 Quality-Rating: {quality_analysis['quality_rating']}")
|
||||
print(f" 📈 Quality-Score: {quality_analysis['total_quality_score']:.3f}")
|
||||
print(f" 🔥 Heiße Zahlen: {quality_analysis['hot_numbers']}/5")
|
||||
print(f" 🎯 Avg. Trend-Score: {quality_analysis['avg_trend_score']:.3f}")
|
||||
print(f" 🎨 Muster: {quality_analysis['pattern']} (Score: {quality_analysis['pattern_score']:.3f})")
|
||||
print(f" 📊 Verbesserungs-Faktor: {probability_analysis['improvement_factor']:.2f}x")
|
||||
|
||||
# Optional: Detaillierte Analyse aller Tipps exportieren
|
||||
export_detailed_analysis(generator, tips)
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Tipp-Generator für NMMHH-Muster
|
||||
|
||||
Generiert 10 Tipp-Felder basierend auf dem erfolgreichsten NMMHH-Muster,
|
||||
die keine bereits gezogenen Kombinationen enthalten.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import random
|
||||
from itertools import combinations
|
||||
|
||||
def load_drawn_numbers():
|
||||
"""Lädt alle bereits gezogenen Kombinationen."""
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
drawn_combinations = set()
|
||||
for _, row in df.iterrows():
|
||||
# Sortierte Kombination für Vergleich
|
||||
combo = tuple(sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']]))
|
||||
drawn_combinations.add(combo)
|
||||
|
||||
print(f"🚫 {len(drawn_combinations)} bereits gezogene Kombinationen geladen")
|
||||
return drawn_combinations
|
||||
|
||||
def generate_nmmhh_numbers():
|
||||
"""Generiert Zahlen nach dem NMMHH-Muster."""
|
||||
|
||||
# Bereichsdefinition basierend auf umschlüsselter Analyse
|
||||
ranges = {
|
||||
'N': list(range(1, 16)), # Niedrig: Gruppen 1-3 (Zahlen 1-15)
|
||||
'M': list(range(16, 36)), # Mittel: Gruppen 4-7 (Zahlen 16-35)
|
||||
'H': list(range(36, 51)) # Hoch: Gruppen 8-10 (Zahlen 36-50)
|
||||
}
|
||||
|
||||
# NMMHH-Muster: 1 Niedrig, 2 Mittel, 2 Hoch
|
||||
selected_numbers = []
|
||||
|
||||
# 1 Niedrige Zahl (Position z1)
|
||||
selected_numbers.extend(random.sample(ranges['N'], 1))
|
||||
|
||||
# 2 Mittlere Zahlen (Positionen z2, z3)
|
||||
selected_numbers.extend(random.sample(ranges['M'], 2))
|
||||
|
||||
# 2 Hohe Zahlen (Positionen z4, z5)
|
||||
selected_numbers.extend(random.sample(ranges['H'], 2))
|
||||
|
||||
# Sortieren für Eurojackpot-Format
|
||||
return sorted(selected_numbers)
|
||||
|
||||
def generate_optimized_nmmhh_numbers():
|
||||
"""Generiert optimierte NMMHH-Zahlen basierend auf Positionsanalyse."""
|
||||
|
||||
# Optimierte Bereiche basierend auf Treffer-Analyse
|
||||
optimized_ranges = {
|
||||
'z1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], # Niedrig (sehr wahrscheinlich)
|
||||
'z2': [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], # Niedrig-Mittel
|
||||
'z3': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35], # Mittel
|
||||
'z4': [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], # Mittel-Hoch
|
||||
'z5': [41, 42, 43, 44, 45, 46, 47, 48, 49, 50] # Hoch (sehr wahrscheinlich)
|
||||
}
|
||||
|
||||
selected_numbers = []
|
||||
|
||||
# Position z1: Niedrig
|
||||
selected_numbers.append(random.choice(optimized_ranges['z1'][:10])) # Fokus auf 1-10
|
||||
|
||||
# Position z2: Niedrig-Mittel
|
||||
selected_numbers.append(random.choice(optimized_ranges['z2'][5:])) # Fokus auf 16-25
|
||||
|
||||
# Position z3: Mittel
|
||||
selected_numbers.append(random.choice(optimized_ranges['z3'])) # 21-35
|
||||
|
||||
# Position z4: Mittel-Hoch
|
||||
selected_numbers.append(random.choice(optimized_ranges['z4'][5:])) # Fokus auf 36-45
|
||||
|
||||
# Position z5: Hoch
|
||||
selected_numbers.append(random.choice(optimized_ranges['z5'])) # 41-50
|
||||
|
||||
# Duplikate vermeiden und sortieren
|
||||
selected_numbers = list(set(selected_numbers))
|
||||
|
||||
# Falls durch Duplikat-Entfernung weniger als 5 Zahlen
|
||||
while len(selected_numbers) < 5:
|
||||
all_available = list(range(1, 51))
|
||||
missing_number = random.choice([n for n in all_available if n not in selected_numbers])
|
||||
selected_numbers.append(missing_number)
|
||||
|
||||
return sorted(selected_numbers[:5])
|
||||
|
||||
def generate_tips_nmmhh(num_tips=10):
|
||||
"""Generiert Tipps basierend auf NMMHH-Muster ohne bereits gezogene Kombinationen."""
|
||||
|
||||
print("🎯 EUROJACKPOT TIPP-GENERATOR (NMMHH-MUSTER)")
|
||||
print("="*50)
|
||||
|
||||
# Bereits gezogene Kombinationen laden
|
||||
drawn_combinations = load_drawn_numbers()
|
||||
|
||||
# Häufigste Zahlen pro Position aus der Analyse
|
||||
frequent_numbers = {
|
||||
'z1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
'z2': [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
|
||||
'z3': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
|
||||
'z4': [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45],
|
||||
'z5': [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
|
||||
}
|
||||
|
||||
print(f"\n📋 NMMHH-MUSTER BEDEUTUNG:")
|
||||
print("N = Niedrig (1-15), M = Mittel (16-35), H = Hoch (36-50)")
|
||||
print("Muster: 1 Niedrig + 2 Mittel + 2 Hoch = 14.7% Erfolgsrate!")
|
||||
|
||||
generated_tips = []
|
||||
attempts = 0
|
||||
max_attempts = 10000
|
||||
|
||||
print(f"\n🎲 GENERIERE {num_tips} OPTIMIERTE TIPPS:")
|
||||
print("="*40)
|
||||
|
||||
while len(generated_tips) < num_tips and attempts < max_attempts:
|
||||
attempts += 1
|
||||
|
||||
# Verschiedene Generierungsstrategien abwechseln
|
||||
if attempts % 3 == 0:
|
||||
tip = generate_optimized_nmmhh_numbers()
|
||||
else:
|
||||
tip = generate_nmmhh_numbers()
|
||||
|
||||
# Prüfen ob bereits gezogen
|
||||
tip_tuple = tuple(sorted(tip))
|
||||
|
||||
if tip_tuple not in drawn_combinations and tip not in generated_tips:
|
||||
generated_tips.append(tip)
|
||||
|
||||
# Muster-Verifikation
|
||||
pattern = []
|
||||
for num in tip:
|
||||
if 1 <= num <= 15:
|
||||
pattern.append('N')
|
||||
elif 16 <= num <= 35:
|
||||
pattern.append('M')
|
||||
else:
|
||||
pattern.append('H')
|
||||
|
||||
pattern_str = ''.join(pattern)
|
||||
|
||||
print(f"Tipp {len(generated_tips):2}: {tip[0]:2}-{tip[1]:2}-{tip[2]:2}-{tip[3]:2}-{tip[4]:2} (Muster: {pattern_str})")
|
||||
|
||||
if len(generated_tips) < num_tips:
|
||||
print(f"\n⚠️ Nur {len(generated_tips)} von {num_tips} Tipps generiert nach {attempts} Versuchen")
|
||||
else:
|
||||
print(f"\n✅ Alle {num_tips} Tipps erfolgreich generiert!")
|
||||
|
||||
print(f"\n📊 MUSTER-ANALYSE DER GENERIERTEN TIPPS:")
|
||||
print("="*45)
|
||||
|
||||
pattern_counts = {}
|
||||
for tip in generated_tips:
|
||||
pattern = []
|
||||
for num in tip:
|
||||
if 1 <= num <= 15:
|
||||
pattern.append('N')
|
||||
elif 16 <= num <= 35:
|
||||
pattern.append('M')
|
||||
else:
|
||||
pattern.append('H')
|
||||
|
||||
pattern_str = ''.join(pattern)
|
||||
pattern_counts[pattern_str] = pattern_counts.get(pattern_str, 0) + 1
|
||||
|
||||
for pattern, count in sorted(pattern_counts.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f"Muster {pattern}: {count} Tipps")
|
||||
|
||||
# Zusätzliche Superzahlen-Empfehlungen
|
||||
print(f"\n🎲 SUPERZAHLEN-EMPFEHLUNGEN:")
|
||||
print("="*30)
|
||||
|
||||
# Lade Superzahlen-Statistiken
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
sz1_counts = df['SZ1'].value_counts().head(5)
|
||||
sz2_counts = df['SZ2'].value_counts().head(5)
|
||||
|
||||
print(f"Top 5 SZ1: {list(sz1_counts.index)}")
|
||||
print(f"Top 5 SZ2: {list(sz2_counts.index)}")
|
||||
|
||||
# Empfohlene Superzahlen für die Tipps
|
||||
recommended_sz1 = list(sz1_counts.index)[:3]
|
||||
recommended_sz2 = list(sz2_counts.index)[:3]
|
||||
|
||||
print(f"\n🎯 KOMPLETTE TIPP-EMPFEHLUNGEN:")
|
||||
print("="*40)
|
||||
print(f"{'Tipp':<5} {'Hauptzahlen':<20} {'SZ1':<4} {'SZ2':<4}")
|
||||
print("-" * 40)
|
||||
|
||||
final_tips = []
|
||||
for i, tip in enumerate(generated_tips, 1):
|
||||
sz1 = random.choice(recommended_sz1)
|
||||
sz2 = random.choice(recommended_sz2)
|
||||
|
||||
tip_str = f"{tip[0]:2}-{tip[1]:2}-{tip[2]:2}-{tip[3]:2}-{tip[4]:2}"
|
||||
print(f"{i:2}. {tip_str:<20} {sz1:<4} {sz2:<4}")
|
||||
|
||||
final_tips.append({
|
||||
'tipp_nr': i,
|
||||
'z1': tip[0],
|
||||
'z2': tip[1],
|
||||
'z3': tip[2],
|
||||
'z4': tip[3],
|
||||
'z5': tip[4],
|
||||
'sz1': sz1,
|
||||
'sz2': sz2,
|
||||
'muster': ''.join(['N' if n <= 15 else 'M' if n <= 35 else 'H' for n in tip])
|
||||
})
|
||||
|
||||
# Export der Tipps
|
||||
print(f"\n💾 TIPPS EXPORTIEREN:")
|
||||
print("="*25)
|
||||
|
||||
tips_df = pd.DataFrame(final_tips)
|
||||
output_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/eurojackpot_tipps_nmmhh.csv"
|
||||
tips_df.to_csv(output_file, sep=';', index=False)
|
||||
|
||||
print(f"✅ 10 Tipps gespeichert: eurojackpot_tipps_nmmhh.csv")
|
||||
print(f"📈 Basierend auf NMMHH-Muster mit 14.7% historischer Erfolgsrate")
|
||||
print(f"🚫 Keine bereits gezogenen Kombinationen enthalten")
|
||||
|
||||
return final_tips
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Zufallsseed für reproduzierbare Ergebnisse (optional)
|
||||
random.seed(42)
|
||||
|
||||
tips = generate_tips_nmmhh(10)
|
||||
@@ -0,0 +1,234 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Tipp-Generator für NMMHH-Muster
|
||||
|
||||
Generiert 10 Tipp-Felder basierend auf dem erfolgreichsten NMMHH-Muster,
|
||||
die keine bereits gezogenen Kombinationen enthalten.
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import random
|
||||
from itertools import combinations
|
||||
|
||||
def load_drawn_numbers():
|
||||
"""Lädt alle bereits gezogenen Kombinationen."""
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
drawn_combinations = set()
|
||||
for _, row in df.iterrows():
|
||||
# Sortierte Kombination für Vergleich
|
||||
combo = tuple(sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']]))
|
||||
drawn_combinations.add(combo)
|
||||
|
||||
print(f"🚫 {len(drawn_combinations)} bereits gezogene Kombinationen geladen")
|
||||
return drawn_combinations
|
||||
|
||||
def generate_nmmhh_numbers():
|
||||
"""Generiert Zahlen nach dem NMMHH-Muster."""
|
||||
|
||||
# Bereichsdefinition basierend auf umschlüsselter Analyse
|
||||
ranges = {
|
||||
'N': list(range(1, 16)), # Niedrig: Gruppen 1-3 (Zahlen 1-15)
|
||||
'M': list(range(16, 36)), # Mittel: Gruppen 4-7 (Zahlen 16-35)
|
||||
'H': list(range(36, 51)) # Hoch: Gruppen 8-10 (Zahlen 36-50)
|
||||
}
|
||||
|
||||
# NMMHH-Muster: 1 Niedrig, 2 Mittel, 2 Hoch
|
||||
selected_numbers = []
|
||||
|
||||
# 1 Niedrige Zahl (Position z1)
|
||||
selected_numbers.extend(random.sample(ranges['N'], 1))
|
||||
|
||||
# 2 Mittlere Zahlen (Positionen z2, z3)
|
||||
selected_numbers.extend(random.sample(ranges['M'], 2))
|
||||
|
||||
# 2 Hohe Zahlen (Positionen z4, z5)
|
||||
selected_numbers.extend(random.sample(ranges['H'], 2))
|
||||
|
||||
# Sortieren für Eurojackpot-Format
|
||||
return sorted(selected_numbers)
|
||||
|
||||
def generate_optimized_nmmhh_numbers():
|
||||
"""Generiert optimierte NMMHH-Zahlen basierend auf Positionsanalyse."""
|
||||
|
||||
# Optimierte Bereiche basierend auf Treffer-Analyse
|
||||
optimized_ranges = {
|
||||
'z1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], # Niedrig (sehr wahrscheinlich)
|
||||
'z2': [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25], # Niedrig-Mittel
|
||||
'z3': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35], # Mittel
|
||||
'z4': [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45], # Mittel-Hoch
|
||||
'z5': [41, 42, 43, 44, 45, 46, 47, 48, 49, 50] # Hoch (sehr wahrscheinlich)
|
||||
}
|
||||
|
||||
selected_numbers = []
|
||||
|
||||
# Position z1: Niedrig
|
||||
selected_numbers.append(random.choice(optimized_ranges['z1'][:10])) # Fokus auf 1-10
|
||||
|
||||
# Position z2: Niedrig-Mittel
|
||||
selected_numbers.append(random.choice(optimized_ranges['z2'][5:])) # Fokus auf 16-25
|
||||
|
||||
# Position z3: Mittel
|
||||
selected_numbers.append(random.choice(optimized_ranges['z3'])) # 21-35
|
||||
|
||||
# Position z4: Mittel-Hoch
|
||||
selected_numbers.append(random.choice(optimized_ranges['z4'][5:])) # Fokus auf 36-45
|
||||
|
||||
# Position z5: Hoch
|
||||
selected_numbers.append(random.choice(optimized_ranges['z5'])) # 41-50
|
||||
|
||||
# Duplikate vermeiden und sortieren
|
||||
selected_numbers = list(set(selected_numbers))
|
||||
|
||||
# Falls durch Duplikat-Entfernung weniger als 5 Zahlen
|
||||
while len(selected_numbers) < 5:
|
||||
all_available = list(range(1, 51))
|
||||
missing_number = random.choice([n for n in all_available if n not in selected_numbers])
|
||||
selected_numbers.append(missing_number)
|
||||
|
||||
return sorted(selected_numbers[:5])
|
||||
|
||||
def generate_tips_nmmhh(num_tips=10):
|
||||
"""Generiert Tipps basierend auf NMMHH-Muster ohne bereits gezogene Kombinationen."""
|
||||
|
||||
print("🎯 EUROJACKPOT TIPP-GENERATOR (NMMHH-MUSTER)")
|
||||
print("="*50)
|
||||
|
||||
# Bereits gezogene Kombinationen laden
|
||||
drawn_combinations = load_drawn_numbers()
|
||||
|
||||
# Häufigste Zahlen pro Position aus der Analyse
|
||||
frequent_numbers = {
|
||||
'z1': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
|
||||
'z2': [11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25],
|
||||
'z3': [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35],
|
||||
'z4': [31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45],
|
||||
'z5': [36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50]
|
||||
}
|
||||
|
||||
print(f"\n📋 NMMHH-MUSTER BEDEUTUNG:")
|
||||
print("N = Niedrig (1-15), M = Mittel (16-35), H = Hoch (36-50)")
|
||||
print("Muster: 1 Niedrig + 2 Mittel + 2 Hoch = 14.7% Erfolgsrate!")
|
||||
|
||||
generated_tips = []
|
||||
attempts = 0
|
||||
max_attempts = 10000
|
||||
|
||||
print(f"\n🎲 GENERIERE {num_tips} OPTIMIERTE TIPPS:")
|
||||
print("="*40)
|
||||
|
||||
while len(generated_tips) < num_tips and attempts < max_attempts:
|
||||
attempts += 1
|
||||
|
||||
# Verschiedene Generierungsstrategien abwechseln
|
||||
if attempts % 3 == 0:
|
||||
tip = generate_optimized_nmmhh_numbers()
|
||||
else:
|
||||
tip = generate_nmmhh_numbers()
|
||||
|
||||
# Prüfen ob bereits gezogen
|
||||
tip_tuple = tuple(sorted(tip))
|
||||
|
||||
if tip_tuple not in drawn_combinations and tip not in generated_tips:
|
||||
generated_tips.append(tip)
|
||||
|
||||
# Muster-Verifikation
|
||||
pattern = []
|
||||
for num in tip:
|
||||
if 1 <= num <= 15:
|
||||
pattern.append('N')
|
||||
elif 16 <= num <= 35:
|
||||
pattern.append('M')
|
||||
else:
|
||||
pattern.append('H')
|
||||
|
||||
pattern_str = ''.join(pattern)
|
||||
|
||||
print(f"Tipp {len(generated_tips):2}: {tip[0]:2}-{tip[1]:2}-{tip[2]:2}-{tip[3]:2}-{tip[4]:2} (Muster: {pattern_str})")
|
||||
|
||||
if len(generated_tips) < num_tips:
|
||||
print(f"\n⚠️ Nur {len(generated_tips)} von {num_tips} Tipps generiert nach {attempts} Versuchen")
|
||||
else:
|
||||
print(f"\n✅ Alle {num_tips} Tipps erfolgreich generiert!")
|
||||
|
||||
print(f"\n📊 MUSTER-ANALYSE DER GENERIERTEN TIPPS:")
|
||||
print("="*45)
|
||||
|
||||
pattern_counts = {}
|
||||
for tip in generated_tips:
|
||||
pattern = []
|
||||
for num in tip:
|
||||
if 1 <= num <= 15:
|
||||
pattern.append('N')
|
||||
elif 16 <= num <= 35:
|
||||
pattern.append('M')
|
||||
else:
|
||||
pattern.append('H')
|
||||
|
||||
pattern_str = ''.join(pattern)
|
||||
pattern_counts[pattern_str] = pattern_counts.get(pattern_str, 0) + 1
|
||||
|
||||
for pattern, count in sorted(pattern_counts.items(), key=lambda x: x[1], reverse=True):
|
||||
print(f"Muster {pattern}: {count} Tipps")
|
||||
|
||||
# Zusätzliche Superzahlen-Empfehlungen
|
||||
print(f"\n🎲 SUPERZAHLEN-EMPFEHLUNGEN:")
|
||||
print("="*30)
|
||||
|
||||
# Lade Superzahlen-Statistiken
|
||||
df = pd.read_csv("/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/AlleEurojackpotzahlen.csv", sep=';')
|
||||
|
||||
sz1_counts = df['SZ1'].value_counts().head(5)
|
||||
sz2_counts = df['SZ2'].value_counts().head(5)
|
||||
|
||||
print(f"Top 5 SZ1: {list(sz1_counts.index)}")
|
||||
print(f"Top 5 SZ2: {list(sz2_counts.index)}")
|
||||
|
||||
# Empfohlene Superzahlen für die Tipps
|
||||
recommended_sz1 = list(sz1_counts.index)[:3]
|
||||
recommended_sz2 = list(sz2_counts.index)[:3]
|
||||
|
||||
print(f"\n🎯 KOMPLETTE TIPP-EMPFEHLUNGEN:")
|
||||
print("="*40)
|
||||
print(f"{'Tipp':<5} {'Hauptzahlen':<20} {'SZ1':<4} {'SZ2':<4}")
|
||||
print("-" * 40)
|
||||
|
||||
final_tips = []
|
||||
for i, tip in enumerate(generated_tips, 1):
|
||||
sz1 = random.choice(recommended_sz1)
|
||||
sz2 = random.choice(recommended_sz2)
|
||||
|
||||
tip_str = f"{tip[0]:2}-{tip[1]:2}-{tip[2]:2}-{tip[3]:2}-{tip[4]:2}"
|
||||
print(f"{i:2}. {tip_str:<20} {sz1:<4} {sz2:<4}")
|
||||
|
||||
final_tips.append({
|
||||
'tipp_nr': i,
|
||||
'z1': tip[0],
|
||||
'z2': tip[1],
|
||||
'z3': tip[2],
|
||||
'z4': tip[3],
|
||||
'z5': tip[4],
|
||||
'sz1': sz1,
|
||||
'sz2': sz2,
|
||||
'muster': ''.join(['N' if n <= 15 else 'M' if n <= 35 else 'H' for n in tip])
|
||||
})
|
||||
|
||||
# Export der Tipps
|
||||
print(f"\n💾 TIPPS EXPORTIEREN:")
|
||||
print("="*25)
|
||||
|
||||
tips_df = pd.DataFrame(final_tips)
|
||||
output_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/eurojackpot_tipps_nmmhh.csv"
|
||||
tips_df.to_csv(output_file, sep=';', index=False)
|
||||
|
||||
print(f"✅ 10 Tipps gespeichert: eurojackpot_tipps_nmmhh.csv")
|
||||
print(f"📈 Basierend auf NMMHH-Muster mit 14.7% historischer Erfolgsrate")
|
||||
print(f"🚫 Keine bereits gezogenen Kombinationen enthalten")
|
||||
|
||||
return final_tips
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Zufallsseed für reproduzierbare Ergebnisse (optional)
|
||||
random.seed(42)
|
||||
|
||||
tips = generate_tips_nmmhh(10)
|
||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Erstellt Beispiel-CSV-Dateien für das Eurojackpot-Processing
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import itertools
|
||||
from datetime import datetime, timedelta
|
||||
import random
|
||||
|
||||
def create_all_combinations():
|
||||
"""Erstellt eine CSV mit allen möglichen 5-aus-50 Kombinationen (Beispiel mit ersten 100)."""
|
||||
print("Erstelle Beispiel-Kombinationsdatei...")
|
||||
|
||||
# Für Demo: nur erste 100 Kombinationen von 5 aus 20 Zahlen
|
||||
# (Vollständige 5-aus-50 Kombinationen wären über 2 Millionen!)
|
||||
combinations = []
|
||||
count = 0
|
||||
|
||||
for combo in itertools.combinations(range(1, 21), 5): # 5 aus 20 für Demo
|
||||
combinations.append({
|
||||
'Zahl1': combo[0],
|
||||
'Zahl2': combo[1],
|
||||
'Zahl3': combo[2],
|
||||
'Zahl4': combo[3],
|
||||
'Zahl5': combo[4]
|
||||
})
|
||||
count += 1
|
||||
if count >= 100: # Limitierung für Demo
|
||||
break
|
||||
|
||||
df = pd.DataFrame(combinations)
|
||||
df.to_csv('alle_kombinationen_beispiel.csv', index=False)
|
||||
print(f"Beispiel-Kombinationsdatei erstellt: {len(df)} Kombinationen")
|
||||
return df
|
||||
|
||||
def create_drawn_numbers():
|
||||
"""Erstellt eine CSV mit gezogenen Zahlen (Beispieldaten)."""
|
||||
print("Erstelle Beispiel-Datei mit gezogenen Zahlen...")
|
||||
|
||||
drawn_numbers = []
|
||||
start_date = datetime(2023, 1, 1)
|
||||
|
||||
# 20 zufällige Ziehungen erstellen
|
||||
for i in range(20):
|
||||
date = start_date + timedelta(days=i*7) # Wöchentliche Ziehungen
|
||||
|
||||
# 5 zufällige Zahlen zwischen 1 und 20 (ohne Wiederholung)
|
||||
numbers = sorted(random.sample(range(1, 21), 5))
|
||||
|
||||
drawn_numbers.append({
|
||||
'Datum': date.strftime('%Y-%m-%d'),
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4]
|
||||
})
|
||||
|
||||
df = pd.DataFrame(drawn_numbers)
|
||||
df.to_csv('gezogene_zahlen_beispiel.csv', index=False)
|
||||
print(f"Beispiel-Datei mit gezogenen Zahlen erstellt: {len(df)} Ziehungen")
|
||||
return df
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("=== Erstelle Beispiel-CSV-Dateien ===\n")
|
||||
|
||||
# Seed für reproduzierbare Ergebnisse
|
||||
random.seed(42)
|
||||
|
||||
combinations_df = create_all_combinations()
|
||||
drawn_df = create_drawn_numbers()
|
||||
|
||||
print("\n=== Beispieldateien erstellt ===")
|
||||
print("- alle_kombinationen_beispiel.csv")
|
||||
print("- gezogene_zahlen_beispiel.csv")
|
||||
print("\nSie können jetzt das Hauptscript testen:")
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Lädt Eurojackpot-Daten von lotto-datenbank.de
|
||||
# Quelle: https://lotto-datenbank.de
|
||||
#
|
||||
|
||||
echo "🔄 EUROJACKPOT CSV DOWNLOADER"
|
||||
echo "=========================================="
|
||||
echo "Quelle: lotto-datenbank.de"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Ziel-Datei
|
||||
TARGET_FILE="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
BACKUP_DIR="$HOME/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/backups"
|
||||
TEMP_FILE="/tmp/eurojackpot_download_$(date +%Y%m%d_%H%M%S).csv"
|
||||
|
||||
# CSV Download URL (aufsteigend sortiert seit 2012)
|
||||
DOWNLOAD_URL="https://lotto-datenbank.de/download.php?p1=5&p3=0.75&p31=en"
|
||||
|
||||
echo "📥 Download von lotto-datenbank.de..."
|
||||
echo " URL: $DOWNLOAD_URL"
|
||||
echo ""
|
||||
|
||||
# Download mit curl
|
||||
if curl -L -o "$TEMP_FILE" "$DOWNLOAD_URL" 2>/dev/null; then
|
||||
echo " ✅ Download erfolgreich"
|
||||
|
||||
# Prüfe Dateigröße
|
||||
FILE_SIZE=$(wc -c < "$TEMP_FILE")
|
||||
echo " 📊 Dateigröße: $FILE_SIZE bytes"
|
||||
|
||||
if [ "$FILE_SIZE" -lt 1000 ]; then
|
||||
echo " ❌ Datei zu klein - möglicherweise Fehler"
|
||||
rm "$TEMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Zeige erste Zeilen
|
||||
echo ""
|
||||
echo " 📋 Vorschau (erste 5 Zeilen):"
|
||||
head -5 "$TEMP_FILE" | sed 's/^/ /'
|
||||
echo ""
|
||||
|
||||
# Backup erstellen falls Datei existiert
|
||||
if [ -f "$TARGET_FILE" ]; then
|
||||
echo "💾 Erstelle Backup..."
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
BACKUP_NAME="AlleEurojackpotzahlen.csv.backup_$(date +%Y%m%d_%H%M%S)"
|
||||
cp "$TARGET_FILE" "$BACKUP_DIR/$BACKUP_NAME"
|
||||
echo " ✅ Backup: $BACKUP_NAME"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
# Verschiebe neue Datei
|
||||
echo "💾 Speichere neue Datei..."
|
||||
mv "$TEMP_FILE" "$TARGET_FILE"
|
||||
|
||||
# Zähle Ziehungen (Zeilen - Header)
|
||||
LINE_COUNT=$(($(wc -l < "$TARGET_FILE") - 1))
|
||||
echo " ✅ $LINE_COUNT Ziehungen gespeichert"
|
||||
echo " 📁 Datei: $(basename "$TARGET_FILE")"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
echo "✅ UPDATE ERFOLGREICH"
|
||||
echo "=========================================="
|
||||
|
||||
else
|
||||
echo " ❌ Download fehlgeschlagen"
|
||||
rm -f "$TEMP_FILE"
|
||||
exit 1
|
||||
fi
|
||||
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot CSV Processor
|
||||
|
||||
Dieses Script liest zwei CSV-Dateien ein:
|
||||
1. Alle möglichen Zahlenkombinationen (5 Zahlen)
|
||||
2. Bereits gezogene Zahlen mit Datumsstempel
|
||||
|
||||
Es markiert in der ersten Datei alle bereits gezogenen Kombinationen mit 1 (sonst 0).
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def load_combinations_file(filepath):
|
||||
"""Lädt die Datei mit allen möglichen Kombinationen."""
|
||||
try:
|
||||
df = pd.read_csv(filepath)
|
||||
print(f"Kombinationen geladen: {len(df)} Zeilen")
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden der Kombinationsdatei: {e}")
|
||||
return None
|
||||
|
||||
def load_drawn_numbers_file(filepath):
|
||||
"""Lädt die Datei mit bereits gezogenen Zahlen."""
|
||||
try:
|
||||
df = pd.read_csv(filepath)
|
||||
print(f"Gezogene Zahlen geladen: {len(df)} Zeilen")
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden der gezogenen Zahlen: {e}")
|
||||
return None
|
||||
|
||||
def create_combination_key(row, z_columns):
|
||||
"""Erstellt einen eindeutigen Schlüssel aus den 5 Zahlen (sortiert)."""
|
||||
numbers = [row[col] for col in z_columns]
|
||||
return tuple(sorted(numbers))
|
||||
|
||||
def process_eurojackpot_data(combinations_file, drawn_numbers_file, output_file):
|
||||
"""Hauptfunktion zur Verarbeitung der Eurojackpot-Daten."""
|
||||
|
||||
# CSV-Dateien laden
|
||||
print("Lade Kombinationsdatei...")
|
||||
combinations_df = load_combinations_file(combinations_file)
|
||||
if combinations_df is None:
|
||||
return False
|
||||
|
||||
print("\nLade Datei mit gezogenen Zahlen...")
|
||||
drawn_df = load_drawn_numbers_file(drawn_numbers_file)
|
||||
if drawn_df is None:
|
||||
return False
|
||||
|
||||
# Spalten für Zahlen identifizieren
|
||||
# Annahme: In der Kombinationsdatei sind die ersten 5 Spalten die Zahlen
|
||||
# In der gezogenen Zahlen-Datei sind es Z1, Z2, Z3, Z4, Z5
|
||||
|
||||
# Für Kombinationsdatei - erste 5 numerische Spalten verwenden
|
||||
numeric_cols = combinations_df.select_dtypes(include=['number']).columns
|
||||
if len(numeric_cols) >= 5:
|
||||
combo_z_columns = numeric_cols[:5].tolist()
|
||||
else:
|
||||
# Fallback: erste 5 Spalten nehmen
|
||||
combo_z_columns = combinations_df.columns[:5].tolist()
|
||||
|
||||
print(f"Verwendete Spalten für Kombinationen: {combo_z_columns}")
|
||||
|
||||
# Für gezogene Zahlen - Z1 bis Z5 Spalten suchen
|
||||
z_columns = [col for col in drawn_df.columns if col.startswith('Z') and col[1:].isdigit()]
|
||||
z_columns = sorted(z_columns)[:5] # Ersten 5 Z-Spalten nehmen
|
||||
|
||||
if not z_columns:
|
||||
# Fallback: nach Spalten mit "Zahl" im Namen suchen oder numerische Spalten
|
||||
z_columns = [col for col in drawn_df.columns if 'zahl' in col.lower()][:5]
|
||||
if not z_columns:
|
||||
z_columns = drawn_df.select_dtypes(include=['number']).columns[:5].tolist()
|
||||
|
||||
print(f"Verwendete Spalten für gezogene Zahlen: {z_columns}")
|
||||
|
||||
# Set mit allen gezogenen Kombinationen erstellen
|
||||
print("\nErstelle Set mit gezogenen Kombinationen...")
|
||||
drawn_combinations = set()
|
||||
|
||||
for _, row in drawn_df.iterrows():
|
||||
combo_key = create_combination_key(row, z_columns)
|
||||
drawn_combinations.add(combo_key)
|
||||
|
||||
print(f"Anzahl eindeutige gezogene Kombinationen: {len(drawn_combinations)}")
|
||||
|
||||
# Neue Spalte für Markierungen hinzufügen
|
||||
print("\nMarkiere gezogene Kombinationen...")
|
||||
combinations_df['bereits_gezogen'] = 0
|
||||
|
||||
marked_count = 0
|
||||
for idx, row in combinations_df.iterrows():
|
||||
combo_key = create_combination_key(row, combo_z_columns)
|
||||
if combo_key in drawn_combinations:
|
||||
combinations_df.at[idx, 'bereits_gezogen'] = 1
|
||||
marked_count += 1
|
||||
|
||||
print(f"Anzahl markierte Kombinationen: {marked_count}")
|
||||
|
||||
# Ergebnis speichern
|
||||
print(f"\nSpeichere Ergebnis in: {output_file}")
|
||||
combinations_df.to_csv(output_file, index=False)
|
||||
|
||||
# Statistiken ausgeben
|
||||
total_combinations = len(combinations_df)
|
||||
drawn_percentage = (marked_count / total_combinations) * 100 if total_combinations > 0 else 0
|
||||
|
||||
print(f"\n=== STATISTIKEN ===")
|
||||
print(f"Gesamte Kombinationen: {total_combinations:,}")
|
||||
print(f"Bereits gezogene Kombinationen: {marked_count:,}")
|
||||
print(f"Prozentsatz bereits gezogen: {drawn_percentage:.4f}%")
|
||||
print(f"Noch nicht gezogene Kombinationen: {total_combinations - marked_count:,}")
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion mit Benutzerinteraktion."""
|
||||
print("=== Eurojackpot CSV Processor ===\n")
|
||||
|
||||
# Dateipfade abfragen oder Standard verwenden
|
||||
if len(sys.argv) >= 4:
|
||||
combinations_file = sys.argv[1]
|
||||
drawn_numbers_file = sys.argv[2]
|
||||
output_file = sys.argv[3]
|
||||
else:
|
||||
print("Geben Sie die Dateipfade ein (oder drücken Sie Enter für Standard):")
|
||||
|
||||
combinations_file = input("Pfad zur Kombinationsdatei (alle_kombinationen.csv): ").strip()
|
||||
if not combinations_file:
|
||||
combinations_file = "alle_kombinationen.csv"
|
||||
|
||||
drawn_numbers_file = input("Pfad zur Datei mit gezogenen Zahlen (gezogene_zahlen.csv): ").strip()
|
||||
if not drawn_numbers_file:
|
||||
drawn_numbers_file = "gezogene_zahlen.csv"
|
||||
|
||||
output_file = input("Pfad für Ausgabedatei (kombinationen_markiert.csv): ").strip()
|
||||
if not output_file:
|
||||
output_file = "kombinationen_markiert.csv"
|
||||
|
||||
# Überprüfen ob Dateien existieren
|
||||
if not Path(combinations_file).exists():
|
||||
print(f"Fehler: Kombinationsdatei '{combinations_file}' nicht gefunden!")
|
||||
return
|
||||
|
||||
if not Path(drawn_numbers_file).exists():
|
||||
print(f"Fehler: Datei mit gezogenen Zahlen '{drawn_numbers_file}' nicht gefunden!")
|
||||
return
|
||||
|
||||
# Verarbeitung starten
|
||||
success = process_eurojackpot_data(combinations_file, drawn_numbers_file, output_file)
|
||||
|
||||
if success:
|
||||
print(f"\n✅ Verarbeitung erfolgreich abgeschlossen!")
|
||||
print(f"Ergebnis gespeichert in: {output_file}")
|
||||
else:
|
||||
print("\n❌ Fehler bei der Verarbeitung!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,220 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot CSV Processor - Angepasst für Semikolon-getrennte CSV-Dateien
|
||||
|
||||
Dieses Script liest zwei CSV-Dateien ein:
|
||||
1. Alle möglichen Zahlenkombinationen (5 Zahlen)
|
||||
2. Bereits gezogene Zahlen mit Datumsstempel
|
||||
|
||||
Es markiert in der ersten Datei alle bereits gezogenen Kombinationen mit 1 (sonst 0).
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
def detect_separator(filepath):
|
||||
"""Erkennt das CSV-Trennzeichen automatisch."""
|
||||
try:
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
first_line = f.readline()
|
||||
if ';' in first_line and first_line.count(';') > first_line.count(','):
|
||||
return ';'
|
||||
return ','
|
||||
except:
|
||||
return ','
|
||||
|
||||
def load_combinations_file(filepath):
|
||||
"""Lädt die Datei mit allen möglichen Kombinationen."""
|
||||
try:
|
||||
sep = detect_separator(filepath)
|
||||
print(f"Erkanntes Trennzeichen für Kombinationen: '{sep}'")
|
||||
|
||||
df = pd.read_csv(filepath, sep=sep)
|
||||
print(f"Kombinationen geladen: {len(df)} Zeilen")
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden der Kombinationsdatei: {e}")
|
||||
return None
|
||||
|
||||
def load_drawn_numbers_file(filepath):
|
||||
"""Lädt die Datei mit bereits gezogenen Zahlen."""
|
||||
try:
|
||||
sep = detect_separator(filepath)
|
||||
print(f"Erkanntes Trennzeichen für gezogene Zahlen: '{sep}'")
|
||||
|
||||
df = pd.read_csv(filepath, sep=sep)
|
||||
print(f"Gezogene Zahlen geladen: {len(df)} Zeilen")
|
||||
print(f"Spalten: {list(df.columns)}")
|
||||
return df
|
||||
except Exception as e:
|
||||
print(f"Fehler beim Laden der gezogenen Zahlen: {e}")
|
||||
return None
|
||||
|
||||
def identify_number_columns(df, is_combinations=True):
|
||||
"""Identifiziert die Spalten mit den Zahlen."""
|
||||
columns = df.columns.tolist()
|
||||
|
||||
if is_combinations:
|
||||
# Für Kombinationsdatei: Z1, Z2, Z3, Z4, Z5 suchen
|
||||
z_cols = [col for col in columns if col.startswith('Z') and len(col) == 2 and col[1:].isdigit()]
|
||||
z_cols = sorted(z_cols)[:5]
|
||||
|
||||
if len(z_cols) >= 5:
|
||||
return z_cols
|
||||
|
||||
# Fallback: erste 5 numerische Spalten
|
||||
numeric_cols = df.select_dtypes(include=['number']).columns[:5].tolist()
|
||||
if len(numeric_cols) >= 5:
|
||||
return numeric_cols
|
||||
|
||||
# Fallback: erste 5 Spalten
|
||||
return columns[:5]
|
||||
|
||||
else:
|
||||
# Für gezogene Zahlen: Z1-Z5 suchen (nicht SZ1, SZ2)
|
||||
z_cols = [col for col in columns if col.startswith('Z') and len(col) == 2 and col[1:].isdigit()]
|
||||
z_cols = [col for col in z_cols if not col.startswith('SZ')] # Superzahlen ausschließen
|
||||
z_cols = sorted(z_cols)[:5]
|
||||
|
||||
if len(z_cols) >= 5:
|
||||
return z_cols
|
||||
|
||||
# Fallback: numerische Spalten (ohne Datum)
|
||||
numeric_cols = df.select_dtypes(include=['number']).columns
|
||||
numeric_cols = [col for col in numeric_cols if 'datum' not in col.lower()][:5]
|
||||
if len(numeric_cols) >= 5:
|
||||
return numeric_cols.tolist()
|
||||
|
||||
# Letzter Fallback
|
||||
return columns[:5]
|
||||
|
||||
def create_combination_key(row, z_columns):
|
||||
"""Erstellt einen eindeutigen Schlüssel aus den 5 Zahlen (sortiert)."""
|
||||
try:
|
||||
numbers = [int(row[col]) for col in z_columns]
|
||||
return tuple(sorted(numbers))
|
||||
except:
|
||||
return None
|
||||
|
||||
def process_eurojackpot_data(combinations_file, drawn_numbers_file, output_file):
|
||||
"""Hauptfunktion zur Verarbeitung der Eurojackpot-Daten."""
|
||||
|
||||
# CSV-Dateien laden
|
||||
print("Lade Kombinationsdatei...")
|
||||
combinations_df = load_combinations_file(combinations_file)
|
||||
if combinations_df is None:
|
||||
return False
|
||||
|
||||
print("\nLade Datei mit gezogenen Zahlen...")
|
||||
drawn_df = load_drawn_numbers_file(drawn_numbers_file)
|
||||
if drawn_df is None:
|
||||
return False
|
||||
|
||||
# Spalten für Zahlen identifizieren
|
||||
combo_z_columns = identify_number_columns(combinations_df, is_combinations=True)
|
||||
drawn_z_columns = identify_number_columns(drawn_df, is_combinations=False)
|
||||
|
||||
print(f"\nVerwendete Spalten für Kombinationen: {combo_z_columns}")
|
||||
print(f"Verwendete Spalten für gezogene Zahlen: {drawn_z_columns}")
|
||||
|
||||
# Datencheck
|
||||
print(f"\nErste Kombination: {combinations_df[combo_z_columns].iloc[0].tolist()}")
|
||||
print(f"Erste gezogene Zahlen: {drawn_df[drawn_z_columns].iloc[0].tolist()}")
|
||||
|
||||
# Set mit allen gezogenen Kombinationen erstellen
|
||||
print("\nErstelle Set mit gezogenen Kombinationen...")
|
||||
drawn_combinations = set()
|
||||
|
||||
for _, row in drawn_df.iterrows():
|
||||
combo_key = create_combination_key(row, drawn_z_columns)
|
||||
if combo_key:
|
||||
drawn_combinations.add(combo_key)
|
||||
|
||||
print(f"Anzahl eindeutige gezogene Kombinationen: {len(drawn_combinations)}")
|
||||
|
||||
# Beispiele anzeigen
|
||||
if drawn_combinations:
|
||||
print(f"Erste 5 gezogene Kombinationen: {list(drawn_combinations)[:5]}")
|
||||
|
||||
# Neue Spalte für Markierungen hinzufügen (falls noch nicht vorhanden)
|
||||
if 'bereits_gezogen' not in combinations_df.columns:
|
||||
combinations_df['bereits_gezogen'] = 0
|
||||
else:
|
||||
combinations_df['bereits_gezogen'] = 0 # Zurücksetzen
|
||||
|
||||
print("\nMarkiere gezogene Kombinationen...")
|
||||
marked_count = 0
|
||||
|
||||
for idx, row in combinations_df.iterrows():
|
||||
combo_key = create_combination_key(row, combo_z_columns)
|
||||
if combo_key and combo_key in drawn_combinations:
|
||||
combinations_df.at[idx, 'bereits_gezogen'] = 1
|
||||
marked_count += 1
|
||||
if marked_count <= 5: # Erste 5 Treffer anzeigen
|
||||
print(f"Treffer gefunden: {combo_key}")
|
||||
|
||||
print(f"Anzahl markierte Kombinationen: {marked_count}")
|
||||
|
||||
# Ergebnis speichern
|
||||
print(f"\nSpeichere Ergebnis in: {output_file}")
|
||||
sep = detect_separator(combinations_file) # Gleiches Trennzeichen wie Eingabe verwenden
|
||||
combinations_df.to_csv(output_file, sep=sep, index=False)
|
||||
|
||||
# Statistiken ausgeben
|
||||
total_combinations = len(combinations_df)
|
||||
drawn_percentage = (marked_count / total_combinations) * 100 if total_combinations > 0 else 0
|
||||
|
||||
print(f"\n=== STATISTIKEN ===")
|
||||
print(f"Gesamte Kombinationen: {total_combinations:,}")
|
||||
print(f"Bereits gezogene Kombinationen: {marked_count:,}")
|
||||
print(f"Prozentsatz bereits gezogen: {drawn_percentage:.4f}%")
|
||||
print(f"Noch nicht gezogene Kombinationen: {total_combinations - marked_count:,}")
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion mit Benutzerinteraktion."""
|
||||
print("=== Eurojackpot CSV Processor (Fixed) ===\n")
|
||||
|
||||
# Dateipfade abfragen oder Standard verwenden
|
||||
if len(sys.argv) >= 4:
|
||||
combinations_file = sys.argv[1]
|
||||
drawn_numbers_file = sys.argv[2]
|
||||
output_file = sys.argv[3]
|
||||
else:
|
||||
print("Geben Sie die Dateipfade ein (oder drücken Sie Enter für Standard):")
|
||||
|
||||
combinations_file = input("Pfad zur Kombinationsdatei: ").strip()
|
||||
if not combinations_file:
|
||||
combinations_file = "Alle_Eurojackpot_Kombinationen_mit_Status.csv"
|
||||
|
||||
drawn_numbers_file = input("Pfad zur Datei mit gezogenen Zahlen: ").strip()
|
||||
if not drawn_numbers_file:
|
||||
drawn_numbers_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
|
||||
output_file = input("Pfad für Ausgabedatei: ").strip()
|
||||
if not output_file:
|
||||
output_file = "Kombinationen_markiert_fixed.csv"
|
||||
|
||||
# Überprüfen ob Dateien existieren
|
||||
if not Path(combinations_file).exists():
|
||||
print(f"Fehler: Kombinationsdatei '{combinations_file}' nicht gefunden!")
|
||||
return
|
||||
|
||||
if not Path(drawn_numbers_file).exists():
|
||||
print(f"Fehler: Datei mit gezogenen Zahlen '{drawn_numbers_file}' nicht gefunden!")
|
||||
return
|
||||
|
||||
# Verarbeitung starten
|
||||
success = process_eurojackpot_data(combinations_file, drawn_numbers_file, output_file)
|
||||
|
||||
if success:
|
||||
print(f"\n✅ Verarbeitung erfolgreich abgeschlossen!")
|
||||
print(f"Ergebnis gespeichert in: {output_file}")
|
||||
else:
|
||||
print("\n❌ Fehler bei der Verarbeitung!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Vereinfachte Version des Eurojackpot Processors
|
||||
Für spezifische Spaltenstrukturen
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
|
||||
def process_eurojackpot_simple(combinations_csv, drawn_numbers_csv, output_csv):
|
||||
"""
|
||||
Vereinfachte Verarbeitung mit festen Spaltenstrukturen
|
||||
|
||||
Parameter:
|
||||
- combinations_csv: CSV mit allen Kombinationen (Spalten: Zahl1, Zahl2, Zahl3, Zahl4, Zahl5)
|
||||
- drawn_numbers_csv: CSV mit gezogenen Zahlen (Spalten: Datum, Z1, Z2, Z3, Z4, Z5)
|
||||
- output_csv: Ausgabedatei
|
||||
"""
|
||||
|
||||
# Dateien laden
|
||||
print("Lade Kombinationsdatei...")
|
||||
combinations = pd.read_csv(combinations_csv)
|
||||
|
||||
print("Lade gezogene Zahlen...")
|
||||
drawn = pd.read_csv(drawn_numbers_csv)
|
||||
|
||||
# Gezogene Kombinationen als Set erstellen (sortiert für Vergleich)
|
||||
drawn_sets = set()
|
||||
for _, row in drawn.iterrows():
|
||||
# Annahme: Spalten Z1, Z2, Z3, Z4, Z5 enthalten die Zahlen
|
||||
numbers = sorted([row['Z1'], row['Z2'], row['Z3'], row['Z4'], row['Z5']])
|
||||
drawn_sets.add(tuple(numbers))
|
||||
|
||||
print(f"Gefundene gezogene Kombinationen: {len(drawn_sets)}")
|
||||
|
||||
# Neue Spalte hinzufügen
|
||||
combinations['bereits_gezogen'] = 0
|
||||
|
||||
# Jede Kombination prüfen
|
||||
marked = 0
|
||||
for idx, row in combinations.iterrows():
|
||||
# Annahme: erste 5 Spalten enthalten die Zahlenkombination
|
||||
combo_cols = combinations.columns[:5]
|
||||
numbers = sorted([row[col] for col in combo_cols])
|
||||
|
||||
if tuple(numbers) in drawn_sets:
|
||||
combinations.at[idx, 'bereits_gezogen'] = 1
|
||||
marked += 1
|
||||
|
||||
# Ergebnis speichern
|
||||
combinations.to_csv(output_csv, index=False)
|
||||
|
||||
print(f"Verarbeitung abgeschlossen!")
|
||||
print(f"Markierte Kombinationen: {marked}")
|
||||
print(f"Ergebnis gespeichert in: {output_csv}")
|
||||
|
||||
# Beispielaufruf
|
||||
if __name__ == "__main__":
|
||||
process_eurojackpot_simple(
|
||||
"alle_kombinationen.csv",
|
||||
"gezogene_zahlen.csv",
|
||||
"kombinationen_markiert.csv"
|
||||
)
|
||||
@@ -0,0 +1,308 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Notification System
|
||||
|
||||
Sendet Benachrichtigungen via:
|
||||
- Telegram Bot
|
||||
- E-Mail (SMTP)
|
||||
|
||||
Konfiguration via config/notifications.json
|
||||
"""
|
||||
|
||||
import os
|
||||
import json
|
||||
import requests
|
||||
from typing import Optional, Dict, List
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
import smtplib
|
||||
|
||||
|
||||
class EurojackpotNotifier:
|
||||
"""Benachrichtigungssystem für Eurojackpot-Events."""
|
||||
|
||||
def __init__(self, config_file: Optional[str] = None):
|
||||
if config_file is None:
|
||||
# Default config path
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
project_dir = os.path.dirname(os.path.dirname(script_dir))
|
||||
config_file = os.path.join(project_dir, "config", "notifications.json")
|
||||
|
||||
self.config_file = config_file
|
||||
self.config = self._load_config()
|
||||
|
||||
def _load_config(self) -> dict:
|
||||
"""Lädt Konfiguration."""
|
||||
if not os.path.exists(self.config_file):
|
||||
return {
|
||||
"telegram": {"enabled": False},
|
||||
"email": {"enabled": False}
|
||||
}
|
||||
|
||||
try:
|
||||
with open(self.config_file, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f"⚠️ Fehler beim Laden der Notification-Config: {e}")
|
||||
return {
|
||||
"telegram": {"enabled": False},
|
||||
"email": {"enabled": False}
|
||||
}
|
||||
|
||||
def send_tips_generated(self, tips: List[Dict], timestamp: str, best_tip: Dict):
|
||||
"""
|
||||
Benachrichtigung: Neue Tipps generiert.
|
||||
|
||||
Args:
|
||||
tips: Liste der generierten Tipps
|
||||
timestamp: Zeitstempel der Generierung
|
||||
best_tip: Bester Tipp mit höchster Confidence
|
||||
"""
|
||||
# Formatiere Nachricht
|
||||
main_numbers = best_tip.get('main_numbers', '?')
|
||||
euro_numbers = best_tip.get('euro_numbers', '?')
|
||||
confidence = best_tip.get('confidence', 0)
|
||||
strategy = best_tip.get('strategy', 'UNKNOWN')
|
||||
|
||||
subject = f"🎲 {len(tips)} neue Eurojackpot-Tipps generiert!"
|
||||
|
||||
message = f"""🎲 NEUE EUROJACKPOT-TIPPS GENERIERT
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📊 Anzahl Tipps: {len(tips)}
|
||||
⏰ Zeitpunkt: {timestamp}
|
||||
|
||||
🏆 BESTER TIPP (höchste Confidence):
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔢 Hauptzahlen: {main_numbers}
|
||||
⭐ Eurozahlen: {euro_numbers}
|
||||
📈 Strategie: {strategy}
|
||||
💎 Confidence: {confidence:.2%}
|
||||
|
||||
💡 Alle Tipps findest du in der CSV-Datei!
|
||||
|
||||
Viel Glück! 🍀
|
||||
"""
|
||||
|
||||
# Sende via Telegram
|
||||
if self.config.get("telegram", {}).get("enabled"):
|
||||
self._send_telegram(message)
|
||||
|
||||
# Sende via Email
|
||||
if self.config.get("email", {}).get("enabled"):
|
||||
self._send_email(subject, message)
|
||||
|
||||
def send_draw_results(self, draw: Dict, evaluation: Dict, best_match: Dict):
|
||||
"""
|
||||
Benachrichtigung: Ziehung evaluiert.
|
||||
|
||||
Args:
|
||||
draw: Gezogene Zahlen
|
||||
evaluation: Evaluierungsergebnisse
|
||||
best_match: Bester Tipp
|
||||
"""
|
||||
# Formatiere gezogene Zahlen
|
||||
main = [draw[f'Z{i}'] for i in range(1, 6)]
|
||||
euro = [draw['SZ1'], draw['SZ2']]
|
||||
date = draw.get('date', 'unknown')
|
||||
|
||||
# Formatiere besten Tipp
|
||||
best_main_matches = best_match.get('main_matches', 0)
|
||||
best_euro_matches = best_match.get('euro_matches', 0)
|
||||
total_matches = best_main_matches + best_euro_matches
|
||||
|
||||
# Bewertungs-Emoji
|
||||
if total_matches >= 5:
|
||||
emoji = "🎉🎉🎉"
|
||||
rating = "FANTASTISCH!"
|
||||
elif total_matches >= 4:
|
||||
emoji = "🎊"
|
||||
rating = "Sehr gut!"
|
||||
elif total_matches >= 3:
|
||||
emoji = "✅"
|
||||
rating = "Gut!"
|
||||
elif total_matches >= 2:
|
||||
emoji = "👍"
|
||||
rating = "OK"
|
||||
else:
|
||||
emoji = "⚪"
|
||||
rating = "Nächstes Mal besser"
|
||||
|
||||
subject = f"🎰 Ziehung vom {date}: {total_matches} Treffer!"
|
||||
|
||||
message = f"""🎰 EUROJACKPOT-ZIEHUNG AUSGEWERTET
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📅 Datum: {date}
|
||||
|
||||
🎲 GEZOGENE ZAHLEN:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔢 Hauptzahlen: {'-'.join(map(str, main))}
|
||||
⭐ Eurozahlen: {'-'.join(map(str, euro))}
|
||||
|
||||
{emoji} DEINE BESTEN TREFFER:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔢 Hauptzahlen: {best_main_matches} Treffer
|
||||
⭐ Eurozahlen: {best_euro_matches} Treffer
|
||||
🎯 Gesamt: {total_matches} Treffer
|
||||
📊 Bewertung: {rating}
|
||||
|
||||
📈 DURCHSCHNITT ALLER TIPPS:
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
🔢 Main: {evaluation.get('avg_main', 0):.1f} Treffer
|
||||
⭐ Euro: {evaluation.get('avg_euro', 0):.1f} Treffer
|
||||
|
||||
💡 Details im Performance-Report!
|
||||
"""
|
||||
|
||||
# Sende via Telegram
|
||||
if self.config.get("telegram", {}).get("enabled"):
|
||||
self._send_telegram(message)
|
||||
|
||||
# Sende via Email
|
||||
if self.config.get("email", {}).get("enabled"):
|
||||
self._send_email(subject, message)
|
||||
|
||||
def send_error(self, error_msg: str, context: str = ""):
|
||||
"""
|
||||
Benachrichtigung: Fehler aufgetreten.
|
||||
|
||||
Args:
|
||||
error_msg: Fehlermeldung
|
||||
context: Kontext (z.B. "Tipp-Generierung")
|
||||
"""
|
||||
subject = f"❌ Eurojackpot Fehler: {context}"
|
||||
|
||||
message = f"""❌ FEHLER IM EUROJACKPOT-SYSTEM
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
📍 Kontext: {context}
|
||||
🔴 Fehler: {error_msg}
|
||||
|
||||
💡 Bitte System-Logs prüfen!
|
||||
"""
|
||||
|
||||
# Sende nur via Telegram (Fehler sind dringender)
|
||||
if self.config.get("telegram", {}).get("enabled"):
|
||||
self._send_telegram(message)
|
||||
|
||||
def _send_telegram(self, message: str):
|
||||
"""Sendet Nachricht via Telegram Bot."""
|
||||
try:
|
||||
telegram_config = self.config.get("telegram", {})
|
||||
bot_token = telegram_config.get("bot_token")
|
||||
chat_id = telegram_config.get("chat_id")
|
||||
|
||||
if not bot_token or not chat_id:
|
||||
return
|
||||
|
||||
url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
||||
|
||||
payload = {
|
||||
"chat_id": chat_id,
|
||||
"text": message,
|
||||
"disable_web_page_preview": True
|
||||
}
|
||||
|
||||
response = requests.post(url, json=payload, timeout=10)
|
||||
|
||||
if response.status_code != 200:
|
||||
error_detail = response.json() if response.text else {}
|
||||
print(f"⚠️ Telegram API Error: {error_detail}")
|
||||
|
||||
response.raise_for_status()
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Telegram-Benachrichtigung fehlgeschlagen: {e}")
|
||||
|
||||
def _send_email(self, subject: str, message: str):
|
||||
"""Sendet E-Mail via SMTP."""
|
||||
try:
|
||||
email_config = self.config.get("email", {})
|
||||
|
||||
smtp_server = email_config.get("smtp_server")
|
||||
smtp_port = email_config.get("smtp_port", 587)
|
||||
smtp_user = email_config.get("smtp_user")
|
||||
smtp_password = email_config.get("smtp_password")
|
||||
from_email = email_config.get("from_email", smtp_user)
|
||||
to_email = email_config.get("to_email")
|
||||
|
||||
if not all([smtp_server, smtp_user, smtp_password, to_email]):
|
||||
return
|
||||
|
||||
# Erstelle E-Mail
|
||||
msg = MIMEMultipart("alternative")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = from_email
|
||||
msg["To"] = to_email
|
||||
|
||||
# Plain text
|
||||
text_part = MIMEText(message, "plain", "utf-8")
|
||||
msg.attach(text_part)
|
||||
|
||||
# Sende via SMTP
|
||||
with smtplib.SMTP(smtp_server, smtp_port) as server:
|
||||
server.starttls()
|
||||
server.login(smtp_user, smtp_password)
|
||||
server.send_message(msg)
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ E-Mail-Benachrichtigung fehlgeschlagen: {e}")
|
||||
|
||||
def test_notifications(self):
|
||||
"""Testet alle konfigurierten Benachrichtigungen."""
|
||||
print("\n🧪 TESTE BENACHRICHTIGUNGEN")
|
||||
print("=" * 70)
|
||||
|
||||
test_message = """🧪 TEST-BENACHRICHTIGUNG
|
||||
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||
|
||||
Das Eurojackpot-Benachrichtigungssystem funktioniert! ✅
|
||||
|
||||
Wenn du diese Nachricht erhältst, ist alles korrekt konfiguriert.
|
||||
"""
|
||||
|
||||
# Test Telegram
|
||||
if self.config.get("telegram", {}).get("enabled"):
|
||||
print("\n📱 Teste Telegram...")
|
||||
self._send_telegram(test_message)
|
||||
print(" ✅ Telegram-Nachricht gesendet")
|
||||
|
||||
# Test Email
|
||||
if self.config.get("email", {}).get("enabled"):
|
||||
print("\n📧 Teste E-Mail...")
|
||||
self._send_email("🧪 Eurojackpot Test", test_message)
|
||||
print(" ✅ E-Mail gesendet")
|
||||
|
||||
print("\n" + "=" * 70)
|
||||
print("✅ Test abgeschlossen - prüfe deine Nachrichten!")
|
||||
|
||||
|
||||
def main():
|
||||
"""Test-Funktion."""
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Eurojackpot Notifier Test")
|
||||
parser.add_argument(
|
||||
'--test',
|
||||
action='store_true',
|
||||
help='Sendet Test-Benachrichtigungen'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--config',
|
||||
type=str,
|
||||
help='Pfad zur Config-Datei'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
notifier = EurojackpotNotifier(config_file=args.config)
|
||||
|
||||
if args.test:
|
||||
notifier.test_notifications()
|
||||
else:
|
||||
print("Nutze --test um Test-Benachrichtigungen zu senden")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,243 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Einfacher Eurojackpot Data Updater
|
||||
|
||||
Manuelle Eingabe oder CSV-Import von neuen Ziehungen.
|
||||
Perfekt als Fallback wenn Web-Scraping nicht funktioniert.
|
||||
"""
|
||||
|
||||
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', 'SZ1', 'SZ2'])
|
||||
|
||||
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_dir = os.path.join(os.path.dirname(data_file), "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 SZ1 SZ2")
|
||||
print("Beispiel: 2025-01-24 7 18 26 37 46 3 11")
|
||||
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, 6)]
|
||||
euro_numbers = [int(parts[i]) for i in range(6, 8)]
|
||||
|
||||
# Validierung
|
||||
if not all(1 <= n <= 50 for n in numbers):
|
||||
print(" ❌ Hauptzahlen müssen zwischen 1 und 50 liegen.")
|
||||
continue
|
||||
|
||||
if not all(1 <= n <= 12 for n in euro_numbers):
|
||||
print(" ❌ Eurozahlen müssen zwischen 1 und 12 liegen.")
|
||||
continue
|
||||
|
||||
if len(set(numbers)) != 5:
|
||||
print(" ❌ Hauptzahlen müssen eindeutig sein.")
|
||||
continue
|
||||
|
||||
if len(set(euro_numbers)) != 2:
|
||||
print(" ❌ Eurozahlen müssen eindeutig sein.")
|
||||
continue
|
||||
|
||||
# Sortiere Zahlen
|
||||
numbers_sorted = sorted(numbers)
|
||||
euro_sorted = sorted(euro_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],
|
||||
'SZ1': euro_sorted[0],
|
||||
'SZ2': euro_sorted[1]
|
||||
})
|
||||
|
||||
print(f" ✅ Hinzugefügt: {date_obj.strftime('%Y-%m-%d')} | "
|
||||
f"{numbers_sorted[0]}-{numbers_sorted[1]}-{numbers_sorted[2]}-{numbers_sorted[3]}-{numbers_sorted[4]} | "
|
||||
f"{euro_sorted[0]}-{euro_sorted[1]}")
|
||||
|
||||
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;SZ1;SZ2")
|
||||
print("Beispiel: 2025-01-24;7;18;26;37;46;3;11\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]):
|
||||
print(f" {i+1}. {draw['datum'].strftime('%Y-%m-%d')} | "
|
||||
f"{draw['Z1']}-{draw['Z2']}-{draw['Z3']}-{draw['Z4']}-{draw['Z5']} | "
|
||||
f"{draw['SZ1']}-{draw['SZ2']}")
|
||||
|
||||
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(" EUROJACKPOT 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/Eurojackpot/data/AlleEurojackpotzahlen.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()
|
||||
@@ -0,0 +1,556 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot API Data Updater
|
||||
|
||||
Lädt Eurojackpot-Ziehungen von verschiedenen APIs:
|
||||
1. lottoAPI (https://lottoapi.herokuapp.com)
|
||||
2. Lottoland API
|
||||
3. Sazka.cz API (Fallback)
|
||||
|
||||
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 EurojackpotAPIUpdater:
|
||||
"""Updater für Eurojackpot ü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 = {
|
||||
'lottoapi': {
|
||||
'url': 'https://lottoapi.herokuapp.com/eurojackpot-results/100',
|
||||
'name': 'lottoAPI',
|
||||
'parser': self._parse_lottoapi
|
||||
},
|
||||
'lottoland': {
|
||||
'url': 'https://media.lottoland.com/api/drawings/euroJackpot',
|
||||
'name': 'Lottoland API',
|
||||
'parser': self._parse_lottoland
|
||||
},
|
||||
'sazka': {
|
||||
'url': 'https://www.sazka.cz/api/draw-info/past-draws/eurojackpot',
|
||||
'name': 'Sazka.cz API',
|
||||
'parser': self._parse_sazka
|
||||
}
|
||||
}
|
||||
|
||||
print("🔄 EUROJACKPOT 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', 'SZ1', 'SZ2']
|
||||
)
|
||||
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), "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', 'lottoland', 'sazka')
|
||||
|
||||
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_lottoapi(self, data: List[Dict]) -> List[Dict]:
|
||||
"""
|
||||
Parst lottoAPI Response.
|
||||
|
||||
Expected format:
|
||||
[
|
||||
{
|
||||
"date": "2025-01-17",
|
||||
"numbers": [3, 9, 12, 24, 39],
|
||||
"euroNumbers": [5, 10]
|
||||
},
|
||||
...
|
||||
]
|
||||
"""
|
||||
draws = []
|
||||
|
||||
try:
|
||||
for item in data:
|
||||
# Datum
|
||||
date_str = item.get('date')
|
||||
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
|
||||
|
||||
# Hauptzahlen
|
||||
numbers = sorted(item.get('numbers', []))
|
||||
if len(numbers) != 5:
|
||||
continue
|
||||
|
||||
# Eurozahlen
|
||||
euro_numbers = sorted(item.get('euroNumbers', []))
|
||||
if len(euro_numbers) != 2:
|
||||
continue
|
||||
|
||||
draw = {
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
}
|
||||
|
||||
if self._validate_draw(draw):
|
||||
draws.append(draw)
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Parse-Fehler: {e}")
|
||||
|
||||
return draws
|
||||
|
||||
def _parse_lottoland(self, data: Dict) -> List[Dict]:
|
||||
"""
|
||||
Parst Lottoland API Response.
|
||||
|
||||
Expected format: {'last': {...}, 'next': {...}}
|
||||
"""
|
||||
draws = []
|
||||
|
||||
try:
|
||||
# Lottoland gibt nur die letzte Ziehung zurück
|
||||
if 'last' in data:
|
||||
item = data['last']
|
||||
|
||||
# Datum extrahieren aus verschachteltem 'date' Objekt
|
||||
date_info = item.get('date', {})
|
||||
day = date_info.get('day')
|
||||
month = date_info.get('month')
|
||||
year = date_info.get('year')
|
||||
|
||||
if day and month and year:
|
||||
date_obj = datetime(year, month, day)
|
||||
else:
|
||||
return draws
|
||||
|
||||
# Hauptzahlen
|
||||
numbers = sorted(item.get('numbers', []))
|
||||
if len(numbers) != 5:
|
||||
return draws
|
||||
|
||||
# Eurozahlen
|
||||
euro_numbers = sorted(item.get('euroNumbers', []))
|
||||
if len(euro_numbers) != 2:
|
||||
return draws
|
||||
|
||||
draw = {
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
}
|
||||
|
||||
if self._validate_draw(draw):
|
||||
draws.append(draw)
|
||||
print(f" ✅ Ziehung vom {date_obj.strftime('%Y-%m-%d')} extrahiert")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Parse-Fehler: {e}")
|
||||
|
||||
return draws
|
||||
|
||||
def _parse_sazka(self, data: Dict) -> List[Dict]:
|
||||
"""
|
||||
Parst Sazka.cz API Response.
|
||||
|
||||
Sazka hat zwei Steps:
|
||||
1. Liste von Draw IDs holen
|
||||
2. Für jede Draw ID Details holen
|
||||
"""
|
||||
draws = []
|
||||
|
||||
try:
|
||||
# Erste Response enthält Liste von Ziehungen
|
||||
if isinstance(data, list):
|
||||
past_draws = data
|
||||
elif isinstance(data, dict) and 'draws' in data:
|
||||
past_draws = data['draws']
|
||||
else:
|
||||
return draws
|
||||
|
||||
print(f" 📊 {len(past_draws)} Ziehungen gefunden, hole Details...")
|
||||
|
||||
# Begrenzen auf letzte 10 Ziehungen (um Requests zu sparen)
|
||||
for i, draw_item in enumerate(past_draws[:10]):
|
||||
draw_id = draw_item.get('id') # Nicht 'drawId' sondern 'id'!
|
||||
if not draw_id:
|
||||
continue
|
||||
|
||||
# Hole Details für diese Ziehung
|
||||
detail_url = f"https://www.sazka.cz/api/draw-info/draws/universal/eurojackpot/{draw_id}"
|
||||
|
||||
try:
|
||||
response = requests.get(detail_url, timeout=10)
|
||||
detail_data = response.json()
|
||||
|
||||
# Parse Detail
|
||||
date_str = detail_data.get('date') # Nicht 'drawDate' sondern 'date'
|
||||
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
|
||||
|
||||
# Zahlen sind direkt in 'numbers' und 'euroNumbers'
|
||||
numbers = sorted(detail_data.get('numbers', []))
|
||||
euro_numbers = sorted(detail_data.get('euroNumbers', []))
|
||||
|
||||
if len(numbers) == 5 and len(euro_numbers) == 2:
|
||||
draw = {
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
}
|
||||
|
||||
if self._validate_draw(draw):
|
||||
draws.append(draw)
|
||||
|
||||
if (i + 1) % 5 == 0:
|
||||
print(f" ⏳ {i + 1}/10 Details geladen...")
|
||||
|
||||
time.sleep(0.3) # Rate limiting
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Fehler bei Draw ID {draw_id}: {e}")
|
||||
continue
|
||||
|
||||
print(f" ✅ {len(draws)} gültige Ziehungen extrahiert")
|
||||
|
||||
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', 'SZ1', 'SZ2']
|
||||
if not all(field in draw for field in required_fields):
|
||||
return False
|
||||
|
||||
main_numbers = [draw[f'Z{i}'] for i in range(1, 6)]
|
||||
if not all(isinstance(n, int) and 1 <= n <= 50 for n in main_numbers):
|
||||
return False
|
||||
|
||||
if len(set(main_numbers)) != 5:
|
||||
return False
|
||||
|
||||
euro_numbers = [draw['SZ1'], draw['SZ2']]
|
||||
if not all(isinstance(n, int) and 1 <= n <= 12 for n in euro_numbers):
|
||||
return False
|
||||
|
||||
if len(set(euro_numbers)) != 2:
|
||||
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
|
||||
for api_name in ['lottoapi', 'lottoland', 'sazka']:
|
||||
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")
|
||||
|
||||
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', 'SZ1', 'SZ2']
|
||||
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', 'lottoapi', 'lottoland', 'sazka'
|
||||
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")
|
||||
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(" EUROJACKPOT API UPDATER")
|
||||
print(" Unterstützt: lottoAPI, Lottoland, Sazka.cz")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.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()
|
||||
|
||||
print("\n🌐 API WÄHLEN")
|
||||
print("=" * 60)
|
||||
print("1. Alle APIs versuchen (empfohlen)")
|
||||
print("2. lottoAPI")
|
||||
print("3. Lottoland API")
|
||||
print("4. Sazka.cz API")
|
||||
print()
|
||||
|
||||
api_choice = input("Wahl (1-4): ").strip()
|
||||
api_map = {'1': 'all', '2': 'lottoapi', '3': 'lottoland', '4': 'sazka'}
|
||||
api_name = api_map.get(api_choice, 'all')
|
||||
|
||||
print()
|
||||
|
||||
updater = EurojackpotAPIUpdater(data_file)
|
||||
success = updater.update(api_name=api_name)
|
||||
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,449 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Data Updater für eurojackpot-zahlen.eu
|
||||
|
||||
Optimierter Scraper für https://www.eurojackpot-zahlen.eu/eurojackpot-zahlenarchiv.php
|
||||
|
||||
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
|
||||
|
||||
|
||||
class EurojackpotUpdater:
|
||||
"""Updater für eurojackpot-zahlen.eu"""
|
||||
|
||||
def __init__(self, data_file: str):
|
||||
self.data_file = data_file
|
||||
self.url = "https://www.eurojackpot-zahlen.eu/eurojackpot-zahlenarchiv.php"
|
||||
self.backup_file = None
|
||||
self.df_existing = None
|
||||
|
||||
print("🔄 EUROJACKPOT DATA 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', 'SZ1', 'SZ2']
|
||||
)
|
||||
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), "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) -> List[Dict]:
|
||||
"""
|
||||
Scrapt alle Ziehungen von eurojackpot-zahlen.eu
|
||||
|
||||
Returns:
|
||||
Liste von Ziehungen
|
||||
"""
|
||||
print(f"\n🌐 Lade Daten von {self.url}...")
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'
|
||||
}
|
||||
|
||||
response = requests.get(self.url, headers=headers, timeout=15)
|
||||
response.raise_for_status()
|
||||
|
||||
print(" ✅ Seite erfolgreich geladen")
|
||||
print(" 🔍 Parse HTML-Struktur...")
|
||||
|
||||
soup = BeautifulSoup(response.content, 'html.parser')
|
||||
|
||||
# Neue Struktur: divs mit Klasse "zahlen_rahmen"
|
||||
# Jeder zahlen_rahmen enthält: zahlen_hinweis01 (#), zahlen_hinweis02 (Datum),
|
||||
# zahlen_hinweis03 (Zahlen), zahlen_hinweis04 (Eurozahlen)
|
||||
|
||||
containers = soup.find_all('div', class_='zahlen_rahmen')
|
||||
|
||||
if not containers:
|
||||
print(" ❌ Keine zahlen_rahmen Container gefunden")
|
||||
# Debug-Info
|
||||
print(f" 🔍 Debug: Seite enthält {len(soup.find_all('div'))} divs")
|
||||
print(f" 🔍 Debug: Erste 500 Zeichen: {response.text[:500]}")
|
||||
return []
|
||||
|
||||
print(f" 📊 {len(containers)} Container gefunden")
|
||||
print(" ⏳ Extrahiere Ziehungen...")
|
||||
|
||||
draws = []
|
||||
|
||||
# Überspringe ersten Container (Header)
|
||||
for i, container in enumerate(containers[1:], 1):
|
||||
try:
|
||||
# Extrahiere Datum aus <time> Element
|
||||
time_element = container.find('time', class_='zahlenarchiv_datum')
|
||||
if not time_element:
|
||||
continue
|
||||
|
||||
date_text = time_element.text.strip()
|
||||
date_obj = self._parse_date(date_text)
|
||||
|
||||
# Extrahiere Hauptzahlen (zahlenarchiv_zahl)
|
||||
main_number_divs = container.find_all('div', class_='zahlenarchiv_zahl')
|
||||
main_numbers = [int(div.text.strip()) for div in main_number_divs if div.text.strip().isdigit()]
|
||||
|
||||
# Extrahiere Eurozahlen (zahlenarchiv_zz)
|
||||
euro_number_divs = container.find_all('div', class_='zahlenarchiv_zz')
|
||||
euro_numbers = [int(div.text.strip()) for div in euro_number_divs if div.text.strip().isdigit()]
|
||||
|
||||
# Validierung
|
||||
if len(main_numbers) != 5:
|
||||
print(f" ⚠️ Zeile {i+1}: Ungültige Anzahl Hauptzahlen ({len(main_numbers)})")
|
||||
continue
|
||||
|
||||
if len(euro_numbers) != 2:
|
||||
print(f" ⚠️ Zeile {i+1}: Ungültige Anzahl Eurozahlen ({len(euro_numbers)})")
|
||||
continue
|
||||
|
||||
# Sortiere Hauptzahlen
|
||||
main_numbers_sorted = sorted(main_numbers)
|
||||
|
||||
# Sortiere Eurozahlen
|
||||
euro_numbers_sorted = sorted(euro_numbers)
|
||||
|
||||
draw = {
|
||||
'datum': date_obj,
|
||||
'Z1': main_numbers_sorted[0],
|
||||
'Z2': main_numbers_sorted[1],
|
||||
'Z3': main_numbers_sorted[2],
|
||||
'Z4': main_numbers_sorted[3],
|
||||
'Z5': main_numbers_sorted[4],
|
||||
'SZ1': euro_numbers_sorted[0],
|
||||
'SZ2': euro_numbers_sorted[1]
|
||||
}
|
||||
|
||||
# Validiere
|
||||
if self._validate_draw(draw):
|
||||
draws.append(draw)
|
||||
else:
|
||||
print(f" ⚠️ Zeile {i+1}: Validierung fehlgeschlagen")
|
||||
|
||||
# Fortschrittsanzeige
|
||||
if (i + 1) % 50 == 0:
|
||||
print(f" ⏳ {i + 1}/{len(data_rows)} Zeilen verarbeitet...")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Zeile {i+1}: Fehler beim Parsen - {e}")
|
||||
continue
|
||||
|
||||
print(f" ✅ {len(draws)} gültige 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_date(self, date_str: str) -> datetime:
|
||||
"""
|
||||
Parst Datum im Format "DD.MM.YYYY"
|
||||
|
||||
Args:
|
||||
date_str: Datum als String
|
||||
|
||||
Returns:
|
||||
datetime Objekt
|
||||
"""
|
||||
try:
|
||||
# Format: "03.01.2025"
|
||||
return datetime.strptime(date_str, '%d.%m.%Y')
|
||||
except ValueError:
|
||||
try:
|
||||
# Fallback: "YYYY-MM-DD"
|
||||
return datetime.strptime(date_str, '%Y-%m-%d')
|
||||
except ValueError:
|
||||
# Letzter Fallback: Aktuelles Datum
|
||||
print(f" ⚠️ Konnte Datum nicht parsen: {date_str}")
|
||||
return datetime.now()
|
||||
|
||||
def _validate_draw(self, draw: Dict) -> bool:
|
||||
"""
|
||||
Validiert eine Ziehung.
|
||||
|
||||
Args:
|
||||
draw: Ziehungs-Dictionary
|
||||
|
||||
Returns:
|
||||
True wenn valide
|
||||
"""
|
||||
try:
|
||||
# Prüfe Pflichtfelder
|
||||
required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2']
|
||||
if not all(field in draw for field in required_fields):
|
||||
return False
|
||||
|
||||
# Prüfe Hauptzahlen (1-50)
|
||||
main_numbers = [draw[f'Z{i}'] for i in range(1, 6)]
|
||||
if not all(isinstance(n, int) and 1 <= n <= 50 for n in main_numbers):
|
||||
return False
|
||||
|
||||
# Prüfe Eindeutigkeit
|
||||
if len(set(main_numbers)) != 5:
|
||||
return False
|
||||
|
||||
# Prüfe Eurozahlen (1-12)
|
||||
euro_numbers = [draw['SZ1'], draw['SZ2']]
|
||||
if not all(isinstance(n, int) and 1 <= n <= 12 for n in euro_numbers):
|
||||
return False
|
||||
|
||||
# Prüfe Eindeutigkeit
|
||||
if len(set(euro_numbers)) != 2:
|
||||
return False
|
||||
|
||||
# Prüfe Datum
|
||||
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.
|
||||
|
||||
Args:
|
||||
new_draws: Liste von neuen Ziehungen
|
||||
|
||||
Returns:
|
||||
Zusammengeführter DataFrame
|
||||
"""
|
||||
print(f"\n🔀 Merge mit existierenden Daten...")
|
||||
|
||||
if not new_draws:
|
||||
print(" ⚠️ Keine neuen Ziehungen zum Mergen")
|
||||
return self.df_existing
|
||||
|
||||
# Erstelle DataFrame aus neuen Ziehungen
|
||||
df_new = pd.DataFrame(new_draws)
|
||||
|
||||
# Kombiniere
|
||||
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)
|
||||
|
||||
# Entferne Duplikate (basierend auf Datum)
|
||||
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")
|
||||
|
||||
# Sortiere nach Datum (aufsteigend)
|
||||
df_combined = df_combined.sort_values('datum', ascending=True)
|
||||
df_combined = df_combined.reset_index(drop=True)
|
||||
|
||||
# Statistiken
|
||||
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 in CSV.
|
||||
|
||||
Args:
|
||||
df: DataFrame zum Speichern
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
try:
|
||||
print(f"\n💾 Speichere Daten...")
|
||||
|
||||
# Erstelle Kopie für Export
|
||||
df_export = df.copy()
|
||||
|
||||
# Format Datum als String
|
||||
df_export['datum'] = df_export['datum'].dt.strftime('%Y-%m-%d')
|
||||
|
||||
# Sortiere Spalten
|
||||
column_order = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2']
|
||||
df_export = df_export[column_order]
|
||||
|
||||
# Speichere
|
||||
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}")
|
||||
|
||||
# Restore backup falls vorhanden
|
||||
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, create_backup: bool = True) -> bool:
|
||||
"""
|
||||
Führt komplettes Update durch.
|
||||
|
||||
Args:
|
||||
create_backup: Backup vor Update erstellen
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
print("\n🚀 STARTE UPDATE")
|
||||
print("=" * 70)
|
||||
|
||||
# 1. Lade existierende Daten
|
||||
if not self.load_existing_data():
|
||||
return False
|
||||
|
||||
# 2. Backup erstellen
|
||||
if create_backup:
|
||||
self.create_backup()
|
||||
|
||||
# 3. Hole Ziehungen von Web
|
||||
new_draws = self.fetch_draws_from_web()
|
||||
|
||||
if not new_draws:
|
||||
print("\n❌ Keine Daten von Website geladen")
|
||||
return False
|
||||
|
||||
# 4. Merge
|
||||
df_updated = self.merge_with_existing(new_draws)
|
||||
|
||||
# 5. Speichern
|
||||
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(" EUROJACKPOT DATA UPDATER")
|
||||
print(" Quelle: eurojackpot-zahlen.eu")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Standard-Pfad
|
||||
default_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
|
||||
# Command-line Argumente
|
||||
if len(sys.argv) > 1:
|
||||
data_file = sys.argv[1]
|
||||
else:
|
||||
print(f"Standard-Datei: {default_file}")
|
||||
print()
|
||||
use_default = input("Standard 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()
|
||||
|
||||
# Update durchführen
|
||||
print()
|
||||
updater = EurojackpotUpdater(data_file)
|
||||
success = updater.update()
|
||||
|
||||
# Exit-Code
|
||||
sys.exit(0 if success else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,681 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Historical Data Updater
|
||||
|
||||
Lädt automatisch die neuesten Eurojackpot-Ziehungen von der offiziellen Website
|
||||
und aktualisiert die lokale CSV-Datei.
|
||||
|
||||
Quellen:
|
||||
- https://www.eurojackpot.de/de/eurojackpot/gewinnzahlen.html
|
||||
- Oder alternative APIs/Websites
|
||||
|
||||
Features:
|
||||
- Automatisches Scraping der neuesten Ziehungen
|
||||
- Duplikate-Vermeidung
|
||||
- Backup vor Update
|
||||
- Validierung der neuen Daten
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
import shutil
|
||||
import os
|
||||
import re
|
||||
from typing import List, Dict, Optional
|
||||
import time
|
||||
|
||||
|
||||
class EurojackpotDataUpdater:
|
||||
"""Aktualisiert historische Eurojackpot-Daten automatisch."""
|
||||
|
||||
def __init__(self, data_file: str):
|
||||
"""
|
||||
Initialisiert den Updater.
|
||||
|
||||
Args:
|
||||
data_file: Pfad zur lokalen CSV-Datei
|
||||
"""
|
||||
self.data_file = data_file
|
||||
self.backup_file = None
|
||||
self.df_existing = None
|
||||
|
||||
# API/Scraping URLs
|
||||
self.sources = {
|
||||
'eurojackpot_de': 'https://www.eurojackpot.de/de/eurojackpot/gewinnzahlen.html',
|
||||
'euro-jackpot_net': 'https://www.euro-jackpot.net/de/gewinnzahlen',
|
||||
'lottode': 'https://www.lotto.de/eurojackpot/gewinnzahlen'
|
||||
}
|
||||
|
||||
print("🔄 EUROJACKPOT DATA UPDATER")
|
||||
print("=" * 60)
|
||||
|
||||
def load_existing_data(self) -> bool:
|
||||
"""Lädt existierende Daten."""
|
||||
try:
|
||||
if not os.path.exists(self.data_file):
|
||||
print(f"⚠️ Datei nicht gefunden: {self.data_file}")
|
||||
print(" Erstelle neue Datei...")
|
||||
self.df_existing = pd.DataFrame(columns=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2'])
|
||||
return True
|
||||
|
||||
self.df_existing = pd.read_csv(self.data_file, sep=';')
|
||||
|
||||
# Datum als datetime
|
||||
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"✅ Existierende Daten geladen: {len(self.df_existing)} Ziehungen")
|
||||
|
||||
if len(self.df_existing) > 0:
|
||||
latest = self.df_existing['datum'].max()
|
||||
print(f" Neueste Ziehung: {latest.strftime('%Y-%m-%d') if pd.notna(latest) else 'Unbekannt'}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler beim Laden: {e}")
|
||||
return False
|
||||
|
||||
def create_backup(self) -> bool:
|
||||
"""Erstellt Backup der existierenden Datei."""
|
||||
if not os.path.exists(self.data_file):
|
||||
return True
|
||||
|
||||
try:
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
backup_dir = os.path.join(os.path.dirname(self.data_file), "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 erstellt: {os.path.basename(self.backup_file)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Backup-Fehler: {e}")
|
||||
return False
|
||||
|
||||
def fetch_from_eurojackpot_de(self) -> List[Dict]:
|
||||
"""
|
||||
Scrapt Daten von eurojackpot.de
|
||||
|
||||
Returns:
|
||||
Liste von Ziehungen als Dictionaries
|
||||
"""
|
||||
print("\n🌐 Versuche eurojackpot.de...")
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
|
||||
}
|
||||
|
||||
response = requests.get(self.sources['eurojackpot_de'], headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.content, 'html.parser')
|
||||
|
||||
# Suche nach Ziehungsergebnissen
|
||||
# Dies ist ein generisches Beispiel - muss an tatsächliche Website-Struktur angepasst werden
|
||||
draws = []
|
||||
|
||||
# Beispiel-Parsing (muss angepasst werden!)
|
||||
result_blocks = soup.find_all('div', class_='drawing-result')
|
||||
|
||||
for block in result_blocks[:10]: # Letzte 10 Ziehungen
|
||||
try:
|
||||
# Datum extrahieren
|
||||
date_elem = block.find('span', class_='date')
|
||||
if not date_elem:
|
||||
continue
|
||||
|
||||
date_str = date_elem.text.strip()
|
||||
date_obj = self._parse_german_date(date_str)
|
||||
|
||||
# Zahlen extrahieren
|
||||
numbers = []
|
||||
number_elems = block.find_all('span', class_='ball')
|
||||
|
||||
for num_elem in number_elems[:5]:
|
||||
num = int(num_elem.text.strip())
|
||||
numbers.append(num)
|
||||
|
||||
# Eurozahlen extrahieren
|
||||
euro_numbers = []
|
||||
euro_elems = block.find_all('span', class_='euro-ball')
|
||||
|
||||
for euro_elem in euro_elems[:2]:
|
||||
euro_num = int(euro_elem.text.strip())
|
||||
euro_numbers.append(euro_num)
|
||||
|
||||
if len(numbers) == 5 and len(euro_numbers) == 2:
|
||||
draws.append({
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
if draws:
|
||||
print(f" ✅ {len(draws)} Ziehungen gefunden")
|
||||
return draws
|
||||
else:
|
||||
print(" ⚠️ Keine Ziehungen gefunden (HTML-Struktur möglicherweise geändert)")
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler: {e}")
|
||||
return []
|
||||
|
||||
def fetch_from_euro_jackpot_net(self) -> List[Dict]:
|
||||
"""
|
||||
Scrapt Daten von euro-jackpot.net
|
||||
|
||||
Returns:
|
||||
Liste von Ziehungen
|
||||
"""
|
||||
print("\n🌐 Versuche euro-jackpot.net...")
|
||||
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36'
|
||||
}
|
||||
|
||||
response = requests.get(self.sources['euro-jackpot_net'], headers=headers, timeout=10)
|
||||
response.raise_for_status()
|
||||
|
||||
soup = BeautifulSoup(response.content, 'html.parser')
|
||||
|
||||
draws = []
|
||||
|
||||
# Parsing-Logik für diese Website
|
||||
# (Platzhalter - muss an tatsächliche Struktur angepasst werden)
|
||||
|
||||
result_rows = soup.find_all('tr', class_='result-row')
|
||||
|
||||
for row in result_rows[:10]:
|
||||
try:
|
||||
cells = row.find_all('td')
|
||||
|
||||
if len(cells) < 8:
|
||||
continue
|
||||
|
||||
# Datum
|
||||
date_str = cells[0].text.strip()
|
||||
date_obj = self._parse_german_date(date_str)
|
||||
|
||||
# Hauptzahlen
|
||||
numbers = [int(cells[i].text.strip()) for i in range(1, 6)]
|
||||
|
||||
# Eurozahlen
|
||||
euro_numbers = [int(cells[i].text.strip()) for i in range(6, 8)]
|
||||
|
||||
draws.append({
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
})
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
if draws:
|
||||
print(f" ✅ {len(draws)} Ziehungen gefunden")
|
||||
else:
|
||||
print(" ⚠️ Keine Ziehungen gefunden")
|
||||
|
||||
return draws
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ Fehler: {e}")
|
||||
return []
|
||||
|
||||
def fetch_from_manual_input(self) -> List[Dict]:
|
||||
"""
|
||||
Manuelle Eingabe von neuen Ziehungen.
|
||||
|
||||
Returns:
|
||||
Liste von Ziehungen
|
||||
"""
|
||||
print("\n⌨️ MANUELLE EINGABE")
|
||||
print("=" * 60)
|
||||
print("Gib die neuesten Ziehungen manuell ein.")
|
||||
print("Format: YYYY-MM-DD Z1 Z2 Z3 Z4 Z5 SZ1 SZ2")
|
||||
print("Beispiel: 2025-01-24 7 18 26 37 46 3 11")
|
||||
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, 6)]
|
||||
euro_numbers = [int(parts[i]) for i in range(6, 8)]
|
||||
|
||||
# Validierung
|
||||
if not all(1 <= n <= 50 for n in numbers):
|
||||
print(" ❌ Hauptzahlen müssen zwischen 1 und 50 liegen.")
|
||||
continue
|
||||
|
||||
if not all(1 <= n <= 12 for n in euro_numbers):
|
||||
print(" ❌ Eurozahlen müssen zwischen 1 und 12 liegen.")
|
||||
continue
|
||||
|
||||
if len(set(numbers)) != 5:
|
||||
print(" ❌ Hauptzahlen müssen eindeutig sein.")
|
||||
continue
|
||||
|
||||
if len(set(euro_numbers)) != 2:
|
||||
print(" ❌ Eurozahlen müssen eindeutig sein.")
|
||||
continue
|
||||
|
||||
draws.append({
|
||||
'datum': date_obj,
|
||||
'Z1': numbers[0],
|
||||
'Z2': numbers[1],
|
||||
'Z3': numbers[2],
|
||||
'Z4': numbers[3],
|
||||
'Z5': numbers[4],
|
||||
'SZ1': euro_numbers[0],
|
||||
'SZ2': euro_numbers[1]
|
||||
})
|
||||
|
||||
print(f" ✅ Ziehung hinzugefügt: {date_obj.strftime('%Y-%m-%d')}")
|
||||
|
||||
except ValueError as e:
|
||||
print(f" ❌ Fehler: {e}")
|
||||
continue
|
||||
|
||||
if draws:
|
||||
print(f"\n✅ {len(draws)} Ziehungen manuell eingegeben")
|
||||
|
||||
return draws
|
||||
|
||||
def _parse_german_date(self, date_str: str) -> datetime:
|
||||
"""
|
||||
Parst deutsches Datumsformat.
|
||||
|
||||
Args:
|
||||
date_str: Datum als String (z.B. "24.01.2025" oder "24. Januar 2025")
|
||||
|
||||
Returns:
|
||||
datetime Objekt
|
||||
"""
|
||||
# Entferne zusätzliche Leerzeichen
|
||||
date_str = re.sub(r'\s+', ' ', date_str.strip())
|
||||
|
||||
# Monatsnamen-Mapping
|
||||
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
|
||||
}
|
||||
|
||||
# Versuche verschiedene Formate
|
||||
formats = [
|
||||
'%d.%m.%Y',
|
||||
'%d.%m.%y',
|
||||
'%Y-%m-%d',
|
||||
'%d/%m/%Y'
|
||||
]
|
||||
|
||||
for fmt in formats:
|
||||
try:
|
||||
return datetime.strptime(date_str, fmt)
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
# Versuche mit Monatsnamen
|
||||
for month_name, month_num in months_de.items():
|
||||
if month_name.lower() in date_str.lower():
|
||||
# Extrahiere Tag und Jahr
|
||||
match = re.search(r'(\d{1,2})\.?\s+' + month_name + r'\s+(\d{4})', date_str, re.IGNORECASE)
|
||||
if match:
|
||||
day = int(match.group(1))
|
||||
year = int(match.group(2))
|
||||
return datetime(year, month_num, day)
|
||||
|
||||
# Fallback: aktuelles Datum
|
||||
print(f" ⚠️ Konnte Datum nicht parsen: {date_str}, nutze aktuelles Datum")
|
||||
return datetime.now()
|
||||
|
||||
def fetch_new_draws(self, source: str = 'auto') -> List[Dict]:
|
||||
"""
|
||||
Holt neue Ziehungen von der gewählten Quelle.
|
||||
|
||||
Args:
|
||||
source: 'auto', 'eurojackpot_de', 'euro_jackpot_net', 'manual'
|
||||
|
||||
Returns:
|
||||
Liste von neuen Ziehungen
|
||||
"""
|
||||
print(f"\n🔍 SUCHE NACH NEUEN ZIEHUNGEN (Quelle: {source})")
|
||||
print("=" * 60)
|
||||
|
||||
all_draws = []
|
||||
|
||||
if source == 'auto':
|
||||
# Versuche alle Quellen
|
||||
sources_to_try = [
|
||||
('eurojackpot_de', self.fetch_from_eurojackpot_de),
|
||||
('euro_jackpot_net', self.fetch_from_euro_jackpot_net)
|
||||
]
|
||||
|
||||
for source_name, fetch_func in sources_to_try:
|
||||
draws = fetch_func()
|
||||
if draws:
|
||||
all_draws.extend(draws)
|
||||
break # Erste erfolgreiche Quelle nutzen
|
||||
time.sleep(1) # Pause zwischen Requests
|
||||
|
||||
if not all_draws:
|
||||
print("\n⚠️ Automatisches Scraping fehlgeschlagen.")
|
||||
print(" Möchtest du Daten manuell eingeben? (j/n): ", end='')
|
||||
if input().lower() in ['j', 'ja', 'y', 'yes']:
|
||||
all_draws = self.fetch_from_manual_input()
|
||||
|
||||
elif source == 'eurojackpot_de':
|
||||
all_draws = self.fetch_from_eurojackpot_de()
|
||||
|
||||
elif source == 'euro_jackpot_net':
|
||||
all_draws = self.fetch_from_euro_jackpot_net()
|
||||
|
||||
elif source == 'manual':
|
||||
all_draws = self.fetch_from_manual_input()
|
||||
|
||||
else:
|
||||
print(f"❌ Unbekannte Quelle: {source}")
|
||||
|
||||
return all_draws
|
||||
|
||||
def validate_draw(self, draw: Dict) -> bool:
|
||||
"""
|
||||
Validiert eine Ziehung.
|
||||
|
||||
Args:
|
||||
draw: Ziehungs-Dictionary
|
||||
|
||||
Returns:
|
||||
True wenn valide
|
||||
"""
|
||||
try:
|
||||
# Prüfe Pflichtfelder
|
||||
required_fields = ['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'SZ1', 'SZ2']
|
||||
if not all(field in draw for field in required_fields):
|
||||
return False
|
||||
|
||||
# Prüfe Hauptzahlen (1-50)
|
||||
main_numbers = [draw[f'Z{i}'] for i in range(1, 6)]
|
||||
if not all(1 <= n <= 50 for n in main_numbers):
|
||||
return False
|
||||
|
||||
if len(set(main_numbers)) != 5: # Eindeutig
|
||||
return False
|
||||
|
||||
# Prüfe Eurozahlen (1-12)
|
||||
euro_numbers = [draw['SZ1'], draw['SZ2']]
|
||||
if not all(1 <= n <= 12 for n in euro_numbers):
|
||||
return False
|
||||
|
||||
if len(set(euro_numbers)) != 2: # Eindeutig
|
||||
return False
|
||||
|
||||
# Prüfe Datum
|
||||
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.
|
||||
|
||||
Args:
|
||||
new_draws: Liste von neuen Ziehungen
|
||||
|
||||
Returns:
|
||||
Zusammengeführter DataFrame
|
||||
"""
|
||||
print(f"\n🔀 MERGE MIT EXISTIERENDEN DATEN")
|
||||
print("=" * 60)
|
||||
|
||||
# Validiere neue Ziehungen
|
||||
valid_draws = [draw for draw in new_draws if self.validate_draw(draw)]
|
||||
|
||||
if len(valid_draws) < len(new_draws):
|
||||
invalid_count = len(new_draws) - len(valid_draws)
|
||||
print(f"⚠️ {invalid_count} ungültige Ziehung(en) übersprungen")
|
||||
|
||||
if not valid_draws:
|
||||
print("❌ Keine gültigen neuen Ziehungen zum Hinzufügen")
|
||||
return self.df_existing
|
||||
|
||||
# Erstelle DataFrame aus neuen Ziehungen
|
||||
df_new = pd.DataFrame(valid_draws)
|
||||
|
||||
# Kombiniere
|
||||
if self.df_existing is None or len(self.df_existing) == 0:
|
||||
df_combined = df_new
|
||||
else:
|
||||
df_combined = pd.concat([self.df_existing, df_new], ignore_index=True)
|
||||
|
||||
# Entferne Duplikate (basierend auf Datum + Zahlen)
|
||||
before_dedup = len(df_combined)
|
||||
df_combined = df_combined.drop_duplicates(
|
||||
subset=['datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5'],
|
||||
keep='first'
|
||||
)
|
||||
after_dedup = len(df_combined)
|
||||
|
||||
duplicates_removed = before_dedup - after_dedup
|
||||
if duplicates_removed > 0:
|
||||
print(f"🗑️ {duplicates_removed} Duplikat(e) entfernt")
|
||||
|
||||
# Sortiere nach Datum
|
||||
df_combined = df_combined.sort_values('datum', ascending=True)
|
||||
df_combined = df_combined.reset_index(drop=True)
|
||||
|
||||
# Berechne neue Einträge
|
||||
new_entries = len(df_combined) - len(self.df_existing) if self.df_existing is not None else len(df_combined)
|
||||
|
||||
print(f"✅ Merge abgeschlossen:")
|
||||
print(f" Vorher: {len(self.df_existing) if self.df_existing is not None else 0} Ziehungen")
|
||||
print(f" Neu hinzugefügt: {new_entries} Ziehungen")
|
||||
print(f" Nachher: {len(df_combined)} Ziehungen")
|
||||
|
||||
return df_combined
|
||||
|
||||
def save_updated_data(self, df: pd.DataFrame) -> bool:
|
||||
"""
|
||||
Speichert aktualisierte Daten.
|
||||
|
||||
Args:
|
||||
df: DataFrame zum Speichern
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
try:
|
||||
# Format Datum als String
|
||||
df_to_save = df.copy()
|
||||
df_to_save['datum'] = df_to_save['datum'].dt.strftime('%Y-%m-%d')
|
||||
|
||||
# Speichern
|
||||
df_to_save.to_csv(self.data_file, sep=';', index=False)
|
||||
|
||||
print(f"\n💾 Daten gespeichert: {self.data_file}")
|
||||
print(f" {len(df)} Ziehungen total")
|
||||
|
||||
if len(df) > 0:
|
||||
latest = df['datum'].max()
|
||||
print(f" Neueste Ziehung: {latest.strftime('%Y-%m-%d')}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Fehler beim Speichern: {e}")
|
||||
|
||||
# Restore backup
|
||||
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, source: str = 'auto', dry_run: bool = False) -> bool:
|
||||
"""
|
||||
Führt komplettes Update durch.
|
||||
|
||||
Args:
|
||||
source: Datenquelle ('auto', 'eurojackpot_de', 'euro_jackpot_net', 'manual')
|
||||
dry_run: Wenn True, keine Änderungen speichern
|
||||
|
||||
Returns:
|
||||
True bei Erfolg
|
||||
"""
|
||||
print(f"\n{'🧪 DRY RUN MODE' if dry_run else '🚀 UPDATE STARTEN'}")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. Lade existierende Daten
|
||||
if not self.load_existing_data():
|
||||
return False
|
||||
|
||||
# 2. Backup erstellen
|
||||
if not dry_run:
|
||||
self.create_backup()
|
||||
|
||||
# 3. Hole neue Ziehungen
|
||||
new_draws = self.fetch_new_draws(source=source)
|
||||
|
||||
if not new_draws:
|
||||
print("\n✅ Keine neuen Ziehungen gefunden - Daten sind aktuell")
|
||||
return True
|
||||
|
||||
# 4. Merge mit existierenden Daten
|
||||
df_updated = self.merge_with_existing(new_draws)
|
||||
|
||||
# 5. Speichern
|
||||
if not dry_run:
|
||||
return self.save_updated_data(df_updated)
|
||||
else:
|
||||
print("\n🧪 DRY RUN - Keine Änderungen gespeichert")
|
||||
print(f" Würde {len(df_updated)} Ziehungen speichern")
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion für interaktive Nutzung."""
|
||||
print("=" * 70)
|
||||
print(" EUROJACKPOT HISTORICAL DATA UPDATER")
|
||||
print("=" * 70)
|
||||
print()
|
||||
|
||||
# Konfiguration
|
||||
default_data_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
|
||||
print("📁 KONFIGURATION")
|
||||
print("=" * 60)
|
||||
print(f"Standard-Datei: {default_data_file}")
|
||||
print()
|
||||
|
||||
use_default = input("Standard-Datei verwenden? (j/n): ").lower().strip()
|
||||
|
||||
if use_default in ['j', 'ja', 'y', 'yes', '']:
|
||||
data_file = default_data_file
|
||||
else:
|
||||
data_file = input("Pfad zur Datendatei: ").strip()
|
||||
|
||||
print()
|
||||
print("🌐 DATENQUELLE WÄHLEN")
|
||||
print("=" * 60)
|
||||
print("1. Auto (versucht alle Quellen)")
|
||||
print("2. eurojackpot.de")
|
||||
print("3. euro-jackpot.net")
|
||||
print("4. Manuelle Eingabe")
|
||||
print()
|
||||
|
||||
source_choice = input("Wahl (1-4): ").strip()
|
||||
|
||||
source_map = {
|
||||
'1': 'auto',
|
||||
'2': 'eurojackpot_de',
|
||||
'3': 'euro_jackpot_net',
|
||||
'4': 'manual'
|
||||
}
|
||||
|
||||
source = source_map.get(source_choice, 'auto')
|
||||
|
||||
print()
|
||||
print("🧪 DRY RUN?")
|
||||
print("=" * 60)
|
||||
print("Dry Run = Keine Änderungen, nur Vorschau")
|
||||
dry_run_choice = input("Dry Run aktivieren? (j/n): ").lower().strip()
|
||||
dry_run = dry_run_choice in ['j', 'ja', 'y', 'yes']
|
||||
|
||||
print()
|
||||
|
||||
# Update durchführen
|
||||
updater = EurojackpotDataUpdater(data_file)
|
||||
success = updater.update(source=source, dry_run=dry_run)
|
||||
|
||||
print()
|
||||
print("=" * 70)
|
||||
if success:
|
||||
print("✅ UPDATE ERFOLGREICH ABGESCHLOSSEN")
|
||||
else:
|
||||
print("❌ UPDATE FEHLGESCHLAGEN")
|
||||
print("=" * 70)
|
||||
|
||||
return success
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
|
||||
# Einfache CLI
|
||||
if len(sys.argv) > 1:
|
||||
# Kommandozeilen-Modus
|
||||
data_file = sys.argv[1]
|
||||
source = sys.argv[2] if len(sys.argv) > 2 else 'auto'
|
||||
dry_run = '--dry-run' in sys.argv
|
||||
|
||||
updater = EurojackpotDataUpdater(data_file)
|
||||
success = updater.update(source=source, dry_run=dry_run)
|
||||
sys.exit(0 if success else 1)
|
||||
else:
|
||||
# Interaktiver Modus
|
||||
main()
|
||||
Executable
+460
@@ -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/AlleLottozahlen.csv',
|
||||
'eurojackpot': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/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()
|
||||
Executable
+401
@@ -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/AlleLottozahlen.csv',
|
||||
'type': 'lotto'
|
||||
},
|
||||
'eurojackpot': {
|
||||
'path': '/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/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()
|
||||
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Eurojackpot Zahlen-Umschlüsselung
|
||||
|
||||
Fügt neue Spalten z1, z2, z3, z4, z5 hinzu mit umschlüsselten Werten:
|
||||
1-5 → 1, 6-10 → 2, 11-15 → 3, 16-20 → 4, 21-25 → 5,
|
||||
26-30 → 6, 31-35 → 7, 36-40 → 8, 41-45 → 9, 46-50 → 10
|
||||
"""
|
||||
|
||||
import pandas as pd
|
||||
|
||||
def convert_number_to_group(number):
|
||||
"""Konvertiert eine Zahl (1-50) in eine Gruppe (1-10)."""
|
||||
if 1 <= number <= 5:
|
||||
return 1
|
||||
elif 6 <= number <= 10:
|
||||
return 2
|
||||
elif 11 <= number <= 15:
|
||||
return 3
|
||||
elif 16 <= number <= 20:
|
||||
return 4
|
||||
elif 21 <= number <= 25:
|
||||
return 5
|
||||
elif 26 <= number <= 30:
|
||||
return 6
|
||||
elif 31 <= number <= 35:
|
||||
return 7
|
||||
elif 36 <= number <= 40:
|
||||
return 8
|
||||
elif 41 <= number <= 45:
|
||||
return 9
|
||||
elif 46 <= number <= 50:
|
||||
return 10
|
||||
else:
|
||||
return 0 # Fehlerfall
|
||||
|
||||
def process_number_conversion():
|
||||
"""Führt die Zahlenumschlüsselung durch."""
|
||||
|
||||
# Eingabedatei laden
|
||||
input_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/data/AlleEurojackpotzahlen.csv"
|
||||
|
||||
print("🔢 EUROJACKPOT ZAHLEN-UMSCHLÜSSELUNG")
|
||||
print("="*50)
|
||||
|
||||
# Daten laden
|
||||
print(f"📁 Lade Daten aus: AlleEurojackpotzahlen.csv")
|
||||
df = pd.read_csv(input_file, sep=';')
|
||||
print(f"✅ {len(df)} Ziehungen geladen")
|
||||
|
||||
# Umschlüsselungsschema anzeigen
|
||||
print(f"\n📋 UMSCHLÜSSELUNGSSCHEMA:")
|
||||
print("="*30)
|
||||
ranges = [
|
||||
(1, 5, 1), (6, 10, 2), (11, 15, 3), (16, 20, 4), (21, 25, 5),
|
||||
(26, 30, 6), (31, 35, 7), (36, 40, 8), (41, 45, 9), (46, 50, 10)
|
||||
]
|
||||
|
||||
for start, end, group in ranges:
|
||||
print(f"Zahlen {start:2}-{end:2} → Gruppe {group:2}")
|
||||
|
||||
# Neue Spalten erstellen
|
||||
print(f"\n🔄 Erstelle neue Spalten z1, z2, z3, z4, z5...")
|
||||
|
||||
source_columns = ['Z1', 'Z2', 'Z3', 'Z4', 'Z5']
|
||||
target_columns = ['z1', 'z2', 'z3', 'z4', 'z5']
|
||||
|
||||
for source_col, target_col in zip(source_columns, target_columns):
|
||||
df[target_col] = df[source_col].apply(convert_number_to_group)
|
||||
print(f" {source_col} → {target_col} ✅")
|
||||
|
||||
# Erste 5 Beispiele anzeigen
|
||||
print(f"\n📊 BEISPIEL-UMSCHLÜSSELUNGEN (erste 5 Ziehungen):")
|
||||
print("="*60)
|
||||
print(f"{'Datum':<12} {'Z1→z1':<8} {'Z2→z2':<8} {'Z3→z3':<8} {'Z4→z4':<8} {'Z5→z5':<8}")
|
||||
print("-" * 60)
|
||||
|
||||
for i in range(min(5, len(df))):
|
||||
row = df.iloc[i]
|
||||
datum = row['datum']
|
||||
conversions = []
|
||||
for source_col, target_col in zip(source_columns, target_columns):
|
||||
original = row[source_col]
|
||||
converted = row[target_col]
|
||||
conversions.append(f"{original:2}→{converted}")
|
||||
|
||||
print(f"{datum:<12} {conversions[0]:<8} {conversions[1]:<8} {conversions[2]:<8} {conversions[3]:<8} {conversions[4]:<8}")
|
||||
|
||||
# Statistiken der Umschlüsselung
|
||||
print(f"\n📈 STATISTIKEN DER UMSCHLÜSSELTEN WERTE:")
|
||||
print("="*45)
|
||||
|
||||
# Häufigkeit der Gruppen über alle Positionen
|
||||
all_converted_values = []
|
||||
for target_col in target_columns:
|
||||
all_converted_values.extend(df[target_col].tolist())
|
||||
|
||||
from collections import Counter
|
||||
group_counts = Counter(all_converted_values)
|
||||
|
||||
print(f"Verteilung der Gruppen (1-10) über alle Positionen:")
|
||||
total_values = len(all_converted_values)
|
||||
|
||||
for group in range(1, 11):
|
||||
count = group_counts.get(group, 0)
|
||||
percentage = (count / total_values) * 100
|
||||
original_range = f"{(group-1)*5 + 1}-{group*5}"
|
||||
print(f"Gruppe {group:2} ({original_range:5}): {count:4}x ({percentage:5.1f}%)")
|
||||
|
||||
# Statistiken pro Position
|
||||
print(f"\n📊 VERTEILUNG PRO POSITION:")
|
||||
print("="*35)
|
||||
|
||||
for i, target_col in enumerate(target_columns, 1):
|
||||
position_counts = Counter(df[target_col])
|
||||
print(f"\nPosition z{i} ({target_col}):")
|
||||
for group in range(1, 11):
|
||||
count = position_counts.get(group, 0)
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f" Gruppe {group:2}: {count:3}x ({percentage:4.1f}%)")
|
||||
|
||||
# Häufigste Kombinationen der umschlüsselten Werte
|
||||
print(f"\n🎯 HÄUFIGSTE KOMBINATIONEN (umschlüsselt):")
|
||||
print("="*45)
|
||||
|
||||
# Kombinationen als Strings erstellen
|
||||
df['kombination_umschluesselt'] = df.apply(
|
||||
lambda row: f"{row['z1']}-{row['z2']}-{row['z3']}-{row['z4']}-{row['z5']}", axis=1
|
||||
)
|
||||
|
||||
combination_counts = Counter(df['kombination_umschluesselt'])
|
||||
|
||||
print(f"Top 15 Kombinationen (z1-z2-z3-z4-z5):")
|
||||
for i, (combination, count) in enumerate(combination_counts.most_common(15), 1):
|
||||
percentage = (count / len(df)) * 100
|
||||
print(f"{i:2}. {combination:15} {count:3}x ({percentage:4.1f}%)")
|
||||
|
||||
# Muster-Analyse
|
||||
print(f"\n🔍 MUSTER-ANALYSE:")
|
||||
print("="*25)
|
||||
|
||||
# Aufsteigende Kombinationen
|
||||
ascending_count = 0
|
||||
descending_count = 0
|
||||
|
||||
for _, row in df.iterrows():
|
||||
values = [row[col] for col in target_columns]
|
||||
if values == sorted(values):
|
||||
ascending_count += 1
|
||||
elif values == sorted(values, reverse=True):
|
||||
descending_count += 1
|
||||
|
||||
print(f"Aufsteigende Kombinationen: {ascending_count} ({(ascending_count/len(df)*100):.1f}%)")
|
||||
print(f"Absteigende Kombinationen: {descending_count} ({(descending_count/len(df)*100):.1f}%)")
|
||||
|
||||
# Gleiche Werte
|
||||
same_values_stats = {}
|
||||
for num_same in range(2, 6):
|
||||
count = 0
|
||||
for _, row in df.iterrows():
|
||||
values = [row[col] for col in target_columns]
|
||||
value_counts = Counter(values)
|
||||
if max(value_counts.values()) >= num_same:
|
||||
count += 1
|
||||
same_values_stats[num_same] = count
|
||||
print(f"Mindestens {num_same} gleiche Werte: {count} ({(count/len(df)*100):.1f}%)")
|
||||
|
||||
# Bereiche der umschlüsselten Werte
|
||||
print(f"\n📋 BEREICHSANALYSE (umschlüsselt):")
|
||||
print("="*35)
|
||||
|
||||
# Niedrig (1-3), Mittel (4-7), Hoch (8-10)
|
||||
for i, target_col in enumerate(target_columns, 1):
|
||||
low_count = sum(1 for val in df[target_col] if 1 <= val <= 3)
|
||||
mid_count = sum(1 for val in df[target_col] if 4 <= val <= 7)
|
||||
high_count = sum(1 for val in df[target_col] if 8 <= val <= 10)
|
||||
|
||||
low_pct = (low_count / len(df)) * 100
|
||||
mid_pct = (mid_count / len(df)) * 100
|
||||
high_pct = (high_count / len(df)) * 100
|
||||
|
||||
print(f"z{i}: Niedrig(1-3)={low_pct:4.1f}% | Mittel(4-7)={mid_pct:4.1f}% | Hoch(8-10)={high_pct:4.1f}%")
|
||||
|
||||
# Ausgabedatei speichern
|
||||
output_file = "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/AlleEurojackpotzahlen_umschluesselt.csv"
|
||||
|
||||
print(f"\n💾 DATEI SPEICHERN:")
|
||||
print("="*25)
|
||||
|
||||
# Spalten neu ordnen (Original + neue Spalten)
|
||||
column_order = ['tag', 'datum', 'Z1', 'Z2', 'Z3', 'Z4', 'Z5', 'z1', 'z2', 'z3', 'z4', 'z5', 'SZ1', 'SZ2', 'kombination_umschluesselt']
|
||||
|
||||
# Prüfen welche Spalten existieren
|
||||
available_columns = [col for col in column_order if col in df.columns]
|
||||
df_output = df[available_columns]
|
||||
|
||||
df_output.to_csv(output_file, sep=';', index=False)
|
||||
print(f"✅ Umschlüsselte Daten gespeichert: AlleEurojackpotzahlen_umschluesselt.csv")
|
||||
print(f"📊 Anzahl Spalten: {len(df_output.columns)}")
|
||||
print(f"📈 Anzahl Zeilen: {len(df_output)}")
|
||||
|
||||
print(f"\n🔍 NEUE SPALTEN:")
|
||||
for col in ['z1', 'z2', 'z3', 'z4', 'z5', 'kombination_umschluesselt']:
|
||||
if col in df_output.columns:
|
||||
print(f" ✅ {col}")
|
||||
|
||||
return df_output
|
||||
|
||||
if __name__ == "__main__":
|
||||
result_df = process_number_conversion()
|
||||
Reference in new issue
Block a user