Feature: Sprint31 — 9 Features merged (Streak, Ausgaben, KI-Tierarzt, Rückrufe, Adoption, Vet+Befunde, Hundepass, Playdate, Rassenerkennung)
- Trainings-Streak: streak.py, DB training_streaks, Scheduler 19:00, Widget in welcome.js, Ping in uebungen.js
- Ausgaben-Tracker: expenses.py, expenses.js, DB expenses-Tabelle
- KI-Tierarztfragen: ki.py /tierarzt, health.js Button+Modal, DB ki_tierarzt_log
- Rückruf-Alarm: recalls.py, recalls.js, DB feed_recalls, Scheduler 08:00 RASFF
- Adoption: adoption.py, adoption.js, DB adoption_cache
- Tierarzt-Favorit + Befunde: tieraerzte.py /my-favorite+/favorite, health_docs.py, health.js, api.js, DB favorite_vets+health_documents
- Digitaler Hundepass: passport.py, dog-profile.js, main.py /pass/{token}, DB vaccinations+medications+dog_passport_meta+passport_shares, requirements.txt fpdf2
- Playdate-Matching: playdate.py, playdate.js, DB playdate_listings+playdate_requests
- Rassen-Erkennung: ki.py /rasse-erkennung (Claude Vision), dog-profile.js+wiki.js, CSS .rasse-result-card, DB ki_rasse_log
This commit is contained in:
parent
031c6028ac
commit
742ad189e8
26 changed files with 5734 additions and 27 deletions
|
|
@ -132,8 +132,24 @@ def start():
|
|||
replace_existing=True,
|
||||
misfire_grace_time=3600,
|
||||
)
|
||||
# Täglich 19:00 Uhr — Streak-Erinnerung
|
||||
_scheduler.add_job(
|
||||
_job_streak_reminder,
|
||||
CronTrigger(hour=19, minute=0),
|
||||
id="streak_reminder",
|
||||
replace_existing=True,
|
||||
misfire_grace_time=3600,
|
||||
)
|
||||
# Täglich 08:00 Uhr — Tierfutter-Rückrufe prüfen (RASFF)
|
||||
_scheduler.add_job(
|
||||
_job_recall_check,
|
||||
CronTrigger(hour=8, minute=0),
|
||||
id="recall_check",
|
||||
replace_existing=True,
|
||||
misfire_grace_time=3600,
|
||||
)
|
||||
_scheduler.start()
|
||||
logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00. OSM-Cache: on-demand (kein Prewarm).")
|
||||
logger.info("Scheduler gestartet — Health-Reminder 08:00, Giftköder-Archiv 03:00, Wetter-Alert 07:30, Meilenstein-Check 00:05, Event-Import So 02:00, Rassen-Seed monatlich 1. des Monats, Status-Report täglich 06:00, Moderation-Overdue 12:00, Quartalsbericht 1. Feb/Mai/Aug/Nov 07:00, Streak-Reminder 19:00, Rückruf-Check 08:00. OSM-Cache: on-demand (kein Prewarm).")
|
||||
|
||||
|
||||
def stop():
|
||||
|
|
@ -855,6 +871,8 @@ async def _job_status_report():
|
|||
"weekly_praise": "Wöchentlicher Lober (Mo 09:00)",
|
||||
"ki_health_report": "KI-Gesundheitsberichte",
|
||||
"quarterly_report": "Quartalsbericht (1. Feb/Mai/Aug/Nov)",
|
||||
"streak_reminder": "Streak-Erinnerung (täglich 19:00)",
|
||||
"recall_check": "Tierfutter-Rückrufe (RASFF, täglich 08:00)",
|
||||
}
|
||||
job_rows_html = ""
|
||||
job_rows_txt = ""
|
||||
|
|
@ -1172,3 +1190,79 @@ async def _job_hdm_winner():
|
|||
|
||||
logger.info(f"HdM-Winner {monat}: Hund {winner['dog_id']} ('{winner['name']}', {winner['stimmen']} Stimmen) eingetragen.")
|
||||
_log_job("hdm_winner", "ok", f"{monat}: {winner['name']} ({winner['stimmen']} Stimmen)")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# JOB: Streak-Erinnerung (täglich 19:00)
|
||||
# ------------------------------------------------------------------
|
||||
async def _job_streak_reminder():
|
||||
"""
|
||||
Findet alle User die heute noch nicht trainiert haben (last_training_date < heute)
|
||||
und deren current_streak > 0. Sendet einen motivierenden Push pro Hund.
|
||||
"""
|
||||
today = str(date.today())
|
||||
logger.info(f"Streak-Reminder Job läuft für {today}")
|
||||
|
||||
with db() as conn:
|
||||
rows = conn.execute("""
|
||||
SELECT ts.user_id, ts.dog_id, ts.current_streak, d.name AS dog_name
|
||||
FROM training_streaks ts
|
||||
JOIN dogs d ON d.id = ts.dog_id
|
||||
WHERE ts.current_streak > 0
|
||||
AND (ts.last_training_date IS NULL OR ts.last_training_date < ?)
|
||||
""", (today,)).fetchall()
|
||||
|
||||
sent_total = 0
|
||||
for r in rows:
|
||||
n = r["current_streak"]
|
||||
sent = send_push_to_user(r["user_id"], {
|
||||
"type": "streak_reminder",
|
||||
"title": f"🔥 {r['dog_name']} wartet auf sein Training!",
|
||||
"body": f"Streak: {n} {'Tag' if n == 1 else 'Tage'} — nicht jetzt aufhören.",
|
||||
"data": {"page": "uebungen"},
|
||||
"tag": f"streak-{r['dog_id']}-{today}",
|
||||
})
|
||||
sent_total += sent
|
||||
|
||||
logger.info(f"Streak-Reminder Job fertig — {len(rows)} Hunde geprüft, {sent_total} Push gesendet.")
|
||||
_log_job("streak_reminder", "ok", f"{sent_total} Push an {len(rows)} Hunde")
|
||||
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# JOB: Tierfutter-Rückrufe prüfen (RASFF, täglich 08:00)
|
||||
# ------------------------------------------------------------------
|
||||
async def _job_recall_check():
|
||||
"""
|
||||
Fragt täglich die RASFF EU-API nach neuen Tierfutter-Rückrufen ab.
|
||||
Neue Einträge werden in DB gespeichert, für jeden wird ein Push
|
||||
an alle abonnierten User gesendet.
|
||||
"""
|
||||
logger.info("Rückruf-Check Job läuft")
|
||||
try:
|
||||
from routes.recalls import fetch_rasff_recalls, save_new_recalls
|
||||
entries = await fetch_rasff_recalls()
|
||||
if not entries:
|
||||
logger.info("Rückruf-Check: Keine Einträge von RASFF erhalten (API-Fehler oder leer).")
|
||||
_log_job("recall_check", "ok", "0 neue Rückrufe (API leer)")
|
||||
return
|
||||
|
||||
new_entries = save_new_recalls(entries)
|
||||
logger.info(f"Rückruf-Check: {len(new_entries)} neue von {len(entries)} geprüften Einträgen.")
|
||||
|
||||
for entry in new_entries:
|
||||
produkt = entry.get("produkt") or entry.get("titel") or "Unbekanntes Produkt"
|
||||
gefahr = entry.get("gefahr") or "Bitte Produktdetails prüfen"
|
||||
ext_id = entry["external_id"]
|
||||
body = f"{produkt} — {gefahr[:80]}"
|
||||
send_push_to_all({
|
||||
"title": "⚠️ Tierfutter-Rückruf",
|
||||
"body": body,
|
||||
"data": {"page": "recalls"},
|
||||
"tag": f"recall-{ext_id}",
|
||||
})
|
||||
logger.info(f"Rückruf-Push gesendet: {ext_id} — {produkt}")
|
||||
|
||||
_log_job("recall_check", "ok", f"{len(new_entries)} neue Rückrufe")
|
||||
except Exception as e:
|
||||
logger.error(f"Rückruf-Check: unerwarteter Fehler: {e}")
|
||||
_log_job("recall_check", "error", str(e))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue