feat(public): published-only public standings page (checklist B) #8
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/public-standings"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Implements checklist item B (B1-B6): a no-login, read-only public standings page.
Decisions (owner, 2026-10-25)
What changed
feat(db):published_at/published_by_idonmonthly_contestandcontest_year, migration0002_publication_markers.feat(public):app/web/routes_public.py+templates/public/—/results,/results/{year}/{month},/results/{year},/results/goto. Reuses the public-safe document builders, so no private field is even present in the template context (R25).feat(manager): audited publish/unpublish for a month and a year, buttons on the dashboard, all throughrecord_auditin the same transaction.test(public): 27 new tests (gate, audit trail, default month, contest-day notice, dropdowns, upcoming table, privacy leak, i18n parity).docs: checklist B1-B6 ticked, decisions recorded, future-work/design/HANDOVER updated (next item = C).Verification
alembic upgrade head->0002_publication_markers, real uvicorn): entrant uploads of corpus01,02,12,13,14,08,09through preview+submit, manager publish, then public pages. Published ranking shows all seven callsigns and the corpus's Section-A score 36; no private value on any public route; unpublish returns 404 and restores the "not published yet" notice; upcoming table rendered 14 dates (Nov/Dec 2026 + 2027).parsing/,scoring/,rules/,reference/still import nothing from i18n.Open questions from the old checklist for B are answered by the decisions above; nothing in this PR depends on the Dutch Locator Reference.
New no-auth surface (checklist B3, B5): - GET /results — month-scoped landing page. Default month is this month once its contest date has been reached, else the previous month; when that month has no published ranking the page says so explicitly and names the contest day when today is it (never falls back silently). - GET /results/{year}/{month} — ranking for a published month, else 404. - GET /results/{year} — Yearly Standing for a published year, else 404. - GET /results/goto — dropdown navigation; unpublished targets bounce back. Year and month dropdowns list everything, marking unpublished options and disabling them. The upcoming-dates table shows the rest of the year with at least six rows (rolling into the next year when it runs out) and adds next year from Q3. Tables are built by the existing public-safe document builders, so only callsign/Section/afdeling/score/multipliers are ever in the context (R25). New msgids added to both catalogs; jargon untouched.Review round 1 — addressed in
f11502eAn independent code review plus an independent functional tester both ran against the live stack. Findings and what happened to them:
Fixed (real defects)
/results. The year is now its own form (GET /results?year=YYYY) and the month form carries the selected year as a hidden field. An older year opens its most recently published month.reason=action) — now no reason unless the manager gives one; action names follow the codebase'snoun.verbconvention (monthly_results.publish,yearly_standing.unpublish).GET /results(per-month lookups) — contests load once now; regression test asserted at ≤5.NoneMonthly Score renders as a dash; an empty month offers no publish button; the yearly button says "year standing";nl.posays Contest Manager consistently.design.md§4 documents the new columns; HANDOVER's main commit and test count corrected.Verification after the fixes
monthly_results.publish/yearly_standing.publishwithreason=None.Still open as an owner decision: whether the yearly standing should instead be computed over published months only (which would make the standings differ from the manager's authoritative figures) — the current behaviour refuses publication until nothing is withheld.