Small joys: Codex audit #2 fixes (route resolution, noindex, sense/tone, exclude-current re-pick)

- Admin joy item route moved to /api/admin/joys/{kind}/items/{item_id} so the
  /add and /repick verbs resolve to their own routes instead of 422-ing as a
  non-int item id (the launch blocker). Frontend mutate URL updated to match.
- Re-pick now excludes the currently-shown item: the endpoint reads today's
  daily pool_id and passes it as `avoid`, so "Re-pick today" yields a different
  item. Added `avoid` to pick_daily/_candidates across wotd/quote/onthisday.
- WOTD sense selection: the LLM now proposes word + intended part of speech, and
  _lookup prefers that sense (fixes "serene" returning the archaic noun).
- On This Day tone prompt tightened to favor genuinely uplifting events and
  exclude merely procedural/political-administrative ones.
- Caddy @hidden now also noindexes /word /quote /onthisday /admin (+ .html).
- Regression tests: add/repick resolve (401 not 422), add/feature/block/delete,
  re-pick excludes current; WOTD pos-preference + proposal parsing units.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-22 20:19:02 -04:00
parent 3bde6534e9
commit 84b1fb514f
9 changed files with 217 additions and 51 deletions
+51 -22
View File
@@ -44,23 +44,33 @@ def _http_bytes(url: str, timeout: int = 30) -> tuple[bytes, str]:
return r.read(), (r.headers.get("Content-Type") or "")
def _propose_words(client, n: int) -> list[str]:
def _propose_words(client, n: int) -> list[dict]:
"""Ask for word + the intended part of speech, so _lookup picks the sense the LLM meant
(e.g. 'serene' the adjective, not the archaic noun)."""
user = (
f"Suggest {n} English vocabulary words for an uplifting 'word of the day' — positive, "
"calming, hopeful, or quietly beautiful in meaning (e.g. serene, kindness, dawn, "
"resilience, wonder). Real, usable words; vary common and slightly elevated. "
'Reply with JSON only: {"words": ["...", "..."]}'
"resilience, wonder). Real, usable words; vary common and slightly elevated. For each, "
"give the part of speech you intend (the everyday modern sense, not an archaic one). "
'Reply with JSON only: {"words": [{"word": "serene", "pos": "adjective"}, ...]}'
)
txt = client.chat_text([{"role": "user", "content": user}])
m = re.search(r"\{.*\}", txt, re.S)
if not m:
return []
words = json.loads(m.group(0)).get("words", [])
return [str(w).strip().lower() for w in words if isinstance(w, str) and w.strip()]
out = []
for w in json.loads(m.group(0)).get("words", []):
if isinstance(w, str) and w.strip():
out.append({"word": w.strip().lower(), "pos": None})
elif isinstance(w, dict) and str(w.get("word", "")).strip():
out.append({"word": str(w["word"]).strip().lower(),
"pos": (str(w.get("pos")).strip().lower() or None) if w.get("pos") else None})
return out
def _lookup(word: str) -> dict | None:
"""Validate + enrich a word via the dictionary. Returns None if it's not a real word."""
def _lookup(word: str, prefer_pos: str | None = None) -> dict | None:
"""Validate + enrich a word via the dictionary. Returns None if it's not a real word.
When prefer_pos is given, picks the meaning of that part of speech (the sense the LLM meant)."""
try:
data = daily.http_json(f"{DICT_BASE}/{urllib.parse.quote(word)}")
except Exception: # noqa: BLE001 — unknown word / network → just skip it
@@ -71,9 +81,22 @@ def _lookup(word: str) -> dict | None:
meanings = entry.get("meanings") or []
if not meanings or not (meanings[0].get("definitions") or []):
return None
definition = (meanings[0]["definitions"][0].get("definition") or "").strip()
if not definition:
# Prefer the meaning whose part of speech matches the LLM's intent; else the first usable one.
chosen = None
if prefer_pos:
for mn in meanings:
if (mn.get("partOfSpeech") or "").strip().lower() == prefer_pos and (mn.get("definitions") or []):
if (mn["definitions"][0].get("definition") or "").strip():
chosen = mn
break
if chosen is None:
for mn in meanings:
if (mn.get("definitions") or []) and (mn["definitions"][0].get("definition") or "").strip():
chosen = mn
break
if chosen is None:
return None
definition = (chosen["definitions"][0].get("definition") or "").strip()
phonetic = entry.get("phonetic")
audio_url = None
for p in (entry.get("phonetics") or []):
@@ -82,13 +105,13 @@ def _lookup(word: str) -> dict | None:
if not audio_url and p.get("audio"):
audio_url = p["audio"]
examples = []
for m in meanings:
for m in [chosen] + [mn for mn in meanings if mn is not chosen]: # chosen sense's examples first
for d in (m.get("definitions") or []):
if d.get("example"):
examples.append(d["example"].strip())
return {
"word": (entry.get("word") or word).strip().lower(),
"part_of_speech": meanings[0].get("partOfSpeech"),
"part_of_speech": chosen.get("partOfSpeech"),
"phonetic": phonetic,
"audio_url": audio_url,
"definition": definition,
@@ -136,10 +159,11 @@ def harvest(conn: sqlite3.Connection, client, count: int = _HARVEST_BATCH) -> di
except Exception: # noqa: BLE001
return {"proposed": 0, "added": 0, "pool": _pool_count(conn)}
rows = []
for w in words:
for item in words:
w = item["word"]
if not w.isalpha() or conn.execute("SELECT 1 FROM wotd_pool WHERE word=?", (w,)).fetchone():
continue
info = _lookup(w)
info = _lookup(w, item.get("pos"))
if not info:
continue
audio_file = _cache_audio(info["audio_url"], info["word"])
@@ -155,23 +179,28 @@ def harvest(conn: sqlite3.Connection, client, count: int = _HARVEST_BATCH) -> di
return {"proposed": len(words), "added": after - before, "pool": after}
def _candidates(conn: sqlite3.Connection) -> list[int]:
def _candidates(conn: sqlite3.Connection, avoid: int | None = None) -> list[int]:
featured = conn.execute("SELECT id FROM wotd_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 wotd_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]
ids = [r[0] for r in featured]
else:
rows = conn.execute(
"SELECT id FROM wotd_pool WHERE blocked=0 ORDER BY shown_at IS NOT NULL, shown_at, id LIMIT ?",
(_NO_REPEAT_POOL,),
).fetchall()
ids = [r[0] for r in 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, force: bool = False) -> dict | None:
def pick_daily(conn: sqlite3.Connection, feature_date: str | None = None, force: bool = False,
avoid: int | None = None) -> dict | None:
feature_date = feature_date or local_today()
existing = conn.execute("SELECT * FROM daily_wotd WHERE feature_date=?", (feature_date,)).fetchone()
if existing and not force:
return dict(existing)
ids = _candidates(conn)
ids = _candidates(conn, avoid)
if not ids:
return None
pick_id = daily.seeded_order(ids, feature_date)[0]