front+api : une heure sans mesure creuse un trou au lieu de passer pour une droite #217

Merged
lenaic merged 2 commits from lenaic/211-trous-visibles into develop 2026-09-08 21:20:33 +00:00
Owner

Ce que ça change

Une heure sans mesure disparaissait de la courbe au lieu d'y creuser un trou :
le trait reliait ses deux voisines et une panne de trois heures se lisait comme
une droite. Elle est désormais rendue, tracée comme un vide, et jamais comme un
zéro.

Closes #211

Preuve

Mesuré en production ce matin — cinq sites sur sept portaient une heure sans
moyenne, et rien à l'écran ne le disait :

site_id   heure sans moyenne
SITE001   2026-09-08 08:00
SITE002   2026-09-08 08:00
SITE004   2026-09-08 08:00
SITE006   2026-09-08 08:00
SITE007   2026-09-08 08:00

Les cas ajoutés ont été passés contre le code d'AVANT, pour vérifier qu'ils
prouvent quelque chose :

API   : 4 nouveaux cas -> 4 échouent sur develop, 4 passent ici
Front : 8 nouveaux cas -> 5 échouent sur develop, 8 passent ici
        (les 3 autres décrivent le comportement déjà correct, ils le gardent)

pytest tests/unit/api        235 passés
npm run test:unit            481 passés
mypy --strict dashboard/     0
ruff check services packages infra bin db   0
npm run build                OK

Relecture

  • Un pair a relu et laissé un commentaire, même court
  • Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas

Où regarder en priorité

1. Un critère de l'énoncé n'est pas repris, et c'est assumé. Le ticket
demande spanGaps: false sur GraphiqueSite.vue et GraphiqueConsommation.vue.
Les deux sont des diagrammes en barres, pas des courbes : spanGaps n'y a
aucun effet, et une valeur nulle y laisse déjà un vide. L'intention est tenue,
l'option ne l'aurait pas été. À contredire si vous préférez la lettre.

2. Le contrat devient nullable, et trois endroits y étaient exposés.
PointSerieOut.valeur_kw passe à float | None. Sans garde, round(float(None))
levait, max() comparait None à un flottant, et l'écart à la référence
divisait sur un trou. Les trois sont protégés, avec un helper qui NE classe pas
les trous : un trou n'est pas un creux.

3. Deux défauts trouvés en route, hors énoncé mais dans le sujet.

  • moyenneDe sommait les null comme des zéros tout en les gardant au
    dénominateur
    . La ligne de moyenne du graphique affichait donc une
    consommation inférieure à la réalité, sans que rien ne le dise. Ce défaut
    était dormant : il ne se réveillait qu'avec l'arrivée des valeurs nulles.
  • MicroCourbe.vue est la seule vraie courbe de l'écran, et elle filtrait
    les trous avant de placer ses abscisses. Le trait les traversait et l'axe se
    resserrait — exactement le défaut du titre du ticket, en vignette de 92 px.
    Elle trace maintenant un tronçon par suite continue, pose une pastille sur
    les mesures isolées (sans quoi elles disparaîtraient), et son équivalent
    textuel annonce les heures manquantes aux lecteurs d'écran.

4. Ce que ça ne fait pas. Combler les heures manquantes. Ce ticket les
MONTRE — les combler serait exactement l'erreur que le #199 a corrigée ailleurs.

