Skip to main content

Palmarès

Cette page couvre le palmarès CEL au sens large: récompenses de saison, honneurs joueurs, TOTS, MVP/Ballon d’Or, TOTW et MVP de match. Elle documente l’état actuellement implémenté dans le code.

Sources principales

  • convex/schema.ts
  • convex/competition/season_awards.ts
  • convex/lib/season_mvp.ts
  • convex/competition/totw.ts
  • convex/awards/recipients.ts
  • web/src/app/admin/season-awards
  • web/src/app/(app)/palmares/[awardId]/page.tsx
  • web/src/components/club/ClubPalmaresTab.tsx
  • web/src/components/player/PlayerHonoursPanel.tsx

Modèle de publication

Le palmarès de fin de saison est publié depuis l’admin season-awards. La publication crée:
  • une ligne season_awards, qui porte le snapshot global publié;
  • des lignes season_award_entries, qui portent les entrées visibles du palmarès de saison;
  • des lignes award_recipients, qui normalisent les honneurs par joueur.
award_recipients sert de table commune pour les honneurs publics joueur:

Catégories de saison

Les catégories de season_award_entries sont: La page publique /palmares/[awardId] expose les trophées de saison et la TOTS publiée. Les onglets de club et de joueur réutilisent les entrées normalisées pour afficher les honneurs dans les profils.

Sortie publique /palmares/[awardId] (contrat)

La query publique getPublishedSeasonAwards retourne désormais:
  • finalists.mvp: les 5 meilleurs nommés MVP depuis season_awards.mvpNomineesSnapshot;
  • finalists.bestGk: les 5 meilleurs nommés meilleur gardien depuis season_awards.bestGkNomineesSnapshot;
Chaque entrée de finalists contient au minimum:
  • playerProfileId, name, avatarUrl, clubName, clubLogoUrl, position, rank, goals, assists, matchesPlayed, score (les champs peuvent être null si le snapshot source est incomplet).
La section tots.players expose aussi une sous-structure stats limitée aux champs:
  • goals, assists, matchesPlayed, performanceScore, ratingAverage, totwCount, matchMvpCount, teamRank.
Pour les catégories SEASON_MVP et BEST_GK, le snapshot persisté par publishSeasonAwards inclut aussi ratingAverage dans entry.stats; la valeur peut être null si elle n’est pas calculable. Pour BEST_GK, entry.stats inclut aussi:
  • saves,
  • cleanSheets.

Règles de publication

Règles vérifiées dans publishSeasonAwards:
  • publication réservée aux admins;
  • ligue requise en statut COMPLETED ou ARCHIVED;
  • sélection MVP requise;
  • sélection meilleur gardien requise;
  • sélection TOTS optionnelle si aucune formation complète n’est disponible;
  • remplacement d’un palmarès existant possible;
  • notifications et emails activés par défaut.

Réactions B4 sur les palmarès

La query publique getAwardCongratulations est disponible côté social:
  • getAwardCongratulations({ awardId, actorUserId? })
    • Retourne { byEntryId: Record<entryId, { count, byReaction, mine }> } pour toutes les entrées de l’award.
    • count: total des réactions de l’entrée.
    • byReaction: compte par réaction (MERITE, QUELLE_SAISON, FIER, RENDEZVOUS).
    • mine: réaction de l’utilisateur courant ou null.
La même surface expose les mutations:
  • congratulate({ awardId, entryId, reaction, actorUserId? })
    • Ajoute/remplace la réaction d’un utilisateur connecté pour une entrée.
  • uncongratulate({ awardId, entryId, actorUserId? })
    • Supprime la réaction de l’utilisateur connecté si elle existe.
Spécification B4:
  • une seule réaction active par (entryId, utilisateur).
  • pas d’usage de collect().length pour les compteurs; lecture via table dénormalisée award_congratulation_counts.

Statut personnel du palmarès (B5)

La query getMyPalmaresStatus({ awardId, actorUserId? }) retourne, pour l’utilisateur connecté:
  • awardedEntries: les entryId publics du palmarès où l’utilisateur est lauréat;
  • totsSlots: les slots TOTS publics associés à cet utilisateur;
  • isAwarded: true dès qu’au moins une entrée du palmarès lui correspond.
La résolution croise:
  • season_award_entries.recipientUserId, utile pour les entrées joueur et les destinataires directs;
  • award_recipients, utile pour les récompenses de club qui sont normalisées vers les joueurs actifs au moment de la publication.

Requêtes de sélection de saison

La couche publique expose désormais deux queries pour trouver des publications:
  • listPublishedSeasonAwards({ leagueId? }){ awardId, leagueId, leagueName, season, publishedAt }[], triées en publishedAt décroissant.
  • getLatestPublishedSeasonAwards({ leagueId? }) → le même format pour la publication la plus récente, ou null si aucune.
Quand leagueId est fourni, ces queries restreignent à une ligue précise.

