Nouveau module app.etl.reading_retention : pour chaque chunk de `reading` entièrement plus
vieux que APP_READING_RETENTION_DAYS (1095 jours), export CSV gzip reproductible vers Garage
(SSE-C, sha256 en métadonnées), relecture et comparaison, puis drop_chunks ciblé sur ce seul
chunk dans une transaction dédiée. --dry-run. Réglages APP_S3_* optionnels, jamais exigés par
l'API. DAG Airflow `retention` quotidien à 03h20. 44 tests unitaires sans réseau ni base,
tests d'intégrité du DAG, vérification --help dans l'image Airflow en CI. Docs 40-data et
20-backend.
Closes#36
L'API Mock est le seul item OWASP API10 du projet, et ce script en est le
premier consommateur. Des quatre garde-fous exigés par la traçabilité OWASP,
seul le timeout était en place.
- plafonne la taille des réponses : MAX_SITES sites, au plus --limit mesures ;
- borne chaque grandeur physique par PHYSICAL_BOUNDS, une valeur hors plage,
d'un type inattendu, NaN ou infinie devenant NULL avec sa raison dans
null_reasons et data_quality à degraded ;
- ne recopie vers la base que les champs attendus, via build_site_row() et
build_reading_row(), au lieu de passer les dictionnaires de l'API en
paramètres SQL ;
- écarte une data_quality que ck_reading_quality refuserait, plutôt que de
faire échouer le lot entier ;
- nomme la cible du ON CONFLICT, qui avalait jusqu'ici toute violation
d'unicité, y compris celle de la clé primaire.
raw_data conserve la réponse d'origine intacte : rien n'est perdu, seule son
exploitation est bornée.