Skip to content

Per-tournament scoring config: rank_by (peak_rating | current_rating) - #304

Merged
amrtgaber merged 1 commit into
mainfrom
rank-by-config
Jul 25, 2026
Merged

Per-tournament scoring config: rank_by (peak_rating | current_rating)#304
amrtgaber merged 1 commit into
mainfrom
rank-by-config

Conversation

@amrtgaber

Copy link
Copy Markdown
Contributor

Summary

Implements #290: the ranking metric becomes per-tournament configuration instead of platform behavior. rank_by = peak_rating (server_default — every existing row and the launch event's behavior, unchanged) or current_rating.

Model + contract (all additive; consumers regenerate orval clients):

  • Tournament.rank_by — non-native Enum stored as the StrEnum value, mirroring MatchState. Expand-only migration 7f07220ac5cb (NOT NULL + server_default → safe under revision rollover; validated on a throwaway Postgres 16 together with its parent).
  • rank_by on TournamentRead/Create/Update (explicit null → 422, like leaderboard_id).
  • TournamentSummary.highest_current_rating — the rating card for current-mode tournaments; exactly one of the two rating cards is ever populated, highest_peak_rating stays the peak-mode card, pre-existing consumers see null in the new field.

Read paths threaded (app/routers/tournaments.py):

  • /standings: SQL ORDER BY ranks on the configured metric (other rating + name as tie-breaks); post-window re-rank and the surfaced value of the ranked metric freeze, the other rating stays live.
  • /teams/standings: member order, combined_rating_sum/combined_rating_average, and team order all follow the metric; field names deliberately unchanged (same documented-semantics approach as Team standings: rank teams by combined PEAK rating, not current; add per-member max_rating #158).
  • /summary: the rating card ranks by the configured metric via the generalized _rank_metric_by_profile (keeps the Mirror FE stat cards on /summary; resolve displayName into name #244 correction in peak mode).
  • /standings/history: peak mode only — 501 for current-mode tournaments. The sweep's carried-in baselines, ratchets, and live clamp are peak-specific machinery; an honest refusal beats a chart whose order contradicts the live table. Tracked as a follow-up issue; /progression covers rating-over-time meanwhile.

Freeze semantics for current mode (the issue's open design point, resolved): new _frozen_current_by_profile behind a _frozen_metric_by_profile dispatcher. Current is a last-value metric, not a ratchet, so the fold differs from peak: (1) the new_rating of the last game started by the bound — authoritative, includes the buzzer-beater that settles after the bound, durable in our matches table for any in-window game; (2) fallback, the last recorded snapshot at-or-before the bound; (3) live current. No clamp — clamping exists for rebased-away peak noise only.

Recorder widened (insert_rating_snapshots): diffs the (max_rating, current_rating) pair instead of max alone, so current-mode freezes have observations to fall back on. Volume stays bounded by play (≤ one row per rated game per tracked pair); self-seeding and rebase-down behavior preserved.

CLAUDE.md's three convention bullets (sort order, team aggregates, freeze) updated to the config-aware wording.

Non-goals (per #290)

No points/W-L/bracket modes — this is the config seam plus the two modes with known semantics; the platform stays a ladder-standings API.

Tests

581 passed. New coverage: rank_by round-trip/default/invalid on create + PATCH (incl. explicit-null 422); current-mode standings order with peak surfaced untouched; frozen-current via match log (beats a stale snapshot, post-window play ignored), snapshot fallback, live fallback; team sums/order on current; summary card routing both modes; history 501; recorder current-only-change append.

Merge order: after #303 (stacked — shares the migration lineage and tournament-write test context; #299's one-line CLAUDE.md fix also lands first and is a trivial rebase here).

Closes #290

@amrtgaber
amrtgaber merged commit ee8dc2e into main Jul 25, 2026
1 of 2 checks passed
@amrtgaber
amrtgaber deleted the rank-by-config branch July 25, 2026 21:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Per-tournament scoring config: rank_by (peak_rating | current_rating)

1 participant