Add Deep Learning (LSTM) + Quick Wins automation features

Major improvements:
- Deep Learning integration with PyTorch LSTM (dual models: main 1-50 + euro 1-12)
- Hybrid predictor: 40% RandomForest + 60% Deep Learning
- LaunchAgent for automatic weekly tip generation (Mon/Thu 21:00)
- Health-Check system with auto-recovery and Telegram alerts
- Fixed health checks for Eurojackpot-specific paths and file names
- Model caching and intelligent retraining logic
- Updated CSV data and generated tips
- Performance reports for recent draws

Technical details:
- PyTorch used instead of TensorFlow (Python 3.14 compatibility)
- Separate LSTM models for main numbers (1-50) and euro numbers (1-12)
- Apple Silicon MPS acceleration support
- Sequence learning with 20-draw history
- Health-check adapted for eurojackpot_ml_models/ and learning_log.json

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-07 09:22:36 +01:00
co-authored by Claude Sonnet 4.5
parent 70e0638dee
commit 9049a66c1a
36 changed files with 9308 additions and 61 deletions
+28 -4
View File
@@ -81,7 +81,9 @@ class AutoUpdateAndLearn:
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)
# Sortiere nach Datum absteigend und nimm neueste Ziehung
df = df.sort_values('datum', ascending=False)
latest_draw = df.iloc[0] # Neueste Ziehung
latest_date = latest_draw['datum']
print(f" 📅 Neueste Ziehung in Daten: {latest_date.strftime('%Y-%m-%d')}")
@@ -148,7 +150,7 @@ class AutoUpdateAndLearn:
print("\n🎯 EVALUIERE LETZTE TIPPS")
print("=" * 70)
# Finde neueste Tipps-Datei
# Finde Tipps-Datei die VOR der Ziehung generiert wurde
if not os.path.exists(self.tips_dir):
print(" ⚠️ Keine Tipps zum Evaluieren")
return {}
@@ -162,8 +164,30 @@ class AutoUpdateAndLearn:
print(" ⚠️ Keine Tipps-Dateien gefunden")
return {}
latest_tips_file = os.path.join(self.tips_dir, tip_files[0])
print(f" 📁 Evaluiere: {tip_files[0]}")
# Finde Tip-Datei die vor der Ziehung erstellt wurde
draw_date = new_draw['date']
selected_tip_file = None
for tip_file in tip_files:
# Parse Timestamp aus Dateiname: weekly_tips_YYYYMMDD_HHMMSS.csv
try:
parts = tip_file.replace('.csv', '').split('_')
tip_date_str = parts[-2] # YYYYMMDD
tip_date = pd.to_datetime(tip_date_str, format='%Y%m%d')
# Nehme erste Datei die vor der Ziehung war
if tip_date < draw_date:
selected_tip_file = tip_file
break
except:
continue
if not selected_tip_file:
# Fallback: nehme älteste Datei
selected_tip_file = tip_files[-1]
latest_tips_file = os.path.join(self.tips_dir, selected_tip_file)
print(f" 📁 Evaluiere: {selected_tip_file}")
try:
tips_df = pd.read_csv(latest_tips_file)
@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- Label - Eindeutige Identifier -->
<key>Label</key>
<string>com.eurojackpot.weekly</string>
<!-- Programm-Pfad -->
<key>ProgramArguments</key>
<array>
<string>/bin/bash</string>
<string>-c</string>
<string>cd "/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot" &amp;&amp; source venv/bin/activate &amp;&amp; python scripts/automation/weekly_tip_generator.py 2>&1 | tee -a logs/launchagent.log</string>
</array>
<!-- Zeitplan: Montag und Donnerstag um 21:00 -->
<key>StartCalendarInterval</key>
<array>
<!-- Montag 21:00 (vor Dienstag-Ziehung) -->
<dict>
<key>Weekday</key>
<integer>1</integer>
<key>Hour</key>
<integer>21</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
<!-- Donnerstag 21:00 (vor Freitag-Ziehung) -->
<dict>
<key>Weekday</key>
<integer>4</integer>
<key>Hour</key>
<integer>21</integer>
<key>Minute</key>
<integer>0</integer>
</dict>
</array>
<!-- Arbeitsverzeichnis -->
<key>WorkingDirectory</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot</string>
<!-- Standard Output/Error Logging -->
<key>StandardOutPath</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/logs/stdout.log</string>
<key>StandardErrorPath</key>
<string>/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Eurojackpot/logs/stderr.log</string>
<!-- Wichtig: RunAtLoad für sofortigen Test -->
<key>RunAtLoad</key>
<false/>
<!-- Environment Variables -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin</string>
<key>LANG</key>
<string>de_DE.UTF-8</string>
</dict>
<!-- Wichtig: Auch bei Sleep/Wake ausführen -->
<key>LaunchOnlyOnce</key>
<false/>
<!-- Process Nice Level (niedrigere Priorität) -->
<key>Nice</key>
<integer>10</integer>
</dict>
</plist>
@@ -57,15 +57,35 @@ try:
except ImportError:
ML_AVAILABLE = False
# Deep Learning (optional)
# Deep Learning (optional) - PyTorch or TensorFlow
DEEP_LEARNING_AVAILABLE = False
try:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
import torch
DEEP_LEARNING_AVAILABLE = True
except ImportError:
DEEP_LEARNING_AVAILABLE = False
try:
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout
from tensorflow.keras.optimizers import Adam
DEEP_LEARNING_AVAILABLE = True
except ImportError:
DEEP_LEARNING_AVAILABLE = False
# Import Deep Learning Engine (PyTorch-based for Python 3.14+ compatibility)
import sys
try:
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'utils'))
from deep_learning_engine_pytorch import DeepLearningEngine, HybridDeepLearningPredictor
DL_ENGINE_AVAILABLE = True
except ImportError:
try:
from deep_learning_engine import DeepLearningEngine, HybridDeepLearningPredictor
DL_ENGINE_AVAILABLE = True
except ImportError:
DL_ENGINE_AVAILABLE = False
DeepLearningEngine = None
HybridDeepLearningPredictor = None
class UltimateAIMLEurojackpotGenerator:
@@ -176,11 +196,14 @@ class UltimateAIMLEurojackpotGenerator:
"""Zeigt System-Status."""
print("\n📊 SYSTEM STATUS:")
print(f" 🧠 ML Available: {'' if ML_AVAILABLE else '❌ (pip install scikit-learn)'}")
print(f" 🚀 Deep Learning: {'' if DEEP_LEARNING_AVAILABLE else '⚠️ Optional'}")
print(f" 🚀 Deep Learning: {' ACTIVE' if self.ai_ml_engine.use_deep_learning else '⚠️ Optional (pip install tensorflow)'}")
print(f" 🎯 Models Trained: {'' if self.is_trained else '⚠️ Using fallback'}")
print(f" 📁 Data Size: {len(self.df):,} drawings")
print(f" 🎨 Patterns: {len(self.pattern_engine.pattern_frequencies)}")
print(f" ⚡ Fast Mode: {'ON' if self.fast_mode else 'OFF'}")
if self.ai_ml_engine.use_deep_learning:
print(f" 🔮 LSTM: Hybrid Mode (RF 40% + DL 60%)")
print(f" 📊 Main Numbers: 1-50 | Euro Numbers: 1-12")
def generate_ultimate_tips(self, num_tips=10):
"""
@@ -662,8 +685,8 @@ class UltimateAIMLEurojackpotGenerator:
# ============================================================================
class EurojackpotAIMLEngine:
"""AI/ML Engine für Eurojackpot."""
"""AI/ML Engine für Eurojackpot mit Deep Learning Support."""
def __init__(self, fast_mode=True):
self.models_main = {}
self.models_euro = {}
@@ -672,11 +695,20 @@ class EurojackpotAIMLEngine:
self.scaler = StandardScaler()
self.is_trained = False
self.fast_mode = fast_mode
# Deep Learning
self.dl_engine_main = None
self.dl_engine_euro = None
self.hybrid_predictor_main = None
self.hybrid_predictor_euro = None
self.use_deep_learning = DL_ENGINE_AVAILABLE and DEEP_LEARNING_AVAILABLE
def initialize(self, df, features_df, cache_path):
"""Initialisiert AI/ML mit intelligentem Retraining."""
self.cache_path = cache_path
self.data_file = None # Wird vom Generator gesetzt
self.df = df # Store for DL
self.features_df = features_df # Store for DL
if not ML_AVAILABLE:
print(" ⚠️ ML not available - using fallback")
@@ -715,6 +747,49 @@ class EurojackpotAIMLEngine:
else:
print(" ⚠️ Insufficient data - using fallback")
# Initialize Deep Learning
if self.use_deep_learning:
print(" 🧠 Initializing Deep Learning (LSTM)...", end=" ", flush=True)
try:
# Main numbers (1-50)
self.dl_engine_main = DeepLearningEngine(
num_numbers=50,
sequence_length=20,
cache_dir=os.path.join(cache_path, 'deep_learning_main'),
fast_mode=self.fast_mode
)
self.dl_engine_main.train(df, features_df, force_retrain=needs_retrain)
# Euro numbers (1-12)
self.dl_engine_euro = DeepLearningEngine(
num_numbers=12,
sequence_length=20,
cache_dir=os.path.join(cache_path, 'deep_learning_euro'),
fast_mode=self.fast_mode
)
self.dl_engine_euro.train(df, features_df, force_retrain=needs_retrain)
# Create hybrid predictors
self.hybrid_predictor_main = HybridDeepLearningPredictor(
dl_engine=self.dl_engine_main,
rf_weight=0.4,
dl_weight=0.6
)
self.hybrid_predictor_euro = HybridDeepLearningPredictor(
dl_engine=self.dl_engine_euro,
rf_weight=0.4,
dl_weight=0.6
)
print("")
except Exception as e:
print(f"⚠️ DL init failed: {e}")
self.use_deep_learning = False
else:
if not DEEP_LEARNING_AVAILABLE:
print(" ️ Deep Learning disabled (TensorFlow not installed)")
elif not DL_ENGINE_AVAILABLE:
print(" ️ Deep Learning Engine not available")
def _needs_retraining(self, main_cache, euro_cache):
"""Prüft ob Retraining nötig ist."""
if not os.path.exists(main_cache) or not os.path.exists(euro_cache):
@@ -827,47 +902,69 @@ class EurojackpotAIMLEngine:
return np.array(X), np.array(y)
def predict_main_numbers(self, features_df):
"""Vorhersage Main Numbers."""
"""Vorhersage Main Numbers - mit Deep Learning Hybrid."""
predictions = {}
# Get RandomForest predictions
if not self.is_trained or not self.trained_models_main:
for num in range(1, 51):
predictions[num] = 0.2 + random.random() * 0.3
return predictions
current_features = self._get_current_features(features_df)
for number in range(1, 51):
if number not in self.trained_models_main:
predictions[number] = 0.15 + (number % 10) * 0.03
continue
predictions[number] = self._predict_single_number(
number, self.trained_models_main[number], current_features
)
else:
current_features = self._get_current_features(features_df)
for number in range(1, 51):
if number not in self.trained_models_main:
predictions[number] = 0.15 + (number % 10) * 0.03
continue
predictions[number] = self._predict_single_number(
number, self.trained_models_main[number], current_features
)
# Use Deep Learning Hybrid if available
if self.use_deep_learning and self.hybrid_predictor_main:
try:
predictions = self.hybrid_predictor_main.predict(
predictions,
self.df,
self.features_df
)
except Exception as e:
print(f"⚠️ DL prediction (main) failed: {e}, using RF only")
return predictions
def predict_euro_numbers(self, features_df):
"""Vorhersage Euro Numbers."""
"""Vorhersage Euro Numbers - mit Deep Learning Hybrid."""
predictions = {}
# Get RandomForest predictions
if not self.is_trained or not self.trained_models_euro:
for num in range(1, 13):
predictions[num] = 0.2 + random.random() * 0.3
return predictions
current_features = self._get_current_features(features_df)
for number in range(1, 13):
if number not in self.trained_models_euro:
predictions[number] = 0.15 + (number % 5) * 0.05
continue
predictions[number] = self._predict_single_number(
number, self.trained_models_euro[number], current_features
)
else:
current_features = self._get_current_features(features_df)
for number in range(1, 13):
if number not in self.trained_models_euro:
predictions[number] = 0.15 + (number % 5) * 0.05
continue
predictions[number] = self._predict_single_number(
number, self.trained_models_euro[number], current_features
)
# Use Deep Learning Hybrid if available
if self.use_deep_learning and self.hybrid_predictor_euro:
try:
predictions = self.hybrid_predictor_euro.predict(
predictions,
self.df,
self.features_df
)
except Exception as e:
print(f"⚠️ DL prediction (euro) failed: {e}, using RF only")
return predictions
def _predict_single_number(self, number, models, features):
+473
View File
@@ -0,0 +1,473 @@
#!/usr/bin/env python3
"""
Deep Learning Engine mit LSTM für Lotto-Vorhersagen
====================================================
Implementiert LSTM-basierte Modelle zur Vorhersage von Lotto-Zahlen
basierend auf historischen Sequenzen und Features.
Features:
- LSTM-Netzwerk für zeitliche Sequenzen
- Sequence-to-Probability Mapping
- Feature Engineering Integration
- Model Persistence & Caching
- Hybrid mit RandomForest
"""
import numpy as np
import pandas as pd
import os
import json
import pickle
from datetime import datetime
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')
# TensorFlow/Keras Imports
try:
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential, load_model
from tensorflow.keras.layers import LSTM, Dense, Dropout, Bidirectional, BatchNormalization
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau
from tensorflow.keras.regularizers import l2
TENSORFLOW_AVAILABLE = True
except ImportError:
TENSORFLOW_AVAILABLE = False
print("⚠️ TensorFlow not available. Install with: pip install tensorflow")
class DeepLearningEngine:
"""
LSTM-basierter Deep Learning Engine für Lotto-Vorhersagen.
Architecture:
- Input: Sequence of historical draws + features
- LSTM layers: Learn temporal patterns
- Dense layers: Map to probability distribution
- Output: Probability for each number
"""
def __init__(
self,
num_numbers: int = 49,
sequence_length: int = 20,
cache_dir: str = None,
fast_mode: bool = True
):
"""
Args:
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
sequence_length: How many past draws to consider
cache_dir: Directory for model persistence
fast_mode: Use faster training (fewer epochs)
"""
self.num_numbers = num_numbers
self.sequence_length = sequence_length
self.cache_dir = cache_dir
self.fast_mode = fast_mode
# Model components
self.model = None
self.is_trained = False
self.training_history = {}
# Configuration
self.config = {
'lstm_units_1': 128,
'lstm_units_2': 64,
'dense_units': 128,
'dropout_rate': 0.3,
'learning_rate': 0.001,
'batch_size': 32,
'epochs': 30 if fast_mode else 100,
'validation_split': 0.2
}
if cache_dir:
os.makedirs(cache_dir, exist_ok=True)
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.h5')
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
else:
self.model_path = None
self.config_path = None
print(f"🧠 Deep Learning Engine initialized")
print(f" Numbers: 1-{num_numbers}")
print(f" Sequence Length: {sequence_length}")
print(f" Fast Mode: {fast_mode}")
def _build_model(self, num_features: int) -> Sequential:
"""
Builds LSTM architecture.
Architecture:
Input (sequence_length, num_features)
Bidirectional LSTM(128) + Dropout(0.3)
Bidirectional LSTM(64) + Dropout(0.3)
Dense(128, relu) + BatchNorm + Dropout(0.3)
Dense(num_numbers, sigmoid)
"""
model = Sequential([
# First Bidirectional LSTM layer
Bidirectional(
LSTM(
self.config['lstm_units_1'],
return_sequences=True,
kernel_regularizer=l2(0.01)
),
input_shape=(self.sequence_length, num_features)
),
Dropout(self.config['dropout_rate']),
BatchNormalization(),
# Second Bidirectional LSTM layer
Bidirectional(
LSTM(
self.config['lstm_units_2'],
return_sequences=False,
kernel_regularizer=l2(0.01)
)
),
Dropout(self.config['dropout_rate']),
BatchNormalization(),
# Dense layers
Dense(
self.config['dense_units'],
activation='relu',
kernel_regularizer=l2(0.01)
),
BatchNormalization(),
Dropout(self.config['dropout_rate']),
# Output layer - probability for each number
Dense(self.num_numbers, activation='sigmoid')
])
# Compile
model.compile(
optimizer=Adam(learning_rate=self.config['learning_rate']),
loss='binary_crossentropy',
metrics=['accuracy', 'AUC']
)
return model
def _prepare_sequences(
self,
df: pd.DataFrame,
features_df: pd.DataFrame
) -> Tuple[np.ndarray, np.ndarray]:
"""
Prepares sequences for LSTM training.
Args:
df: Historical draws (with columns Z1-Z6)
features_df: Engineered features
Returns:
X: (num_samples, sequence_length, num_features)
y: (num_samples, num_numbers) - binary matrix
"""
print(f" Preparing sequences (length={self.sequence_length})...")
# Ensure data is sorted by date
if 'datum' in df.columns:
df = df.sort_values('datum').reset_index(drop=True)
# Extract number columns
num_cols = [col for col in df.columns if col.startswith('Z')]
# Normalize features to [0, 1]
features_normalized = features_df.copy()
for col in features_normalized.columns:
min_val = features_normalized[col].min()
max_val = features_normalized[col].max()
if max_val > min_val:
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
else:
features_normalized[col] = 0.5
X_sequences = []
y_targets = []
# Create sequences
for i in range(self.sequence_length, len(df)):
# Get sequence of features
sequence = features_normalized.iloc[i - self.sequence_length:i].values
X_sequences.append(sequence)
# Target: next draw as binary vector
target = np.zeros(self.num_numbers)
next_draw = df.iloc[i][num_cols].values
for num in next_draw:
if 1 <= num <= self.num_numbers:
target[int(num) - 1] = 1
y_targets.append(target)
X = np.array(X_sequences)
y = np.array(y_targets)
print(f" ✅ Created {len(X)} sequences")
print(f" Shape: X={X.shape}, y={y.shape}")
return X, y
def train(
self,
df: pd.DataFrame,
features_df: pd.DataFrame,
force_retrain: bool = False
) -> bool:
"""
Trains LSTM model on historical data.
Args:
df: Historical draws
features_df: Engineered features
force_retrain: Retrain even if cached model exists
Returns:
Success status
"""
if not TENSORFLOW_AVAILABLE:
print("❌ TensorFlow not available")
return False
# Check for cached model
if not force_retrain and self.model_path and os.path.exists(self.model_path):
print(" 📦 Loading cached LSTM model...")
try:
self.model = load_model(self.model_path)
self.is_trained = True
# Load config
if os.path.exists(self.config_path):
with open(self.config_path, 'r') as f:
self.training_history = json.load(f)
print(f" ✅ Loaded cached model")
return True
except Exception as e:
print(f" ⚠️ Failed to load cached model: {e}")
print(" 🔄 Training new model...")
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (LSTM)")
print("=" * 70)
# Prepare data
X, y = self._prepare_sequences(df, features_df)
if len(X) < 100:
print(" ⚠️ Not enough data for training (need >100 sequences)")
return False
# Build model
print(f" Building LSTM architecture...")
num_features = X.shape[2]
self.model = self._build_model(num_features)
# Show summary
print(f"\n 📊 Model Summary:")
total_params = self.model.count_params()
print(f" Total parameters: {total_params:,}")
# Callbacks
callbacks = [
EarlyStopping(
monitor='val_loss',
patience=10,
restore_best_weights=True,
verbose=0
),
ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=5,
verbose=0
)
]
# Train
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
print(f" Batch size: {self.config['batch_size']}")
print(f" Validation split: {self.config['validation_split']:.1%}")
try:
history = self.model.fit(
X, y,
batch_size=self.config['batch_size'],
epochs=self.config['epochs'],
validation_split=self.config['validation_split'],
callbacks=callbacks,
verbose=1
)
# Store training history
self.training_history = {
'trained_at': datetime.now().isoformat(),
'num_samples': len(X),
'num_features': num_features,
'final_loss': float(history.history['loss'][-1]),
'final_val_loss': float(history.history['val_loss'][-1]),
'final_accuracy': float(history.history['accuracy'][-1]),
'final_val_accuracy': float(history.history['val_accuracy'][-1]),
'epochs_trained': len(history.history['loss'])
}
self.is_trained = True
# Save model
if self.model_path:
print(f"\n 💾 Saving model to cache...")
self.model.save(self.model_path)
with open(self.config_path, 'w') as f:
json.dump(self.training_history, f, indent=2)
print(f" ✅ Model saved")
# Print results
print(f"\n ✅ TRAINING COMPLETED")
print(f" Final Loss: {self.training_history['final_loss']:.4f}")
print(f" Final Val Loss: {self.training_history['final_val_loss']:.4f}")
print(f" Final Accuracy: {self.training_history['final_accuracy']:.4f}")
print(f" Epochs: {self.training_history['epochs_trained']}")
return True
except Exception as e:
print(f"\n ❌ Training failed: {e}")
import traceback
traceback.print_exc()
return False
def predict(
self,
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""
Predicts probabilities for each number.
Args:
recent_df: Recent draws (at least sequence_length)
recent_features: Recent features
Returns:
{number: probability} for numbers 1-num_numbers
"""
if not self.is_trained or self.model is None:
print("⚠️ Model not trained, returning uniform distribution")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Prepare last sequence
if len(recent_df) < self.sequence_length:
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Get last sequence
recent_features_normalized = recent_features.copy()
for col in recent_features_normalized.columns:
min_val = recent_features_normalized[col].min()
max_val = recent_features_normalized[col].max()
if max_val > min_val:
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
else:
recent_features_normalized[col] = 0.5
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
X = np.array([sequence]) # Shape: (1, sequence_length, num_features)
# Predict
predictions = self.model.predict(X, verbose=0)[0] # Shape: (num_numbers,)
# Convert to dictionary
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
return result
def get_model_info(self) -> Dict:
"""Returns model information."""
return {
'is_trained': self.is_trained,
'tensorflow_available': TENSORFLOW_AVAILABLE,
'num_numbers': self.num_numbers,
'sequence_length': self.sequence_length,
'config': self.config,
'training_history': self.training_history,
'model_exists': self.model is not None
}
class HybridDeepLearningPredictor:
"""
Kombiniert RandomForest + LSTM für robustere Vorhersagen.
"""
def __init__(
self,
dl_engine: DeepLearningEngine,
rf_weight: float = 0.4,
dl_weight: float = 0.6
):
"""
Args:
dl_engine: Deep Learning Engine
rf_weight: Weight for RandomForest predictions
dl_weight: Weight for Deep Learning predictions
"""
self.dl_engine = dl_engine
self.rf_weight = rf_weight
self.dl_weight = dl_weight
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
def predict(
self,
rf_predictions: Dict[int, float],
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""
Combines RandomForest and Deep Learning predictions.
Args:
rf_predictions: Predictions from RandomForest
recent_df: Recent draws for DL
recent_features: Recent features for DL
Returns:
Combined predictions
"""
# Get DL predictions
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
# Combine
combined = {}
for num in range(1, self.dl_engine.num_numbers + 1):
rf_score = rf_predictions.get(num, 0.5)
dl_score = dl_predictions.get(num, 0.5)
combined[num] = (
self.rf_weight * rf_score +
self.dl_weight * dl_score
)
# Normalize to [0, 1]
min_score = min(combined.values())
max_score = max(combined.values())
if max_score > min_score:
combined = {
num: (score - min_score) / (max_score - min_score)
for num, score in combined.items()
}
return combined
@@ -0,0 +1,526 @@
#!/usr/bin/env python3
"""
Deep Learning Engine mit PyTorch LSTM für Lotto-Vorhersagen
============================================================
PyTorch-basierte Implementierung für Python 3.14+ Kompatibilität.
TensorFlow unterstützt Python 3.14 noch nicht, daher verwenden wir PyTorch.
Features:
- LSTM-Netzwerk für zeitliche Sequenzen
- Sequence-to-Probability Mapping
- Feature Engineering Integration
- Model Persistence & Caching
- Hybrid mit RandomForest
"""
import numpy as np
import pandas as pd
import os
import json
import pickle
from datetime import datetime
from typing import Dict, List, Tuple, Optional
import warnings
warnings.filterwarnings('ignore')
# PyTorch Imports
try:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
PYTORCH_AVAILABLE = True
except ImportError:
PYTORCH_AVAILABLE = False
print("⚠️ PyTorch not available. Install with: pip install torch")
class LSTMModel(nn.Module):
"""PyTorch LSTM Model for Lotto prediction."""
def __init__(
self,
num_features: int,
num_numbers: int,
sequence_length: int,
lstm_units_1: int = 128,
lstm_units_2: int = 64,
dense_units: int = 128,
dropout_rate: float = 0.3
):
super(LSTMModel, self).__init__()
self.lstm1 = nn.LSTM(
input_size=num_features,
hidden_size=lstm_units_1,
batch_first=True,
bidirectional=True
)
self.dropout1 = nn.Dropout(dropout_rate)
self.batch_norm1 = nn.BatchNorm1d(lstm_units_1 * 2)
self.lstm2 = nn.LSTM(
input_size=lstm_units_1 * 2,
hidden_size=lstm_units_2,
batch_first=True,
bidirectional=True
)
self.dropout2 = nn.Dropout(dropout_rate)
self.batch_norm2 = nn.BatchNorm1d(lstm_units_2 * 2)
self.fc1 = nn.Linear(lstm_units_2 * 2, dense_units)
self.batch_norm3 = nn.BatchNorm1d(dense_units)
self.dropout3 = nn.Dropout(dropout_rate)
self.fc2 = nn.Linear(dense_units, num_numbers)
def forward(self, x):
# LSTM 1
x, _ = self.lstm1(x)
x = self.dropout1(x)
# Take last output
x = x[:, -1, :]
x = self.batch_norm1(x)
# LSTM 2 needs 3D input
x = x.unsqueeze(1)
x, _ = self.lstm2(x)
x = x[:, -1, :]
x = self.dropout2(x)
x = self.batch_norm2(x)
# Dense layers
x = torch.relu(self.fc1(x))
x = self.batch_norm3(x)
x = self.dropout3(x)
# Output
x = torch.sigmoid(self.fc2(x))
return x
class LottoDataset(Dataset):
"""PyTorch Dataset for Lotto sequences."""
def __init__(self, X, y):
self.X = torch.FloatTensor(X)
self.y = torch.FloatTensor(y)
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
return self.X[idx], self.y[idx]
class DeepLearningEngine:
"""
PyTorch-based LSTM Deep Learning Engine for Lotto predictions.
"""
def __init__(
self,
num_numbers: int = 49,
sequence_length: int = 20,
cache_dir: str = None,
fast_mode: bool = True
):
"""
Args:
num_numbers: Maximum number (49 for Lotto, 50 for Eurojackpot)
sequence_length: How many past draws to consider
cache_dir: Directory for model persistence
fast_mode: Use faster training (fewer epochs)
"""
self.num_numbers = num_numbers
self.sequence_length = sequence_length
self.cache_dir = cache_dir
self.fast_mode = fast_mode
# Device
self.device = torch.device('mps' if torch.backends.mps.is_available() else 'cpu')
# Model components
self.model = None
self.is_trained = False
self.training_history = {}
# Configuration
self.config = {
'lstm_units_1': 128,
'lstm_units_2': 64,
'dense_units': 128,
'dropout_rate': 0.3,
'learning_rate': 0.001,
'batch_size': 32,
'epochs': 30 if fast_mode else 100,
'validation_split': 0.2
}
if cache_dir:
os.makedirs(cache_dir, exist_ok=True)
self.model_path = os.path.join(cache_dir, f'lstm_model_{num_numbers}.pth')
self.config_path = os.path.join(cache_dir, f'lstm_config_{num_numbers}.json')
else:
self.model_path = None
self.config_path = None
print(f"🧠 Deep Learning Engine initialized (PyTorch)")
print(f" Numbers: 1-{num_numbers}")
print(f" Sequence Length: {sequence_length}")
print(f" Device: {self.device}")
print(f" Fast Mode: {fast_mode}")
def _build_model(self, num_features: int) -> LSTMModel:
"""Builds LSTM architecture."""
model = LSTMModel(
num_features=num_features,
num_numbers=self.num_numbers,
sequence_length=self.sequence_length,
lstm_units_1=self.config['lstm_units_1'],
lstm_units_2=self.config['lstm_units_2'],
dense_units=self.config['dense_units'],
dropout_rate=self.config['dropout_rate']
)
return model.to(self.device)
def _prepare_sequences(
self,
df: pd.DataFrame,
features_df: pd.DataFrame
) -> Tuple[np.ndarray, np.ndarray]:
"""Prepares sequences for LSTM training."""
print(f" Preparing sequences (length={self.sequence_length})...")
# Ensure data is sorted by date
if 'datum' in df.columns:
df = df.sort_values('datum').reset_index(drop=True)
# Extract number columns
num_cols = [col for col in df.columns if col.startswith('Z')]
# Normalize features to [0, 1]
features_normalized = features_df.copy()
for col in features_normalized.columns:
min_val = features_normalized[col].min()
max_val = features_normalized[col].max()
if max_val > min_val:
features_normalized[col] = (features_normalized[col] - min_val) / (max_val - min_val)
else:
features_normalized[col] = 0.5
X_sequences = []
y_targets = []
# Create sequences
for i in range(self.sequence_length, len(df)):
# Get sequence of features
sequence = features_normalized.iloc[i - self.sequence_length:i].values
X_sequences.append(sequence)
# Target: next draw as binary vector
target = np.zeros(self.num_numbers)
next_draw = df.iloc[i][num_cols].values
for num in next_draw:
if 1 <= num <= self.num_numbers:
target[int(num) - 1] = 1
y_targets.append(target)
X = np.array(X_sequences)
y = np.array(y_targets)
print(f" ✅ Created {len(X)} sequences")
print(f" Shape: X={X.shape}, y={y.shape}")
return X, y
def train(
self,
df: pd.DataFrame,
features_df: pd.DataFrame,
force_retrain: bool = False
) -> bool:
"""Trains LSTM model on historical data."""
if not PYTORCH_AVAILABLE:
print("❌ PyTorch not available")
return False
# Check for cached model
if not force_retrain and self.model_path and os.path.exists(self.model_path):
print(" 📦 Loading cached LSTM model...")
try:
checkpoint = torch.load(self.model_path, weights_only=False)
num_features = checkpoint['num_features']
self.model = self._build_model(num_features)
self.model.load_state_dict(checkpoint['model_state_dict'])
self.model.eval()
self.is_trained = True
# Load config
if os.path.exists(self.config_path):
with open(self.config_path, 'r') as f:
self.training_history = json.load(f)
print(f" ✅ Loaded cached model")
return True
except Exception as e:
print(f" ⚠️ Failed to load cached model: {e}")
print(" 🔄 Training new model...")
print(f"\n🧠 TRAINING DEEP LEARNING MODEL (PyTorch LSTM)")
print("=" * 70)
# Prepare data
X, y = self._prepare_sequences(df, features_df)
if len(X) < 100:
print(" ⚠️ Not enough data for training (need >100 sequences)")
return False
# Split data
split_idx = int(len(X) * (1 - self.config['validation_split']))
X_train, X_val = X[:split_idx], X[split_idx:]
y_train, y_val = y[:split_idx], y[split_idx:]
# Create datasets
train_dataset = LottoDataset(X_train, y_train)
val_dataset = LottoDataset(X_val, y_val)
train_loader = DataLoader(
train_dataset,
batch_size=self.config['batch_size'],
shuffle=True
)
val_loader = DataLoader(
val_dataset,
batch_size=self.config['batch_size'],
shuffle=False
)
# Build model
print(f" Building LSTM architecture...")
num_features = X.shape[2]
self.model = self._build_model(num_features)
# Show summary
total_params = sum(p.numel() for p in self.model.parameters())
print(f"\n 📊 Model Summary:")
print(f" Total parameters: {total_params:,}")
# Loss and optimizer
criterion = nn.BCELoss()
optimizer = optim.Adam(self.model.parameters(), lr=self.config['learning_rate'])
scheduler = optim.lr_scheduler.ReduceLROnPlateau(
optimizer, mode='min', factor=0.5, patience=5
)
# Train
print(f"\n 🚀 Training for {self.config['epochs']} epochs...")
print(f" Batch size: {self.config['batch_size']}")
print(f" Validation split: {self.config['validation_split']:.1%}")
best_val_loss = float('inf')
patience_counter = 0
patience = 10
try:
for epoch in range(self.config['epochs']):
# Training
self.model.train()
train_loss = 0.0
for batch_X, batch_y in train_loader:
batch_X = batch_X.to(self.device)
batch_y = batch_y.to(self.device)
optimizer.zero_grad()
outputs = self.model(batch_X)
loss = criterion(outputs, batch_y)
loss.backward()
optimizer.step()
train_loss += loss.item()
train_loss /= len(train_loader)
# Validation
self.model.eval()
val_loss = 0.0
with torch.no_grad():
for batch_X, batch_y in val_loader:
batch_X = batch_X.to(self.device)
batch_y = batch_y.to(self.device)
outputs = self.model(batch_X)
loss = criterion(outputs, batch_y)
val_loss += loss.item()
val_loss /= len(val_loader)
# Learning rate scheduling
scheduler.step(val_loss)
# Print progress every 5 epochs
if (epoch + 1) % 5 == 0:
print(f" Epoch {epoch+1}/{self.config['epochs']}: "
f"Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}")
# Early stopping
if val_loss < best_val_loss:
best_val_loss = val_loss
patience_counter = 0
# Save best model
if self.model_path:
torch.save({
'model_state_dict': self.model.state_dict(),
'num_features': num_features,
'config': self.config
}, self.model_path)
else:
patience_counter += 1
if patience_counter >= patience:
print(f" Early stopping at epoch {epoch+1}")
break
# Load best model
if self.model_path and os.path.exists(self.model_path):
checkpoint = torch.load(self.model_path, weights_only=False)
self.model.load_state_dict(checkpoint['model_state_dict'])
# Store training history
self.training_history = {
'trained_at': datetime.now().isoformat(),
'num_samples': len(X),
'num_features': num_features,
'final_loss': float(train_loss),
'final_val_loss': float(val_loss),
'best_val_loss': float(best_val_loss),
'epochs_trained': epoch + 1
}
self.is_trained = True
# Save config
if self.config_path:
with open(self.config_path, 'w') as f:
json.dump(self.training_history, f, indent=2)
# Print results
print(f"\n ✅ TRAINING COMPLETED")
print(f" Best Val Loss: {best_val_loss:.4f}")
print(f" Epochs: {self.training_history['epochs_trained']}")
return True
except Exception as e:
print(f"\n ❌ Training failed: {e}")
import traceback
traceback.print_exc()
return False
def predict(
self,
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""Predicts probabilities for each number."""
if not self.is_trained or self.model is None:
print("⚠️ Model not trained, returning uniform distribution")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
if len(recent_df) < self.sequence_length:
print(f"⚠️ Not enough recent data (need {self.sequence_length}, got {len(recent_df)})")
return {i: 0.5 for i in range(1, self.num_numbers + 1)}
# Prepare sequence
recent_features_normalized = recent_features.copy()
for col in recent_features_normalized.columns:
min_val = recent_features_normalized[col].min()
max_val = recent_features_normalized[col].max()
if max_val > min_val:
recent_features_normalized[col] = (recent_features_normalized[col] - min_val) / (max_val - min_val)
else:
recent_features_normalized[col] = 0.5
sequence = recent_features_normalized.iloc[-self.sequence_length:].values
X = torch.FloatTensor(sequence).unsqueeze(0).to(self.device)
# Predict
self.model.eval()
with torch.no_grad():
predictions = self.model(X)[0].cpu().numpy()
# Convert to dictionary
result = {i + 1: float(predictions[i]) for i in range(self.num_numbers)}
return result
def get_model_info(self) -> Dict:
"""Returns model information."""
return {
'is_trained': self.is_trained,
'pytorch_available': PYTORCH_AVAILABLE,
'num_numbers': self.num_numbers,
'sequence_length': self.sequence_length,
'device': str(self.device),
'config': self.config,
'training_history': self.training_history,
'model_exists': self.model is not None
}
class HybridDeepLearningPredictor:
"""Kombiniert RandomForest + LSTM für robustere Vorhersagen."""
def __init__(
self,
dl_engine: DeepLearningEngine,
rf_weight: float = 0.4,
dl_weight: float = 0.6
):
"""
Args:
dl_engine: Deep Learning Engine
rf_weight: Weight for RandomForest predictions
dl_weight: Weight for Deep Learning predictions
"""
self.dl_engine = dl_engine
self.rf_weight = rf_weight
self.dl_weight = dl_weight
print(f"🔀 Hybrid Predictor: RF={rf_weight:.1%} + DL={dl_weight:.1%}")
def predict(
self,
rf_predictions: Dict[int, float],
recent_df: pd.DataFrame,
recent_features: pd.DataFrame
) -> Dict[int, float]:
"""Combines RandomForest and Deep Learning predictions."""
# Get DL predictions
dl_predictions = self.dl_engine.predict(recent_df, recent_features)
# Combine
combined = {}
for num in range(1, self.dl_engine.num_numbers + 1):
rf_score = rf_predictions.get(num, 0.5)
dl_score = dl_predictions.get(num, 0.5)
combined[num] = (
self.rf_weight * rf_score +
self.dl_weight * dl_score
)
# Normalize to [0, 1]
min_score = min(combined.values())
max_score = max(combined.values())
if max_score > min_score:
combined = {
num: (score - min_score) / (max_score - min_score)
for num, score in combined.items()
}
return combined
+363
View File
@@ -0,0 +1,363 @@
#!/usr/bin/env python3
"""
Health-Check System mit Auto-Recovery
======================================
Prüft System-Gesundheit und behebt automatisch Probleme:
- CSV-Datei vorhanden und aktuell?
- Models trainiert und verfügbar?
- Learning State konsistent?
- Logs rotieren?
- Telegram-Bot erreichbar?
Bei Problemen:
- Auto-Retry
- Telegram-Alerts
- Logging
"""
import os
import sys
import json
import time
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Tuple
import pandas as pd
# Add parent dir to path
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.notifier import EurojackpotNotifier
class HealthCheck:
"""System Health-Check mit Auto-Recovery."""
def __init__(self, data_dir: str, lottery_name: str = "Eurojackpot"):
self.data_dir = data_dir
self.lottery_name = lottery_name
self.notifier = EurojackpotNotifier()
self.health_log = os.path.join(data_dir, "health_check.json")
self.issues = []
self.warnings = []
self.recovered = []
print(f"🏥 HEALTH-CHECK SYSTEM - {lottery_name}")
print("=" * 70)
def run_all_checks(self) -> bool:
"""Führt alle Health-Checks aus."""
all_ok = True
checks = [
("CSV Data", self.check_csv_data),
("ML Models", self.check_ml_models),
("Learning State", self.check_learning_state),
("Logs", self.check_logs),
("Telegram", self.check_telegram),
("Disk Space", self.check_disk_space)
]
for name, check_func in checks:
print(f"\n🔍 Checking: {name}...", end=" ", flush=True)
try:
status, message = check_func()
if status == "OK":
print(f"{message}")
elif status == "WARNING":
print(f"⚠️ {message}")
self.warnings.append(f"{name}: {message}")
elif status == "ERROR":
print(f"{message}")
self.issues.append(f"{name}: {message}")
all_ok = False
elif status == "RECOVERED":
print(f"🔧 {message}")
self.recovered.append(f"{name}: {message}")
except Exception as e:
print(f"❌ Exception: {e}")
self.issues.append(f"{name}: Exception - {e}")
all_ok = False
# Summary
print("\n" + "=" * 70)
self._print_summary()
# Save health log
self._save_health_log(all_ok)
# Send alert if issues
if self.issues:
self._send_alert()
return all_ok
def check_csv_data(self) -> Tuple[str, str]:
"""Prüft CSV-Datei."""
# Different CSV names for different lotteries
if "Eurojackpot" in self.lottery_name:
csv_file = os.path.join(self.data_dir, "AlleEurojackpotzahlen.csv")
else:
csv_file = os.path.join(self.data_dir, "AlleLottozahlen.csv")
if not os.path.exists(csv_file):
return ("ERROR", f"CSV file not found: {csv_file}")
# Check age
mtime = os.path.getmtime(csv_file)
age_days = (time.time() - mtime) / 86400
if age_days > 10:
return ("WARNING", f"CSV file is {age_days:.1f} days old")
# Check content
try:
df = pd.read_csv(csv_file, sep=';')
if len(df) < 100:
return ("ERROR", f"CSV has only {len(df)} rows")
return ("OK", f"{len(df):,} draws, {age_days:.1f} days old")
except Exception as e:
return ("ERROR", f"CSV parse error: {e}")
def check_ml_models(self) -> Tuple[str, str]:
"""Prüft ML Models."""
# Eurojackpot uses different directory name
if "Eurojackpot" in self.lottery_name:
models_dir = os.path.join(self.data_dir, "eurojackpot_ml_models")
rf_main_model = os.path.join(models_dir, "trained_models_main.pkl")
rf_euro_model = os.path.join(models_dir, "trained_models_euro.pkl")
dl_main_model = os.path.join(models_dir, "deep_learning_main", "lstm_model_50.pth")
dl_euro_model = os.path.join(models_dir, "deep_learning_euro", "lstm_model_12.pth")
else:
models_dir = os.path.join(self.data_dir, "ultimate_ml_models")
rf_main_model = os.path.join(models_dir, "trained_models.pkl")
rf_euro_model = None
dl_main_model = os.path.join(models_dir, "deep_learning", "lstm_model_49.pth")
dl_euro_model = None
if not os.path.exists(models_dir):
return ("WARNING", "No models cache found (will train on next run)")
# Check RandomForest models
if os.path.exists(rf_main_model):
age_days = (time.time() - os.path.getmtime(rf_main_model)) / 86400
status = "OK" if age_days < 10 else "WARNING"
msg = f"RandomForest models {age_days:.1f} days old"
else:
status = "WARNING"
msg = "RandomForest models not found"
# Check Deep Learning models
if os.path.exists(dl_main_model):
age_days = (time.time() - os.path.getmtime(dl_main_model)) / 86400
msg += f", LSTM {age_days:.1f} days old"
else:
msg += ", LSTM not found"
return (status, msg)
def check_learning_state(self) -> Tuple[str, str]:
"""Prüft Learning State."""
# Eurojackpot uses learning_log.json instead of learning_state.json
if "Eurojackpot" in self.lottery_name:
state_file = os.path.join(self.data_dir, "learning_log.json")
else:
state_file = os.path.join(self.data_dir, "learning_state.json")
if not os.path.exists(state_file):
return ("WARNING", "No learning state found")
try:
with open(state_file, 'r') as f:
state = json.load(f)
cycles = state.get('learning_cycle', 0)
last_update = state.get('last_update', '')
if not last_update:
return ("WARNING", f"{cycles} cycles, no last_update timestamp")
last_dt = datetime.fromisoformat(last_update)
age_days = (datetime.now() - last_dt).days
if age_days > 10:
return ("WARNING", f"{cycles} cycles, last update {age_days} days ago")
return ("OK", f"{cycles} cycles, last update {age_days} days ago")
except Exception as e:
return ("ERROR", f"State parse error: {e}")
def check_logs(self) -> Tuple[str, str]:
"""Prüft und rotiert Logs."""
logs_dir = os.path.join(os.path.dirname(self.data_dir), "logs")
if not os.path.exists(logs_dir):
os.makedirs(logs_dir, exist_ok=True)
return ("RECOVERED", "Created logs directory")
# Check log sizes
total_size = 0
large_logs = []
for log_file in Path(logs_dir).glob("*.log"):
size_mb = log_file.stat().st_size / 1024 / 1024
total_size += size_mb
if size_mb > 50: # > 50 MB
large_logs.append(log_file.name)
# Rotate large logs
if large_logs:
for log_name in large_logs:
self._rotate_log(os.path.join(logs_dir, log_name))
return ("RECOVERED", f"Rotated {len(large_logs)} large logs, total {total_size:.1f} MB")
return ("OK", f"Total size {total_size:.1f} MB")
def check_telegram(self) -> Tuple[str, str]:
"""Prüft Telegram-Bot."""
config = self.notifier.config
if not config.get("telegram", {}).get("enabled"):
return ("WARNING", "Telegram disabled in config")
bot_token = config.get("telegram", {}).get("bot_token")
if not bot_token or bot_token == "YOUR_BOT_TOKEN":
return ("WARNING", "Telegram bot_token not configured")
# Simple check: Token format
if len(bot_token) < 20 or ':' not in bot_token:
return ("ERROR", "Invalid bot_token format")
return ("OK", "Telegram configured")
def check_disk_space(self) -> Tuple[str, str]:
"""Prüft Festplatten-Speicher."""
import shutil
usage = shutil.disk_usage(self.data_dir)
free_gb = usage.free / 1024 / 1024 / 1024
percent_free = (usage.free / usage.total) * 100
if percent_free < 10:
return ("ERROR", f"Only {free_gb:.1f} GB free ({percent_free:.1f}%)")
elif percent_free < 20:
return ("WARNING", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
return ("OK", f"{free_gb:.1f} GB free ({percent_free:.1f}%)")
def _rotate_log(self, log_path: str):
"""Rotiert ein Log-File."""
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_path = f"{log_path}.{timestamp}"
os.rename(log_path, backup_path)
print(f" 📦 Rotated: {os.path.basename(log_path)}{os.path.basename(backup_path)}")
def _print_summary(self):
"""Druckt Zusammenfassung."""
print("\n📊 SUMMARY:")
if not self.issues and not self.warnings and not self.recovered:
print(" ✅ All checks passed - System healthy!")
return
if self.recovered:
print(f"\n 🔧 Auto-Recovered ({len(self.recovered)}):")
for item in self.recovered:
print(f"{item}")
if self.warnings:
print(f"\n ⚠️ Warnings ({len(self.warnings)}):")
for item in self.warnings:
print(f"{item}")
if self.issues:
print(f"\n ❌ Issues ({len(self.issues)}):")
for item in self.issues:
print(f"{item}")
def _save_health_log(self, all_ok: bool):
"""Speichert Health-Log."""
log_entry = {
"timestamp": datetime.now().isoformat(),
"status": "OK" if all_ok else "ISSUES",
"issues": self.issues,
"warnings": self.warnings,
"recovered": self.recovered
}
# Load existing log
if os.path.exists(self.health_log):
with open(self.health_log, 'r') as f:
log_data = json.load(f)
else:
log_data = {"checks": []}
# Append new entry
log_data["checks"].append(log_entry)
# Keep only last 100 entries
log_data["checks"] = log_data["checks"][-100:]
# Save
with open(self.health_log, 'w') as f:
json.dump(log_data, f, indent=2)
def _send_alert(self):
"""Sendet Telegram-Alert bei Problemen."""
message = f"🚨 *HEALTH-CHECK ALERT - {self.lottery_name}*\n\n"
message += f"❌ *{len(self.issues)} Issues detected:*\n"
for issue in self.issues:
message += f"{issue}\n"
if self.warnings:
message += f"\n⚠️ {len(self.warnings)} Warnings:\n"
for warning in self.warnings:
message += f"{warning}\n"
message += f"\n🕐 {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
try:
self.notifier._send_telegram(message)
except Exception as e:
print(f" ⚠️ Could not send alert: {e}")
def main():
"""Main function."""
import argparse
parser = argparse.ArgumentParser(description="System Health-Check")
parser.add_argument(
'--data-dir',
type=str,
default="/Users/sebastianfrohlich/Library/Mobile Documents/com~apple~CloudDocs/Jupyter Notebooks/Lotto/data",
help='Data directory'
)
parser.add_argument(
'--lottery',
type=str,
default="Lotto",
help='Lottery name (Lotto or Eurojackpot)'
)
args = parser.parse_args()
# Run health check
checker = HealthCheck(args.data_dir, args.lottery)
success = checker.run_all_checks()
sys.exit(0 if success else 1)
if __name__ == "__main__":
main()
+24 -13
View File
@@ -58,28 +58,39 @@ class EurojackpotNotifier:
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!"
# Sortiere Tips nach Confidence
sorted_tips = sorted(tips, key=lambda x: x.get('confidence', 0), reverse=True)
# Top 5 formatieren
top5_text = ""
for i, tip in enumerate(sorted_tips[:5], 1):
main = tip.get('main_numbers', '?')
euro = tip.get('euro_numbers', '?')
conf = tip.get('confidence', 0)
strat = tip.get('strategy', 'UNKNOWN')
qual = tip.get('quality', 0)
# Emoji basierend auf Rang
emoji = "🏆" if i == 1 else "🥈" if i == 2 else "🥉" if i == 3 else ""
top5_text += f"""{emoji} #{i} - Confidence: {conf:.2%}
🔢 {main} + ⭐ {euro}
📈 {strat} | Quality: {qual:.3f}
"""
message = f"""🎲 NEUE EUROJACKPOT-TIPPS GENERIERT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📊 Anzahl Tipps: {len(tips)}
⏰ Zeitpunkt: {timestamp}
🏆 BESTER TIPP (höchste Confidence):
🏆 TOP 5 EMPFEHLUNGEN:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔢 Hauptzahlen: {main_numbers}
⭐ Eurozahlen: {euro_numbers}
📈 Strategie: {strategy}
💎 Confidence: {confidence:.2%}
💡 Alle Tipps findest du in der CSV-Datei!
{top5_text}
💡 Alle 10 Tipps findest du in der CSV-Datei!
Viel Glück! 🍀
"""