Sprint 15: Zeitzone-Fix, Gewichts-Sync, Öffnungszeiten, KI-Bericht, POI-Moderation — SW by-v432, APP_VER 411

- client_time: Browser-Lokalzeit bei allen Creates mitschicken (Tagebuch, Notizen,
  Forum, Verlorener Hund, Routen) — kein UTC-Versatz mehr bei Einträgen
- Gewicht-Sync: health typ=gewicht schreibt dogs.gewicht_kg, einmalige Migration
- Praxen: opening_hours + lat/lon/osm_id in tieraerzte-Tabelle, OSM-Nearby-Lookup,
  Öffnungszeiten in Karte und Detailansicht
- KI-Gesundheitsbericht: alle 2 Wochen automatisch, ki_health_reports-Tabelle,
  Frontend-Banner mit Archiv (letzten 5 Berichte)
- POI-Korrekturen: User schlägt Öffnungszeiten-Änderung vor, Moderatoren-Tab
  genehmigt/lehnt ab, user_edited-Flag schützt vor Overpass-Überschreibung
- timeutils.py: safe_client_time() zentral für alle Routen
This commit is contained in:
rene 2026-04-26 15:38:50 +02:00
parent 679dbdd862
commit 06bd8525ed
21 changed files with 724 additions and 75 deletions

View file

@ -100,6 +100,14 @@ def start():
replace_existing=True,
misfire_grace_time=1800,
)
# Jeden Montag 07:00 — KI-Gesundheitsberichte (alle 2 Wochen)
_scheduler.add_job(
_job_ki_health_report,
CronTrigger(day_of_week='mon', hour=7, minute=0),
id="ki_health_report",
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 beim Start. OSM-Cache: on-demand (kein Prewarm).")
@ -745,6 +753,76 @@ async def _job_weekly_praise():
_log_job("weekly_praise", "ok", f"{generated} Lob-Texte f\u00fcr KW {d[1]}")
# ------------------------------------------------------------------
# JOB: KI-Gesundheitsberichte (alle 2 Wochen, jeden Montag 07:00)
# ------------------------------------------------------------------
async def _job_ki_health_report():
"""
Erstellt für jeden Hund, der seit mehr als 13 Tagen keinen KI-Gesundheitsbericht
hat (oder noch keinen hatte), einen neuen Bericht via ki.health_summary() und
schickt eine Push-Notification an den Besitzer. Maximal 20 Hunde pro Lauf.
"""
import ki as KI
with db() as conn:
dogs = conn.execute("""
SELECT d.id AS dog_id, d.name, d.rasse, d.geburtstag, d.gewicht_kg, d.user_id
FROM dogs d
WHERE d.id NOT IN (
SELECT dog_id FROM ki_health_reports
WHERE erstellt_at >= datetime('now', '-13 days')
)
ORDER BY d.id
LIMIT 20
""").fetchall()
dogs = [dict(d) for d in dogs]
if not dogs:
logger.info("KI-Gesundheitsbericht: Keine fälligen Hunde.")
_log_job("ki_health_report", "ok", "0 Berichte erstellt")
return
count = 0
for dog in dogs:
try:
with db() as conn:
health_rows = conn.execute(
"SELECT * FROM health WHERE dog_id=? ORDER BY datum DESC",
(dog["dog_id"],)
).fetchall()
health_data = [dict(r) for r in health_rows]
dog_info = {
"name": dog["name"],
"rasse": dog.get("rasse"),
"geburtstag": dog.get("geburtstag"),
"gewicht_kg": dog.get("gewicht_kg"),
}
bericht = await KI.health_summary(health_data=health_data, dog_info=dog_info)
with db() as conn:
conn.execute(
"INSERT INTO ki_health_reports (dog_id, user_id, bericht) VALUES (?, ?, ?)",
(dog["dog_id"], dog["user_id"], bericht)
)
send_push_to_user(dog["user_id"], {
"type": "ki_health_report",
"title": f"Gesundheitsbericht für {dog['name']}",
"body": "Dein KI-Assistent hat einen neuen Bericht erstellt.",
"data": {"page": "health"},
})
count += 1
logger.info(f"KI-Gesundheitsbericht: Bericht für Hund {dog['dog_id']} ({dog['name']}) erstellt.")
except Exception as e:
logger.error(f"KI-Gesundheitsbericht: Fehler für Hund {dog['dog_id']} ({dog['name']}): {e}")
logger.info(f"KI-Gesundheitsbericht Job fertig — {count}/{len(dogs)} Berichte erstellt.")
_log_job("ki_health_report", "ok", f"{count} Berichte erstellt")
# ------------------------------------------------------------------
# JOB: Status-Report per Mail (4× täglich)
# ------------------------------------------------------------------
@ -801,6 +879,7 @@ async def _job_status_report():
"seed_breeds_startup": "Rassen-Seed (TheDogAPI)",
"seed_wikidata_startup":"Rassen-Seed (Wikidata)",
"weekly_praise": "Wöchentlicher Lober (Mo 09:00)",
"ki_health_report": "KI-Gesundheitsberichte",
}
job_rows_html = ""
job_rows_txt = ""