from __future__ import annotations from datetime import datetime from typing import Any from zoneinfo import ZoneInfo import aiosqlite import httpx from server.db import utc_now_iso from server.messages import pick from server.settings import settings from server.stats import compute_stats async def _get_cache(db: aiosqlite.Connection, key: str) -> str | None: cur = await db.execute("SELECT content FROM ai_cache WHERE key = ?", (key,)) row = await cur.fetchone() await cur.close() return row[0] if row else None async def _set_cache(db: aiosqlite.Connection, key: str, content: str) -> None: await db.execute( "INSERT INTO ai_cache (key, content, created_at) VALUES (?, ?, ?) ON CONFLICT(key) DO UPDATE SET content=excluded.content, created_at=excluded.created_at", (key, content, utc_now_iso()), ) await db.commit() async def _call_openrouter(prompt: str) -> str | None: if not settings.openrouter_api_key: return None try: async with httpx.AsyncClient(timeout=30.0) as client: resp = await client.post( "https://openrouter.ai/api/v1/chat/completions", headers={ "Authorization": f"Bearer {settings.openrouter_api_key}", "HTTP-Referer": "https://medis.schwenk.online", "X-Title": "TakeYourMeds", }, json={ "model": settings.openrouter_model, "messages": [ { "role": "system", "content": "Du bist ein sarkastischer, dark-humor Medikamenten-Coach für jemanden mit ADHS. Kurz (max 2 Sätze), deutsch, witzig aber nicht gemein. Keine medizinischen Ratschläge.", }, {"role": "user", "content": prompt}, ], "max_tokens": 120, }, ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"].strip() except Exception: return None async def get_roast_of_the_day(db: aiosqlite.Connection, tz: ZoneInfo) -> dict[str, Any]: day = datetime.now(tz).strftime("%Y-%m-%d") key = f"roast:{day}" cached = await _get_cache(db, key) if cached: return {"text": cached, "source": "cache", "day": day} stats = await compute_stats(db) prompt = f"Roast-of-the-Day für jemanden mit ADHS. Streak: {stats['streak']} Tage, Compliance 90d: {stats['compliance_percent']}%. Ein sarkastischer Spruch." text = await _call_openrouter(prompt) if not text: text = pick("roast_fallback") source = "fallback" else: source = "ai" await _set_cache(db, key, text) 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") key = f"oracle:{week}" cached = await _get_cache(db, key) if cached: return {"text": cached, "source": "cache", "week": week} stats = await compute_stats(db) prompt = ( f"Wöchentlicher KI-Orakel-Report (passiv-aggressiv, max 4 Sätze). " f"Streak: {stats['streak']}, Compliance 90d: {stats['compliance_percent']}%, " f"genommen gesamt: {stats['total_taken']}." ) text = await _call_openrouter(prompt) if not text: text = pick("streak", streak=stats["streak"]) + f" Compliance: {stats['compliance_percent']}%." source = "fallback" else: source = "ai" await _set_cache(db, key, text) return {"text": text, "source": source, "week": week}