# UtilityLogs — Plan d'amélioration (issu de l'audit de recette)
## Contexte du projet
UtilityLogs est un site d'analyse de logs World of Warcraft (extension actuelle : Midnight, WoW 12.x).
Les données proviennent de l'API GraphQL v2 de Warcraft Logs, récupérées en tâche de fond
(fair-use) puis stockées en base locale. Aucune requête WCL n'est faite au moment de la visite.
**Proposition de valeur** : mettre en évidence les actions *utiles* des joueurs qui font réussir
les donjons et raids — tout ce que les damage meters ne mesurent pas : interruptions, dispels,
cooldowns défensifs externes, battle rez, CC, soaks.
**Règle générale pour toutes les tâches** : ne pas casser le pipeline d'ingestion WCL existant.
Toute modification de la taxonomie doit être rétrocompatible avec les données déjà en base
(prévoir une migration si le schéma change).
---
## TÂCHE 0 — [URGENT — BUG] Gestion d'erreur sur les IDs inexistants
**Problème** : `GET /player/100` (ID inexistant) provoque une erreur 500 qui rend le serveur
indisponible pendant plusieurs minutes.
**À faire** :
- Auditer toutes les routes paramétrées (`/player/{id}`, `/fight/{id}`, `/report/{code}`) :
toute ressource introuvable doit renvoyer un 404 propre avec une page d'erreur utilisateur.
- Identifier pourquoi le serveur reste indisponible plusieurs minutes (crash du process ?
pool de connexions DB épuisé ? requête sans timeout ?) et corriger la cause racine.
- Ajouter un handler d'erreur global : aucune exception non gérée ne doit faire tomber le serveur.
- Ajouter des tests : ID inexistant, ID non numérique, ID négatif, injection dans le paramètre.
**Critères d'acceptation** : `/player/100`, `/fight/0`, `/report/zzz` renvoient un 404 en < 1 s ;
le serveur continue de répondre normalement aux autres requêtes.
---
## TÂCHE 1 — [FOND — CRITIQUE] Refonte de la taxonomie des catégories
**Problème actuel** : incohérences et chevauchements.
- « Rals./Roots » mélange deux mécaniques distinctes (slow = déplacement réduit ; root = immobilisation).
- « Stuns/CCs » est un fourre-tout : « CC » est une méta-catégorie qui englobe stuns, roots,
slows, peurs, polymorphes. Un Polymorph n'a pas de catégorie claire aujourd'hui.
- La page d'accueil dit « Stuns, slows, externes et dispels » alors que la page /utility
sépare différemment : harmoniser les libellés partout.
**Nouvelle taxonomie cible** (chaque sort appartient à UNE seule catégorie) :
1. **Interruptions** (kicks au sens strict : sort dédié, sur le school lock)
2. **Crowd Control** — avec sous-types : Stun, Root, Slow, Incapacitate (Polymorph, Imprison,
Freezing Trap…), Disorient (Blind, Dragon's Breath…), Knockback/Déplacement (Typhoon,
Ring of Peace, Death Grip)
3. **Dispels** — sous-types : dispel allié (magie/maladie/poison/malédiction), purge ennemi (offensive)
4. **Défensifs externes** — sous-types : réduction de dégâts ciblée (Pain Suppression, Ironbark,
Blessing of Sacrifice), réduction de raid (Rallying Cry, Darkness, Anti-Magic Zone),
heal d'urgence/transfert (Spirit Link Totem, Life Cocoon)
5. **Résurrections de combat** (battle rez : Rebirth, Raise Ally, Soulstone, Intercession)
6. **Soutien de ressource** (Innervate, Power Infusion — optionnel, phase 2)
**Cas particuliers à traiter explicitement** :
- **Cécité (Blind, Rogue)** : actuellement classée en « Interruptions ». La déplacer en
CC > Disorient. Elle interrompt une incantation mais n'est pas un kick (cassable par les dégâts,
pas de school lock fiable).
- **Sigil of Silence (DH Vengeance)** : interrupt de zone, à classer en Interruptions.
- **Interrupts de pet** (Spell Lock de la Felhunter du Démoniste, kicks de pets Chasseur) :
doivent être attribués au propriétaire du pet dans les Interruptions. Vérifier que
l'ingestion WCL remonte bien les events des pets avec résolution du owner.
- **Shadow Word: Death (Prêtre)** : ne pas le compter comme interrupt générique ; le Prêtre
n'a pas de kick natif — ajouter une note explicative sur sa fiche/classe.
**À faire techniquement** :
- Externaliser la taxonomie dans un fichier de données (JSON/YAML ou table DB) :
`{ spell_id, classe, spécialisation(s), catégorie, sous_type, actif_depuis_patch, retiré_depuis_patch }`.
Ne PAS coder les sorts en dur dans la logique.
- Écrire une migration qui reclasse les données existantes selon la nouvelle taxonomie.
- Harmoniser tous les libellés UI (accueil, /utility, /fight, tooltips) sur cette taxonomie.
**Critères d'acceptation** : chaque sort n'apparaît que dans une catégorie ; les libellés sont
identiques sur toutes les pages ; Cécité n'est plus dans Interruptions ; un test vérifie
qu'aucun spell_id n'est présent dans deux catégories.
---
## TÂCHE 2 — [FOND — CRITIQUE] Catégories manquantes : battle rez, externals détaillés, soaks
**À faire** :
- Implémenter l'ingestion des **résurrections de combat** (event type `resurrect` dans WCL,
filtré sur les sorts de battle rez) : qui a rez, qui a été rez, à quel timestamp du combat.
- Détailler les **défensifs externes** selon les sous-types de la Tâche 1 : l'ingestion doit
capter le lanceur, la cible, et le timestamp.
- Vérifier la liste des externes : exclure les effets passifs ou auto (Atonement, Beacon of
Light ne sont pas des « externes » actifs).
- **Phase 2 (ne pas bloquer)** : soaks de mécaniques et immunités défensives (Ice Block,
Divine Shield utilisés pour absorber une mécanique). Nécessite une table de correspondance
boss → mécaniques soakables : créer la structure de données, remplir pour 2-3 boss pilotes.
**Couverture par classe à compléter** (sous-représentations relevées en audit) :
- Voleur : Kick, Blind, Gouge, Sap, Smoke Bomb (défensif de groupe majeur en M+)
- Mage : Polymorph, Dragon's Breath, Frost Nova, Ring of Frost
- Chasseur : Freezing Trap, Tar Trap, Concussive Shot, Binding Shot, interrupts de pet
- Demon Hunter : Imprison, Chaos Nova, Sigil of Silence/Misery/Chains
- Chaman et Moine : vérifier que leurs kicks (Wind Shear, Spear Hand Strike) sont bien ingérés
- Démoniste : interrupt de Felhunter attribué au joueur
**Critères d'acceptation** : sur un rapport de test contenant un battle rez, celui-ci apparaît
dans la vue du combat ; chaque classe a au moins ses sorts emblématiques listés dans la
taxonomie ; un script de contrôle liste les spécialisations sans aucun sort par catégorie
(pour détecter les trous de couverture).
---
## TÂCHE 3 — [FOND — IMPORTANT] Pondération : criticité du sort interrompu
**Problème** : un interrupt sur un sort qui wipe le raid vaut autant qu'un interrupt anecdotique.
Le site compte les actions mais ne les qualifie pas.
**À faire** :
- Créer une table `enemy_spell_criticality` : `{ spell_id_ennemi, rencontre/donjon, niveau ∈
{critique, important, mineur}, commentaire }`.
- Remplir manuellement pour les donjons de la saison M+ en cours et le raid actuel
(sources : guides Wowhead/Archon des priorités d'interrupt). Prévoir un niveau par défaut
« non classé ».
- Dans le tableau des interruptions de `/fight/{id}` : badge visuel sur les sorts critiques
(icône « ! » rouge pour critique, orange pour important) + tooltip expliquant pourquoi.
- Préparer (sans l'afficher encore) un « score d'utilité » par joueur : somme pondérée
interruptions (×criticité) + dispels + CC + externals + battle rez. Les coefficients doivent
être dans un fichier de configuration, pas en dur.
**Critères d'acceptation** : sur un combat de test, les interrupts de sorts critiques sont
visuellement distincts ; la table de criticité est éditable sans redéploiement.
---
## TÂCHE 4 — [FORME — IMPORTANT] Vue par combat : afficher TOUS les utilitaires
**Problème** : `/fight/{id}` ne montre que les interruptions, alors que c'est la page centrale
du site. Les dispels, CC, externals, battle rez n'y figurent pas.
**À faire** :
- Ajouter dans `/fight/{id}` des sections sur le modèle du tableau d'interruptions existant :
« Dispels », « Crowd Control », « Défensifs externes », « Résurrections de combat »
(avec lanceur, sort, cible, icône, compteur ; timestamp pour les battle rez).
- Masquer les sections vides plutôt que d'afficher des tableaux vides.
- **Remplacer le graphique « Activité par joueur »** : supprimer la métrique « Lancers »
(nombre total de sorts lancés — sans valeur analytique). À la place : barres empilées par
joueur montrant interruptions / dispels / CC / externals / battle rez, avec légende et
couleurs distinctes. Quand le score d'utilité (Tâche 3) sera activé, trier par ce score.
- Ajouter un tooltip pédagogique sur chaque titre de section (une phrase : pourquoi cette
métrique compte). Centraliser ces textes pour la future i18n.
**Critères d'acceptation** : un combat contenant dispels + stuns + un battle rez les affiche
tous ; le graphique ne montre plus « Lancers ».
---
## TÂCHE 5 — [FORME — IMPORTANT] Navigation joueurs + fiche joueur + classement corrigé
**Problèmes** :
- Les noms de joueurs dans `/fight/{id}` sont des liens qui ne naviguent nulle part.
- Le bouton « Analyse » de `/player` est inactif (routing client-side SPA non opérationnel).
- Le classement d'accueil trie par total brut d'interruptions toutes sessions confondues :
il favorise le volume de logs uploadés, pas la qualité.
**À faire** :
- Réparer le routing client-side ; chaque nom de joueur (combat, classements) doit mener à
`/player/{id}`.
- Construire la fiche joueur : classe/spé, top sorts utilisés par catégorie, ratio
interruptions par combat ET par minute de combat, nombre de combats analysés, derniers
combats (liens).
- Sur l'accueil : remplacer le tri « total brut » par « interruptions par combat » (ou par
minute) avec un seuil minimal de combats (ex. ≥ 5) pour éviter le bruit statistique.
Garder le total brut en colonne secondaire.
**Critères d'acceptation** : navigation fonctionnelle depuis toutes les pages vers la fiche
joueur ; le classement affiche un ratio et un nombre de combats ; un joueur avec 2 combats
n'apparaît pas en tête du classement.
---
## Ordre d'exécution recommandé
1. Tâche 0 (bug 500) — immédiat, isolé, sans dépendance.
2. Tâche 1 (taxonomie) — fondation de tout le reste, inclut la migration de données.
3. Tâche 2 (catégories manquantes) — dépend de la taxonomie.
4. Tâche 4 (vue par combat) — dépend des données des tâches 1 et 2.
5. Tâche 5 (navigation/fiche joueur) — indépendante, peut être parallélisée.
6. Tâche 3 (pondération) — en dernier : nécessite la table de criticité et bénéficie de tout le reste.
## Consignes transverses
- Tests : chaque tâche livre ses tests (unitaires sur la classification des sorts ; intégration
sur les routes).
- i18n : tous les nouveaux libellés passent par le système de traduction existant (ou, s'il
n'existe pas encore, créer des fichiers de traduction JSON dès maintenant — le site sera
multilingue).
- Performance : les nouvelles sections de `/fight/{id}` doivent lire la base locale uniquement,
jamais l'API WCL en direct.
- Données : si l'ingestion doit être enrichie (battle rez, events de pets), prévoir un script
de ré-ingestion des rapports déjà en base, en respectant le quota de points de l'API WCL
(étaler dans le temps).