feat: restore roast-of-the-day with 14-day prompt history

Separate roast generation from motivation again and pass cached roasts
from the last 14 days into the OpenRouter prompt to reduce repetition.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Frank Schwenk
2026-06-17 08:45:09 +02:00
parent c59c3069c4
commit 24ac7f2f48
4 changed files with 104 additions and 11 deletions
+74 -2
View File
@@ -1,7 +1,7 @@
from __future__ import annotations
import logging
from datetime import datetime
from datetime import datetime, timedelta
from typing import Any
from zoneinfo import ZoneInfo
@@ -21,6 +21,14 @@ MOTIVATION_SYSTEM = (
"Genau ein Satz auf Deutsch, max 25 Wörter. Keine Anführungszeichen um den Spruch."
)
ROAST_SYSTEM = (
"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. "
"Keine Anführungszeichen um den Spruch. Wiederhole keine Formulierungen oder Ideen aus der Historie."
)
ROAST_HISTORY_DAYS = 14
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()
@@ -87,6 +95,26 @@ def _motivation_key(day: str) -> str:
return f"motivation:v2:{day}"
def _roast_key(day: str) -> str:
return f"roast:{day}"
async def _recent_roasts(
db: aiosqlite.Connection,
*,
before_day: str,
days: int = ROAST_HISTORY_DAYS,
) -> list[str]:
end = datetime.strptime(before_day, "%Y-%m-%d").date()
roasts: list[str] = []
for offset in range(1, days + 1):
day = (end - timedelta(days=offset)).isoformat()
text = await _get_cache(db, _roast_key(day))
if text:
roasts.append(text)
return roasts
async def _cached_motivation(db: aiosqlite.Connection, day: str) -> str | None:
cached = await _get_cache(db, _motivation_key(day))
if cached:
@@ -134,5 +162,49 @@ async def get_daily_motivation(db: aiosqlite.Connection, tz: ZoneInfo) -> dict[s
return {"text": pick("motivation_fallback"), "source": "fallback", "day": day}
async def generate_roast_of_the_day(
db: aiosqlite.Connection,
tz: ZoneInfo,
day: str | None = None,
*,
force: bool = False,
) -> dict[str, Any]:
day = day or datetime.now(tz).strftime("%Y-%m-%d")
key = _roast_key(day)
if not force:
cached = await _get_cache(db, key)
if cached:
return {"text": cached, "source": "cache", "day": day}
stats = await compute_stats(db)
recent = await _recent_roasts(db, before_day=day)
history = ""
if recent:
lines = "\n".join(f"- {text}" for text in recent)
history = (
f"\n\nDiese Roasts der letzten {ROAST_HISTORY_DAYS} Tage wurden bereits verwendet "
f"(nicht wiederholen, neue Idee):\n{lines}"
)
prompt = (
"Schreibe den Roast-of-the-Day. "
f"Streak: {stats['streak']} Tage. "
f"Compliance über {stats['compliance_window_days']} aktive Tage: {stats['compliance_percent']}%. "
f"Ein sarkastischer Spruch im dark-humor Medikamenten-Coach Stil.{history}"
)
text = await _call_openrouter(prompt, system=ROAST_SYSTEM)
if not text:
text = pick("roast_fallback")
source = "fallback"
else:
source = "ai"
await _set_cache(db, key, text)
logger.info("Generated roast of the day for %s (%s)", day, source)
return {"text": text, "source": source, "day": day}
async def get_roast_of_the_day(db: aiosqlite.Connection, tz: ZoneInfo) -> dict[str, Any]:
return await get_daily_motivation(db, tz)
day = datetime.now(tz).strftime("%Y-%m-%d")
cached = await _get_cache(db, _roast_key(day))
if cached:
return {"text": cached, "source": "cache", "day": day}
return {"text": pick("roast_fallback"), "source": "fallback", "day": day}