<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Maxim Cruz</title>
    <description>The latest articles on DEV Community by Maxim Cruz (@maxim_cruz_d1f45e130b19d5).</description>
    <link>https://dev.to/maxim_cruz_d1f45e130b19d5</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3974471%2F70a92cf2-1384-4dd9-afb0-9012610ad87f.jpg</url>
      <title>DEV Community: Maxim Cruz</title>
      <link>https://dev.to/maxim_cruz_d1f45e130b19d5</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/maxim_cruz_d1f45e130b19d5"/>
    <language>en</language>
    <item>
      <title>Quels clubs de foot dépensent le mieux ?</title>
      <dc:creator>Maxim Cruz</dc:creator>
      <pubDate>Mon, 08 Jun 2026 16:07:44 +0000</pubDate>
      <link>https://dev.to/maxim_cruz_d1f45e130b19d5/quels-clubs-de-foot-depensent-le-mieux--3p6d</link>
      <guid>https://dev.to/maxim_cruz_d1f45e130b19d5/quels-clubs-de-foot-depensent-le-mieux--3p6d</guid>
      <description>&lt;p&gt;Chaque été, les clubs de foot européens battent des records de dépenses. Et pourtant, certains finissent devant des géants en ayant investi dix fois moins. Le classement final ne dit jamais &lt;em&gt;qui a bien dépensé son argent&lt;/em&gt;, il dit juste qui a gagné.&lt;/p&gt;

&lt;p&gt;Alors on s'est posé la question autrement, et on en a fait un projet data complet :&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quels clubs investissent le mieux leur argent ?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Pour y répondre, on a construit un pipeline de bout en bout : ingestion, data lake en 3 couches, orchestration Airflow, calcul métier en Spark, clustering ML et dashboard Kibana. Voici comment, et surtout ce que les données racontent.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Code source :&lt;/strong&gt; &lt;a href="https://github.com/Nassim821/football-roi-pipeline" rel="noopener noreferrer"&gt;https://github.com/Nassim821/football-roi-pipeline&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  La métrique : le ROI sportif
&lt;/h2&gt;

&lt;p&gt;Tout le projet tourne autour d'une métrique qu'on a définie nous-mêmes :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROI = points du championnat / valeur de l'effectif (en M€)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;L'idée est simple : plus un club obtient de points avec un petit effectif, plus il est &lt;strong&gt;efficace&lt;/strong&gt;. À l'inverse, un club avec un effectif énorme mais peu de points &lt;strong&gt;gaspille&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On a appliqué ça à :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;5 championnats majeurs&lt;/strong&gt; : Ligue 1, Premier League, La Liga, Bundesliga, Serie A&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La Champions League&lt;/strong&gt; (avec un ROI adapté basé sur le stade atteint)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 saisons&lt;/strong&gt; : 2022, 2023, 2024&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Et pour aller plus loin que la moyenne, on a ajouté du &lt;strong&gt;clustering K-Means&lt;/strong&gt; afin de découvrir automatiquement des profils de clubs : Efficaces, Dominants, Standards, Gaspilleurs.&lt;/p&gt;




&lt;h2&gt;
  
  
  L'architecture
&lt;/h2&gt;

&lt;p&gt;Le projet suit le pattern &lt;strong&gt;Medallion&lt;/strong&gt;, classique en data engineering : on organise le data lake en trois couches successives (raw → formatted → usage).&lt;/p&gt;

&lt;p&gt;Voici la stack et le rôle de chaque brique :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Étape&lt;/th&gt;
&lt;th&gt;Technologie&lt;/th&gt;
&lt;th&gt;Rôle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Ingestion&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;requests&lt;/code&gt; + &lt;code&gt;cloudscraper&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;API REST + scraping HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stockage&lt;/td&gt;
&lt;td&gt;JSON puis Parquet&lt;/td&gt;
&lt;td&gt;Brut traçable / Parquet typé ~10× plus rapide&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Compute&lt;/td&gt;
&lt;td&gt;PySpark + pandas&lt;/td&gt;
&lt;td&gt;Spark pour les jointures, pandas pour le formatage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Orchestration&lt;/td&gt;
&lt;td&gt;Apache Airflow 3.2.1&lt;/td&gt;
&lt;td&gt;Planification + retries + observabilité&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML&lt;/td&gt;
&lt;td&gt;scikit-learn (K-Means)&lt;/td&gt;
&lt;td&gt;Clustering non supervisé&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Indexation&lt;/td&gt;
&lt;td&gt;Elasticsearch 9.0.0&lt;/td&gt;
&lt;td&gt;Stockage analytique et recherche&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visualisation&lt;/td&gt;
&lt;td&gt;Kibana 9.0.0&lt;/td&gt;
&lt;td&gt;Dashboard interactif&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conteneurisation&lt;/td&gt;
&lt;td&gt;Docker Compose&lt;/td&gt;
&lt;td&gt;Reproductibilité de l'environnement&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Quelques choix qu'on revendique :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Airflow plutôt qu'un cron&lt;/strong&gt; : pour gérer les dépendances entre tâches, relancer automatiquement en cas d'erreur, et visualiser l'avancement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parquet plutôt que CSV/JSON&lt;/strong&gt; : format colonnaire compressé, beaucoup plus rapide à lire et plus léger.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spark pour les JOINs&lt;/strong&gt; : meilleure gestion des volumes, et le code reste lisible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Elasticsearch + Kibana plutôt que Tableau/PowerBI&lt;/strong&gt; : open source, performant pour la recherche, intégré nativement.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Les données : 3 sources combinées
&lt;/h2&gt;

