api : le seuil du seau reconstitué se calcule sur le type que la base rend (#267) #272
No reviewers
Labels
No labels
Compat/Breaking
EC01
EC02
EC03
EC04
EC05
EC06
Kind/BDD
Kind/Back
Kind/Bug
Kind/CICD
Kind/Cloud
Kind/Contenu
Kind/Data
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Front
Kind/IA
Kind/Infra
Kind/Monitoring
Kind/Security
Kind/Testing
Portée/Post-jury
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
ops/alerte
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
g2/enervision!272
Loading…
Reference in a new issue
No description provided.
Delete branch "gabriel/267-seau-impute-decimal"
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?
Ce qui se passait
Depuis le déploiement de
maindu 10/09 (#271), les quatre fenêtres du tableau de bord rendent 500 en production. L'écran/graphiquesaffiche « Aucune source n'a répondu » : les sept appels de courbe échouent ensemble, doncetatErreurTotaleest vrai. Le message est exact, c'est l'API qui tombe.Journal d'accès de Caddy :
La cause
Les deux comptes n'ont pas le même type :
minutes_imputeescount(*) filter (…)bigint→intminutes_retenuessum(releves_pris_en_compte)bigintélargi ennumeric→DecimalDecimalrefuse d'être multiplié par unfloat, etSEUIL_SEAU_IMPUTEen est un.La garde
if not minutes_retenues or not minutes_imputeesmasque la faute sur les seaux sans imputation. Elle n'en masquait aucun ce jour-là :Pourquoi la chaîne était verte
Deux raisons qui s'additionnent, et la première est la vraie :
tests/unit/api/conftest.pyrendaitminutes_retenuesetminutes_ecarteesenintnu, là où la base rend unDecimal— alors qu'elle prenait soin de modéliser lenumericdemoyenne_kw._seau_imputene voyait donc jamais le type qui casse ;tests/integration/test_lectures_tableau_de_bord.pyfait bien varier la part imputée autour du seuil sur un vrai PostgreSQL, mais il portepytestmark = pytest.mark.skipif(not URL, …)et la chaîne ne lance quepytest tests/unit(ci.yml:374). Le cas existe, il n'a jamais été exécuté.Ce que fait cette demande
_seau_imputeconvertit les deux comptes avant de comparer, avec le motif écrit dans la docstring ;Vérifié
Ce que cette demande ne fait PAS
Le trou de couverture — des cas d'intégration écrits mais jamais exécutés par la chaîne — dépasse ce correctif et n'est pas refermé ici. Il mérite son propre ticket : tant qu'il est ouvert, une régression de type peut repasser exactement par le même chemin.
Urgence
L'écran principal est mort et la soutenance EC02 est demain 11/09 à 10h45. À relire en priorité, puis à déployer sur
main.Les quatre fenêtres du tableau de bord rendaient 500 en production depuis le déploiement de main du 10/09 : l'écran affichait « Aucune source n'a répondu » sur les sept sites, les sept appels de courbe échouant ensemble. File "enervision_api/dashboard/repository.py", line 732, in _seau_impute return minutes_imputees >= SEUIL_SEAU_IMPUTE * minutes_retenues TypeError: unsupported operand type(s) for *: 'float' and 'decimal.Decimal' Les deux comptes n'ont pas le même type. « minutes_imputees » vient d'un count(*) filter, donc un bigint, donc un int. « minutes_retenues » vient d'un sum(releves_pris_en_compte) : PostgreSQL élargit la somme d'un bigint en numeric, et psycopg rend un Decimal. Or Decimal refuse d'être multiplié par un float, et SEUIL_SEAU_IMPUTE en est un. La garde « if not minutes_retenues or not minutes_imputees » masquait la faute sur les seaux sans imputation. Elle n'en masquait aucun ce jour-là : les vingt-cinq seaux de la fenêtre 24 h comme les huit de la fenêtre 7 j portaient tous au moins une minute reconstituée. Pourquoi la suite unitaire était verte : la fausse connexion rendait « minutes_retenues » et « minutes_ecartees » en int nu là où la base rend un Decimal — elle prenait pourtant soin de modéliser le numeric de « moyenne_kw ». Le jeu d'essai est corrigé, et il suffit à faire tomber les deux cas préexistants du #267 sur la ligne fautive. Un troisième cas nomme les deux types explicitement, pour que la couverture ne se perde pas si quelqu'un simplifie la fixture un jour. Le cas d'intégration qui aurait attrapé ceci existe (tests/integration/test_lectures_tableau_de_bord.py, la part imputée autour du seuil) mais ne tourne pas : il est marqué skipif sans base, et la chaîne ne lance que « pytest tests/unit ». Ce trou-là dépasse ce correctif et n'est pas refermé ici.Relu et rejoué en local :
pytest tests/unit/api→ 248 passed sur la branche, et 4 failed dès qu'on rétablit la ligne fautive (les 2 cas préexistants du #267 + le nouveau). La correction de la fixture qui rend enfinminutes_retenues/minutes_ecarteesenDecimalest ce qui redonne du mordant à la couverture.ruff+mypy --strictOK surrepository.py, les 6 jobs de la chaîne sont verts.Le correctif est juste : conversion des deux opérandes, robuste aussi pour le chemin 10 min où
minutes_retenuesest unint.OK pour fusionner et déployer sur
main. Le trou de couverture (cas d'intégration jamais exécutés par la chaîne) reste ouvert et mérite son propre ticket, comme le dit la demande.