From b5390b8ced95629433a633663f5b3f8766b4bf96 Mon Sep 17 00:00:00 2001 From: Frank Schwenk Date: Wed, 10 Jun 2026 10:38:43 +0200 Subject: [PATCH] haha yes it works now --- server/ai.py | 38 ++++++++++++++++++++-------------- server/app.py | 4 ++-- server/scheduler.py | 10 ++++----- server/stats.py | 47 +++++++++++++++++++++++++++++++++++++++++- tools/regenerate_ai.py | 2 +- 5 files changed, 77 insertions(+), 24 deletions(-) diff --git a/server/ai.py b/server/ai.py index 6534bb5..d555447 100644 --- a/server/ai.py +++ b/server/ai.py @@ -22,7 +22,7 @@ MOTIVATION_SYSTEM = ( ) ORACLE_SYSTEM = ( - "Du bist das wöchentliche KI-Orakel einer ADHS-Medikamenten-App. " + "Du bist das tägliche KI-Orakel einer ADHS-Medikamenten-App. " "Passiv-aggressiv, trocken, max 4 Sätze auf Deutsch. Keine medizinischen Ratschläge." ) @@ -93,8 +93,16 @@ def _motivation_key(day: str) -> str: return f"motivation:v2:{day}" -def _oracle_key(week: str) -> str: - return f"oracle:v2:{week}" +def _oracle_key(day: str) -> str: + return f"oracle:v3:{day}" + + +async def _cached_oracle(db: aiosqlite.Connection, day: str) -> str | None: + cached = await _get_cache(db, _oracle_key(day)) + if cached: + return cached + week = datetime.strptime(day, "%Y-%m-%d").strftime("%Y-W%W") + return await _get_cache(db, f"oracle:v2:{week}") async def _cached_motivation(db: aiosqlite.Connection, day: str) -> str | None: @@ -151,16 +159,16 @@ async def get_roast_of_the_day(db: aiosqlite.Connection, tz: ZoneInfo) -> dict[s async def generate_oracle( db: aiosqlite.Connection, tz: ZoneInfo, - week: str | None = None, + day: str | None = None, *, force: bool = False, ) -> dict[str, Any]: - week = week or datetime.now(tz).strftime("%Y-W%W") - key = _oracle_key(week) + day = day or datetime.now(tz).strftime("%Y-%m-%d") + key = _oracle_key(day) if not force: - cached = await _get_cache(db, key) + cached = await _cached_oracle(db, day) if cached: - return {"text": cached, "source": "cache", "week": week} + return {"text": cached, "source": "cache", "day": day} stats = await compute_stats(db) days_active = stats["days_active"] @@ -178,7 +186,7 @@ async def generate_oracle( else "" ) prompt = ( - f"Wöchentlicher Orakel-Report. {context}{caveat}" + f"Täglicher Orakel-Report für heute. {context}{caveat}" f"Streak: {stats['streak']} Tage. " f"Compliance über {window} Tag(e): {stats['compliance_percent']}%. " f"Genommen gesamt: {stats['total_taken']}." @@ -197,17 +205,17 @@ async def generate_oracle( else: source = "ai" await _set_cache(db, key, text) - logger.info("Generated oracle for %s (%s)", week, source) - return {"text": text, "source": source, "week": week} + logger.info("Generated daily oracle for %s (%s)", day, source) + return {"text": text, "source": source, "day": day} async def get_oracle(db: aiosqlite.Connection, tz: ZoneInfo) -> dict[str, Any]: - week = datetime.now(tz).strftime("%Y-W%W") - cached = await _get_cache(db, _oracle_key(week)) + day = datetime.now(tz).strftime("%Y-%m-%d") + cached = await _cached_oracle(db, day) if cached: - return {"text": cached, "source": "cache", "week": week} + return {"text": cached, "source": "cache", "day": day} return { "text": "Das Orakel bereitet sich vor. Schau später nochmal rein.", "source": "pending", - "week": week, + "day": day, } diff --git a/server/app.py b/server/app.py index a4930fd..4e79793 100644 --- a/server/app.py +++ b/server/app.py @@ -37,7 +37,7 @@ async def _ensure_today_motivation() -> None: await db.close() -async def _ensure_weekly_oracle() -> None: +async def _ensure_today_oracle() -> None: config = load_meds_config() db = await get_db() try: @@ -55,7 +55,7 @@ async def lifespan(app: FastAPI): await db.close() start_scheduler() asyncio.create_task(_ensure_today_motivation()) - asyncio.create_task(_ensure_weekly_oracle()) + asyncio.create_task(_ensure_today_oracle()) yield diff --git a/server/scheduler.py b/server/scheduler.py index a0f9079..a21ca0f 100644 --- a/server/scheduler.py +++ b/server/scheduler.py @@ -49,13 +49,13 @@ async def _generate_daily_motivation() -> None: await db.close() -async def _generate_weekly_oracle() -> None: +async def _generate_daily_oracle() -> None: config = load_meds_config() db = await get_db() try: await generate_oracle(db, config.timezone) except Exception: - logger.exception("Weekly oracle generation failed") + logger.exception("Daily oracle generation failed") finally: await db.close() @@ -131,9 +131,9 @@ def start_scheduler() -> None: ) scheduler.add_job( - _generate_weekly_oracle, - trigger=CronTrigger(day_of_week="mon", hour=3, minute=5, timezone=tz), - id="weekly-oracle", + _generate_daily_oracle, + trigger=CronTrigger(hour=3, minute=5, timezone=tz), + id="daily-oracle", replace_existing=True, ) diff --git a/server/stats.py b/server/stats.py index c9ecd05..44d3474 100644 --- a/server/stats.py +++ b/server/stats.py @@ -8,6 +8,7 @@ import aiosqlite from server.config_loader import load_meds_config, today_str from server.db import new_id, utc_now_iso +from server.slots import compute_status, get_log_for_day, get_snooze_for_day async def get_first_intake_day(db: aiosqlite.Connection, tz: ZoneInfo) -> str: @@ -88,11 +89,55 @@ async def day_compliance(db: aiosqlite.Connection, day: str, slot_ids: list[str] return int(row[0]) >= len(slot_ids) if row else False +async def _today_streak_state( + db: aiosqlite.Connection, + tz: ZoneInfo, + slot_ids: list[str], +) -> str: + """Return 'complete', 'failed', or 'pending' for today.""" + config = load_meds_config() + day = today_str(tz) + logs = await get_log_for_day(db, day) + slots_by_id = {s.id: s for s in config.slots} + + for sid in slot_ids: + entry = logs.get(sid) + if entry and entry["status"] == "missed": + return "failed" + + now = datetime.now(tz) + snoozes = await get_snooze_for_day(db, day) + all_taken = True + + for sid in slot_ids: + entry = logs.get(sid) + if entry and entry["status"] == "taken": + continue + all_taken = False + slot = slots_by_id[sid] + status = compute_status(slot, now, tz, entry, snoozes.get(sid)) + if status == "overdue": + return "failed" + + if all_taken: + return "complete" + return "pending" + + async def compute_streak(db: aiosqlite.Connection, tz: ZoneInfo) -> int: config = load_meds_config() slot_ids = [s.id for s in config.slots] + if not slot_ids: + return 0 + + today = datetime.now(tz).date() + today_state = await _today_streak_state(db, tz, slot_ids) + + if today_state == "failed": + return 0 + + day = today if today_state == "complete" else today - timedelta(days=1) streak = 0 - day = datetime.now(tz).date() while True: day_str = day.isoformat() ok = await day_compliance(db, day_str, slot_ids) diff --git a/tools/regenerate_ai.py b/tools/regenerate_ai.py index cecf427..3db6cda 100644 --- a/tools/regenerate_ai.py +++ b/tools/regenerate_ai.py @@ -14,7 +14,7 @@ from server.db import get_db async def main() -> None: parser = argparse.ArgumentParser(description="Regenerate cached AI texts") parser.add_argument("--motivation", action="store_true", help="Regenerate today's motivation") - parser.add_argument("--oracle", action="store_true", help="Regenerate this week's oracle") + parser.add_argument("--oracle", action="store_true", help="Regenerate today's oracle") parser.add_argument("--force", action="store_true", help="Regenerate even if cache exists") args = parser.parse_args()