Skip to content

feat(api): champion scaling endpoint (win rate by game duration)#544

Merged
ilyanfraimbault merged 2 commits into
developfrom
feat/537-champion-scaling-index
Jun 18, 2026
Merged

feat(api): champion scaling endpoint (win rate by game duration)#544
ilyanfraimbault merged 2 commits into
developfrom
feat/537-champion-scaling-index

Conversation

@ilyanfraimbault

Copy link
Copy Markdown
Owner

What

The cheapest item of the timeline-analytics epic (#539): a champion scaling stat — and it needs no timeline at all.

GET /champions/{championId}/scaling?position=&patch= returns win rate bucketed by game duration (<20, 20-25, 25-30, 30-35, 35m+) plus a single scaling index = long-game win rate minus short-game win rate (positive = the champion scales into the late game).

How

  • ChampionScalingQueryService: filters this champion at this position (tracked accounts, queue, optional patch), joins matches for GameDurationSeconds, buckets via a CASE in GROUP BY, counts games + wins per bucket, drops buckets below the sample floor — one SQL round-trip. Index computed in-memory from the qualifying buckets.
  • Read-model + endpoint follow the existing champion conventions; cached 60s.
  • Uses only gameDuration + Win, both already in base — no new ingestion or table.

Testing

  • dotnet build --configuration Release clean (0 warnings).
  • New ChampionScalingApiIntegrationTests (3 tests) pass on real Postgres: deterministic per-bucket win rate + scaling index, thin-bucket drop + null index, invalid-position 400.

Remaining for #537

  • Web UI: champion-page chart (win rate by duration bucket) consuming this endpoint.

Part of #537

GET /champions/{id}/scaling?position=&patch= returns win rate bucketed by game
duration (<20/20-25/25-30/30-35/35m+) plus a scaling index (long-game win rate
minus short-game; positive = scales late). Computed live from match participants
via a CASE bucket in GROUP BY — no timeline or aggregation table. Same queue/
patch/tracked-account population and sample floor as the sibling champion reads,
cached 60s.

Part of #537.
Comment thread backend/Api/Controllers/Champions/ChampionsController.cs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — feat(api): champion scaling endpoint

Ce que fait ce PR

Nouvel endpoint GET /champions/{id}/scaling?position=&patch= : win rate bucketed par durée de partie (5 tranches) + scaling index (WR long - WR court). Un seul round-trip SQL via GROUP BY conditionnel, cache 60 s, zéro nouvelle table.

Points vérifiés

  • Logique du scaling index : buckets[^1].WinRate - buckets[0].WinRate sur la liste triée par bucket → toujours « plus long qualifié - plus court qualifié ». Correct.
  • Filtre thin-bucket en SQL (.Where(x => x.Games >= minGames)) : correct, évite une collection côté client.
  • Traduction EF Core : le CASE conditionnel dans le GroupBy est supporté par le provider Npgsql et les tests passent sur vrai Postgres. OK.
  • Sécurité : championId est un int path-param, position et patch sont validés avant usage, le LIKE est paramétré par EF Core. Pas d'injection.
  • Population cohérente avec les siblings (tracked accounts, queue, patch optionnel).
  • Tests : 3 tests d'intégration sur vrai Postgres couvrant le chemin nominal, le drop de thin-bucket + null index, et le 400 sur position invalide.

Suggestions (non bloquantes)

  • [FromQuery] string? position devrait être [FromQuery, Required] string position pour que le schéma OpenAPI reflète le caractère obligatoire du paramètre (commentaire inline).
  • Deux cas de test à envisager : filtre patch (vérifier que EF.Functions.Like exclut bien les autres patchs) et jeu de données vide → Buckets = [], ScalingIndex = null (commentaire inline).

Aucun point bloquant identifié. ✅

Comment thread backend/Api/Services/Champions/ChampionScalingQueryService.cs
Comment thread backend/Api/ReadModels/Champions/ChampionScalingResponse.cs

@claude claude Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bon ajout, l'implémentation est propre et les tests couvrent bien les cas importants (buckets, floor, patch filter, réponse vide, 400).

Aucun point bloquant.

Suggestions (non bloquantes) :

  1. Double normalisation du patch (ChampionScalingQueryService.cs l.35-37) — ChampionQueryParameterNormalizer.NormalizePatch étant identique au re-parse interne (PatchVersion.TryParse + ToMajorMinor), le contrôleur passe déjà soit null soit une chaîne validée. La ligne dans le service peut être remplacée par var normalizedPatch = patch;. Détail inline.

  2. Bucket entier dans le read-model (ChampionScalingResponse.cs l.23) — exposer l'index 0-4 dans l'API publique en plus du Label crée un couplage fragile ; si les tranches bougent, les clients qui s'appuient sur la valeur numérique cassent silencieusement. Détail inline.

@ilyanfraimbault ilyanfraimbault merged commit 38f432b into develop Jun 18, 2026
9 checks passed
@ilyanfraimbault ilyanfraimbault deleted the feat/537-champion-scaling-index branch June 18, 2026 08:41
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.

1 participant