Retour aux projets
PubliéPersonnel

Spike Stats

Un tracker de stats volleyball pour les équipes d'Adelaide University en SAVL — sans base de données, sans auth, juste de la data.

Publié mai 20265 min de lecture

🎯 Contexte & Objectif

Je joue pour l'équipe Adelaide University A en Division 5 de la SAVL (South Australian Volleyball League). Le site de VolleyballSA affiche les classements et les résultats, mais ne permet pas de suivre l'évolution d'une équipe sur la saison — pas de tendances, pas de pourcentages de sets, pas de détail par match.

J'ai construit Spike Stats en une seule journée pour combler ce manque : un tableau de bord sombre et épuré couvrant les 5 divisions masculines, avec les équipes d'Adelaide Uni mises en valeur partout. L'objectif était de transformer des données brutes scrapées en quelque chose de réellement utile — évolution du classement, statistiques par équipe, et accès aux scorecards de chaque match.


Aperçu du tableau de bord Spike Stats, montrant l'évolution du classement et les statistiques par équipe.

🛠️ Ma Contribution & Mon Rôle

Projet solo, de bout en bout. J'ai pris en charge le scraper, le pipeline de données, la couche de stockage GitHub, la configuration du cron, et l'intégralité du frontend React, y compris les graphiques Nivo.

💻 Stack Technique

  • Frontend : React + Vite, Tailwind CSS — rapide à construire, mobile-first, thème sombre natif
  • Graphiques : Nivo@nivo/bump pour la courbe d'évolution du classement, @nivo/bar et @nivo/radar pour les statistiques par équipe
  • Scraping : Node.js + Cheerio — le site VolleyballSA est entièrement rendu côté serveur, Cheerio suffit ; aucune automatisation de navigateur nécessaire
  • Stockage : fichiers JSON commités directement dans ce dépôt GitHub via l'API Contents — pas de base de données
  • Hébergement & Cron : Vercel — fonction serverless déclenchée par un cron chaque samedi à 22h00 ACST

⚙️ Architecture & Défis Techniques

Architecture zéro-base de données, zéro-auth

La contrainte principale que je me suis fixée : pas de base de données, pas d'authentification, pas de serveur en cours d'exécution. Les données sont scrapées une fois par semaine, sérialisées en trois fichiers JSON (standings.json, fixtures.json, results.json) et commitées directement dans le dépôt GitHub via l'API Contents. L'application React lit depuis raw.githubusercontent.com en production — complètement statique, coût d'infrastructure nul.

En développement, Vite sert les mêmes fichiers depuis le dossier data/ sur disque, et un script seed-local.js permet de lancer le scraper localement sans toucher à GitHub.

Scraping avec Cheerio

Le site VolleyballSA est rendu côté serveur — pas besoin de navigateur headless. Je parse les classements, les matchs à venir, les résultats et les scorecards individuels avec Cheerio. La partie la plus délicate était la structure des tableaux de fixtures/résultats : les rounds précédents sont masqués avec style="display:none" côté client, mais Cheerio les voit parfaitement. J'inclus toutes les lignes indépendamment de leur visibilité.

Les scorecards nécessitaient un filtrage supplémentaire : les sets 4 et 5 affichent 0-0 quand ils ne sont pas joués (une victoire 3–0 génère quand même 5 lignes de sets dans le HTML), donc je supprime tout set où les deux scores sont à zéro.

L'API GitHub Contents comme couche de commit

Chaque PUT vers l'API GitHub nécessite le SHA actuel du fichier — sinon l'écriture est rejetée. Je le récupère avant chaque mise à jour, ce qui rend le scraper sûr à exécuter plusieurs fois sans conflit. Les scorecards ne sont jamais écrasés : je vérifie d'abord l'existence du fichier et je saute les existants, donc le cron n'écrit que les données genuinement nouvelles chaque semaine.

Source unique de vérité

Toute la configuration des divisions — IDs de grade, URLs, noms d'équipes, couleurs — réside dans un unique src/constants.js. L'application React et le scraper Node.js importent depuis ce même fichier. Si une URL change côté VolleyballSA, une seule ligne corrige tout.

// src/constants.js — une seule source de vérité
export const DIVISIONS = [
  {
    id: 'D5M',
    label: 'Division 5 Men',
    gradeId: 2168,
    adelaideUniTeams: ['Adelaide Uni A', 'Adelaide Uni B'],
    // URLs de classements, matchs et résultats dérivées du gradeId
  },
  // ...4 autres divisions
];

Graphique d'évolution du classement

La fonctionnalité visuellement la plus marquante est le LadderBumpChart : un graphique Nivo bump qui trace la position au classement de chaque équipe semaine après semaine sur l'ensemble de la saison. Il montre immédiatement si une équipe monte, stagne ou chute — quelque chose que le tableau de classement brut ne peut pas transmettre d'un coup d'œil.

🚀 Résultats & Impact

  • 5 divisions couvertes, toutes les équipes d'Adelaide Uni mises en valeur dans les classements, matchs et résultats
  • Mises à jour hebdomadaires automatiques via le cron Vercel — zéro intervention manuelle après le déploiement
  • Détail des scorecards pour chaque match joué, avec le score set par set
  • Graphique d'évolution du classement sur l'intégralité de la saison
  • Construit et déployé en une journée — du premier commit à l'URL Vercel live

💡 Ce que j'ai appris

La décision architecturale principale — traiter GitHub comme une base de données — s'est révélée étonnamment solide. Les données sont versionnées par défaut, l'application n'a aucun coût d'hébergement au-delà du tier gratuit de Vercel, et il n'y a pas d'infrastructure à maintenir. Pour un dataset aussi petit, mis à jour une fois par semaine et principalement en lecture, c'est genuinement le bon outil pour le travail.

La seule contrainte qu'elle introduit est la latence d'écriture : l'API GitHub Contents est séquentielle (un fichier à la fois), donc scraper les 5 divisions prend quelques secondes par exécution. Pour un cron qui tourne une fois par semaine, c'est totalement négligeable — mais ce n'est pas un pattern que j'utiliserais si les données devaient se mettre à jour en temps réel.

Si le projet grandit — plus de divisions, statistiques au niveau joueur, saisons historiques — la prochaine étape naturelle serait de migrer les fichiers JSON vers une vraie base de données, tout en conservant l'architecture du scraper intacte.