&lt;p&gt;Pour calculer le ROI, il nous fallait deux types d'infos : les &lt;strong&gt;performances&lt;/strong&gt; (points, rang) et les &lt;strong&gt;valeurs marchandes&lt;/strong&gt; (budget de l'effectif).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Donnée&lt;/th&gt;
&lt;th&gt;Volume / run&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API-Football (standings)&lt;/td&gt;
&lt;td&gt;API REST&lt;/td&gt;
&lt;td&gt;Classements des 5 ligues&lt;/td&gt;
&lt;td&gt;15 appels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transfermarkt&lt;/td&gt;
&lt;td&gt;Scraping HTML&lt;/td&gt;
&lt;td&gt;Valeurs marchandes des effectifs&lt;/td&gt;
&lt;td&gt;15 scrapes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API-Football (CL)&lt;/td&gt;
&lt;td&gt;API REST&lt;/td&gt;
&lt;td&gt;Matchs Champions League&lt;/td&gt;
&lt;td&gt;3 appels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Le data lake respecte une convention de nommage stricte — chaque entité a sa place et sa version datée :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data/&amp;lt;layer&amp;gt;/&amp;lt;group&amp;gt;/&amp;lt;dataEntity&amp;gt;/&amp;lt;dateVersion&amp;gt;/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Concrètement :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;data/
&lt;/span&gt;&lt;span class="gp"&gt;├── raw/          #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;JSON brut, immuable
&lt;span class="go"&gt;│   ├── football/standings/2022/
│   ├── transfermarkt/squad_values/2022/
│   └── champions_league/fixtures/2022/
&lt;/span&gt;&lt;span class="gp"&gt;├── formatted/    #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Parquet nettoyé et typé
&lt;span class="go"&gt;│   └── ...
&lt;/span&gt;&lt;span class="gp"&gt;└── usage/        #&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Métriques business
&lt;span class="go"&gt;    ├── roi_sportif/
    ├── cl_roi/
    └── roi_clusters/
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pourquoi 3 couches ? Parce que chacune a un rôle distinct :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raw&lt;/strong&gt; est immuable. Si l'API change ou tombe demain, on garde nos données originales pour tout recalculer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Formatted&lt;/strong&gt; est propre techniquement (bons types, dates UTC, doublons supprimés) mais sans logique métier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage&lt;/strong&gt; porte le cas d'usage spécifique (le calcul du ROI). D'autres cas d'usage pourraient cohabiter à ce niveau.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Au final : &lt;strong&gt;312 lignes business&lt;/strong&gt; indexées dans Elasticsearch (254 pour les championnats, 58 pour la CL, le tout enrichi par le clustering).&lt;/p&gt;




&lt;h2&gt;
  
  
  Le pipeline Airflow
&lt;/h2&gt;

