Small joys backend: shared daily framework + On This Day engine
- goodnews/daily.py: shared helpers for the daily "small joys" (http_json, date-seeded deterministic pick, dedup key) so each joy is a small self-contained module. - goodnews/onthisday.py: harvest today's MM-DD from Wikimedia's On-this-day feed → tone-filter to good/neutral (keyword floor + optional LLM refine) → pool → deterministic daily pick (idempotent, respects blocked/featured) → cached row. Network/LLM before any DB write. Multi-source ready (source column). - db.py: onthisday_pool + daily_onthisday tables. - api.py: GET /api/onthisday/today (edge-cacheable). - cli.py: cycle step (run after Daily Art; --no-joys to skip), LLM client for tone refine. - tests/test_onthisday.py: 7 tests (filter+dedup, pick idempotent, blocked/featured, never-empty, empty-pool, LLM-narrow). 382 backend tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+15
-1
@@ -36,7 +36,7 @@ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel
|
||||
|
||||
from . import art, auth, bloom, email_send, feeds, games, oauth_google, publishing, queries, share, sources, summarize
|
||||
from . import art, auth, bloom, email_send, feeds, games, oauth_google, onthisday, publishing, queries, share, sources, summarize
|
||||
from .localtime import local_today
|
||||
from .markup import reply_html_to_text, sanitize_reply_html
|
||||
from .db import connect
|
||||
@@ -2290,6 +2290,20 @@ def create_app() -> FastAPI:
|
||||
# Cached museum image: immutable for a given object id.
|
||||
return FileResponse(str(matches[0]), headers={"Cache-Control": "public, max-age=31536000, immutable"})
|
||||
|
||||
@app.get("/api/onthisday/today")
|
||||
def onthisday_today(response: Response) -> dict:
|
||||
with get_conn() as conn:
|
||||
a = onthisday.get_today(conn)
|
||||
if not a:
|
||||
response.headers["Cache-Control"] = _PRIVATE
|
||||
raise HTTPException(status_code=404, detail="No fact yet.")
|
||||
response.headers["Cache-Control"] = _EDGE_FEED # one fact a day, same for everyone
|
||||
return {
|
||||
"date": a["feature_date"], "year": a["year"], "text": a["text"],
|
||||
"summary": a["summary"], "image_url": a["image_url"], "source_url": a["page_url"],
|
||||
"source": a["source"],
|
||||
}
|
||||
|
||||
@app.get("/api/replacement", response_model=Article | None)
|
||||
def replacement(
|
||||
exclude: str = Query("", description="comma-separated article ids already shown"),
|
||||
|
||||
Reference in New Issue
Block a user