Feat: APScheduler — täglich Push für Health-Erinnerungen
- apscheduler==3.10.4 in requirements.txt - scheduler.py: AsyncIOScheduler, täglich 08:00 Uhr (Europe/Berlin) - Job prüft naechstes IN (heute, in 7 Tagen, gestern): heute → "Heute fällig", 7 Tage → Vorwarnung, gestern → Überfällig - Nur Impfung, Entwurmung, Medikament - misfire_grace_time=3600 (robust nach Container-Neustart) - Scheduler start/stop im FastAPI lifespan
This commit is contained in:
parent
c721d051c8
commit
5518064be3
3 changed files with 93 additions and 0 deletions
|
|
@ -11,6 +11,7 @@ from contextlib import asynccontextmanager
|
||||||
|
|
||||||
from database import init_db
|
from database import init_db
|
||||||
import ki
|
import ki
|
||||||
|
import scheduler as sched
|
||||||
|
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level = logging.INFO,
|
level = logging.INFO,
|
||||||
|
|
@ -27,7 +28,9 @@ async def lifespan(app: FastAPI):
|
||||||
logger.info("Ban Yaro startet...")
|
logger.info("Ban Yaro startet...")
|
||||||
init_db()
|
init_db()
|
||||||
logger.info(f"KI-Modus: {ki.KI_MODE}")
|
logger.info(f"KI-Modus: {ki.KI_MODE}")
|
||||||
|
sched.start()
|
||||||
yield
|
yield
|
||||||
|
sched.stop()
|
||||||
logger.info("Ban Yaro beendet.")
|
logger.info("Ban Yaro beendet.")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,4 @@ httpx==0.27.2
|
||||||
openai==1.50.0
|
openai==1.50.0
|
||||||
anthropic==0.34.0
|
anthropic==0.34.0
|
||||||
pywebpush==2.0.0
|
pywebpush==2.0.0
|
||||||
|
apscheduler==3.10.4
|
||||||
|
|
|
||||||
89
backend/scheduler.py
Normal file
89
backend/scheduler.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
"""
|
||||||
|
BAN YARO — Hintergrund-Scheduler
|
||||||
|
Täglich: Gesundheits-Erinnerungen per Push versenden.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from datetime import date, timedelta
|
||||||
|
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||||
|
from apscheduler.triggers.cron import CronTrigger
|
||||||
|
|
||||||
|
from database import db
|
||||||
|
from routes.push import send_push_to_user
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
_scheduler = AsyncIOScheduler(timezone="Europe/Berlin")
|
||||||
|
|
||||||
|
|
||||||
|
def start():
|
||||||
|
_scheduler.add_job(
|
||||||
|
_job_health_reminders,
|
||||||
|
CronTrigger(hour=8, minute=0), # täglich 08:00 Uhr
|
||||||
|
id="health_reminders",
|
||||||
|
replace_existing=True,
|
||||||
|
misfire_grace_time=3600, # bis zu 1h Verzug ok (z.B. nach Neustart)
|
||||||
|
)
|
||||||
|
_scheduler.start()
|
||||||
|
logger.info("Scheduler gestartet — Health-Reminder täglich 08:00 Uhr.")
|
||||||
|
|
||||||
|
|
||||||
|
def stop():
|
||||||
|
_scheduler.shutdown(wait=False)
|
||||||
|
logger.info("Scheduler gestoppt.")
|
||||||
|
|
||||||
|
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
# JOB: Gesundheits-Erinnerungen
|
||||||
|
# ------------------------------------------------------------------
|
||||||
|
async def _job_health_reminders():
|
||||||
|
"""
|
||||||
|
Findet alle Health-Einträge mit `naechstes`-Datum:
|
||||||
|
- genau heute → sofortige Erinnerung
|
||||||
|
- in 7 Tagen → Vorwarnung
|
||||||
|
- gestern → Überfällig-Erinnerung (nur einmal, 1 Tag nach Fälligkeit)
|
||||||
|
Schickt jeweils eine Push-Notification an den Hundebesitzer.
|
||||||
|
"""
|
||||||
|
today = date.today()
|
||||||
|
in7 = today + timedelta(days=7)
|
||||||
|
yesterday = today - timedelta(days=1)
|
||||||
|
|
||||||
|
logger.info(f"Health-Reminder Job läuft für {today}")
|
||||||
|
|
||||||
|
with db() as conn:
|
||||||
|
# Alle fälligen Einträge der nächsten 7 Tage + gestrige (überfällig)
|
||||||
|
rows = conn.execute("""
|
||||||
|
SELECT h.id, h.typ, h.bezeichnung, h.naechstes,
|
||||||
|
d.user_id, d.name AS hund_name
|
||||||
|
FROM health h
|
||||||
|
JOIN dogs d ON d.id = h.dog_id
|
||||||
|
WHERE h.naechstes IN (?, ?, ?)
|
||||||
|
AND h.typ IN ('impfung', 'entwurmung', 'medikament')
|
||||||
|
""", (str(today), str(in7), str(yesterday))).fetchall()
|
||||||
|
|
||||||
|
sent_total = 0
|
||||||
|
for r in rows:
|
||||||
|
naechstes = date.fromisoformat(r["naechstes"])
|
||||||
|
delta = (naechstes - today).days
|
||||||
|
|
||||||
|
if delta == 7:
|
||||||
|
title = f"⏰ Erinnerung: {r['bezeichnung']}"
|
||||||
|
body = f"In 7 Tagen fällig für {r['hund_name']}."
|
||||||
|
elif delta == 0:
|
||||||
|
title = f"📅 Heute fällig: {r['bezeichnung']}"
|
||||||
|
body = f"Bitte heute erledigen — {r['hund_name']} wartet."
|
||||||
|
else: # delta == -1 → gestern überfällig
|
||||||
|
title = f"⚠️ Überfällig: {r['bezeichnung']}"
|
||||||
|
body = f"War gestern fällig für {r['hund_name']} — bitte bald erledigen."
|
||||||
|
|
||||||
|
sent = send_push_to_user(r["user_id"], {
|
||||||
|
"type": "health_reminder",
|
||||||
|
"title": title,
|
||||||
|
"body": body,
|
||||||
|
"data": {"page": "health"},
|
||||||
|
})
|
||||||
|
sent_total += sent
|
||||||
|
if sent:
|
||||||
|
logger.info(f"Reminder Push: user={r['user_id']} entry={r['id']} delta={delta}d")
|
||||||
|
|
||||||
|
logger.info(f"Health-Reminder Job fertig — {len(rows)} Einträge, {sent_total} Push gesendet.")
|
||||||
Loading…
Add table
Add a link
Reference in a new issue