docs: documente l'ingestion depuis l'API Mock
This commit is contained in:
+307
-22
@@ -2,9 +2,10 @@
|
||||
|
||||
## Objectif
|
||||
|
||||
Le pipeline ETL EnerVision permet d'intégrer les données énergétiques historiques dans PostgreSQL/TimescaleDB.
|
||||
Le pipeline ETL EnerVision permet d'intégrer les données énergétiques dans PostgreSQL/TimescaleDB à partir de deux sources :
|
||||
|
||||
Cette première étape du pipeline Data permet de charger le dataset fourni dans le cadre du projet, contenant les mesures énergétiques de 7 sites sur la période du 1er janvier 2023 au 31 décembre 2024.
|
||||
- le dataset historique CSV/JSON fourni dans le cadre du projet ;
|
||||
- l'API Mock EnerVision.
|
||||
|
||||
Le pipeline assure :
|
||||
|
||||
@@ -12,12 +13,15 @@ Le pipeline assure :
|
||||
- la validation de leur structure et de leur cohérence ;
|
||||
- la normalisation des données nécessaires au stockage ;
|
||||
- le suivi de la qualité des données ;
|
||||
- la traçabilité du dataset importé ;
|
||||
- la traçabilité des données importées ;
|
||||
- le chargement des données dans PostgreSQL/TimescaleDB ;
|
||||
- la conservation des valeurs manquantes et des informations de qualité ;
|
||||
- l'idempotence du chargement afin d'éviter la création de doublons.
|
||||
|
||||
## Données sources
|
||||
|
||||
### Dataset historique
|
||||
|
||||
Le dataset est fourni par le formateur dans le cadre du projet EnerVision.
|
||||
|
||||
Il contient les deux fichiers suivants :
|
||||
@@ -29,7 +33,7 @@ dataset_metadata.json
|
||||
|
||||
Ces fichiers sont nécessaires une seule fois pour initialiser les données historiques de l'environnement.
|
||||
|
||||
Ils ne sont pas versionnés dans Git. Chaque membre de l'équipe récupère manuellement une fois les fichiers fournis par le formateur et les place dans :
|
||||
Ils ne sont pas versionnés dans Git. Chaque membre de l'équipe récupère manuellement les fichiers fournis par le formateur et les place dans :
|
||||
|
||||
```text
|
||||
data/raw/
|
||||
@@ -47,14 +51,26 @@ data/
|
||||
|
||||
Le fichier `.gitkeep` est versionné afin de conserver le répertoire `data/raw/` dans Git. Les fichiers CSV et JSON sont ignorés par Git.
|
||||
|
||||
### API Mock
|
||||
|
||||
La deuxième source est l'API Mock EnerVision.
|
||||
|
||||
Elle permet de récupérer :
|
||||
|
||||
- les informations des sites avec `GET /api/v1/sites` ;
|
||||
- les mesures simulées avec `GET /api/v1/readings`.
|
||||
|
||||
L'API Mock est utilisée pour compléter les données historiques avec des mesures simulées récupérées sur une période donnée.
|
||||
|
||||
## Technologies utilisées
|
||||
|
||||
| Technologie | Utilisation |
|
||||
|---|---|
|
||||
| Python | Développement du pipeline ETL |
|
||||
| Pandas | Lecture, validation et transformation des données |
|
||||
| JSON | Lecture des métadonnées du dataset |
|
||||
| hashlib / SHA-256 | Identification, intégrité et traçabilité du dataset |
|
||||
| Pandas | Lecture, validation et transformation du dataset historique |
|
||||
| JSON | Lecture des métadonnées et conservation des données sources |
|
||||
| HTTPX | Appels HTTP asynchrones vers l'API Mock |
|
||||
| hashlib / SHA-256 | Identification, intégrité et traçabilité du dataset historique |
|
||||
| SQLAlchemy Async | Connexion et chargement asynchrone en base |
|
||||
| PostgreSQL | Stockage relationnel |
|
||||
| TimescaleDB | Stockage des séries temporelles énergétiques |
|
||||
@@ -62,11 +78,14 @@ Le fichier `.gitkeep` est versionné afin de conserver le répertoire `data/raw/
|
||||
| Alembic | Gestion des migrations du schéma |
|
||||
| uv | Gestion et exécution de l'environnement Python |
|
||||
| Ruff | Contrôle de la qualité du code |
|
||||
| mypy | Vérification du typage |
|
||||
| Pytest | Tests automatisés |
|
||||
|
||||
## Fonctionnement du pipeline
|
||||
# Import du dataset historique
|
||||
|
||||
Le script principal d'import se trouve dans :
|
||||
## Fonctionnement du pipeline historique
|
||||
|
||||
Le script d'import se trouve dans :
|
||||
|
||||
```text
|
||||
apps/backend/app/etl/historical_import.py
|
||||
@@ -207,7 +226,7 @@ Valeurs manquantes identifiées :
|
||||
| `humidity_percent` | 3 423 |
|
||||
| `solar_irradiance_wm2` | 3 964 |
|
||||
|
||||
## Exécution en dry-run
|
||||
## Exécution historique en dry-run
|
||||
|
||||
Depuis le dossier :
|
||||
|
||||
@@ -227,7 +246,7 @@ uv run python -m app.etl.historical_import `
|
||||
|
||||
Aucune donnée n'est écrite dans la base pendant cette exécution.
|
||||
|
||||
## Chargement réel
|
||||
## Chargement historique réel
|
||||
|
||||
Depuis `apps/backend/` :
|
||||
|
||||
@@ -249,7 +268,7 @@ Chargement : 2000/122647
|
||||
Chargement : 122647/122647
|
||||
```
|
||||
|
||||
## Résultats obtenus
|
||||
## Résultats obtenus pour le dataset historique
|
||||
|
||||
Après le chargement initial, les contrôles en base ont confirmé :
|
||||
|
||||
@@ -266,7 +285,7 @@ Le premier import a créé :
|
||||
nouvelles lectures : 122647
|
||||
```
|
||||
|
||||
## Idempotence
|
||||
## Idempotence du dataset historique
|
||||
|
||||
Le pipeline a été exécuté une deuxième fois avec exactement le même dataset afin de vérifier son idempotence.
|
||||
|
||||
@@ -280,7 +299,7 @@ nouvelles lectures : 0
|
||||
|
||||
Une nouvelle exécution du même import ne crée donc pas de mesures supplémentaires pour le dataset testé.
|
||||
|
||||
## Vérifications SQL
|
||||
## Vérifications SQL du dataset historique
|
||||
|
||||
Depuis la racine du projet, vérifier le nombre d'enregistrements avec :
|
||||
|
||||
@@ -302,21 +321,213 @@ Vérifier la source des mesures avec :
|
||||
docker compose exec db psql -U enervision -d enervision -c "SELECT source, COUNT(*) FROM reading GROUP BY source ORDER BY source;"
|
||||
```
|
||||
|
||||
Résultat attendu :
|
||||
Résultat attendu pour le dataset historique :
|
||||
|
||||
```text
|
||||
csv | 122647
|
||||
```
|
||||
|
||||
## Tests et qualité
|
||||
# Import depuis l'API Mock
|
||||
|
||||
Les tests automatisés du pipeline sont situés dans :
|
||||
## Fonctionnement
|
||||
|
||||
Le script d'import de l'API Mock se trouve dans :
|
||||
|
||||
```text
|
||||
apps/backend/app/etl/mock_api_import.py
|
||||
```
|
||||
|
||||
Le flux est le suivant :
|
||||
|
||||
```text
|
||||
API Mock
|
||||
|
|
||||
+-----+------+
|
||||
| |
|
||||
v v
|
||||
/sites /readings
|
||||
| |
|
||||
+-----+------+
|
||||
|
|
||||
v
|
||||
mock_api_import.py
|
||||
|
|
||||
v
|
||||
Transformation
|
||||
+ qualité data
|
||||
|
|
||||
v
|
||||
PostgreSQL / TimescaleDB
|
||||
| |
|
||||
v v
|
||||
site reading
|
||||
```
|
||||
|
||||
Le pipeline commence par récupérer les sites avec :
|
||||
|
||||
```text
|
||||
GET /api/v1/sites
|
||||
```
|
||||
|
||||
Il récupère ensuite les mesures de chaque site avec :
|
||||
|
||||
```text
|
||||
GET /api/v1/readings
|
||||
```
|
||||
|
||||
Les paramètres envoyés à `/api/v1/readings` sont :
|
||||
|
||||
```text
|
||||
site_id
|
||||
start_time
|
||||
end_time
|
||||
limit
|
||||
```
|
||||
|
||||
Le paramètre `limit` doit être compris entre 1 et 1000.
|
||||
|
||||
## Configuration de l'API Mock
|
||||
|
||||
La connexion à l'API Mock est configurée avec les variables d'environnement suivantes :
|
||||
|
||||
```text
|
||||
APP_MOCK_API_BASE_URL
|
||||
APP_MOCK_API_USERNAME
|
||||
APP_MOCK_API_PASSWORD
|
||||
APP_MOCK_API_TIMEOUT_SECONDS
|
||||
```
|
||||
|
||||
Les identifiants réels ne sont pas versionnés dans Git.
|
||||
|
||||
Les fichiers `.env.example` indiquent uniquement les variables nécessaires à l'exécution.
|
||||
|
||||
## Transformation des mesures API
|
||||
|
||||
Les mesures provenant de l'API Mock sont enregistrées dans `reading` avec :
|
||||
|
||||
```text
|
||||
source = "api_history"
|
||||
dataset_id = NULL
|
||||
```
|
||||
|
||||
Les mesures provenant de l'API ne sont donc pas rattachées à un dataset historique.
|
||||
|
||||
Le timestamp reçu depuis l'API est converti en `datetime` avec timezone avant le chargement.
|
||||
|
||||
La réponse source est conservée dans :
|
||||
|
||||
```text
|
||||
raw_data
|
||||
```
|
||||
|
||||
afin de préserver la donnée reçue et faciliter la traçabilité.
|
||||
|
||||
## Qualité des données API
|
||||
|
||||
Les valeurs `NULL` fournies par l'API sont conservées telles quelles.
|
||||
|
||||
Une valeur manquante n'est pas transformée en zéro et la mesure n'est pas supprimée.
|
||||
|
||||
Le pipeline conserve également :
|
||||
|
||||
```text
|
||||
data_quality
|
||||
null_reasons
|
||||
```
|
||||
|
||||
Les niveaux de qualité possibles sont :
|
||||
|
||||
```text
|
||||
good
|
||||
partial
|
||||
degraded
|
||||
critical
|
||||
```
|
||||
|
||||
Aucune imputation n'est réalisée pendant l'ingestion :
|
||||
|
||||
```text
|
||||
imputed_values = NULL
|
||||
imputation_method = NULL
|
||||
```
|
||||
|
||||
Cette stratégie permet de distinguer une véritable valeur nulle ou manquante d'une consommation égale à zéro et de conserver les informations liées aux défaillances de capteurs.
|
||||
|
||||
## Dry-run de l'API Mock
|
||||
|
||||
Le mode `--dry-run` permet de tester la connexion, la récupération des sites et la récupération des mesures sans écrire dans PostgreSQL.
|
||||
|
||||
Depuis `apps/backend/` :
|
||||
|
||||
```powershell
|
||||
uv run python -m app.etl.mock_api_import `
|
||||
--start-time "2024-06-15T12:00:00" `
|
||||
--end-time "2024-06-15T13:00:00" `
|
||||
--limit 60 `
|
||||
--dry-run
|
||||
```
|
||||
|
||||
## Chargement réel depuis l'API Mock
|
||||
|
||||
Depuis `apps/backend/` :
|
||||
|
||||
```powershell
|
||||
uv run python -m app.etl.mock_api_import `
|
||||
--start-time "2024-06-15T12:00:00" `
|
||||
--end-time "2024-06-15T13:00:00" `
|
||||
--limit 60
|
||||
```
|
||||
|
||||
## Résultat validé pour l'API Mock
|
||||
|
||||
Le scénario de validation utilisé couvre la période :
|
||||
|
||||
```text
|
||||
15/06/2024 12:00 UTC
|
||||
à
|
||||
15/06/2024 13:00 UTC
|
||||
```
|
||||
|
||||
avec une limite de 60 lectures par site.
|
||||
|
||||
Résultat obtenu :
|
||||
|
||||
```text
|
||||
sites récupérés : 7
|
||||
lectures par site : 60
|
||||
lectures récupérées : 420
|
||||
source : api_history
|
||||
dataset_id : NULL
|
||||
```
|
||||
|
||||
Les contrôles effectués directement dans PostgreSQL/TimescaleDB ont confirmé :
|
||||
|
||||
- l'enregistrement des mesures dans `reading` ;
|
||||
- la présence des 7 sites ;
|
||||
- `source = "api_history"` ;
|
||||
- `dataset_id = NULL` ;
|
||||
- la conservation des valeurs `NULL` ;
|
||||
- la conservation de `data_quality` ;
|
||||
- la conservation de `null_reasons` ;
|
||||
- la conservation de la donnée source dans `raw_data`.
|
||||
|
||||
## Idempotence de l'import API Mock
|
||||
|
||||
Le même import a été exécuté plusieurs fois afin de vérifier qu'une mesure déjà présente n'est pas créée une seconde fois.
|
||||
|
||||
L'idempotence repose sur la contrainte d'unicité de la table `reading` et sur la gestion des conflits lors de l'insertion.
|
||||
|
||||
Un test d'intégration automatisé vérifie également ce comportement.
|
||||
|
||||
# Tests et qualité
|
||||
|
||||
Les tests automatisés des pipelines ETL sont situés dans :
|
||||
|
||||
```text
|
||||
apps/backend/tests/etl/
|
||||
```
|
||||
|
||||
Ils couvrent notamment :
|
||||
Les tests de l'import historique couvrent notamment :
|
||||
|
||||
- la validation du dataset ;
|
||||
- les colonnes obligatoires ;
|
||||
@@ -328,22 +539,96 @@ Ils couvrent notamment :
|
||||
- la construction des mesures destinées à la BDD ;
|
||||
- le respect des contraintes du modèle de données.
|
||||
|
||||
Les tests de l'import API Mock couvrent notamment :
|
||||
|
||||
- la récupération des sites ;
|
||||
- l'appel à `/api/v1/readings` ;
|
||||
- les paramètres `site_id`, `start_time`, `end_time` et `limit` ;
|
||||
- la gestion des erreurs HTTP ;
|
||||
- la validation du format de la réponse ;
|
||||
- la transformation des mesures ;
|
||||
- la conservation des valeurs `NULL` ;
|
||||
- la conservation de `data_quality` et `null_reasons` ;
|
||||
- `source = "api_history"` ;
|
||||
- `dataset_id = NULL` ;
|
||||
- la conservation de `raw_data` ;
|
||||
- l'idempotence du chargement.
|
||||
|
||||
Exécuter les tests ETL :
|
||||
|
||||
```powershell
|
||||
uv run pytest tests\etl -v
|
||||
```
|
||||
|
||||
Exécuter les tests unitaires de l'import API Mock :
|
||||
|
||||
```powershell
|
||||
uv run pytest tests\etl\test_mock_api_import.py -v
|
||||
```
|
||||
|
||||
Exécuter le test d'intégration de l'import API Mock :
|
||||
|
||||
```powershell
|
||||
uv run pytest tests\etl\test_mock_api_import.py -m integration -v
|
||||
```
|
||||
|
||||
Contrôler la qualité du code :
|
||||
|
||||
```powershell
|
||||
uv run ruff check app\etl tests\etl
|
||||
```
|
||||
|
||||
## Suite du pipeline Data
|
||||
Contrôler le typage :
|
||||
|
||||
L'import historique constitue la première brique du pipeline Data EnerVision.
|
||||
```powershell
|
||||
uv run mypy app
|
||||
```
|
||||
|
||||
La prochaine étape consiste à orchestrer les traitements ETL avec Apache Airflow, puis à préparer les données nécessaires à l'entraînement du modèle de Machine Learning.
|
||||
Exécuter la suite complète avec le seuil de couverture :
|
||||
|
||||
Airflow sera utilisé comme orchestrateur des traitements existants et ne remplacera pas la logique métier déjà implémentée dans le pipeline ETL.
|
||||
```powershell
|
||||
uv run pytest --cov-fail-under=85
|
||||
```
|
||||
|
||||
Lors de la validation de l'import API Mock :
|
||||
|
||||
```text
|
||||
8 tests unitaires passés
|
||||
1 test d'intégration passé
|
||||
```
|
||||
|
||||
La suite backend complète a également été validée avec une couverture supérieure au seuil de 85 %.
|
||||
|
||||
# Suite du pipeline Data
|
||||
|
||||
Deux sources de données sont maintenant prises en charge :
|
||||
|
||||
```text
|
||||
Dataset CSV/JSON
|
||||
|
|
||||
v
|
||||
historical_import.py
|
||||
|
|
||||
+-----------------+
|
||||
|
|
||||
v
|
||||
PostgreSQL / TimescaleDB
|
||||
^
|
||||
|
|
||||
+-----------------+
|
||||
|
|
||||
mock_api_import.py
|
||||
^
|
||||
|
|
||||
API Mock
|
||||
```
|
||||
|
||||
La logique d'extraction, de transformation et de chargement est donc disponible pour les deux sources de données du MVP.
|
||||
|
||||
La prochaine étape consiste à orchestrer ces traitements avec Apache Airflow.
|
||||
|
||||
Airflow permettra de planifier les traitements, gérer leur ordre d'exécution, suivre leur état et remonter les erreurs.
|
||||
|
||||
Airflow ne remplacera pas la logique ETL Python existante. Les scripts actuels resteront responsables de l'extraction, de la validation, de la transformation et du chargement.
|
||||
|
||||
Le pipeline Data servira ensuite à préparer les données nécessaires au modèle de Machine Learning.
|
||||
Reference in New Issue
Block a user