#78 — data-deletion-apdp
PLANIFIÉ
Priorité: 🟠 HAUTE · Type: TYPE C · Conteneur: rgz-beat · Code: app/tasks/apdp.py
Dépendances: #4 rgz-db, #46 module-apdp
Description
Tâche Celery d'anonymisation automatique des données personnelles des abonnés ayant exercé leur droit à l'oubli (APDP Bénin). Exécutée chaque jour à 05:00 UTC, traite toutes les demandes dont le délai de 90 jours est dépassé.
Conformément à la loi APDP du Bénin, un abonné peut demander la suppression de ses données personnelles. RGZ dispose de 90 jours pour s'y conformer. Cette tâche automatise la conformité en anonymisant les champs sensibles tout en préservant les données agrégées nécessaires aux obligations légales (ARCEP, comptabilité).
Chaque anonymisation est tracée dans immutable_logs (#45) pour preuve de conformité.
Architecture Interne
Celery Beat → rgz.apdp.purge_expired → 05:00 UTC daily
↓
SELECT FROM subscribers
WHERE forget_requested_at < NOW() - INTERVAL '90 days'
AND anonymized_at IS NULL
↓
Pour chaque abonné:
UPDATE subscribers SET
msisdn = 'ANONYMIZED_' || id,
msisdn_e164 = 'ANONYMIZED_' || id,
full_name = 'ANONYMIZED',
id_document_hash = 'ANONYMIZED',
anonymized_at = NOW()
WHERE id = subscriber_id;
↓
DELETE FROM subscriber_devices WHERE subscriber_id = id;
↓
INSERT INTO immutable_logs (event_type, data, hash, prev_hash)
VALUES ('APDP_ANONYMIZATION', {subscriber_id, requested_at, anonymized_at}, ...);
↓
Rapport: N abonnés anonymisés, log dans ELKDonnées anonymisées vs préservées
| Champ | Action | Raison |
|---|---|---|
msisdn | Anonymisé | Donnée personnelle |
msisdn_e164 | Anonymisé | Donnée personnelle |
full_name | Anonymisé | Donnée personnelle |
id_document_hash | Anonymisé | Donnée personnelle |
subscriber_ref | Conservé | Référence comptable |
total_sessions | Conservé | Stats agrégées ARCEP |
total_data_mb | Conservé | Stats agrégées ARCEP |
created_at | Conservé | Obligation légale |
subscriber_devices | Supprimé | MACs = données perso |
Configuration
APDP_RETENTION_DAYS=90 # Délai légal avant anonymisation
APDP_BATCH_SIZE=100 # Abonnés traités par batch
DATABASE_URL=postgresql://rgz:password@rgz-db:5432/rgz
CELERY_BROKER_URL=redis://:password@rgz-redis:6379/0Celery Task
# app/tasks/apdp.py
@app.task(name='rgz.apdp.purge_expired', acks_late=True)
def purge_expired_apdp_requests():
"""
Anonymise les données des abonnés dont la demande APDP
date de plus de 90 jours.
"""
cutoff = datetime.utcnow() - timedelta(days=settings.APDP_RETENTION_DAYS)
# SELECT subscribers WHERE forget_requested_at < cutoff AND anonymized_at IS NULL
# UPDATE + DELETE + INSERT immutable_logSchedule (app/celery_app.py)
'rgz.apdp.purge_expired': {
'task': 'rgz.apdp.purge_expired',
'schedule': crontab(hour=5, minute=0), # 05:00 UTC daily
'options': {'queue': 'rgz.compliance'}
},Commandes Utiles
# Lancer manuellement (test)
docker exec rgz-beat celery -A app.celery_app call rgz.apdp.purge_expired
# Vérifier demandes en attente
docker exec rgz-db psql -U postgres -c \
"SELECT id, subscriber_ref, forget_requested_at,
NOW() - forget_requested_at AS age,
CASE WHEN NOW() - forget_requested_at > INTERVAL '90 days'
THEN 'DUE' ELSE 'PENDING' END AS status
FROM subscribers
WHERE forget_requested_at IS NOT NULL AND anonymized_at IS NULL
ORDER BY forget_requested_at;"
# Vérifier anonymisations effectuées
docker exec rgz-db psql -U postgres -c \
"SELECT COUNT(*), DATE(anonymized_at) FROM subscribers
WHERE anonymized_at IS NOT NULL
GROUP BY DATE(anonymized_at) ORDER BY 2 DESC LIMIT 30;"
# Voir logs immutables APDP
docker exec rgz-db psql -U postgres -c \
"SELECT created_at, data->>'subscriber_id' as subscriber_id
FROM immutable_logs
WHERE event_type = 'APDP_ANONYMIZATION'
ORDER BY created_at DESC LIMIT 10;"Sécurité
| Règle | Implémentation |
|---|---|
| Atomicité | Transaction DB — tout ou rien par abonné |
| Idempotence | WHERE anonymized_at IS NULL — rejeu sûr |
| Traçabilité | Chaque anonymisation → immutable_logs (SEC-08) |
| Logs | LL#27: max-size 10m max-file 3 |
| APDP | Délai configurable via APDP_RETENTION_DAYS |
Implémentation TODO
- [ ]
app/tasks/apdp.py— tâche Celery anonymisation - [ ] Migration: ajout colonnes
forget_requested_at,anonymized_atàsubscribers - [ ] Transaction atomique par abonné
- [ ] Insert
immutable_logsaprès chaque anonymisation - [ ] Rapport journalier: N anonymisations effectuées → ELK
- [ ] Alerte si demande APDP en retard >85 jours (buffer avant 90j)
- [ ] Tests unitaires (mock DB, vérifier champs anonymisés)
Dernière mise à jour: 2026-02-21