API Rust — Rythme et Budget : /api/rythme et /api/budget en parité avec Python (t_3b3e3701) #1660

Merged
hermes-fusion merged 4 commits from hermes/api-rythme-budget-rust into main 2026-10-14 22:50:03 +02:00
Collaborator

Mandat : carte t_3b3e3701 (chantier « tableau 132 : 100 % Svelte et 100 % Rust », carte maître t_abdd8136).

Deux routes NOUVELLES : GET /api/rythme et GET /api/budget. Le front en dépend — la carte Svelte t_e1c8c087 (replis de l'Atelier) les lira : le chef doit prévenir svelte. Rien n'est recalculé côté front : les textes et les montants sortent écrits comme l'onglet Python les écrit.

Contrat

  • GET /api/rythme → {"vide", "message", "t", "sections"}. Une section par entrée du rapport, genre h (titre), p (note) ou t (tableau tete/lignes) — un genre inconnu est un tableau, comme la branche else de l'onglet. Les textes sont nettoyés des accents graves et des ** et NON échappés (le front échappe).
  • GET /api/budget → {"vide", "message", "t", "jour", "depense", "plafond", "plafond_atteint", "solde", "solde_t", "episodes": [{episode, depense, budget, generations, etat}], "arrets": [{date, raison}]}. Montants écrits au centime (« 3.20 $ »), etat dans l'ordre de l'onglet (« budget atteint », « seconde génération refusée », « forme à valider », sinon vide), arrets = les huit derniers, du plus récent au plus ancien.
  • Un relevé absent, illisible ou qui n'est pas un objet JSON rend {"vide": true, "message": …} (la phrase de l'onglet, ses balises retirées) et la route répond 200 — le contrôle des routes de rust/deployer.sh exige 200. GET seulement : un POST répond 405.
  • Entrées : SERVICE_RYTHME (défaut /opt/studio/rythme.json) et SERVICE_BUDGET (défaut /opt/studio/budget-runware.json), relus à chaque requête, rien en cache.

