Files
cbazzaandClaude Sonnet 5 416c646fd9 Add watchdog for missed launchd runs, add backup retention
Mirrors the same fix on the Lotto sibling project. watchdog.py
(com.eurojackpot.watchdog.plist, daily 09:00) checks whether the
latest draw or trained models are older than the launchd schedule
allows and alerts via EurojackpotNotifier.send_error() - catches a
silently-missed StartCalendarInterval run (e.g. Mac asleep at the
scheduled time) that launchd itself never retries.

EurojackpotAPIUpdater.create_backup() didn't have Lotto's path bug,
but had the same unbounded growth (85 backups, 2.4MB accumulated).
Adds retention (keep last 30) and prunes the existing backlog.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-12 10:02:40 +02:00

78 lines
2.7 KiB
Python

#!/usr/bin/env python3
"""
Watchdog für die Eurojackpot-Automatisierung.
Prüft die Aktualität von Ziehungsdaten und trainierten Modellen anhand des
Ziehungskalenders (Di + Fr) und meldet sich per Telegram, wenn etwas länger
stillsteht als der launchd-Zeitplan erlaubt. Das fängt genau den Fall ab, in
dem ein StartCalendarInterval-Job verpasst wurde (z.B. weil der Mac im Schlaf
war) - launchd holt das selbst nicht nach, und ohne diesen Check fällt es
sonst erst bei der nächsten manuellen Nachfrage auf. Analog zum gleichnamigen
Watchdog im Schwesterprojekt Lotto.
Aufruf: python3 scripts/utils/watchdog.py
Gedacht für einen täglichen launchd-Job (siehe com.eurojackpot.watchdog.plist).
"""
import os
import sys
from datetime import datetime
import pandas as pd
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from notifier import EurojackpotNotifier
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DATA_FILE = os.path.join(PROJECT_ROOT, "data", "AlleEurojackpotzahlen.csv")
MODEL_FILE = os.path.join(PROJECT_ROOT, "data", "eurojackpot_ml_models", "trained_models_main.pkl")
# Eurojackpot zieht Di + Fr -> maximal 4 Tage zwischen zwei Ziehungen. 5 Tage
# Toleranz lassen dem Update-Job (nächster Morgen, 08 Uhr) Luft, ohne echte
# Ausfälle zu verpassen.
MAX_DAYS_SINCE_DRAW = 5
def check() -> list[str]:
"""Prüft Daten- und Modell-Aktualität, gibt gefundene Probleme zurück."""
problems = []
df = pd.read_csv(DATA_FILE, sep=';')
last_draw = pd.to_datetime(df['datum'], format='%Y-%m-%d').max().date()
days_since_draw = (datetime.now().date() - last_draw).days
if days_since_draw > MAX_DAYS_SINCE_DRAW:
problems.append(
f"Letzte Ziehung in den Daten ist {last_draw.isoformat()} "
f"({days_since_draw} Tage her) - der automatische Update-Lauf "
f"scheint ausgefallen zu sein."
)
if os.path.exists(MODEL_FILE):
model_date = datetime.fromtimestamp(os.path.getmtime(MODEL_FILE)).date()
if model_date < last_draw and (datetime.now().date() - model_date).days > 1:
problems.append(
f"Modelle sind vom {model_date.isoformat()}, aber die neueste "
f"Ziehung ({last_draw.isoformat()}) wurde noch nicht eingelernt."
)
else:
problems.append("Modell-Datei trained_models_main.pkl fehlt komplett.")
return problems
def main():
problems = check()
if not problems:
print("✅ Watchdog: alles aktuell.")
return
print("⚠️ Watchdog hat Probleme gefunden:")
for p in problems:
print(f" - {p}")
EurojackpotNotifier().send_error("\n".join(problems), context="Watchdog")
if __name__ == "__main__":
main()