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>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
"""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")}
|
||||
Reference in New Issue
Block a user