Add design for participation stats and podium stamps

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
simandClaude Sonnet 5.5 committed 2026-11-05 23:28:21 +00:00
1 parent a2e4321ac7
commit 0e94b1dba2
1 file changed
+47
@@ -0,0 +1,47 @@
# Participation fun: live stats and podium stamps (short design)
## Intent
Make the participant order screen (`ParticipantView.tsx`) feel rewarding: an anonymous, live "number go up" panel and a personal stamp for the first three people to submit.
## Goals
- Live horizontal bar chart per category: how often each item was picked, plus a participant count.
- A personal stamp (🥇🥈🥉) shown only to the participant themself, in their "Your order" card, if they are 1st, 2nd or 3rd to submit.
- Live updates only while the order is open; stop when it closes, resume if reopened.
## Non-goals / anti-goals
- No usernames, no emails, no way to tell who picked what or to reconstruct a full order.
- No medals for most-picked items or any other item-based reward.
- No websockets/SSE, no new infrastructure.
- The order creator never receives a medal (their submission is skipped in the ranking).
## Constraints
- Frontend already polls every 5s (`ParticipantView.tsx`); follow that pattern.
- Admin status changes (accepted/paid/handed_out) bump `submissions.updated_at`, so it cannot be the ranking key.
## Approaches considered
- **Rank key:** new `ranked_at` column (chosen) vs reusing `updated_at` (rejected: admin clicks would reshuffle medals).
- **Delivery:** new `GET /api/orders/{id}/stats` polled only while open (chosen); extending `GET /api/orders/{id}` (rejected: no user identity, and its poll must keep running after close); SSE/websockets (rejected: new infra for a counter).
## Design
**DB** (`db.py`): add `submissions.ranked_at TEXT` via the existing `PRAGMA table_info` + `ALTER TABLE` pattern; backfill from `created_at`.
**Backend**
- `create_submission` and `update_submission` (the participant's own PUT) set `ranked_at = now`. `admin_update_submission_status` does not touch it. Deleting a submission removes it from the ranking naturally.
- `GET /api/orders/{order_id}/stats`, public like `get_order`, optional `X-User-Id` header. Returns:
`{ participant_count, categories: [{id, label, items: [{name, count}]}], my_rank: 1|2|3|null }`
- Counts come from every submission's `choices_json`, including the creator's.
- `my_rank` is the caller's position among non-creator submissions ordered by `ranked_at`, only if 1–3. The creator is found via `get_order_creator_info` and their submission via `user_order_tokens`. Null for the creator, for non-submitters, and for rank 4 and above.
- The stats logic goes in a new function in `stats_service.py`, with a small test in `backend/tests/` (ranking: creator skipped, edit moves you back, admin status change does not, delete promotes others).
**Frontend**
- `statsService` (or `ordersService`) gets a `getOrderStats(orderId)` call.
- `ParticipantView`: a `useEffect` polls it every 5s while `!isClosed` and clears the interval when closed. The effect depends on `isClosed`, so a reopen restarts it. The last result stays on screen, frozen, after close.
- A small stats card (antd horizontal bars per category, participant count) and a stamp inside the "Your order" card driven by `my_rank`.
## Left to the builder
Bar styling, stamp wording and animation, number-change animation, how to lay out many categories or items (e.g. cap long lists).
## My calls (not discussed)
- The count and bars include the creator; only the medal skips them.
- After close the last stats stay visible but frozen.
- Backfill `ranked_at` from `created_at`.