STUD-602 — courbe de vie longitudinale des longs, en Rust (passage standalone) #1664
Loading…
Reference in a new issue
No description provided.
Delete branch "hermes/vie-longue-rust"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Carte t_1bfed611. Porte la courbe de vie LONGITUDINALE sur le backend Rust, en Python inchangé.
Ce qui est livré
rust/collecteur/src/vie_longue.rs:Passage/Entree,cumuls(),ajuster(),taux_heures_vues(),points(),format_de(), et la pagepage().rust/collecteur/tests/vie_longue.rs+tests/fixtures/vie-longue/(3 vidéos, 3 passages) : 13 tests, dont un qui lance le binaire.main.rs: sous-commande--vie-longue <videos-vues.jsonl> <publications.json>, en tête demain, sans toucher aux autres sous-commandes.La forme réelle du fichier (le mandat la supposait autre)
Le mandat annonçait
{"date": "...", "videos": [{"id", "vues"}]}. Mesuré sur les copies du 132 disponibles le 2026-10-14 (/home/atelier/tmp-relecteur/*/videos-vues.jsonl, et l'écriture deVUES_VIDEOSdanstableau-bord.py) : une ligne par passage,{"t": "2026-10-13T23:00", "v": {"<id>": <vues>}}. C'est la forme mesurée que le parser lit ; le schéma decollecte.pyn'est pas touché.Décisions, mesurées
null, jamais 0 (un zéro relevé, lui, est une mesure et sort en 0).serde_json, doncCargo.lockne bouge pas et--lockedtient. Les deux types sont lus à la main depuisserde_json::Value, commeconso.rs.ajuster()porteonglet_projection.ajuster(): pente log-log bornée [0,05 ; 1,5], quartiles des résidus, extrêmes sielargie.Preuves
cargo test --release --locked: 4 + 18 + 8 + 13 tests,test result: ok, 0 échec (collecteur), plus les bancs du service inchangés.cargo build --release --locked: aucun avertissement.python3 _tableau/garde_fous.py: « rien à redire » (code 0).Hors périmètre, comme demandé
Aucun fichier Python touché (
onglet_projection.py,collecte.py, le front : intacts). Le câblage de la courbe dans l'onglet Projection et l'affichage Svelte restent sous t_db44afed / t_b50a3d24.Cause : la courbe de vie de l'onglet Projection est transversale — un point par long, tous âges confondus — donc la forme de la croissance n'y est mesurée qu'entre vidéos, jamais le long d'une seule. Le mandat t_1bfed611 demande la mesure longitudinale sur `videos-vues.jsonl`, côté Rust, en Python inchangé. Remède : le module `collecteur::vie_longue` lit la forme RÉELLE du fichier, relevée sur les copies du 132 (`{"t": "2026-10-13T23:00", "v": {"<id>": vues}}`, une ligne par passage ; le mandat la supposait `{"date": ..., "videos": [...]}`). Pour chaque long, il relève le premier passage dont l'âge atteint J+1, J+7 puis J+28 — un passage ne sert qu'une fois, donc les âges montent toujours et un jalon sans relevé neuf vaut null, jamais zéro. L'ajustement est celui de `onglet_projection.ajuster()` : moindres carrés en log-log, pente bornée [0,05 ; 1,5], quartiles des résidus (extrêmes si élargie). Le taux heures/vues est mesuré sur les vidéos présentes des deux côtés. La sous-commande `--vie-longue <jsonl> <publications.json>` sort la page JSON sur stdout. Aucune dépendance ajoutée (`Cargo.lock` intact, `--locked` tient), aucun fichier Python touché. Banc : `rust/collecteur/tests/vie_longue.rs`, 13 tests sur les fixtures (3 vidéos, 3 passages), dont un qui lance le binaire.Fusionnée automatiquement par
fusion-auto(forge) : CI verte (Rust et Front Svelte), PR relue (étiquetterelu), sans conflit.Relu, conforme — 8/10. Relecture du 2026-10-14 par le relecteur (carte
t_2727900f), sur la têteb6cb3409, dans un arbre dédié (wt/t_2727900f, HEAD détachée sur la tête de la branche). Étiquettereluposée.Périmètre.
git diff --stat origin/main...b6cb3409: 6 fichiers, +662/-1, tous sous_tableau/rust/collecteur/. Aucun.py, aucun schéma :collecte.py,tableau-bord.py(l'écriture deVUES_VIDEOS, ligne 660),onglet_projection.pyetCargo.locksont inchangés. Branche à jour deorigin/main(3b006a30), 1 commit devant, 0 derrière.Les quatre points du mandat, mesurés.
ages[1,8,29](v1),[4,25,null](v2, aucun passage à J+1 : l'âge réel 4 est porté),[10,null,null](v3, un seul passage, servi une fois) ;vues[100,400,900],[220,800,null],[260,null,null]. Un zéro relevé sort en0, une absence ennull: les deux cas sont rejoués par le banc.taux=0.010459183673469388sur les fixtures, soit exactement 20,5 h / 1 960 vues ;courbeporteA_heures/bas_heures/haut_heures= la courbe des vues × le taux (vérifié au bit près).onglet_projection.ajuster(), mesuré sur les 6 points de la fixture : Python rendA=91.54511782943057, p=0.6424350214280312, bas=1.011797274121199, haut=1.123766338795036, n=6, Rust rendA=91.54511782943057, p=0.6424350214280313, bas=1.011797274121199, haut=1.1237663387950354, n=6(mêmequantilepar interpolation, mêmes résidus sur la pente déjà bornée, mêmes extrêmes sielargie).cargo test --release --locked: 4 + 18 + 8 + 13 (collecteur) et 4 + 4 + 2 + 5 + 3 + 1 + 3 (service), tousok, 0 échec.cargo build --release --locked: 0 avertissement.python3 _tableau/garde_fous.py: « rien à redire », code 0. CI Forgejo 4/4successsurb6cb3409(lu par l'API, pas d'après le compte rendu). Aucun emoji, aucun secret, aucun base64 dans le diff.Front. Aucun fichier de
_tableau/front/touché : rien à regarder au navigateur, aucun onglet écrasé, aucune fonction visible retirée.vitest run: 25 fichiers, 189 tests passés ;vite build: 122 modules,✓ built(lancés dans l'arbre de relecture,node_modulesdu dépôt lié).Écarts mesurés.
jour_deignore le fuseau horaire (le seul écart qui touche la mesure de la carte). Il lit les 10 premiers caractères de l'ISO, là oùonglet_projection.jour_deconvertit en heure de Paris. Le commentaire du module annonce pourtant « commeonglet_projection.points()(jour_de) ». Mesure sur les publications réelles (/studio/_tableau/collecte-132.json, lu le 13/10 19 h 35) : 11 des 433videos_pub(2,5 %) ont unpubentre 22 h et 24 h UTC, donc un jour de Paris de plus que le jour UTC. Cas rejoué de bout en bout :pub = 2026-09-30T22:30:00Z, passages2026-10-01T23:00,2026-10-08T23:00,2026-10-29T23:00→ Rust rend les âges1, 8, 29; la référence Python rend0, 7, 28. Conséquence : pour ces longs, le J+1 est relevé le jour même de la publication et les trois jalons glissent d'un jour. La forme réelle det(2026-10-13T23:00, heure locale, sans fuseau) n'est pas concernée : seulpub, fourni par l'appelant, porte unZ. Carte de suite créée pour la correction.page()sort une courbe dès 3 points (n=3mesuré) alors que la règle du studio estMIN_MESURES = 5: la constante est déclarée et documentée, mais appliquée nulle part — c'est l'appelant qui doit comparer (c'est écrit dans le module). À tenir au câblage.points(cumuls, None)ne retire pas lescourt: la courbe « studio » inclurait les Shorts si l'appelant les fournit, alors que la courbe studio de Python se construit surtb_chaines.est_long, qui a en plus une exception par chaîne (SANS_SHORTS) queEntreene peut pas porter (pas de nom de chaîne). À trancher au câblage, pas un défaut du mandat.heures_video/vuesdivise des heures de 12 mois glissants (collecte.py,d365) par des vues cumulées depuis toujours : deux fenêtres différentes, donc un taux biaisé pour les longs de plus de douze mois. C'est le mandat qui le demande ainsi (« tauxheures_video/vuesmesuré ») — noté, pas compté contre.Non mesuré. Pas d'exécution sur le
videos-vues.jsonlde production : le 132 est injoignable depuis le PC (no route to host), la mesure porte sur les copies présentes sur le PC et sur la lecture detableau-bord.py; pas de fichier de 40 jours complet rejoué. Aucun câblage dans le service ni dans l'onglet Projection (hors périmètre de la carte).