## Ce que ça change Une heure sans mesure disparaissait de la courbe au lieu d'y creuser un trou : le trait reliait ses deux voisines et une panne de trois heures se lisait comme une droite. Elle est désormais rendue, tracée comme un vide, et jamais comme un zéro. Closes #211 ## Preuve Mesuré en production ce matin — cinq sites sur sept portaient une heure sans moyenne, et rien à l'écran ne le disait : ``` site_id heure sans moyenne SITE001 2026-09-08 08:00 SITE002 2026-09-08 08:00 SITE004 2026-09-08 08:00 SITE006 2026-09-08 08:00 SITE007 2026-09-08 08:00 ``` Les cas ajoutés ont été passés contre le code d'AVANT, pour vérifier qu'ils prouvent quelque chose : ``` API : 4 nouveaux cas -> 4 échouent sur develop, 4 passent ici Front : 8 nouveaux cas -> 5 échouent sur develop, 8 passent ici (les 3 autres décrivent le comportement déjà correct, ils le gardent) pytest tests/unit/api 235 passés npm run test:unit 481 passés mypy --strict dashboard/ 0 ruff check services packages infra bin db 0 npm run build OK ``` ## Relecture - [ ] Un pair a relu et laissé un commentaire, même court - [ ] Ses remarques sont traitées, ou une réponse explique pourquoi elles ne le sont pas ## Où regarder en priorité **1. Un critère de l'énoncé n'est pas repris, et c'est assumé.** Le ticket demande `spanGaps: false` sur `GraphiqueSite.vue` et `GraphiqueConsommation.vue`. Les deux sont des diagrammes en **barres**, pas des courbes : `spanGaps` n'y a aucun effet, et une valeur nulle y laisse déjà un vide. L'intention est tenue, l'option ne l'aurait pas été. À contredire si vous préférez la lettre. **2. Le contrat devient nullable, et trois endroits y étaient exposés.** `PointSerieOut.valeur_kw` passe à `float | None`. Sans garde, `round(float(None))` levait, `max()` comparait `None` à un flottant, et l'écart à la référence divisait sur un trou. Les trois sont protégés, avec un helper qui NE classe pas les trous : un trou n'est pas un creux. **3. Deux défauts trouvés en route, hors énoncé mais dans le sujet.** - `moyenneDe` sommait les `null` comme des zéros **tout en les gardant au dénominateur**. La ligne de moyenne du graphique affichait donc une consommation inférieure à la réalité, sans que rien ne le dise. Ce défaut était dormant : il ne se réveillait qu'avec l'arrivée des valeurs nulles. - `MicroCourbe.vue` est la seule **vraie courbe** de l'écran, et elle filtrait les trous avant de placer ses abscisses. Le trait les traversait et l'axe se resserrait — exactement le défaut du titre du ticket, en vignette de 92 px. Elle trace maintenant un tronçon par suite continue, pose une pastille sur les mesures isolées (sans quoi elles disparaîtraient), et son équivalent textuel annonce les heures manquantes aux lecteurs d'écran. **4. Ce que ça ne fait pas.** Combler les heures manquantes. Ce ticket les MONTRE — les combler serait exactement l'erreur que le #199 a corrigée ailleurs.
front+api : une heure sans mesure creuse un trou au lieu de passer pour une droite (#211)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 9s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 21s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 45s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 6m11s
ed75470bf4
Cinq sites sur sept portaient ce matin une heure sans moyenne. Leur courbe
passait de 07:00 à 09:00 par un segment droit, et 08:00 ne figurait même pas
sur l'axe : les étiquettes se construisent depuis les points reçus, donc l'axe
se resserrait en silence. Rien à l'écran ne disait qu'il manquait une heure.

Deux causes, une de chaque côté.

Côté base, `SQL_SERIE_HEURE` et `SQL_SERIE_JOUR` portaient
`and h.moyenne_kw is not null`. Le seau existe dans `mesure_horaire`, sa
moyenne est nulle parce que toutes ses minutes ont été écartées, et la requête
le supprimait de la réponse au lieu de le rendre. Le filtre part ; `sum()` rend
`null` quand tous les sites du seau sont muets, et le point traverse.

Le contrat déclare donc `valeur_kw: float | None`. Trois endroits s'en
trouvaient exposés et sont protégés : `round(float(None))` levait, la pointe
et le creux comparaient `None` à un flottant, et l'écart à la référence
divisait sur un trou.

Côté écran, un `null` ne devient jamais un zéro — un zéro dirait « le parc a
consommé zéro », ce qui est une mesure, alors que l'absence de mesure n'en est
pas une. Chart.js ne dessine pas de barre, l'espace reste, l'heure garde son
étiquette. L'infobulle nomme le trou, et un clic dessus n'ouvre plus un panneau
de traçabilité vide.

Deux défauts trouvés en route, hors énoncé du ticket mais dans son sujet :

- `moyenneDe` sommait les `null` comme des zéros tout en les gardant au
  dénominateur. La ligne de moyenne du graphique affichait donc une
  consommation inférieure à la réalité, sans que rien ne le dise.
- `MicroCourbe` est la seule VRAIE courbe de l'écran, et elle filtrait les
  trous avant de placer ses abscisses. Le trait les traversait et l'axe se
  resserrait — exactement le défaut du titre du ticket, en vignette. Elle
  trace maintenant un tronçon par suite continue, pose une pastille sur les
  mesures isolées, et son équivalent textuel annonce les heures manquantes.

Le critère `spanGaps: false` de l'énoncé n'est pas repris : il vise une courbe
Chart.js, or les deux graphiques nommés sont des diagrammes en BARRES, où une
valeur nulle laisse déjà un vide. L'intention est tenue autrement.

Éprouvé : quatre cas côté API et huit côté front, tous vérifiés en échec sur
le code d'avant. 235 cas API, 481 cas front, mypy --strict et ruff au vert.

Hors périmètre, comme le dit le ticket : combler les heures manquantes.
Celui-ci les MONTRE.
lenaic requested review from gabriel 2026-09-08 21:02:39 +00:00
front : le profil du parc coupe son trait sur un trou au lieu de plonger à zéro (#211)
All checks were successful
Intégration / Contrôles statiques du dépôt (pull_request) Successful in 8s
Intégration / Workflows — lint et audit de sécurité (pull_request) Successful in 22s
Intégration / Tableau de bord — dépendances, tests et construction (pull_request) Successful in 41s
Intégration / Python — qualité, tests et dépendances (pull_request) Successful in 5m36s
ff9ef62970
`ProfilHoraireParc` consomme la même série que `MicroCourbe` et `GraphiqueSite`
— `store.profilParc`, servi par `serie_parc` — mais n'avait pas été adapté aux
points nuls introduits par ce ticket. Deux conséquences, toutes deux sur le
graphique principal de l'écran parc :

- `y(null)` valant `Y1 - (null / haut) * (Y1 - Y0)`, soit la ligne de base, le
  trait PLONGEAIT À ZÉRO sur chaque heure sans mesure. C'est pire que la droite
  que le ticket corrige : une panne se lisait comme une consommation nulle
  relevée.
- `null < 1000` étant vrai, le trou devenait le minimum du parc, et le résumé
  lu à voix haute annonçait « minimum — kW à 14 h ».

Le trait se trace donc par tronçons continus, une polyligne par tronçon, avec
une pastille pour les heures mesurées seules entre deux trous — une polyligne
d'un seul sommet ne dessine rien. L'abscisse reste celle du rang dans la série,
trous compris : l'axe des heures ne se resserre pas. Les extrêmes se cherchent
sur les seules heures mesurées, et le résumé annonce le nombre de trous, qu'un
lecteur d'écran ne peut pas voir.

Une fenêtre entièrement creuse se dit désormais « aucune mesure sur les 24
heures de la période » plutôt que « indisponible » : les seaux sont là, aucun
n'a de valeur, et les deux ne sont pas la même chose.
gabriel approved these changes 2026-09-08 21:14:58 +00:00
gabriel left a comment

Relu, et corrigé sur la branche (ff9ef62) : je pousse plutôt que de te renvoyer la PR, on n'a pas le temps d'un aller-retour.

Ce qui manquait

ProfilHoraireParc.vue consomme la même série que MicroCourbe et GraphiqueSitestore.profilParc, servi par serie_parc que cette PR rend nullable — mais n'avait pas été adapté. Reproduit sur ta branche avec trois points dont celui du milieu à null :

polyline : "56.0,81.3  382.0,192.0  708.0,59.2"   ← 192.0 = Y1, la ligne de base
résumé   : "Maximum 1 200 kW à 15 h, minimum — kW à 14 h."

Deux défauts, tous deux sur le graphique principal de l'écran parc :

  • ProfilHoraireParc.vue:116y(null) vaut Y1 - (null / haut) * (Y1 - Y0), soit la ligne de base. Le trait plongeait à zéro sur chaque heure sans mesure. C'est pire que la droite que le ticket corrige : une panne se lisait comme une consommation nulle relevée.
  • ProfilHoraireParc.vue:143-144null < 1000 est vrai, null se comparant comme zéro. Le trou devenait le minimum du parc, et le résumé lu à voix haute annonçait « minimum — kW ».

Ce que j'ai poussé

Le même traitement que MicroCourbe, puisque c'est le même problème : tronçons continus (une polyligne par tronçon), pastille pour les heures mesurées seules entre deux trous, abscisse gardée sur le rang dans la série pour que l'axe ne se resserre pas, extrêmes cherchés sur les seules heures mesurées, et le nombre de trous annoncé dans le résumé. Une fenêtre entièrement creuse dit « aucune mesure sur les 24 heures de la période » au lieu de « indisponible » — les seaux sont là, aucun n'a de valeur.

Sept cas ajoutés dans ProfilHoraireParc.test.js. Suite front : 482 verts (475 avant).

Le reste

Vérifié et RAS : GraphiqueConsommation s'en sort seul (Chart.js laisse le trou, formaterKw(null) rend « — », moyenneDe est corrigé ici). Côté SQL, retirer moyenne_kw is not null ne change aucun agrégat — sum() et avg() ignorent les nuls — seuls les seaux entièrement muets apparaissent désormais, ce qui est exactement le but.

Point mineur non bloquant, pour plus tard : _POINT_ABSENT garde valeur_kw=0.0, donc une fenêtre entièrement creuse affiche pointe/creux à « 0 kW le 01/01/1970 ». Cas limite pré-existant, juste devenu atteignable.

Approuvée.

Relu, et **corrigé sur la branche** (`ff9ef62`) : je pousse plutôt que de te renvoyer la PR, on n'a pas le temps d'un aller-retour. ### Ce qui manquait `ProfilHoraireParc.vue` consomme la **même série** que `MicroCourbe` et `GraphiqueSite` — `store.profilParc`, servi par `serie_parc` que cette PR rend nullable — mais n'avait pas été adapté. Reproduit sur ta branche avec trois points dont celui du milieu à `null` : ``` polyline : "56.0,81.3 382.0,192.0 708.0,59.2" ← 192.0 = Y1, la ligne de base résumé : "Maximum 1 200 kW à 15 h, minimum — kW à 14 h." ``` Deux défauts, tous deux sur le graphique principal de l'écran parc : - `ProfilHoraireParc.vue:116` — `y(null)` vaut `Y1 - (null / haut) * (Y1 - Y0)`, soit la ligne de base. Le trait **plongeait à zéro** sur chaque heure sans mesure. C'est pire que la droite que le ticket corrige : une panne se lisait comme une consommation nulle relevée. - `ProfilHoraireParc.vue:143-144` — `null < 1000` est vrai, `null` se comparant comme zéro. Le trou devenait le minimum du parc, et le résumé lu à voix haute annonçait « minimum — kW ». ### Ce que j'ai poussé Le même traitement que `MicroCourbe`, puisque c'est le même problème : tronçons continus (une polyligne par tronçon), pastille pour les heures mesurées seules entre deux trous, abscisse gardée sur le rang dans la série pour que l'axe ne se resserre pas, extrêmes cherchés sur les seules heures mesurées, et le nombre de trous annoncé dans le résumé. Une fenêtre entièrement creuse dit « aucune mesure sur les 24 heures de la période » au lieu de « indisponible » — les seaux sont là, aucun n'a de valeur. Sept cas ajoutés dans `ProfilHoraireParc.test.js`. Suite front : **482 verts** (475 avant). ### Le reste Vérifié et RAS : `GraphiqueConsommation` s'en sort seul (Chart.js laisse le trou, `formaterKw(null)` rend « — », `moyenneDe` est corrigé ici). Côté SQL, retirer `moyenne_kw is not null` ne change aucun agrégat — `sum()` et `avg()` ignorent les nuls — seuls les seaux entièrement muets apparaissent désormais, ce qui est exactement le but. Point mineur non bloquant, pour plus tard : `_POINT_ABSENT` garde `valeur_kw=0.0`, donc une fenêtre entièrement creuse affiche pointe/creux à « 0 kW le 01/01/1970 ». Cas limite pré-existant, juste devenu atteignable. **Approuvée.**
lenaic merged commit b51af033af into develop 2026-09-08 21:20:33 +00:00
lenaic deleted branch lenaic/211-trous-visibles 2026-09-08 21:20:34 +00:00
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
g2/enervision!217
No description provided.