Ce que la branche contient

  • _tableau/atelier_replis_donnees.py : les deux rubriques dans UN module de données (donnees_rythme, donnees_budget), la source de vérité du portage. Ce qu'elles partagent n'y existe qu'en une copie : la date d'un relevé, le str() d'une valeur, et le montant d'onglet_budget._d (importé, pas recopié) — c'est ce que refuse test_garde_doublons.py.
  • rust/service/src/replis/ : un module, les deux routes, et leurs règles communes écrites une fois ; les deux lignes dans le match de main.rs.
  • rust/service/tests/replis.rs + tests/fixtures/rythme/, tests/fixtures/budget/ : huit entrées par rubrique (le relevé réel du dépôt, abîmé, absent, non-objet, arrondis au centime, douze arrêts, cinq états d'épisode).
  • _tableau/test_rythme_parite.py, _tableau/test_budget_parite.py : les bancs qui garantissent que le JSON doré est la sortie de Python, et qui reconstruisent le HTML de la rubrique à partir du JSON pour exiger qu'il soit identique à celui de l'onglet.
  • _tableau/LXC-132.md : la section des deux routes.
  • Aucun .svelte touché ; les deux <details> Python restent en place jusqu'à la bascule du front ; le LXC 130 reste éteint.

Preuves (tête cfc5f1c9)

  • CI pc-studio verte 4/4 (Rust push, Front Svelte push, Rust pull_request, Front Svelte pull_request).
  • cargo test --release --locked : code 0, 56 tests, dont 4 neufs (tests/replis.rs), 0 avertissement. cargo build --release --locked : code 0.
  • Bancs Python : test_rythme_parite.py et test_budget_parite.py code 0 (8 entrées chacun, « HTML identique ») ; test_onglet_budget.py code 0 ; test_100_svelte_rust.py code 0 (épreuve passée : Calendrier forcé en Python → verdict 1).
  • Bancs des garde-fous : test_garde_doublons.py, test_garde_couches.py, test_garde_plafond.py — rouges avec la première version du portage (_texte et _date en double, _d recopié), verts après le correctif du 4e commit. C'est un rouge de cette branche, pas d'avant : les trois sont verts sur origin/main (1465663e).
  • Sept épreuves font sortir les bancs en 1 (6 mutations du portage — Rust et Python — et 1 de l'onglet) : le détail est en commentaire de la carte t_3b3e3701.
  • HTTP, sur le binaire réel : GET /api/rythme et GET /api/budget → 200, corps identique au JSON doré de Python ; POST /api/rythme → 405 ; relevé absent → le contrat {"vide": true, …}.

Non mesuré

Le curl des deux routes sur le 132 (aucun accès depuis cette carte : elles y arrivent par le déploiement automatique après fusion) et l'onglet au navigateur (porteur : la carte Svelte t_e1c8c087).

Bancs du tableau : 153 verts, 9 rouges — tous pré-existants, mesurés sur origin/main (1465663e) dans un arbre de travail séparé, sans cette branche : test_accueil_taches, test_atelier_a_valider, test_atelier_actions_uniques, test_deploy_132, test_images_apercu, test_navigateur_blocage, test_navigateur_prevoir, test_navigateur_question, test_navigateur_retour.

Mandat : carte t_3b3e3701 (chantier « tableau 132 : 100 % Svelte et 100 % Rust », carte maître t_abdd8136). **Deux routes NOUVELLES : `GET /api/rythme` et `GET /api/budget`.** Le front en dépend — la carte Svelte t_e1c8c087 (replis de l'Atelier) les lira : **le chef doit prévenir `svelte`**. Rien n'est recalculé côté front : les textes et les montants sortent écrits comme l'onglet Python les écrit. ## Contrat - `GET /api/rythme` → `{"vide", "message", "t", "sections"}`. Une section par entrée du rapport, `genre` `h` (titre), `p` (note) ou `t` (tableau `tete`/`lignes`) — un `genre` inconnu est un tableau, comme la branche `else` de l'onglet. Les textes sont nettoyés des accents graves et des `**` et NON échappés (le front échappe). - `GET /api/budget` → `{"vide", "message", "t", "jour", "depense", "plafond", "plafond_atteint", "solde", "solde_t", "episodes": [{episode, depense, budget, generations, etat}], "arrets": [{date, raison}]}`. Montants écrits au centime (« 3.20 $ »), `etat` dans l'ordre de l'onglet (« budget atteint », « seconde génération refusée », « forme à valider », sinon vide), `arrets` = les huit derniers, du plus récent au plus ancien. - Un relevé absent, illisible ou qui n'est pas un objet JSON rend `{"vide": true, "message": …}` (la phrase de l'onglet, ses balises retirées) et la route répond **200** — le contrôle des routes de `rust/deployer.sh` exige 200. GET seulement : un POST répond 405. - Entrées : `SERVICE_RYTHME` (défaut `/opt/studio/rythme.json`) et `SERVICE_BUDGET` (défaut `/opt/studio/budget-runware.json`), relus à chaque requête, rien en cache. ## Ce que la branche contient - `_tableau/atelier_replis_donnees.py` : les deux rubriques dans UN module de données (`donnees_rythme`, `donnees_budget`), la source de vérité du portage. Ce qu'elles partagent n'y existe qu'en une copie : la date d'un relevé, le `str()` d'une valeur, et le montant d'`onglet_budget._d` (importé, pas recopié) — c'est ce que refuse `test_garde_doublons.py`. - `rust/service/src/replis/` : un module, les deux routes, et leurs règles communes écrites une fois ; les deux lignes dans le `match` de `main.rs`. - `rust/service/tests/replis.rs` + `tests/fixtures/rythme/`, `tests/fixtures/budget/` : huit entrées par rubrique (le relevé réel du dépôt, abîmé, absent, non-objet, arrondis au centime, douze arrêts, cinq états d'épisode). - `_tableau/test_rythme_parite.py`, `_tableau/test_budget_parite.py` : les bancs qui garantissent que le JSON doré est la sortie de Python, et qui **reconstruisent le HTML de la rubrique à partir du JSON** pour exiger qu'il soit identique à celui de l'onglet. - `_tableau/LXC-132.md` : la section des deux routes. - Aucun `.svelte` touché ; les deux `<details>` Python restent en place jusqu'à la bascule du front ; le LXC 130 reste éteint. ## Preuves (tête `cfc5f1c9`) - **CI pc-studio verte 4/4** (Rust push, Front Svelte push, Rust pull_request, Front Svelte pull_request). - `cargo test --release --locked` : code 0, **56 tests, dont 4 neufs** (`tests/replis.rs`), 0 avertissement. `cargo build --release --locked` : code 0. - Bancs Python : `test_rythme_parite.py` et `test_budget_parite.py` code 0 (8 entrées chacun, « HTML identique ») ; `test_onglet_budget.py` code 0 ; `test_100_svelte_rust.py` code 0 (épreuve passée : Calendrier forcé en Python → verdict 1). - Bancs des garde-fous : `test_garde_doublons.py`, `test_garde_couches.py`, `test_garde_plafond.py` — rouges avec la première version du portage (`_texte` et `_date` en double, `_d` recopié), verts après le correctif du 4e commit. C'est un rouge de cette branche, pas d'avant : les trois sont verts sur `origin/main` (`1465663e`). - Sept épreuves font sortir les bancs en 1 (6 mutations du portage — Rust et Python — et 1 de l'onglet) : le détail est en commentaire de la carte t_3b3e3701. - HTTP, sur le binaire réel : `GET /api/rythme` et `GET /api/budget` → 200, corps **identique au JSON doré de Python** ; `POST /api/rythme` → 405 ; relevé absent → le contrat `{"vide": true, …}`. ## Non mesuré Le `curl` des deux routes sur le 132 (aucun accès depuis cette carte : elles y arrivent par le déploiement automatique après fusion) et l'onglet au navigateur (porteur : la carte Svelte t_e1c8c087). Bancs du tableau : **153 verts, 9 rouges — tous pré-existants**, mesurés sur `origin/main` (`1465663e`) dans un arbre de travail séparé, sans cette branche : `test_accueil_taches`, `test_atelier_a_valider`, `test_atelier_actions_uniques`, `test_deploy_132`, `test_images_apercu`, `test_navigateur_blocage`, `test_navigateur_prevoir`, `test_navigateur_question`, `test_navigateur_retour`.
Cause. Les deux rubriques repliées du panneau de l'Atelier (« Rythme de fabrication » et « Budget Runware
par épisode ») sont les derniers contenus du tableau que Python rend : `onglet_rythme.py` et
`onglet_budget.py` écrivent du HTML. Tant qu'elles en viennent, l'onglet n'est pas 100 % Svelte, et le
service Rust n'a rien à servir au front.

Remède. Deux modules de données, sur le modèle de `videos_donnees.py` et `top_donnees.py` :
`rythme_donnees.donnees_rythme` et `budget_donnees.donnees_budget` mettent en JSON exactement ce que les
deux onglets écrivent en HTML — la lecture par `socle.lire_json` (un relevé absent, illisible ou qui n'est
pas un objet rend la phrase de l'onglet), le nettoyage des accents graves et des `**`, la date tronquée à
seize caractères, les montants écrits au centime comme l'onglet les écrit, l'état de chaque épisode dans son
ordre, et les huit derniers arrêts du plus récent au plus ancien.

Le banc `test_rythme_parite.py` / `test_budget_parite.py` tient les deux bouts : il recalcule le JSON doré
avec le Python d'aujourd'hui (une sortie recopiée à la main, ou un calcul Python qui change sans que le doré
suive, le font sortir en 1) et il RECONSTRUIT le HTML de la rubrique à partir du JSON pour exiger qu'il soit
identique à celui de l'onglet — un chiffre ou une phrase qui ne passerait pas par le JSON le fait échouer.
Les entrées vivent dans `rust/service/tests/fixtures/`, là où le banc Rust les lit.
Cause. Les deux rubriques repliées du panneau de l'Atelier n'avaient aucune route : leur contenu ne pouvait
venir que du HTML rendu par Python, et le front n'avait rien à lire.

Remède. `rust/service/src/replis/` porte les deux routes (`replis::rythme::api`, `replis::budget::api`),
à l'identique de `rythme_donnees.donnees_rythme` et `budget_donnees.donnees_budget` ; `main.rs` les sert en
GET sur `/api/rythme` et `/api/budget`, avec `SERVICE_RYTHME` (défaut `/opt/studio/rythme.json`) et
`SERVICE_BUDGET` (défaut `/opt/studio/budget-runware.json`) — les fichiers que `pousser-rythme.sh` et
`pousser-budget.sh` posent à côté de la page. Le relevé est relu à chaque requête, rien n'est mis en cache ;
un relevé absent, illisible ou qui n'est pas un objet JSON rend `{"vide": true, "message": …}` en 200, comme
l'onglet écrit sa phrase dans la page.

Le banc `tests/replis.rs` compare les deux sorties sur les mêmes fichiers d'entrée que le banc Python
(`tests/fixtures/rythme/`, `tests/fixtures/budget/`) : huit entrées par rubrique, dont le relevé réel du
dépôt, le fichier abîmé, le fichier absent et les arrondis au centime. Il éprouve aussi le contrat du relevé
absent et la relecture à chaque appel.
LXC-132.md : les deux routes des replis de l'Atelier, et ce qui reste Python
Some checks failed
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 24s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 13s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Has been cancelled
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Has been cancelled
a0f1c845b2
Cause. La doc du 132 décrit chaque route portée en Rust (Top vidéos, Analytique, Vidéos) ; les deux replis de
l'Atelier n'y étaient pas, et le contrat de leur JSON n'était écrit nulle part.

Remède. Une section qui dit les entrées (`SERVICE_RYTHME`, `SERVICE_BUDGET`), le corps des deux routes, le cas
du relevé absent (200, `{"vide": true, …}`), les bancs de parité — dont la reconstruction du HTML depuis le
JSON — et ce qui reste Python jusqu'à la carte Svelte t_e1c8c087.
Rythme et Budget : une seule copie des règles partagées, et le garde-fou des doublons est vert
All checks were successful
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (push) Successful in 17s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (push) Successful in 11s
Tests du tableau (Rust + Svelte) / Rust (_tableau/rust) (pull_request) Successful in 17s
Tests du tableau (Rust + Svelte) / Front Svelte (_tableau/front) (pull_request) Successful in 13s
cfc5f1c9b8
Cause. Les trois bancs des garde-fous (`test_garde_doublons`, `test_garde_couches`, `test_garde_plafond`)
refusaient le portage : `_texte` et `_date` étaient définis dans les deux modules de données, et `_d` une
seconde fois à côté d'`onglet_budget._d`. Mesuré : les trois sortent en 1 avec le portage, en 0 sur
`origin/main` — c'est bien une régression de cette branche, pas un rouge d'avant.

Remède. Les deux rubriques vivent dans UN module de données, `_tableau/atelier_replis_donnees.py`
(`donnees_rythme`, `donnees_budget`), comme leurs deux routes vivent dans `rust/service/src/replis/` :
`_texte` et la date d'un relevé n'y sont définis qu'une fois, et le montant vient d'`onglet_budget._d`,
la copie unique de cette règle — les deux rendus ne peuvent donc plus diverger sur elle. La date est
nommée `_date_releve` : `conso_claude._date` convertit une date en secondes, ce n'est pas la même règle,
et deux règles sous un même nom sont exactement ce que le garde-fou refuse.
hermes-fusion deleted branch hermes/api-rythme-budget-rust 2026-10-14 22:50:04 +02:00
Collaborator

Fusionnée automatiquement par fusion-auto (forge) : CI verte (Rust et Front Svelte), PR relue (étiquette relu), sans conflit.

Fusionnée automatiquement par `fusion-auto` (forge) : CI verte (Rust et Front Svelte), PR relue (étiquette `relu`), sans conflit.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
foXaCe/studio!1660
No description provided.