Rapatrie dans dev les mises à jour de dépendances mergées sur main :
#142 (SonarQube ignoré pour Dependabot), #129 checkout v7, #130 setup-uv v7,
#132 setup-node v7, #128 upload-artifact v7, #131 download-artifact v8,
#127 Node 26 (image et CI frontend), #126 nginx 1.31-alpine, #133 pandas 3.0.6
et ruff 0.16.8, #134 Angular 22.1.7, jsdom 30, prettier 3.9.8 (TypeScript 6 et
vitest 4 conservés, majeures ignorées côté Dependabot).
Conflit README.md : statuts « En place » de main, « Quatre DAGs » de dev (#138).
L'image frontend passe sur node:26-alpine3.22 ; la CI restait sur Node 24
et ne validait donc plus l'interpréteur qui construit le SPA. Angular 22.1.8
accepte >=26.0.0 ; Node 26 devient LTS le 28 octobre 2026, Node 24 passe en
maintenance le 20.
Le tag de l'image y est cité deux fois (présentation, commande nginx -t) ;
il suit celui de docker-compose.prod.yml. Le runner du Dockerfile frontend
est une image durcie dhi.io, suivie séparément, et n'est pas concerné.
Tant qu'Angular épingle typescript (>=6.0 <6.1) et vitest (^4), une majeure
de l'un ou l'autre casse npm ci. Dependabot arrête de les proposer ; à lever
quand Angular relâchera ces bornes.
@angular/build 22.1.8 exige typescript >=6.0 <6.1 et vitest ^4.0.8 : le
groupe Dependabot proposait TypeScript 7.0.2 et vitest 5.0.1, et npm ci
sortait en ERESOLVE. Le commit précédent avait ramené TypeScript à ~6.0.2
sans régénérer le verrou, qui pointait encore 7.0.2.
Verrou régénéré avec npm 11.19.0 (packageManager). Conservés : Angular
22.1.7, jsdom 30.1.0, prettier 3.9.8, @vitest/coverage-v8 aligné sur vitest.
GitHub ne fournit pas les secrets du dépôt aux workflows déclenchés par
dependabot[bot] : SONAR_TOKEN arrive vide et le scan échoue sans rien
analyser, ce qui marque rouge toutes les PR de mise à jour de dépendances.
Les jobs de build et de tests du workflow restent joués sur ces PR.
Le DAG `historical_import` arrivé par #138 lit `./data/raw`, monté en lecture seule dans
le scheduler. Le dossier est vide dans un clone : sans dépôt manuel des fichiers, le DAG
n'a rien à charger sur la VM.
`make stack-up` enchaîne `alembic upgrade head` dans le conteneur backend. Rien ne
migrait la base sur le chemin de déploiement, et `/api/v1/health/ready`, qui ne teste
que la connexion et l'extension TimescaleDB, aurait laissé passer un déploiement vert
sur une base sans schéma applicatif.
deploy.yml borne le job à 30 minutes et sort les journaux du backend et du proxy quand
la sonde échoue. provision-host.sh rappelle la création du premier administrateur, et
la propriété de /srv/enervision sans laquelle le runner ne peut ni manipuler les clones
ni lire un `.env` en 600.
Les statuts de livraison continue repassent à `En cours` : le code est écrit, la machine
n'est pas provisionnée, le runner n'est pas enregistré, rien n'a été déployé. À basculer
sur `Fait` au premier déploiement vert. Décompte des jobs corrigé, 18 et non 17.
Refs #21, #22
`make dev` ne lançait que le backend et le frontend : la base, Mailpit et
Airflow restaient à démarrer à la main, et les tables `prediction`, `alert` et
`recommendation` vides laissaient les vues correspondantes sans rien à afficher.
- `services-up` démarre les conteneurs, `db-wait` attend la base.
- `db-ensure-airflow` crée la base de métadonnées Airflow quand le volume
`pgdata` est antérieur à `db/init/120-airflow-database.sql` : `db/init` ne
rejoue qu'à la première initialisation, et `airflow-init` bouclait dessus.
- `demo-data` renseigne prédictions, alertes et recommandations si elles
manquent, en ancrant scoring et détection au 31/12/2024 (`DEMO_NOW`), fin du
jeu historique, plutôt qu'à l'horloge réelle.
- `ml-score` accepte `NOW=`, les cibles hors conteneur reçoivent
`ML_DATABASE_URL` dérivé du `.env`.
`AlertRepository.create_many` envoyait un `INSERT` d'un seul tenant. À douze
colonnes par alerte, le plafond asyncpg de 32 767 paramètres tombe à 2 730
lignes : une détection sur une fenêtre chargée échouait en `InterfaceError`,
et le DAG `alertes` avec elle.
Reprend le patron déjà en place dans `RecommendationRepository.create_missing`.
`.ev-table-card` et le `:host` de `ev-card`, compilé en `[_nghost-…]`, ont la
même spécificité. Les styles de composant sont injectés dans le head après la
feuille globale : le padding de la carte gagnait, et le tableau de la liste des
sites comme celui des prévisions perdaient leur mise à plat. Le sélecteur
d'élément `ev-card.ev-table-card` tranche, comme `_auth-page.scss` le fait déjà.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Un projet Compose par environnement sur la même machine : ports du proxy et origine
publique en variables dans docker-compose.prod.yml, réglage mémoire des deux bases
TimescaleDB et du webserver Airflow. Le workflow deploy.yml déploie dev en recette et
main en production depuis un runner installé sur la VM, jamais sur pull_request.
scripts/provision-host.sh prépare les deux dossiers, secrets et certificats compris,
sans rien démarrer.
L'image frontend quitte dhi.io/nginx, registre authentifié dont personne n'a l'accès,
pour nginx:1.28-alpine : elle n'avait jamais été construite.
ADR 0009, vues infra et CI/CD, README et .env.example mis à jour.
Refs #21, #22
Route, paramètres ?site= et ?alert=, jointure côté client et sa raison (pas de
site_id sur une recommandation, aucun filtre sur GET /recommendations,
/alerts non paginé), génération réservée aux admins, points d'entrée.
La vue détail pose app-recommendation-list restreint au site consulté (les
alertes sont demandées avec site_id) et renvoie vers la vue complète préfiltrée
sur ce site. Le spec fournit les deux services du composant enfant et vérifie le
filtre transmis.
Page derrière authGuard, ouverte à tous les rôles : filtre site (.form-select,
présélectionné par ?site=), focus sur une alerte par ?alert= (entier strictement
positif, sinon ignoré), et pour les admins un bouton « Générer les
recommandations » qui appelle POST /recommendations/generate sur le site filtré,
affiche le bilan accordé en nombre et recharge la liste. Route ajoutée dans
app.routes.ts, lien « Recommandations » dans la navigation du tableau de bord.
Une recommandation ne porte que alert_id et GET /recommendations n'a aucun
filtre : le composant charge en parallèle les alertes (filtrées par site quand
`siteId` est fourni) et toutes les recommandations, puis les joint côté client
(joinByAlert, fonction pure testée à part) en groupes par alerte, du plus récent
au plus ancien. Chaque groupe montre le contexte de l'alerte (sévérité, type,
site, horodatage, message) puis ses actions avec l'explication et la règle.
L'input `alertId` réduit la vue à une alerte et la met en évidence ; `reload()`
rejoue les deux appels. Un échec de l'un des deux vide tout : une demi-jointure
tromperait.
Types alignés sur RecommendationResponse et RecommendationGenerationResponse
du backend. RecommendationsService couvre GET /recommendations,
GET /recommendations/{id} et POST /recommendations/generate?site_id= (réservé
admin côté API). recommendation-presentation.ts traduit les sept règles connues
du moteur et retombe sur la référence brute pour une règle inconnue, la
politique de renommage en -v2 de l'ADR 0006 l'impose.
30-frontend.md : le mode fixtures est inactif dans les deux environnements
(la doc affirmait l'inverse), services et pages manquants, widget alertes et
états de chargement, strict activé. 32-design-systeme-frontend.md : tokens
typographiques, ev-icon, app-alert-feed, classes de tableau.
L'afterEach forçait le drapeau à true alors qu'il vaut false dans les deux
environnements : avec isolate désactivé, tout spec joué ensuite sur
/stats/summary ou /alerts aurait reçu une fixture au lieu d'atteindre
HttpTestingController.
- ligne d'état « Actualisé à HH:mm:ss · N sites suivis » avec un point animé,
figé sous prefers-reduced-motion
- indicateurs en cartes : libellé en capitales, grand nombre en chiffres
tabulaires, filet supérieur, ombre au survol ; la barre de charge moyenne
passe au jaune à 70 % et au rouge à 90 %, avec un texte d'aide
- grille à deux colonnes : graphique de charge et prévisions à gauche, flux
d'alertes en colonne collante à droite, une colonne sous 900 px
- prévisions présentées en tableau (site, prévision, échéance, lien détail)
- liens de navigation de l'en-tête restylés en pastilles, par SCSS seulement :
le bloc HTML de navigation est inchangé octet pour octet
- styles de tableau sortis dans _tables.scss (.ev-table, .ev-table-card), la
liste des sites les adopte au lieu de sa copie locale