Merge remote-tracking branch 'origin/dev' into feat/design-system
# Conflicts: # apps/frontend/src/app/features/auth/change-password/change-password.html # apps/frontend/src/app/features/auth/change-password/change-password.ts # apps/frontend/src/app/features/auth/login/login.html # apps/frontend/src/app/features/auth/login/login.ts
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
# 0005 - Modèle de prédiction de consommation : LightGBM
|
||||
|
||||
- Statut : accepté
|
||||
- Date : 2026-09-17
|
||||
|
||||
## Contexte
|
||||
|
||||
Le schéma `prediction` contraint déjà la forme de la solution (deux cibles de régression,
|
||||
`consumption_kw` instantané et `consumption_kwh` sur `period_minutes`, un statut
|
||||
`insufficient_data` à détecter explicitement), mais aucun modèle n'était choisi. Trois
|
||||
contraintes non négociables cadrent le choix, discutées dans l'issue #89 :
|
||||
|
||||
1. **EC06** (grille de notation individuelle) exige un modèle **entraîné, versionné avec
|
||||
MLflow**, exposé via un endpoint fonctionnel, avec **surveillance du drift** en production.
|
||||
2. **Aucun GPU dédié** : l'infra tourne on-premise sur une VM à 4 CPU / 8 Gio RAM (ou
|
||||
`Standard_B2s`/`B2ms` côté Azure, 2 vCPU max) — Azure Machine Learning est de toute façon
|
||||
bloqué par la politique Azure du projet.
|
||||
3. **Délai serré** : le jalon J3 arrive à échéance le lendemain de la décision, J4 concentre déjà
|
||||
26 issues sur 4 jours. Un modèle long à mettre en œuvre retarde la chaîne complète (service de
|
||||
scoring #37, moteur de recommandations #38, tests ML #44/#45, tous bloqués par ce choix).
|
||||
|
||||
Le jeu de données est déjà disponible (`all_sites_combined.csv`, fourni par le formateur) : 7
|
||||
sites, 2 ans au pas horaire (~17 500 lignes/site), avec `temperature_celsius`,
|
||||
`humidity_percent`, `solar_irradiance_wm2` en régresseurs exogènes et des features calendaires
|
||||
déjà dérivées.
|
||||
|
||||
## Options comparées
|
||||
|
||||
| Critère | Prophet | LightGBM/XGBoost | NeuralProphet | SARIMA | Holt-Winters | Mistral (LLM) |
|
||||
|---|---|---|---|---|---|---|
|
||||
| Saisonnalités multiples (jour/semaine/an) | Oui, nativement | Oui, via features engineered | Oui, nativement, + autorégression | Une seule, lourd à régler (SARIMAX) | Une seule, aucune | Non conçu pour ça |
|
||||
| Régresseurs exogènes | Oui, mais doivent être connus dans le futur au moment de la prédiction | Oui, via lags/moyennes glissantes sur le passé | Oui, natif | Difficile en multivarié | Aucun support | Contexte de prompt seulement, non appris |
|
||||
| Coût de calcul (VM sans GPU) | Faible | Faible | Élevé (deep learning) | Faible | Faible | Élevé à prohibitif |
|
||||
| Versionnable MLflow | Oui, nativement | Oui, nativement | Pas de support direct | Oui, générique | Pas de support direct | Rien à versionner (pas un modèle entraîné) |
|
||||
| Granularité | Un modèle par site (ou par site × métrique) | Un seul modèle global sur tous les sites | Un par site | Un par site | Un par site | — |
|
||||
| Effort avant l'échéance | Faible | Moyen (feature engineering) | Élevé | Moyen à élevé | Faible en soi | Élevé, ou factice |
|
||||
|
||||
## Décision
|
||||
|
||||
**LightGBM, un seul modèle global** couvrant tous les sites, plutôt qu'un modèle par site
|
||||
(Prophet) ou par famille de site. Cible : `consumption_kwh`, avec `period_minutes` comme feature
|
||||
d'entrée plutôt que comme étape d'agrégation post-prédiction. Suivi et versioning via **MLflow**
|
||||
(tracking + registre de modèles), sur le magasin local par défaut dans un premier temps —
|
||||
l'hébergement sur l'infra k3s reste une question ouverte, non bloquante pour démarrer.
|
||||
|
||||
Raisons retenues, au-delà du tableau ci-dessus :
|
||||
|
||||
- **Un modèle global plutôt qu'un modèle par site** évite la fragilité des sites les moins
|
||||
fournis en historique : ils bénéficient de ce qu'apprennent les autres sites, ce qu'un Prophet
|
||||
par site ne permet pas.
|
||||
- **Aucune dépendance à une prévision météo future.** Prophet exige que ses régresseurs
|
||||
(`add_regressor`) soient connus au moment prédit ; `temperature_celsius`,
|
||||
`humidity_percent` et `solar_irradiance_wm2` sont des mesures passées, pas des prévisions, et
|
||||
aucune source de prévision météo n'existe dans le projet. LightGBM s'en sort avec des features
|
||||
de lag/moyenne glissante calculées sur l'historique déjà présent dans `reading`, cf.
|
||||
`ml/enervision_ml/features.py` — un choix qui vaut aussi bien à l'entraînement qu'au futur
|
||||
scoring.
|
||||
- **Apprentissage direct sur `consumption_kwh`** avec `period_minutes` en feature, sans étape
|
||||
d'agrégation intermédiaire que la sortie continue de Prophet aurait demandée.
|
||||
- **Coût de calcul compatible avec l'infra on-premise sans GPU.**
|
||||
|
||||
Débat complet, comparatif détaillé et décision finale : issue #89 (Johan, phyri0s,
|
||||
ValentinDeFaria), actée en réunion d'équipe du 2026-09-17 et validée par l'ensemble de l'équipe.
|
||||
|
||||
## Conséquences
|
||||
|
||||
- Le pipeline d'entraînement (`ml/`, ce commit) lit `reading` + `site` par connexion PostgreSQL
|
||||
directe et construit ses features par lags/moyennes glissantes plutôt que par régresseurs
|
||||
contemporains, cf. `docs/ML-START.md`.
|
||||
- Le rôle PostgreSQL dédié `enervision_ml` (lecture seule sur `reading`/`site`) n'est pas encore
|
||||
provisionné : dette déjà assumée par l'ADR 0003 pour les comptes ETL/ML, `ML_DATABASE_URL`
|
||||
pointe pour l'instant vers la même base que le backend applicatif en développement.
|
||||
- Le service de scoring (#37), le moteur de recommandations (#38) et les tests de dérive
|
||||
(#44/#45) restent à construire ; ils consommeront le même module `enervision_ml.features`, qui
|
||||
doit rester strictement identique entre entraînement et scoring pour éviter un train/serve skew
|
||||
silencieux.
|
||||
- La surveillance de drift exigée par EC06 n'est pas encore implémentée : ce ticket ne livre que
|
||||
l'entraînement et son suivi MLflow (paramètres, métriques, artefact modèle), pas le monitoring
|
||||
en production.
|
||||
- L'hébergement de MLflow sur l'infra k3s reste une question ouverte ; le magasin SQLite local
|
||||
(`ml/mlflow.db`, ignoré par git) suffit pour l'instant à comparer des runs sur un poste.
|
||||
|
||||
## Alternatives écartées
|
||||
|
||||
- **Prophet** : proposition initiale, écartée après débat pour les raisons ci-dessus (modèle par
|
||||
site, dépendance à une météo future indisponible, agrégation kWh en post-traitement). Reste un
|
||||
candidat solide si un jour le projet doit produire une décomposition tendance/saisonnalité
|
||||
explicable pour un usage différent.
|
||||
- **Mistral (LLM)** : aucun produit dédié aux séries temporelles ; interroger un LLM généraliste
|
||||
ne constitue pas un modèle entraîné et versionnable au sens MLflow, et le fine-tuning est hors
|
||||
budget de calcul et hors délai.
|
||||
- **SARIMA** : ne gère pas nativement plusieurs régresseurs exogènes ; réglage (p,d,q,P,D,Q) plus
|
||||
long que le délai disponible.
|
||||
- **NeuralProphet** : fait tout ce que fait Prophet et apprend en plus des motifs autorégressifs,
|
||||
mais coûte plus cher en calcul (pas de GPU disponible) et n'a pas d'outil MLflow direct — piste
|
||||
d'évolution possible, non engageante à ce stade.
|
||||
- **Holt-Winters** : écarté d'entrée, pas seulement différé — aucun support de régresseurs
|
||||
exogènes, alors que la météo et l'irradiance sont nécessaires ici.
|
||||
- **CatBoost** : même famille que LightGBM, gère nativement les colonnes catégorielles (comme
|
||||
`site_type`) sans encodage manuel. Non rejeté, différé : candidat à comparer si LightGBM
|
||||
plafonne en précision.
|
||||
@@ -74,9 +74,10 @@ collecteur ne vient le lire.
|
||||
|
||||
| Domaine | Technologie | Emplacement | Statut | Ce qui existe réellement |
|
||||
|---|---|---|---|---|
|
||||
| Backend | FastAPI, Python 3.14 | `apps/backend` | `En cours` | Factory, configuration, journalisation, 2 sondes de santé, `/metrics`, contrat OpenAPI versionné, routes `sites` et `recommendations` en lecture (endpoints → services → repositories → models) |
|
||||
| Backend | FastAPI, Python 3.14 | `apps/backend` | `En cours` | Factory, configuration, journalisation, 2 sondes de santé, `/metrics`, contrat OpenAPI versionné, routes `sites`, `alerts`, `recommendations`, `stats/summary` et `readings` en lecture (endpoints → services → repositories → models) |
|
||||
| Frontend | Angular 22, Node 24 | `apps/frontend` | `En cours` | Tableau de bord sur route `/dashboard`, deux services HTTP, graphiques Chart.js, données servies par des fixtures |
|
||||
| Base | PostgreSQL 17 + TimescaleDB | `db` | `Fait` | Bootstrap de l'extension, base de test, chaîne Alembic. Schéma applicatif créé (`site`, `dataset`, `reading` en hypertable, `prediction`, `alert`, `recommendation`) |
|
||||
| ML | LightGBM, MLflow | `ml` | `En cours` | Pipeline d'entraînement (features par lags/moyennes glissantes, baseline de persistance saisonnière, suivi MLflow local), voir [ADR 0005](../adr/0005-modele-prediction-lightgbm.md) et [ML-START.md](../../ML-START.md). Scoring, endpoint et surveillance de dérive pas encore construits |
|
||||
| Infra | Terraform, k3s single-node | `infra/terraform` | `En cours` | Module d'installation du cluster. Jamais appliqué, aucune ressource Kubernetes déclarée |
|
||||
| Monitoring | Prometheus, Grafana, Alertmanager | `monitoring` | `Cible` | Rien, hors le `/metrics` exposé par l'API |
|
||||
| ETL | Apache Airflow | `etl/airflow` | `Cible` | Rien |
|
||||
|
||||
@@ -146,6 +146,7 @@ Deux fichiers d'environnement, deux usages : `.env` à la racine alimente `docke
|
||||
| GET | `/api/v1/recommendations` | Liste les recommandations. `lecteur` | 401, 403, 500 |
|
||||
| GET | `/api/v1/recommendations/{recommendation_id}` | Décrit une recommandation. `lecteur` | 401, 403, 404, 422, 500 |
|
||||
| GET | `/api/v1/stats/summary` | Résume la consommation instantanée du parc. `lecteur` | 401, 403, 500 |
|
||||
| GET | `/api/v1/readings` | Historique des lectures, filtrable par `site_id`, fenêtre `start`/`end` (24h par défaut, 90 jours maximum) et paginé par `limit`/`offset`. `lecteur` | 400, 401, 403, 422, 500 |
|
||||
| GET | `/api/v1/sensors/status` | État de santé des capteurs par site, dérivé de la dernière lecture. `admin` | 401, 403, 500 |
|
||||
| GET | `/metrics` | Format Prometheus, hors du schéma. Jeton requis si `APP_METRICS_TOKEN` est posé | |
|
||||
| GET | `/docs`, `/redoc`, `/openapi.json` | Hors du schéma. Fermés en `staging` et en `prod` | |
|
||||
@@ -159,21 +160,33 @@ Les codes de la dernière colonne sont ceux que le schéma **déclare**, et le f
|
||||
donc de modifier la liste dans ce fichier de test.
|
||||
|
||||
`GET /sites` et `GET /sites/{site_id}` sont la première route métier, et le gabarit repris pour
|
||||
`GET /alerts` puis pour les suivantes (`reading`, `dataset`, `prediction`, `recommendation`) : les
|
||||
quatre couches `endpoints → services → repositories → models` y sont toutes présentes, sur des
|
||||
tables déjà créées par la révision Alembic `e6d2026091501`. Elles n'exigent que le rôle `lecteur`,
|
||||
contrairement aux routes d'administration qui exigent `admin`. `SiteRepository` lit par
|
||||
`AsyncSession.scalar()` (une ligne) et `AsyncSession.scalars()` (plusieurs lignes) plutôt que par
|
||||
`execute()`, ce qui la rend testable par la fixture `fake_session` au niveau endpoint sans base
|
||||
réelle. `GET /recommendations` et `GET /recommendations/{recommendation_id}` reprennent le même
|
||||
gabarit à la lettre, `recommendation_id` étant un entier plutôt qu'un texte. Une recommandation ne
|
||||
porte pas `site_id` : elle remonte à un site par sa seule `alert_id`, `alert` n'étant pas encore
|
||||
exposée. `GET /stats/summary` et `GET /sensors/status` agrègent chacune deux repositories
|
||||
(`SiteRepository`, `ReadingRepository`) dans un service dédié plutôt que d'exposer une table :
|
||||
elles n'entrent donc pas dans ce gabarit route-par-table. Le contrat détaillé pour le frontend est
|
||||
dans
|
||||
`GET /alerts` puis pour les suivantes (`dataset`, `prediction`) : les quatre couches
|
||||
`endpoints → services → repositories → models` y sont toutes présentes, sur des tables déjà créées
|
||||
par la révision Alembic `e6d2026091501`. Elles n'exigent que le rôle `lecteur`, contrairement aux
|
||||
routes d'administration qui exigent `admin`. `SiteRepository` lit par `AsyncSession.scalar()` (une
|
||||
ligne) et `AsyncSession.scalars()` (plusieurs lignes) plutôt que par `execute()`, ce qui la rend
|
||||
testable par la fixture `fake_session` au niveau endpoint sans base réelle. `GET /recommendations`
|
||||
et `GET /recommendations/{recommendation_id}` reprennent le même gabarit à la lettre,
|
||||
`recommendation_id` étant un entier plutôt qu'un texte. Une recommandation ne porte pas `site_id` :
|
||||
elle remonte à un site par sa seule `alert_id`, `alert` n'étant pas encore exposée. `GET
|
||||
/stats/summary` et `GET /sensors/status` agrègent chacune deux repositories (`SiteRepository`,
|
||||
`ReadingRepository`) dans un service dédié plutôt que d'exposer une table : elles n'entrent donc
|
||||
pas dans ce gabarit route-par-table. Le contrat détaillé pour le frontend est dans
|
||||
[31-contrat-authentification.md](31-contrat-authentification.md).
|
||||
|
||||
`GET /readings` reprend le même gabarit mais s'en écarte sur un point : `reading` est l'hypertable,
|
||||
donc la seule table métier pouvant porter des années d'historique, ce que `docs/architecture/
|
||||
owasp-traceabilite.md` documentait comme un risque ouvert (API4, aucune pagination plafonnée ni
|
||||
fenêtre temporelle maximale). `ReadingService` porte donc une couche de validation absente des
|
||||
autres routes de lecture : `start`/`end` sont optionnels (24 dernières heures par défaut si les
|
||||
deux sont omis, l'un défaut par rapport à l'autre sinon), l'écart entre les deux est plafonné à 90
|
||||
jours (`FENETRE_MAXIMALE`), et `limit`/`offset` (défaut 500, plafond 2000) empêchent qu'une fenêtre
|
||||
large mais peu dense reste malgré tout coûteuse. Un dépassement de plafond répond `400` (règle
|
||||
métier, portée par le service) plutôt que `422` (réservé à la validation structurelle de FastAPI,
|
||||
par exemple `limit` hors bornes). Un datetime sans fuseau dans `start`/`end` est traité comme de
|
||||
l'UTC plutôt que rejeté : le comparer tel quel à `reading.timestamp` (`timestamptz`) échouerait
|
||||
côté pilote, en `500` plutôt qu'un refus propre.
|
||||
|
||||
### `/health/ready`
|
||||
|
||||
Cette sonde porte une garde décrite dans l'[ADR 0001](../adr/0001-postgresql-timescaledb.md) : un
|
||||
@@ -249,7 +262,7 @@ Les modèles de `app/schemas/errors.py` décrivent ce que les gestionnaires renv
|
||||
### Ajouter une route métier
|
||||
|
||||
Checklist pour toute nouvelle route sur le gabarit `sites`/`alerts`/`recommendations`/`stats`/
|
||||
`sensors` (`reading`, `dataset`, `prediction`) :
|
||||
`readings`/`sensors` (`dataset`, `prediction`) :
|
||||
|
||||
1. Composer ses `responses=` depuis `app/api/openapi.py` : `REPONSES_LECTEUR`/`REPONSES_ADMIN`
|
||||
au niveau de l'`include_router()` du routeur, `REPONSE_VALIDATION` et les codes locaux
|
||||
@@ -326,7 +339,9 @@ Trois fichiers méritent d'être connus avant de toucher à l'authentification :
|
||||
agir sur le site B. C'est la limite connue du modèle, et le risque BOLA du top 10 API.
|
||||
- **Rôles PostgreSQL cantonnés** pour l'ETL et le travail d'apprentissage, plus le `REVOKE` sur
|
||||
`audit_log`. Dette assumée, décrite dans les ADR 0003 et 0004.
|
||||
- **Pagination et fenêtrage** des lectures de séries temporelles, qui conditionnent la forme des
|
||||
endpoints métier. Sans plafond dur, une requête sur dix ans d'historique suffit à faire tomber
|
||||
l'API.
|
||||
- **Pagination et fenêtrage** : posés sur `GET /readings` (fenêtre plafonnée à 90 jours,
|
||||
`limit`/`offset` plafonné à 2000), mais toujours en `limit`/`offset` simple — pas de curseur ni
|
||||
de plan de secours si un `offset` élevé sur une fenêtre dense devient lent en pratique.
|
||||
`statement_timeout` reste absent au niveau de la connexion, donc rien n'empêche une requête
|
||||
individuelle de tourner longtemps si les plafonds au-dessus d'elle s'avéraient insuffisants.
|
||||
- **Politique de versionnement de l'API** au-delà du préfixe `/api/v1`.
|
||||
|
||||
@@ -20,6 +20,8 @@ gérer : il suffit d'envoyer les requêtes avec `withCredentials`.
|
||||
| POST | `/api/v1/auth/logout` | cookie | `204` |
|
||||
| POST | `/api/v1/auth/logout-all` | jeton d'accès | `204` |
|
||||
| POST | `/api/v1/auth/password` | jeton d'accès | `200` `TokenResponse` |
|
||||
| POST | `/api/v1/auth/forgot-password` | aucune | `202` (toujours, que le compte existe ou non) |
|
||||
| POST | `/api/v1/auth/reset-password` | aucune (jeton dans le corps) | `200` `TokenResponse` |
|
||||
| GET | `/api/v1/auth/me` | jeton d'accès | `200` `PrincipalResponse` |
|
||||
| GET | `/api/v1/users` | jeton d'accès, `admin` | `200` `UserResponse[]` |
|
||||
| POST | `/api/v1/users` | jeton d'accès, `admin` | `201` `TemporaryPasswordResponse` |
|
||||
@@ -51,7 +53,17 @@ codes d'erreur ci-dessous reste la référence de comportement, le schéma celle
|
||||
}
|
||||
|
||||
// POST /auth/password
|
||||
{ "current_password": "...", "new_password": "..." } // 12 à 128 caractères
|
||||
{ "current_password": "...", "new_password": "..." } // 8 à 128 caractères, au moins 1 majuscule, 1 minuscule, 1 chiffre, 1 caractère spécial
|
||||
|
||||
// POST /auth/forgot-password
|
||||
{ "email": "operateur@enervision.fr" }
|
||||
// Répond toujours 202, sans corps, que le compte existe, soit inactif, ou soit inconnu.
|
||||
|
||||
// POST /auth/reset-password
|
||||
{ "token": "...", "new_password": "..." } // même règle de complexité que /auth/password
|
||||
// Le jeton vient du lien reçu par email, valable 15 minutes, à usage unique. Répond
|
||||
// TokenResponse au succès (l'appareil qui pose le nouveau mot de passe reste connecté), ou 400
|
||||
// si le jeton est invalide, déjà utilisé, ou expiré.
|
||||
```
|
||||
|
||||
Le secret de rafraîchissement **n'apparaît jamais** dans le corps de la réponse.
|
||||
@@ -70,6 +82,9 @@ Le secret de rafraîchissement **n'apparaît jamais** dans le corps de la répon
|
||||
| `403` avec `detail: "Droits insuffisants"` | rôle trop bas | masquer ou griser l'action, ne pas déconnecter |
|
||||
| `403` sur `/auth/refresh`, `/logout`, `/logout-all`, `/password` | origine hors liste autorisée (voir « Origines autorisées ») | erreur de configuration réseau, pas un cas à gérer par l'utilisateur |
|
||||
| `422` | corps invalide | le détail donne `champ` et `type`, jamais la valeur envoyée |
|
||||
| `429` sur `/auth/forgot-password` | trop de demandes | afficher l'attente, l'en-tête `Retry-After` donne les secondes |
|
||||
| `400` sur `/auth/reset-password` | lien invalide, déjà utilisé, ou expiré | inviter à redemander un lien depuis `/forgot-password` |
|
||||
| `403` sur `/auth/reset-password` | origine hors liste autorisée | erreur de configuration réseau, pas un cas à gérer par l'utilisateur |
|
||||
|
||||
## Les quatre règles qui comptent
|
||||
|
||||
|
||||
@@ -231,3 +231,73 @@ et ne sont pas considérées comme des alertes actuelles.
|
||||
- Les mesures API ne sont pas rattachées à un dataset historique.
|
||||
- Une alerte peut être associée à une prévision du même site.
|
||||
- Une alerte peut donner lieu à plusieurs recommandations.
|
||||
|
||||
## Ingestion des données historiques
|
||||
|
||||
Le MVP EnerVision initialise les données énergétiques à partir du dataset fourni dans le cadre du projet.
|
||||
|
||||
Le dataset de référence contient 122 647 mesures issues de 7 sites et couvre la période du 1er janvier 2023 au 31 décembre 2024.
|
||||
|
||||
Les fichiers sources CSV et JSON sont nécessaires uniquement pour l'initialisation des données. Ils ne sont pas versionnés dans Git et sont placés localement dans `data/raw/`.
|
||||
|
||||
### Architecture du flux
|
||||
|
||||
```text
|
||||
Dataset CSV + métadonnées JSON
|
||||
|
|
||||
v
|
||||
historical_import.py
|
||||
|
|
||||
+------+------+
|
||||
| |
|
||||
v v
|
||||
Validation SHA-256
|
||||
| Traçabilité
|
||||
+------+------+
|
||||
|
|
||||
v
|
||||
Normalisation
|
||||
+ qualité data
|
||||
|
|
||||
v
|
||||
Chargement par batches
|
||||
|
|
||||
v
|
||||
PostgreSQL / TimescaleDB
|
||||
| | |
|
||||
v v v
|
||||
dataset site reading
|
||||
```
|
||||
|
||||
Le pipeline est développé en Python.
|
||||
|
||||
Pandas est utilisé pour l'extraction, la validation et la préparation des données. SQLAlchemy Async assure le chargement transactionnel dans PostgreSQL/TimescaleDB.
|
||||
|
||||
Une empreinte SHA-256 permet d'identifier le dataset utilisé et d'assurer sa traçabilité.
|
||||
|
||||
Les valeurs manquantes sont conservées pendant l'ingestion afin de préserver les données sources. Aucune imputation n'est réalisée à cette étape.
|
||||
|
||||
Le chargement des mesures est effectué par batches de 1 000 lignes.
|
||||
|
||||
Les données provenant du dataset CSV sont identifiées par `source = "csv"` et associées à leur `dataset_id`.
|
||||
|
||||
### Résultats validés
|
||||
|
||||
Le chargement de référence a permis d'obtenir :
|
||||
|
||||
- 1 dataset ;
|
||||
- 7 sites ;
|
||||
- 122 647 mesures ;
|
||||
- 0 doublon détecté dans le dataset source.
|
||||
|
||||
L'idempotence a également été vérifiée par une deuxième exécution du pipeline : aucune nouvelle mesure n'a été créée et le nombre de `reading` est resté à 122 647.
|
||||
|
||||
La procédure détaillée d'installation, d'exécution, de validation et de contrôle du pipeline est disponible dans `etl/README.md`.
|
||||
|
||||
### Évolution prévue
|
||||
|
||||
L'étape suivante consiste à orchestrer les traitements Data avec Apache Airflow.
|
||||
|
||||
L'orchestration réutilisera la logique ETL existante afin de séparer la logique de traitement de la planification, du suivi des exécutions et de la gestion des erreurs.
|
||||
|
||||
Le pipeline servira ensuite de base à la préparation des données nécessaires au modèle de Machine Learning.
|
||||
|
||||
@@ -22,6 +22,7 @@ lecture seule ; plusieurs lignes resteront à compléter une fois les endpoints
|
||||
| Argon2id m=19456 t=2 p=1, re-hachage passif quand les paramètres changent | `app/core/hashing.py` | A02 Cryptographic Failures, A07 Identification and Authentication Failures |
|
||||
| Message et temps de réponse identiques quelle que soit la cause de l'échec, haché leurre sur adresse inconnue | `app/services/auth.py` | A07, API2 |
|
||||
| Limitation de débit à fenêtre glissante sur trois clés, évaluée avant le hachage | `app/services/auth.py`, `app/repositories/login_attempt.py` | A07, API4 Unrestricted Resource Consumption |
|
||||
| `GET /readings` : fenêtre temporelle plafonnée à 90 jours (24h par défaut), `limit`/`offset` plafonné à 2000, refus `400` si la fenêtre est inversée ou trop large | `app/services/reading.py` | API4 |
|
||||
| Absence de verrouillage de compte, qui serait un déni de service | ADR 0002 | API4 |
|
||||
| Jeton de rafraîchissement opaque, haché en base, rotation avec détection de réutilisation | `app/services/auth.py`, `app/repositories/refresh_token.py` | A07, API2 |
|
||||
| Séparation structurelle accès / rafraîchissement, impossible à confondre | ADR 0002 | API2 |
|
||||
@@ -50,7 +51,7 @@ règles Bandit. Ajouter Bandit à la CI serait redondant, contrairement à ce qu
|
||||
| Item | État | Raison |
|
||||
|---|---|---|
|
||||
| **API1 Broken Object Level Authorization** | **ouvert** | Les rôles sont globaux, il n'y a pas de portée par site : `GET /sites/{site_id}` et `GET /recommendations/{recommendation_id}` répondent à tout compte `lecteur` pour n'importe quel site ou recommandation, sans vérifier une affectation compte-site qui n'existe pas encore. Un opérateur du site A pourra agir sur le site B dès que les endpoints d'écriture métier existeront. Correctif prévu : table d'affectation compte-site, contrôle d'appartenance dans la même dépendance que le contrôle de rôle. |
|
||||
| **API4, lectures de séries temporelles** | **ouvert** | Pas encore d'endpoint métier, donc ni pagination plafonnée, ni fenêtre temporelle maximale, ni `statement_timeout`. C'est la façon la plus probable dont la démonstration tombera : une requête sur dix ans d'historique suffit. |
|
||||
| **API4, lectures de séries temporelles** | **partiel** | `GET /readings` plafonne la fenêtre temporelle (90 jours) et la pagination (`limit` ≤ 2000), voir plus haut. Reste ouvert : pagination en `limit`/`offset` simple plutôt qu'en curseur (un `offset` élevé sur une fenêtre dense reste coûteux), et aucun `statement_timeout` au niveau de la connexion pour borner une requête individuelle si les plafonds au-dessus s'avéraient insuffisants. |
|
||||
| **API8 Security Misconfiguration, transport** | **ouvert** | Pas de TLS, donc ni HSTS, ni cookie `Secure` réellement posé en production. Ils appartiennent au terminateur TLS, qui n'existe pas. |
|
||||
| **API10 Unsafe Consumption of APIs** | **ouvert, et spécifique à ce projet** | L'API Mock de l'école n'a aucune authentification, tourne en HTTP clair sur le réseau de l'école, et expose un endpoint mutatif à quiconque. Sa réponse doit être traitée comme une entrée hostile : bornes physiques, taille de tableau plafonnée, timeout, et frontière d'anti-corruption. La conséquence la plus sérieuse n'est pas la fausse alerte, c'est l'empoisonnement du jeu d'entraînement du modèle de prédiction. |
|
||||
| **A08 Software and Data Integrity Failures** | **partiel** | La CI vérifie le code mais n'analyse ni les dépendances ni les images. `.terraform.lock.hcl` reste ignoré par git, ce qui contredit une chaîne d'approvisionnement maîtrisée. |
|
||||
|
||||
Reference in New Issue
Block a user