Рейтинг: витрины статистики на проигрывании истории

SQL-агрегат League Points (SCORED_CTE, сглаженное среднее) заменён
проигрыванием завершённых партий по порядку played_at, finished_at, id:
load_history — один запрос на партии с участниками, scoring.replay считает
рейтинг. Цепочки две: общая (все партии) и групповая (только партии группы,
K — по партиям внутри группы). Правка или удаление прошлой партии
пересчитывает всё после неё без отдельной логики.

Топ, профиль, главная и статистика группы берут игры, победы, винрейт
и среднее место из той же истории; score — рейтинг целым числом, сортировка
по неокруглённому. Главная грузит историю один раз. Лучшая партия — наибольший
ΔR в общей цепочке, лучшая/худшая фракция — средний S − E. League Points
из scoring.py удалён.

Тесты: дуэль новичков ±32, ранжирование после 10 партий, пересчёт после
правки прошлой партии, отдельная групповая цепочка, лучшая партия по ΔR,
одна загрузка истории на главной. #23

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LqSoRj99iwVEH5U5fnZgsd
This commit is contained in:
2026-09-14 22:38:57 +03:00
co-authored by Claude Opus 5
parent 8aa6bb7235
commit 6ea4a67672
7 changed files with 380 additions and 291 deletions
+4 -1
View File
@@ -375,7 +375,8 @@ class OverallStats(BaseModel):
wins: int
win_rate: float
avg_place: float | None = None
score: float | None = None
# Рейтинг (Elo, старт 1500) целым числом; None — игрок ещё не сыграл ни одной партии.
score: int | None = None
class LeaderboardEntry(OverallStats):
@@ -416,6 +417,8 @@ class FactionStat(BaseModel):
wins: int
win_rate: float
avg_place: float | None = None
# Средний результат относительно ожидания (S − E) × 100 — метрика лучшей/худшей
# фракции: выше нуля — игрок на ней выступает лучше своих рейтинговых шансов.
score: float | None = None