&lt;p&gt;Le DAG &lt;code&gt;football_roi_pipeline&lt;/code&gt; enchaîne &lt;strong&gt;10 tâches&lt;/strong&gt; en 5 étapes, et tourne automatiquement chaque jour à 6h UTC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F083vmqcn05wsn80kvebu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F083vmqcn05wsn80kvebu.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tâche&lt;/th&gt;
&lt;th&gt;Couche&lt;/th&gt;
&lt;th&gt;Rôle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_to_raw_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;source → raw&lt;/td&gt;
&lt;td&gt;Classements via API-Football&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_to_raw_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;source → raw&lt;/td&gt;
&lt;td&gt;Scraping Transfermarkt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source_to_raw_3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;source → raw&lt;/td&gt;
&lt;td&gt;Matchs Champions League&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;raw_to_formatted_1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw → formatted&lt;/td&gt;
&lt;td&gt;JSON football → Parquet typé&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;raw_to_formatted_2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw → formatted&lt;/td&gt;
&lt;td&gt;Nettoyage Transfermarkt (&lt;code&gt;"€45.20m"&lt;/code&gt; → &lt;code&gt;45.2&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;raw_to_formatted_3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;raw → formatted&lt;/td&gt;
&lt;td&gt;Stade max atteint en CL par club&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;produce_usage&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;formatted → usage&lt;/td&gt;
&lt;td&gt;JOIN Spark standings × valeurs, calcul du ROI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;produce_usage_cl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;formatted → usage&lt;/td&gt;
&lt;td&gt;JOIN CL × valeurs, calcul du &lt;code&gt;cl_roi&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ml_clustering&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;usage → usage&lt;/td&gt;
&lt;td&gt;K-Means 4 clusters + labels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;index_to_elastic&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;usage → ES&lt;/td&gt;
&lt;td&gt;Push des 3 datasets vers Elasticsearch&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Un peu de machine learning
&lt;/h2&gt;

&lt;p&gt;Pour le clustering, on a choisi &lt;strong&gt;K-Means&lt;/strong&gt; parce que le problème est &lt;strong&gt;non supervisé&lt;/strong&gt; (on n'a pas de labels au départ, on veut découvrir des groupes naturels), qu'il est &lt;strong&gt;simple à interpréter&lt;/strong&gt; (un cluster = un centroïde), et qu'il est &lt;strong&gt;rapide&lt;/strong&gt; (254 lignes traitées en moins d'une seconde).&lt;/p&gt;

&lt;p&gt;Trois features, normalisées avec &lt;code&gt;StandardScaler&lt;/code&gt; pour éviter que le budget en M€ n'écrase tout :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;squad_value_m&lt;/code&gt; — la valeur de l'effectif&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roi&lt;/code&gt; — le retour sur investissement sportif&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;points&lt;/code&gt; — les points obtenus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On a fixé &lt;strong&gt;k = 4&lt;/strong&gt; pour coller aux 4 quadrants du plan (budget, ROI) :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;petit budget + gros ROI → &lt;strong&gt;Efficaces&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;gros budget + gros ROI → &lt;strong&gt;Dominants&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;gros budget + petit ROI → &lt;strong&gt;Gaspilleurs&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;petit budget + petit ROI → &lt;strong&gt;Standards&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Le résultat :&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cluster&lt;/th&gt;
&lt;th&gt;Budget moyen&lt;/th&gt;
&lt;th&gt;ROI moyen&lt;/th&gt;
&lt;th&gt;Points moyens&lt;/th&gt;
&lt;th&gt;Nb clubs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Efficaces&lt;/td&gt;
&lt;td&gt;85 M€&lt;/td&gt;
&lt;td&gt;0.53&lt;/td&gt;
&lt;td&gt;43&lt;/td&gt;
&lt;td&gt;119&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dominants&lt;/td&gt;
&lt;td&gt;155 M€&lt;/td&gt;
&lt;td&gt;0.27&lt;/td&gt;
&lt;td&gt;40&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standards&lt;/td&gt;
&lt;td&gt;423 M€&lt;/td&gt;
&lt;td&gt;0.15&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gaspilleurs&lt;/td&gt;
&lt;td&gt;980 M€&lt;/td&gt;
&lt;td&gt;0.08&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;104&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Ce que les données racontent
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Le ROI inverse complètement le palmarès.&lt;/strong&gt; En tête : Clermont Foot (ROI ~0.82), Rayo Vallecano, Holstein Kiel, Cadiz. Que des clubs à petit budget. Ils marquent beaucoup de points par rapport à ce qu'ils ont investi — c'est ça, l'efficacité.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La ligue la plus riche est la moins efficiente.&lt;/strong&gt; La Liga et la Ligue 1 mènent (ROI ~0.28-0.29), et la &lt;strong&gt;Premier League ferme la marche avec 0.10&lt;/strong&gt;. L'inflation des salaires anglais ne se traduit pas par une supériorité proportionnelle en points.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;La relation budget → points est non linéaire.&lt;/strong&gt; Plus le budget monte, plus les points montent… mais la courbe sature au-delà de ~600 M€. C'est un argument économique fort : le rendement marginal de chaque euro investi décroît vite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Et c'est là que le ML apporte sa vraie valeur.&lt;/strong&gt; L'algorithme a découvert tout seul qu'il existe des &lt;em&gt;seuils d'efficience&lt;/em&gt; : au-delà d'un certain budget, l'argent ne se transforme quasiment plus en points. Un résultat invisible sur une simple moyenne globale.&lt;/p&gt;




&lt;p&gt;Parce qu'un pipeline qui marche du premier coup, ça n'existe pas :&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Deadlock virtioFS sur macOS.&lt;/strong&gt; &lt;code&gt;OSError: [Errno 35] Resource deadlock avoided&lt;/code&gt; à chaque écriture Parquet dans Docker. Bug connu de virtioFS : le memory-mapping de PyArrow sur un bind mount ne libère jamais les locks. &lt;strong&gt;Fix :&lt;/strong&gt; remplacer le bind mount de &lt;code&gt;data/&lt;/code&gt; par un &lt;strong&gt;volume Docker nommé&lt;/strong&gt;, qui ne passe pas par virtioFS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Conflit de namespace package dans Airflow 3.&lt;/strong&gt; &lt;code&gt;ModuleNotFoundError: No module named 'config.config'&lt;/code&gt;. Le dossier &lt;code&gt;config/&lt;/code&gt; était monté à deux endroits, Python le traitait comme un namespace package PEP 420 mal géré par le task runner. &lt;strong&gt;Fix :&lt;/strong&gt; un simple &lt;code&gt;__init__.py&lt;/code&gt; vide pour forcer un package régulier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Timeout Elasticsearch à la création d'index.&lt;/strong&gt; Par défaut ES crée les indices avec 1 réplique ; sur un cluster single-node, elle ne peut pas être allouée, le cluster passe en YELLOW et la création boucle jusqu'au timeout. &lt;strong&gt;Fix :&lt;/strong&gt; &lt;code&gt;"number_of_replicas": 0&lt;/code&gt; dans les mappings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Labels de clustering qui s'effondrent.&lt;/strong&gt; 4 clusters détectés, mais seulement 2 labels attribués. Notre première logique utilisait des seuils de rangs qui se chevauchaient. &lt;strong&gt;Fix :&lt;/strong&gt; une &lt;strong&gt;assignation 1-pour-1&lt;/strong&gt; basée sur la distance euclidienne aux 4 archétypes dans le plan normalisé — chaque label utilisé exactement une fois.&lt;/p&gt;




&lt;h2&gt;
  
  
  Bilan
&lt;/h2&gt;

&lt;p&gt;Côté technique, le pipeline est complet et reproductible : 3 sources hétérogènes ingérées chaque jour, un data lake en 3 couches, 10 tâches Airflow orchestrées, un calcul métier en PySpark, un enrichissement ML, et une visualisation Kibana — le tout containerisé avec Docker.&lt;/p&gt;

&lt;p&gt;Côté insights, trois choses ressortent :&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Le ROI inverse le palmarès&lt;/strong&gt; : les « petits » clubs sont souvent les plus efficaces.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;La relation budget-points est non linéaire&lt;/strong&gt; : au-delà de ~600 M€, dépenser plus ne rapporte plus grand-chose.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Le ML révèle des archétypes économiques&lt;/strong&gt; : certains clubs sont structurellement condamnés à un ROI faible par leur taille.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Limites connues :&lt;/strong&gt; batch quotidien (pas de temps réel), plan API gratuit limité à 100 requêtes/jour, Elasticsearch en single-node.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pistes futures :&lt;/strong&gt; Kafka pour l'ingestion temps réel, monitoring Prometheus/Grafana, tests &lt;code&gt;pytest&lt;/code&gt;, et extension à d'autres sports (basket, F1) pour tester la généralité de la métrique.&lt;/p&gt;




&lt;p&gt;Le projet est entièrement sur GitHub, avec un guide d'installation et le dashboard Kibana exporté en &lt;code&gt;.ndjson&lt;/code&gt; :&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/Nassim821/football-roi-pipeline" rel="noopener noreferrer"&gt;https://github.com/Nassim821/football-roi-pipeline&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Si vous bossez sur de la data sportive ou des pipelines Airflow, on est preneurs de retours — surtout sur la métrique ROI et la façon dont vous l'auriez définie. &lt;/p&gt;

</description>
      <category>elasticsearch</category>
      <category>spark</category>
      <category>python</category>
      <category>hustle</category>
    </item>
  </channel>
</rss>
