Files
upbeatBytes/goodnews/quote.py
T
thejayman77 67d4bc32cb Small joys: Quote of the Day + Word of the Day engines
- quote.py: curated public-domain quote pool (16 seeded, admin-grows), deterministic daily
  pick, lazy AI "what it means" explainer of the real quote (cached). No LLM-invented quotes.
- wotd.py: LLM proposes positive words → validated/enriched against dictionaryapi.dev (real
  definition, IPA, examples, audio) → audio clip cached to our origin (TTS fallback) →
  deterministic daily pick. Tops the pool up toward 30/day.
- db.py: quote_pool/daily_quote + wotd_pool/daily_wotd tables.
- api.py: /api/quote/today, /api/word/today, /api/word/audio/{word} (GET+HEAD).
- cli.py: cycle steps for both (under --no-joys), shared LLM client.
- tests: test_quote.py (6) + test_wotd.py (5). 393 backend tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 17:28:55 -04:00

119 lines
6.0 KiB
Python

"""Quote of the Day — a hopeful, public-domain quote a day.
Curated, never LLM-invented (misattribution is the one thing that would burn trust). A
vetted starter set seeds the pool; admin grows it. The "what it means" explainer IS
LLM-generated, but it only *interprets a real, known quote* — low risk — and is filled
lazily the first time a quote is shown, then cached.
Same lifecycle as the other small joys: pool → deterministic daily pick → cached row.
"""
from __future__ import annotations
import sqlite3
from . import daily
from .localtime import local_today
_NO_REPEAT_POOL = 60
# Public-domain (ancient / author died well over a century ago), uplifting. Admin curates.
SEED = [
("Very little is needed to make a happy life; it is all within yourself, in your way of thinking.", "Marcus Aurelius", "Meditations"),
("The happiness of your life depends upon the quality of your thoughts.", "Marcus Aurelius", "Meditations"),
("The journey of a thousand miles begins with a single step.", "Lao Tzu", "Tao Te Ching"),
("Nature does not hurry, yet everything is accomplished.", "Lao Tzu", "Tao Te Ching"),
("It does not matter how slowly you go as long as you do not stop.", "Confucius", None),
("Knowing yourself is the beginning of all wisdom.", "Aristotle", None),
("We suffer more often in imagination than in reality.", "Seneca", None),
("It's not what happens to you, but how you react to it that matters.", "Epictetus", None),
("The wound is the place where the Light enters you.", "Rumi", None),
("Write it on your heart that every day is the best day in the year.", "Ralph Waldo Emerson", None),
("Go confidently in the direction of your dreams. Live the life you have imagined.", "Henry David Thoreau", None),
("Hope is the thing with feathers that perches in the soul.", "Emily Dickinson", None),
("The best portion of a good man's life: his little, nameless, unremembered acts of kindness and of love.", "William Wordsworth", None),
("Great things are done by a series of small things brought together.", "Vincent van Gogh", None),
("I am not afraid of storms, for I am learning how to sail my ship.", "Louisa May Alcott", "Little Women"),
("I exist as I am, that is enough.", "Walt Whitman", "Leaves of Grass"),
]
def seed(conn: sqlite3.Connection) -> int:
"""Insert the curated starter quotes (idempotent via content key). Returns # added."""
before = conn.execute("SELECT COUNT(*) FROM quote_pool").fetchone()[0]
conn.executemany(
"INSERT OR IGNORE INTO quote_pool (source, ckey, text, author, work) VALUES ('curated', ?, ?, ?, ?)",
[(daily.content_key(text, author), text, author, work) for text, author, work in SEED],
)
conn.commit()
return conn.execute("SELECT COUNT(*) FROM quote_pool").fetchone()[0] - before
def _explain(client, text: str, author: str | None) -> str | None:
user = (
"In one or two plain, warm sentences, explain what this quote means and why it's worth "
"remembering, for a general audience who may not be familiar with it. No preamble, no "
f'quoting it back.\n\nQuote: "{text}"' + (f"\n{author}" if author else "")
)
out = " ".join(client.chat_text([{"role": "user", "content": user}]).split()).strip()
return out or None
def _candidates(conn: sqlite3.Connection) -> list[int]:
featured = conn.execute(
"SELECT id FROM quote_pool WHERE blocked=0 AND featured=1 ORDER BY id"
).fetchall()
if featured:
return [r[0] for r in featured]
rows = conn.execute(
"SELECT id FROM quote_pool WHERE blocked=0 ORDER BY shown_at IS NOT NULL, shown_at, id LIMIT ?",
(_NO_REPEAT_POOL,),
).fetchall()
return [r[0] for r in rows]
def pick_daily(conn: sqlite3.Connection, feature_date: str | None = None, client=None, force: bool = False) -> dict | None:
feature_date = feature_date or local_today()
existing = conn.execute("SELECT * FROM daily_quote WHERE feature_date=?", (feature_date,)).fetchone()
if existing and not force:
return dict(existing)
ids = _candidates(conn)
if not ids:
return None
pick_id = daily.seeded_order(ids, feature_date)[0]
row = conn.execute("SELECT * FROM quote_pool WHERE id=?", (pick_id,)).fetchone()
meaning = row["meaning"]
if not meaning and client: # lazy, cached; LLM done before the write
try:
meaning = _explain(client, row["text"], row["author"])
if meaning:
conn.execute("UPDATE quote_pool SET meaning=? WHERE id=?", (meaning, pick_id))
except Exception: # noqa: BLE001 — explainer is optional
meaning = None
conn.execute(
"INSERT INTO daily_quote (feature_date, pool_id, source, text, author, work, year, meaning) "
"VALUES (?,?,?,?,?,?,?,?) "
"ON CONFLICT(feature_date) DO UPDATE SET pool_id=excluded.pool_id, text=excluded.text, "
"author=excluded.author, work=excluded.work, year=excluded.year, meaning=excluded.meaning",
(feature_date, row["id"], row["source"], row["text"], row["author"], row["work"], row["year"], meaning),
)
conn.execute("UPDATE quote_pool SET shown_at=? WHERE id=?", (feature_date, pick_id))
conn.commit()
return dict(conn.execute("SELECT * FROM daily_quote WHERE feature_date=?", (feature_date,)).fetchone())
def get_today(conn: sqlite3.Connection, feature_date: str | None = None) -> dict | None:
if feature_date:
row = conn.execute("SELECT * FROM daily_quote WHERE feature_date=?", (feature_date,)).fetchone()
if row:
return dict(row)
row = conn.execute("SELECT * FROM daily_quote ORDER BY feature_date DESC LIMIT 1").fetchone()
return dict(row) if row else None
def run_daily(conn: sqlite3.Connection, client=None) -> dict:
if conn.execute("SELECT COUNT(*) FROM quote_pool").fetchone()[0] == 0:
seed(conn)
picked = pick_daily(conn, client=client)
return {"pool": conn.execute("SELECT COUNT(*) FROM quote_pool").fetchone()[0],
"picked": (picked or {}).get("author")}