0ae789752e
Both selectors ordered candidates least-recently-shown, then daily.seeded_order() ROTATED the whole list and took [0] — an arbitrary date-hashed item, undoing the ordering. Result: repeats (quote id 2 on 6/28+6/29; word "harmony" on 6/25+6/28), no guarantee a pool item is shown before it recurs. Fix: daily.freshest(rows) returns the freshest cohort only — every NEVER-shown item while any remain, else the oldest-shown group. quote/wotd _candidates use it; seeded_order now picks deterministically WITHIN that cohort. So every pool item is featured once before any repeat, then cycles oldest-first. Dropped the unused _NO_REPEAT_POOL window. Tests: no-repeat-until-exhausted (quote + wotd) + a freshest() unit test. 428 backend tests green. (Separate follow-up: expand the QOTD pool from 16 → 90+ vetted public-domain quotes for a longer no-repeat window.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
121 lines
6.3 KiB
Python
121 lines
6.3 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
|
|
|
|
# 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, avoid: int | None = None) -> list[int]:
|
|
featured = conn.execute(
|
|
"SELECT id FROM quote_pool WHERE blocked=0 AND featured=1 ORDER BY id"
|
|
).fetchall()
|
|
if featured:
|
|
ids = [r[0] for r in featured]
|
|
else:
|
|
# The freshest cohort only (never-shown, else the oldest-shown group) — picking
|
|
# across the whole pool is what re-fed recent quotes day to day.
|
|
rows = conn.execute("SELECT id, shown_at FROM quote_pool WHERE blocked=0").fetchall()
|
|
ids = daily.freshest(rows)
|
|
if avoid is not None:
|
|
ids = [i for i in ids if i != avoid] or ids
|
|
return ids
|
|
|
|
|
|
def pick_daily(conn: sqlite3.Connection, feature_date: str | None = None, client=None,
|
|
force: bool = False, avoid: int | None = None) -> 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, avoid)
|
|
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")}
|