Modèle de comparaison par poste

TOTS et MVP partagent le même principe: un joueur n’est jamais comparé à toute la ligue en vrac, mais aux autres joueurs de son poste. La chaîne de résolution du poste se fait en trois temps.

1. Normalisation du poste détaillé

Les libellés bruts sont d’abord ramenés à un code détaillé canonique (normalizePositionDetailedForStats). Exemples d’alias: On obtient ~13 postes détaillés: GK, DG, DC, DD, MDC, MC, MOC, MG, MD, AG, AD, BU.

2. Famille de comparaison

Après normalisation, le poste détaillé est rattaché à une famille de scoring (resolveScorePositionFamily). C’est cette famille qui sert au calcul des barèmes de performance. Conséquence: un DG et un DD partagent le benchmark FULLBACK; un MG, un MD, un AG et un AD partagent le benchmark WIDE. Les barèmes de performance (meilleur total, moyenne des 10 meilleurs) sont calculés par famille de poste, pas par poste détaillé isolé. Le poste détaillé reste conservé pour l’affichage, l’éligibilité aux cases de formation et la sélection du slot TOTS. Il évite par exemple de traiter un MD comme un latéral DD, même si les deux jouent dans un couloir.

3. Agrégation multi-postes d’un même joueur

Un joueur joué à plusieurs libellés qui retombent sur la même famille de comparaison voit ses lignes fusionnées (aggregateTotsPositionStats): matchs dédoublonnés par matchId, buts/passes/points cumulés. Le poste représentatif est celui où il a le plus de matchs.

Formule TOTS (tots-v3)

La TOTS désigne les meilleurs joueurs à chaque poste. La comparaison statistique se fait au sein de la famille de poste du joueur. Pour être candidat à un poste, un joueur doit franchir deux seuils:
  • un seuil de saison: avoir joué au moins min(10, matchs max de la saison) matchs au total;
  • un seuil de poste: avoir joué au moins max(15, plafond(20% de ses matchs de saison)) matchs dans la famille de comparaison considérée.
Le 15 n’est donc qu’un plancher: un joueur qui a disputé 100 matchs doit en avoir joué au moins 20 au poste pour y être candidat. La régularité ne doit pas ajouter de points séparés: elle est portée par ces seuils d’éligibilité.

Performance /70

La performance utilise le total de points cumulés dans la famille, pas une moyenne par match.
Exemple:
  • meilleur total de la famille: 500 points;
  • total du joueur: 450 points;
  • performance: 450 / 500 x 70 = 63.

Distinctions /10

Collectif /20

Total TOTS

Formule MVP / Ballon d’Or (mvp-v2)

Le MVP/Ballon d’Or doit comparer toute la ligue sans favoriser automatiquement les postes offensifs. Il utilise donc un Performance Index basé sur la famille de poste.

Famille retenue et éligibilité (changement v3)

Avant la v3, le MVP notait chaque joueur sur ses totaux globaux de saison (tous postes confondus) et son poste principal. La v3 change cela (selectMvpScoringPosition + toSeasonMvpCandidate):
  • on regroupe les stats du joueur par famille de comparaison (même chaîne que la TOTS), on agrège, puis on ne garde que les postes franchissant le seuil d’éligibilité max(15, plafond(20% des matchs de saison));
  • parmi ces postes éligibles, on retient le meilleur (plus haut total de points, puis plus de matchs);
  • les matchs, buts, passes et points du candidat MVP proviennent de ce seul poste, pas de ses totaux globaux;
  • un joueur qui ne franchit le seuil à aucun poste est exclu du classement.
Ensuite, le Performance Index compare le total du joueur aux barèmes de sa famille de poste (meilleur total + moyenne des 10 meilleurs de cette famille). C’est ce qui neutralise l’avantage des postes offensifs: un défenseur excellent est noté par rapport aux meilleurs défenseurs, pas par rapport aux buteurs. Pour le meilleur gardien, le même mécanisme s’applique en forçant le poste GK.

Performance Index /70

Exemple:
  • meilleur total du poste: 500;
  • moyenne des 10 meilleurs du poste: 476;
  • total du joueur: 500;
  • A = 500 / 500 = 1;
  • B = 500 / 476 = 1,05;
  • Performance Index = 0,7 x 1 + 0,3 x 1,05 = 1,015.
La performance MVP doit rester plafonnée à 70 points pour conserver un total final sur 100.

Distinctions /10

Le MVP utilise le même système de distinctions que la TOTS:
Les bonus Top 5 buteur, passeur ou gardien ne font pas partie de la formule.

Collectif /20

Le MVP utilise le même bonus collectif que la TOTS.

Total MVP

Versioning

  • TOTS: tots-v3;
  • MVP/Ballon d’Or: mvp-v2.
Les versions de formule sont stockées dans les snapshots publiés afin de préserver les palmarès déjà générés.
Last modified on July 18, 2026