0081 — Modèle de prévision du volume d'articles par université (mediawatch)
Contexte
Section intitulée « Contexte »mediawatch (ADR 0064) produit le mart
marts_university_timeline : une série temporelle journalière (university_id, event_date, n_articles) du volume d’articles de presse (GKG/GDELT) mentionnant chaque université. Jusqu’ici, ce
chronogramme est descriptif (ce qui s’est passé). Nouvelle finalité : prévoir le volume futur —
à horizon semaine, mois, trimestre — par université.
citation a posé le patron maison d’un modèle prédictif dans un pipeline DataOps : module pur,
asset Dagster, validation honnête, porte de décision prédictif/descriptif, suivi MLflow, drift
Evidently, contrat Parquet + manifest (ADR 0067,
ADR 0068,
ADR 0029). mediawatch, lui, n’a
aucune instrumentation MLOps (ni MLflow, ni Evidently, ni modèle). On réplique le patron, en
l’adaptant à une tâche de prévision de série temporelle, et on instrumente mediawatch au même
niveau que citation.
Deux contraintes cadrent la conception :
- Échelle. En production, le référentiel ROR (type education) compte des milliers d’universités. Un modèle de série indépendant par université (ARIMA/ETS/Prophet ×N) ne scale pas.
- Honnêteté temporelle. Une série temporelle ne se valide pas par validation croisée aléatoire : tester un modèle sur du passé alors qu’il a vu le futur est une fuite. Le découpage doit respecter l’ordre du temps.
Décision
Section intitulée « Décision »
mediawatchgagne un modèle de prévision du volume d’articles par université, à horizon semaine / mois / trimestre. C’est UN modèle GLOBAL de gradient boosting (scikit-learn), où l’identité de l’université est une feature catégorielle — jamais un modèle par série. Il est validé honnêtement par découpage TEMPOREL contre une baseline saisonnière, avec la même porte de décision prédictif/descriptif que l’ADR 0067, et instrumenté en MLOps complet (MLflow + drift Evidently) au niveau decitation.
Un modèle global, pas un modèle par série
Section intitulée « Un modèle global, pas un modèle par série »Le modèle est un HistGradientBoostingRegressor unique pour toutes les universités. L’identité de
l’université entre comme feature catégorielle (univ_code, encodage ordinal stable), que le
gradient boosting traite nativement — il apprend un niveau par université sans exploser la dimension
(un one-hot de milliers de colonnes serait non-scalable). Un seul fit, une seule version à suivre :
O(1) modèle quel que soit le nombre d’universités.
Multi-horizon par un seul modèle, agrégation en aval
Section intitulée « Multi-horizon par un seul modèle, agrégation en aval »Le modèle est direct multi-step : l’horizon h (en jours) est une feature. Il prédit le volume
journalier à t+h pour chaque h, puis les trois horizons métier sont obtenus par agrégation aval
(somme sur 7 / ~30 / ~92 jours). Avantage : cohérence interne (la prévision « mois » est la somme
des jours qui le composent) et un seul artefact. Le recursive multi-step (réinjecter la prédiction
comme lag) est écarté : il propage l’erreur et transforme un fait passé en prédiction (fuite).
Features anti-fuite et baseline exigeante
Section intitulée « Features anti-fuite et baseline exigeante »Les features sont calendaires (jour de semaine, mois, semaine ISO en sin/cos ; week-end) de la date
cible, des lags et moyennes mobiles strictement passés (jamais une date > t), une
tendance, et univ_code. Les jours sans article sont comblés à 0 (sinon un lag sauterait des trous
et lirait le futur). La baseline est le saisonnier naïf hebdomadaire (S=7, repli persistance) —
la baseline « difficile à battre » d’une série à fort creux week-end ; battre la simple moyenne ne
prouverait rien.
Validation honnête et porte de décision
Section intitulée « Validation honnête et porte de décision »La validation est temporelle (TimeSeriesSplit sur les dates d’origine distinctes) : chaque pli
s’entraîne sur le passé et teste sur le futur immédiat. Jamais de découpage aléatoire. La porte de
décision est identique à l’ADR 0067 :
R² honnête > 0,05 ET MAE < MAE_baseline → le modèle est servi en mode prédictif ; sinon
repli descriptif (la baseline saisonnière est servie). La porte est explicite, pas un doute.
Instrumentation MLOps complète sur mediawatch
Section intitulée « Instrumentation MLOps complète sur mediawatch »mediawatch est aligné sur citation : runs MLflow (best-effort, no-op sans serveur — le serveur
est fourni par le socle via MLFLOW_TRACKING_URI, ADR 0033).
Le piège connu (documenté côté cluster) : les variables posées sur le Deployment de la
code-location gRPC ne se propagent pas aux pods de run du K8sRunLauncher ; MLFLOW_TRACKING_URI
doit donc être réinjecté au niveau du run (comme OPENLINEAGE_URL l’est déjà), sinon le suivi tombe
en no-op silencieux dans le pod de run. Drift Evidently sur la distribution prédite, asset check
dont la seule bascule predictive → descriptive est BLOQUANTE (parité
ADR 0068 : une perte de pouvoir prédictif est un
changement de contrat majeur).
Un asset Python, un contrat de sortie
Section intitulée « Un asset Python, un contrat de sortie »Le modèle vit dans un asset Python (comme pair_uplift_model), pas un modèle dbt : le pouvoir
prédictif vient du gradient boosting non linéaire, qu’une approximation SQL abandonnerait. dbt reste la
couche staging → curated → marts_university_timeline (la source) ; le forecast est en aval. La
sortie est un mart servi marts/university_timeline_forecast/ (Parquet + manifest.json atomique),
sous le contrat ADR 0029. Prévision
jamais négative (clip ≥ 0). Déterminisme figé (RANDOM_STATE, ADR 0057).
Alternatives écartées
Section intitulée « Alternatives écartées »- Un modèle de série par université (ARIMA/ETS/Prophet). Écartée : ne scale pas à des milliers de séries (coût CPU × N), et certaines universités ont trop peu d’historique. Le modèle global capte la saisonnalité partagée et un niveau par université à coût constant.
- Trois modèles, un par horizon. Écartée : triple le coût (entraînement, drift, manifest) et risque l’incohérence (prévision « semaine » non incluse dans « mois »). Un modèle multi-step + agrégation garantit la cohérence.
- Recursive multi-step (réinjecter la prédiction comme lag). Écartée : propage l’erreur sur l’horizon et casse l’anti-fuite (un lag devient une prédiction).
- Modèle dbt (SQL). Écartée : abandonne le non-linéaire — même raison que l’ADR 0067 pour l’uplift.
- Validation croisée aléatoire (KFold). Écartée : fuite temporelle (tester sur du passé avec un
modèle ayant vu le futur). Le
TimeSeriesSplitest obligatoire.
Accepted (2026-06-26). Réplique le patron de l’ADR 0067
(module pur + porte prédictif/descriptif) et de l’ADR 0068
(drift, bascule bloquante), appliqué à une prévision de série temporelle. S’appuie sur
l’ADR 0029 (contrat Parquet + manifest),
l’ADR 0057 (reproductibilité, graine figée),
l’ADR 0072 (tests basés sur les
propriétés), l’ADR 0062 (MLOps niveau 2) et
l’ADR 0033 (MLFLOW_TRACKING_URI réinjecté au pod
de run). Étend l’ADR 0064 : mediawatch passe de la
description à la prévision, et reçoit sa première instrumentation MLOps. Nouvelles dépendances
dataops/mediawatch-dagster (scikit-learn, Evidently, mlflow-skinny), aux mêmes versions que
citation.
Conséquences
Section intitulée « Conséquences »Bénéfices. mediawatch devient prospectif (prévoir, pas seulement décrire). L’instrumentation
MLOps devient homogène entre les deux pipelines DataOps. Le modèle scale en O(1) modèle. En
gagnant un modèle — donc un signal de dérive —, mediawatch devient éligible au CT autonome
(ADR 0079), jusqu’ici impossible
faute de mesure de dérive.
Prix à payer. Trois dépendances de plus dans dataops/mediawatch-dagster (image ~+50–80 Mo, aligné
sur citation). Un modèle de plus à surveiller. En banc (échantillon de jours borné), l’historique
est souvent trop court pour un pouvoir prédictif honnête → repli descriptif fréquent — ce qui est le
comportement correct (la porte ne sur-vend pas).
Garde-fous. Anti-fuite temporelle obligatoire (lags strictement passés, TimeSeriesSplit, jamais
KFold) — éprouvée par des tests de propriété. Porte de décision explicite (repli descriptif si le
modèle ne bat pas la baseline). Drift bloquant sur la seule bascule predictive → descriptive.
Jamais de prévision négative. Déterminisme (graine figée). MLflow best-effort (no-op sans
serveur, hermétique en test). Neutralité : expériences MLflow et identifiants nommés mediawatch_*,
jamais une marque (ADR 0022).