#70 — rapport-sla-mensuel
PLANIFIÉ
Priorité: 🟠 HAUTE · Type: C (Celery) · Conteneur: rgz-beat · Code: app/tasks/reports.pyDépendances: #43 sla-probe-engine, #10 rgz-beat, #63 email-notification
Description
Rapport SLA mensuel automatique généré à J+5 du mois suivant (ex. le 5 février pour janvier). Ce rapport synthétise la qualité de service fournie à chaque revendeur : uptime global du réseau, latence P95, incidents du mois, temps moyen de résolution, et éventuels crédits SLA dus en cas de violation du contrat.
Le rapport est généré en PDF beauté (avec logos, couleurs ACCESS) et envoyé automatiquement par email au revendeur (#63). Un tableau de bord JSON accompagne le PDF pour intégration dans les dashboards custom revendeur (#51).
Objectifs métier :
- Transparence totale sur la qualité de service
- Justification des crédits SLA automatiques (#25)
- Evidence pour ARCEP (traceabilité uptime/incidents)
- Engagement client : "nous mesons et rapportons"
Architecture Interne
Flux de Génération
Tous les jours 05:30 UTC:
↓
Celery Beat déclenche rgz.reports.sla_monthly
↓
Vérifier si J+5 du mois (ex. 5 février pour janvier)
↓
SI OUI:
1. Requête TimescaleDB:
- SLA results: SELECT * FROM sla_results WHERE month=DATE_TRUNC('month', current_date - INTERVAL 1 MONTH)
- Incidents: SELECT * FROM incidents WHERE created_at IN [month_start, month_end)
- Uptime%: COUNT(success) / COUNT(*) * 100
- Latency P95: PERCENTILE_CONT(0.95) latency_ms
2. Calcul crédits:
- SI uptime < 99.5% → crédit = (99.5 - uptime) * 0.05 * MIR
- Enregistrer dans credits_sla table
3. Rendu PDF avec WeasyPrint:
- Template: templates/reports/sla_monthly.html
- Injection données: uptime%, P95, incidents, crédits
- Branding: logo revendeur (#18), couleurs ACCESS
4. Email: SMTP → revendeur + NOC
5. Archive: /var/reports/sla_monthly_{reseller_id}_{year}_{month}.pdfSchéma de Données
-- Table source (créée par #43)
TABLE sla_results:
id UUID PK
reseller_id UUID FK
nas_id TEXT
probe_timestamp TIMESTAMP
response_time_ms INT
is_success BOOLEAN
probe_type (ICMP|TCP)
-- Table analytique (créée par #70)
TABLE sla_monthly_report:
id UUID PK
reseller_id UUID FK
year INT
month INT
uptime_percent DECIMAL(5,2)
latency_p95_ms INT
incident_count INT
incident_duration_total_minutes INT
credit_fcfa DECIMAL(12,2)
generated_at TIMESTAMP
pdf_path TEXT
email_sent_at TIMESTAMP
UNIQUE(reseller_id, year, month)
-- Crédits SLA (consulté par #25)
TABLE credits_sla:
id UUID PK
reseller_id UUID FK
sla_monthly_report_id UUID FK
credit_amount_fcfa DECIMAL(12,2)
reason TEXT (ex: "Uptime 98.2% < 99.5% threshold")
applied_date DATE
invoice_id UUID FK (lien vers facture)Contenu du Rapport PDF
┌─────────────────────────────────────┐
│ RAPPORT SLA MENSUEL — JANVIER │
│ Revendeur: Tech Connect Abomey │
│ Période: 01/01 — 31/01/2026 │
└─────────────────────────────────────┘
📊 RÉSUMÉ EXÉCUTIF
Uptime global: 99.8% ✓ (seuil: 99.5%)
Latence P95: 42ms ✓
Incidents: 2 (durée totale: 45 minutes)
Crédit SLA appliqué: 0 FCFA (performance respectée)
📈 DÉTAILS PAR SITE
┌─────────────────────────────────┐
│ Site: Abomey Centre │
│ NAS-ID: access_tech_connect_s1 │
│ Uptime: 99.9% | P95: 38ms │
│ Incidents: 0 | Crédit: 0 FCFA │
└─────────────────────────────────┘
⚠️ INCIDENTS DU MOIS
01/15 — 14:30 : Perte connectivité NAS (P1)
Durée: 25 minutes | Cause: Perte route BGP
Résolution: Basculement redundancy
01/28 — 09:15 : Dégradation CPU NAS (P2)
Durée: 20 minutes | Cause: Spike trafic
Résolution: Throttle débit
📋 CONTRAT SLA
Uptime garanti: 99.5% (3.6h downtime max/mois)
Latence max P95: 150ms
Temps réparation P1: 1h (RTO)
Crédit: 5% MIR par 0.1% shortfall
✅ CONFORMITÉ
SLA respecté ✓ | Incidents documentés ✓ | Crédits calculés ✓
─────────────────────────────────────
Généré: 2026-02-05 à 05:30 UTC
Prochaine révision: 2026-03-05Configuration
# Rapports SLA — Génération
SLA_REPORT_DAY_OF_MONTH=5 # Jour de génération (J+5)
SLA_REPORT_TIME_UTC=05:30 # Heure déclenchement
SLA_UPTIME_THRESHOLD=99.5 # Seuil garanti (%)
SLA_LATENCY_P95_THRESHOLD_MS=150 # Latence max P95
SLA_CREDIT_PERCENTAGE=5 # Crédit par 0.1% shortfall
# Rapports — Hébergement
REPORTS_OUTPUT_DIR=/var/reports
REPORTS_RETENTION_DAYS=365
REPORT_TEMPLATE_DIR=/app/templates/reports
# Email
SMTP_HOST=smtp.rgz.local
SMTP_PORT=587
SMTP_USER=noreply@rgz.local
SMTP_PASSWORD=***
EMAIL_FROM=noreply@rgz.local
EMAIL_SUBJECT_SLA="Rapport SLA Mensuel — {reseller_name} — {month}/{year}"Endpoints API
| Méthode | Route | Description | Réponse |
|---|---|---|---|
| GET | /api/v1/reports/sla?reseller_id={uuid}&month=1&year=2026 | Récupérer rapport JSON | {uptime_percent, latency_p95_ms, incident_count, credit_fcfa, generated_at} |
| GET | /api/v1/reports/sla/{report_id} | Détails complets | JSON complet |
| GET | /api/v1/reports/sla/{report_id}/pdf | Télécharger PDF | PDF binary |
| GET | /api/v1/resellers/{reseller_id}/reports/sla?months=12 | Historique SLA 12 mois | List[SlaReportSummary] |
| POST | /api/v1/reports/sla/regenerate?month=1&year=2026 | Régénérer rapport (admin) | 202 Accepted |
Sécurité API:
- SEC-01 IDOR : revendeur ne peut voir que ses rapports
- Administrateur peut voir tous les rapports
Celery Task
| Champ | Valeur |
|---|---|
| Task name | rgz.reports.sla_monthly |
| Schedule | Daily 05:30 UTC (cron exécution) |
| Queue | rgz.reports |
| Timeout | 600s (10 minutes) |
| Retry | 3x avec backoff exponentiel |
| Log | INFO "SLA report generated" ou ERROR "SLA generation failed" |
Logique Celery:
@app.task(name='rgz.reports.sla_monthly', bind=True)
def generate_sla_monthly_report(self):
"""
Génère rapports SLA pour tous les revendeurs
du mois précédent à J+5 du mois courant
"""
# 1. Vérifier si J+5 du mois
today = datetime.utcnow()
if today.day != settings.SLA_REPORT_DAY:
logger.info("Not SLA report day, skipping")
return {'status': 'skipped'}
# 2. Récupérer tous revendeurs actifs
resellers = db.query(Reseller).filter(
Reseller.status == ResellerStatus.active
).all()
# 3. Pour chaque revendeur
results = []
for reseller in resellers:
try:
report = _generate_sla_report_for_reseller(
reseller.id,
month=today.month - 1,
year=today.year
)
# 4. Générer PDF
pdf_path = _render_sla_pdf(report, reseller)
# 5. Envoyer email
send_email.delay(
to=reseller.primary_contact_email,
subject=f"Rapport SLA {today.strftime('%B %Y')}",
template='sla_monthly_email',
context={'report': report}
)
results.append({'reseller_id': reseller.id, 'status': 'success'})
except Exception as e:
logger.error(f"Failed SLA report {reseller.id}: {e}")
results.append({'reseller_id': reseller.id, 'status': 'failed'})
self.retry(exc=e, countdown=300) # Retry in 5min
logger.info(f"SLA monthly: {len(results)} reports processed")
return {'status': 'completed', 'reports': results}Commandes Utiles
# Déclencher SLA mensuel manuellement (admin)
docker-compose exec rgz-api celery -A app.celery_app call rgz.reports.sla_monthly
# Récupérer rapport JSON derniers 3 mois
curl -H "Authorization: Bearer {token}" \
"http://api-rgz.duckdns.org/api/v1/reports/sla?reseller_id={uuid}&months=3"
# Télécharger PDF rapport janvier 2026
curl -H "Authorization: Bearer {token}" \
"http://api-rgz.duckdns.org/api/v1/reports/sla/pdf?year=2026&month=1" \
-o sla_janvier_2026.pdf
# Vérifier queue celery reports
docker-compose exec rgz-redis redis-cli llen celery:rgz.reports
# Logs dernières générations
docker-compose logs -f rgz-beat | grep "rgz.reports.sla_monthly"
# Archiver rapports anciens (>1 an)
find /var/reports/sla_* -mtime +365 -deleteImplémentation TODO
- [ ] Schéma TimescaleDB
sla_monthly_report+ index month/reseller_id - [ ] Tâche Celery
rgz.reports.sla_monthlydansapp/tasks/reports.py - [ ] Template Jinja2
templates/reports/sla_monthly.html - [ ] WeasyPrint CSS pour PDF branding ACCESS
- [ ] Endpoints API GET/POST /api/v1/reports/sla*
- [ ] Fonction
_generate_sla_report_for_reseller()(query TimescaleDB) - [ ] Fonction
_render_sla_pdf()(WeasyPrint) - [ ] Intégration #63
send_email.delay()pour distribution - [ ] Tests: données synthétiques (uptime 99.8%, incidents, crédits)
- [ ] Monitoring: alerte si rapport pas généré J+5
- [ ] Documentation: schéma SLA, contrats, calcul crédits
Dernière mise à jour: 2026-02-21