Repository navigation
Conversation
…, batched member lookup - judging_round_locks collection read uncached on every judge write; POST /score and /draft answer 409 round_locked once an admin locks the round (fail-open on read errors). GET /api/judge/locks/<event>, POST /api/judge/admin/locks/<event>/<round>. - GET /api/judge/admin/progress/<event>/<round>: judge x team submitted/draft/ missing matrix + summary + lock, computed server-side in 3 queries. - POST /api/judge/admin/assignments/bulk: idempotent cross-product assignment creation with optional replace (prunes pairs outside the panel's set). - get_judge_teams embeds event summary + locks and resolves members with ONE db.get_all instead of a serial get_user_by_id per member per team. - get_teams_batch chunks the __name__ in query by 30 and dedupes ids. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ignment check, strict bulk input, raise on failed reads, public lock view
- /score and /draft now require an assignment for THIS event + round
(is_judge_assigned_for_round); the any-round check let a Round-1-only judge
post a Round 2 score that counted in results.
- bulk-assign: replace must be a JSON bool, empty judge/team lists are rejected
(empty_cross_product — replace would otherwise wipe the panel), ids must be
non-empty strings, room/demo_times typed; replace collapses duplicate docs.
- _rows() raises on a fetcher's {error} result instead of treating a failed
Firestore read as no rows (bulk would re-insert everything; progress would
show an empty board mid-round).
- GET /locks and the judge teams payload use public_lock_view (no locked_by /
locked_by_name — PropelAuth ids are PII); admins keep the full lock.
- Dead except-TypeError branch removed; _locks_db renamed _db.
- Tests: batched member lookup (one get_all, missing docs dropped, failure →
per-user fallback), get_judge_teams shape (event + public locks + round2
slot preference), failed-read paths, bulk validation, duplicate collapse,
round-scoped 403 at the route.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ress feat(judging): round locks, judge progress endpoint, bulk assignments — backend for the Fall 2026 judging overhaul
| if is_round_locked(data['event_id'], data['round']): | ||
| info(logger, "Score rejected: round locked", judge_id=user_id, | ||
| team_id=data['team_id'], event_id=data['event_id'], round_name=data['round']) | ||
| return _round_locked_response(data['event_id'], data['round']) |
| return {"error": "Forbidden: Judge not assigned to this team for this round"}, 403 | ||
|
|
||
| if is_round_locked(data['event_id'], data['round']): | ||
| return _round_locked_response(data['event_id'], data['round']) |
| """Lock state for both rounds. Any signed-in user (judges read it to | ||
| render a read-only scoring page); it carries no scores.""" | ||
| info(logger, "API called: GET /locks/{event_id}", event_id=event_id) | ||
| return {"locks": get_round_locks(event_id, public=True)} |
| result = set_round_lock(event_id, round_name, locked, | ||
| actor_id=user_id, actor_name=_actor_display_name(user_id)) | ||
| if not result.get("success"): | ||
| return result, 400 |
| actor_id=user_id, actor_name=_actor_display_name(user_id)) | ||
| if not result.get("success"): | ||
| return result, 400 | ||
| return result |
| return {"error": "invalid_round"}, 400 | ||
| result = get_judging_progress(event_id, round_name) | ||
| if "error" in result: | ||
| return result, 500 |
| result = get_judging_progress(event_id, round_name) | ||
| if "error" in result: | ||
| return result, 500 | ||
| return result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Everything on
developsince the Oct 16 release (#306): PR #307, 3 commits. Backend half of the Fall 2026 judging overhaul — merge and deploy this before the frontend release (frontend-ohack.devdevelop → main). Fly auto-deploysmain. No new env vars. One new Firestore collection,judging_round_locks, created on first use — no migration.Release notes
Judging (#307)
POST /api/judge/scoreandPOST /api/judge/draftanswer 409round_locked; the lock is read uncached on every write, so it takes effect immediately. New routes:GET /api/judge/locks/<event>(public shape, no admin identity) andPOST /api/judge/admin/locks/<event>/<round>(judge.admin, Slack-audited).GET /api/judge/admin/progress/<event>/<round>returns who has submitted, who is mid-draft and who hasn't started, per judge and per team, in one call.POST /api/judge/admin/assignments/bulkcreates every judge × team pair for a panel in one request (idempotent;replaceprunes pairs no longer on the panel; empty or mistyped input is rejected so a client bug can't wipe a panel).get_teams_batchchunks Firestoreinqueries by 30.Prod checklist (after Fly deploys
main)GET https://api.ohack.dev/api/judge/locks/fall-2026with a judge token → 200{"locks": {"round1": {"locked": false, …}, "round2": …}}.POST /api/judge/admin/locks/fall-2026/round2 {"locked": true}→ 200; a judge's round 2 score POST → 409round_locked; unlock → 200 again.GET /api/judge/admin/progress/fall-2026/round1(admin) → judge rows withassigned/submitted/missing_team_ids.🤖 Generated with Claude Code