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:
| Type | Source |
|---|
season_champion | palmarès de saison |
season_runner_up | palmarès de saison |
season_mvp | palmarès de saison |
best_gk | palmarès de saison |
tots | palmarès de saison |
totw | équipe de la semaine |
motm | match |
Catégories de saison
Les catégories de season_award_entries sont:
| Catégorie | Sens |
|---|
CHAMPION | club champion de la saison |
RUNNER_UP | club vice-champion |
SEASON_MVP | MVP/Ballon d’Or de la saison |
BEST_GK | meilleur gardien |
TOTS | onze de saison |
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:
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:
| Codes bruts | Code canonique |
|---|
GK, G, GOALKEEPER | GK |
CB, DEF, DEFENDER | DC |
LB, LWB | DG |
RB, RWB | DD |
CDM | MDC |
CM, MID | MC |
CAM | MOC |
LM / LW | MG / AG |
RM / RW | MD / AD |
ST, CF, ATT, FORWARD | BU |
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.
| Famille | Postes détaillés |
|---|
GK | GK |
DC | DC |
FULLBACK | DG, DD |
MDC | MDC |
MID | MC, MOC |
WIDE | MG, MD, AG, AD |
BU | BU |
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.
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é.
La performance utilise le total de points cumulés dans la famille, pas une
moyenne par match.
Performance = (total points du joueur dans la famille / meilleur total de la famille) x 70
Exemple:
- meilleur total de la famille: 500 points;
- total du joueur: 450 points;
- performance:
450 / 500 x 70 = 63.
Distinctions /10
Distinctions = min(TOTW x 2, 6) + min(MVP match x 2, 4)
Collectif /20
| Classement | Points |
|---|
| 1er | 20 |
| 2e | 18 |
| 3e | 16 |
| 4e | 14 |
| 5e | 12 |
| 6e | 10 |
| 7e | 8 |
| 8e | 6 |
| 9e | 4 |
| 10e à 14e | 2 |
Total TOTS
Score TOTS = performance /70 + distinctions /10 + collectif /20
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.
A = total points du joueur au poste / meilleur total du poste
B = total points du joueur au poste / moyenne des 10 meilleurs totaux du poste
Performance Index = 0,7 x A + 0,3 x B
Performance MVP = Performance Index x 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:
Distinctions = min(TOTW x 2, 6) + min(MVP match x 2, 4)
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
Score MVP = performance index /70 + distinctions /10 + collectif /20
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 June 25, 2026