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