Skip to content

#71 — rapport-arcep

PLANIFIÉ

Priorité: 🔴 CRITIQUE · Type: C (Celery) · Conteneur: rgz-beat · Code: app/tasks/arcep.pyDépendances: #47 arcep-reporting, #10 rgz-beat


Description

Rapport de conformité réglementaire ARCEP (Autorité de Régulation de la Poste et des Communications, Bénin) généré automatiquement tous les trimestres. Non-négociable pour la légalité du réseau RGZ.

Ce rapport consolide les données obligatoires pour un opérateur télécomunications FAI au Bénin :

  • Abonnés uniques (COUNT DISTINCT subscriber_id)
  • Volume de données (sum bytes_in+out par trimestre, en To)
  • Couverture géographique (8 villes principales avec % uptime par site)
  • Incidents > 1h (P0/P1 avec timeline, cause, résolution)
  • Traçabilité de sécurité : MAC address + MSISDN + timestamp + NAS-ID (immuable SHA-256)

Généré en PDF selon template ARCEP officiel, avec dossier complet (sécurité + incidents + SLA). Soumission via API ARCEP portail (ou email backup si API indisponible).

Impact légal: Violation délai → amende 10M FCFA + suspension licence. Priorité CRITIQUE.

Architecture Interne

Flux de Génération Trimestrielle

Every Q1/Q2/Q3/Q4 transition:

Celery Beat déclenche rgz.arcep.quarterly

Vérifier trimestre (Q4 = 01/01, Q1 = 01/04, Q2 = 01/07, Q3 = 01/10)

Générer dossier conformité (3-4 fichiers PDF):
  1. arcep_q{N}_{year}_summary.pdf      (synthèse 10 pages)
  2. arcep_q{N}_{year}_security.pdf     (traçabilité MAC)
  3. arcep_q{N}_{year}_incidents.pdf    (détail P0/P1)
  4. arcep_q{N}_{year}_sites.pdf        (coverage par ville)

Signature SHA-256 + timestamp

Upload ARCEP Portal API v2 (oauth2 credentials)

Sauvegarde locale + logs immutables (#45)

Email confirmation NOC

Schéma de Données

sql
-- Tables source (creées ailleurs)
TABLE subscribers:
  id UUID | subscriber_ref | msisdn | status | created_at

TABLE radius_sessions:
  id UUID | subscriber_id | bytes_in BIGINT | bytes_out BIGINT | session_start | session_stop

TABLE incidents:
  id UUID | priority | title | created_at | resolved_at | description

TABLE subscriber_devices:
  subscriber_id UUID | mac_address TEXT | first_seen_at TIMESTAMP

-- Table de tracking (créée par #71)
TABLE arcep_quarterly_submissions:
  id UUID PK
  year INT
  quarter INT (1-4)
  submission_date TIMESTAMP
  dossier_path TEXT (ex: /arcep/2026/q1/)
  summary_pdf TEXT (ex: arcep_q1_2026_summary.pdf)
  security_pdf TEXT (ex: arcep_q1_2026_security.pdf)
  incidents_pdf TEXT
  sites_pdf TEXT
  submission_status CHECK(draft|submitted|accepted|rejected|failed)
  arcep_reference_number TEXT (après acceptation)
  submission_hash_sha256 TEXT
  signed_at TIMESTAMP
  UNIQUE(year, quarter)

Contenu PDF 1 : Summary (10 pages)

ARCEP — RAPPORT TRIMESTRIEL
RGZ S.A. — Opérateur FAI Bénin
Trimestre: Q1 2026 (01/01 — 31/03/2026)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. SYNTHÈSE EXÉCUTIVE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Abonnés actifs (end of quarter):        487
Volume data (trimestre):                12.3 To (DL 8.9, UL 3.4)
ARPU moyen:                             45,000 FCFA
Uptime global:                          99.7%
Incidents > 1h (P0/P1):                 2

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2. STATISTIQUES PAR RÉSEAU
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Revendeur              Abonnés  Volume(GB)  Uptime   Incidents
─────────────────────────────────────────────────────────────
Tech Connect          142      3,240      99.9%    0
Kossou WiFi           98       2,150      99.5%    1
Digital Benin         65       1,890      99.8%    0
Abomey Connect        45       980        99.2%    1
[...]
TOTAL                487      12,260     99.7%    2

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
3. COUVERTURE GÉOGRAPHIQUE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Cotonou (principal):           Uptime 99.9%, 234 abonnés
Abomey:                        Uptime 99.6%, 87 abonnés
Parakou:                       Uptime 99.4%, 56 abonnés
Lokoja:                        Uptime 99.8%, 45 abonnés
Natitingou:                    Uptime 99.1%, 32 abonnés
Ouidah:                        Uptime 99.7%, 22 abonnés
Tchetti:                       Uptime 99.5%, 11 abonnés
Porto-Novo:                    Uptime 99.8%, 28 abonnés

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
4. CONFORMITÉ RÉGLEMENTAIRE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✓ Traçabilité AAA complète (RADIUS logs)
✓ SHA-256 immutable logs (12 mois)
✓ Sauvegardes quotidiennes (3x30j)
✓ DNSSEC enabled
✓ Encryption TLS 1.3
✓ Incidents documentés (RCA disponible)
✓ Pas de bascules non autorisées
✓ Pas de fuites données client

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5. INCIDENTS > 1h (DETAILS COMPLETS EN PDF 3)

01/15 — Perte BGP (P0)
  Durée: 25 minutes (< 1h, INFO seulement)

[Aucun incident >= 1h ce trimestre]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
6. DÉCLARATION D'EXACTITUDE

Je certifie sur l'honneur que les données ci-dessus sont exactes
et conformes à nos logs immuables, audités régulièrement.

PDG : [Signature électronique e-signing.bj]
Date: 2026-04-01

Contenu PDF 2 : Security (Traçabilité)

ARCEP — TRAÇABILITÉ SECURITY
RGZ S.A. — Q1 2026

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
LOGS IMMUABLES (12 DERNIERS MOIS)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Format: MSISDN | MAC Address | NAS-ID | Timestamp | Bytes In | Bytes Out

0197979964 | AA:BB:CC:DD:EE:FF | access_tech_connect_s1 | 2026-01-15T14:30:42Z | 512000 | 256000
0197979965 | 11:22:33:44:55:66 | access_kossou         | 2026-01-15T14:35:10Z | 768000 | 384000
[... 10,000s de lignes]

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
INTÉGRITÉ SHA-256
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Logs Period:        2025-01-15 — 2026-01-15
Total Entries:      487,234
Hash SHA-256:       f7e42c9f5a3e4b2d8c1f6a9e2b4d7c8f (immutable storage)
Last Entry Hash:    3d4f5e6a7b8c9d0e1f2a3b4c5d6e7f8a
Merkle Root:        abc123def456...
Verification:       ✓ PASSED (audited 2026-01-20)

Contenu PDF 3 : Incidents

ARCEP — DÉTAIL INCIDENTS > 1h
RGZ S.A. — Q1 2026

[Ce trimestre: 0 incident >= 1h]

Historique P0/P1 < 1h (reportage optionnel):

2026-01-15 — 14:30 Perte BGP route (P0)
  Durée: 25 minutes
  Cause: Configuration erreur NAS-Cotonou switch
  Impact: ~50 abonnés sans accès
  Résolution: Bascule route secondaire (auto)
  RCA: /api/v1/incidents/incident-id/rca

Configuration

env
# ARCEP Reporting
ARCEP_SUBMISSION_ENABLED=true
ARCEP_API_ENDPOINT=https://arcep-portal.bj/api/v2/submissions
ARCEP_CLIENT_ID=rgz-sa-12345
ARCEP_CLIENT_SECRET=***
ARCEP_OAUTH_TOKEN_URL=https://arcep-portal.bj/oauth/token

# Trimestres : Q1=01/04, Q2=01/07, Q3=01/10, Q4=01/01
ARCEP_Q1_DEADLINE=04/10   # 10 jours avant fin Q2
ARCEP_Q2_DEADLINE=07/10
ARCEP_Q3_DEADLINE=10/10
ARCEP_Q4_DEADLINE=01/10

# Archivage
ARCEP_DOSSIER_PATH=/arcep/{year}/q{quarter}/
ARCEP_RETENTION_YEARS=10  # Conservation légale

# Contact
ARCEP_CONTACT_NAME=Directeur Conformité
ARCEP_CONTACT_EMAIL=compliance@rgz.bj
ARCEP_CONTACT_PHONE=+229 97 97 97 97

Endpoints API

MéthodeRouteDescriptionRéponse
GET/api/v1/reports/arcep/auto?quarter=Q1&year=2026Générer automatiquement202 Accepted +
GET/api/v1/reports/arcep/{year}/{quarter}Récupérer rapport Q
GET/api/v1/reports/arcep/{year}/{quarter}/pdfTélécharger dossier ZIPZIP binary (4 PDF)
POST/api/v1/reports/arcep/{year}/{quarter}/submitSoumettre ARCEP portal202 Accepted
GET/api/v1/reports/arcep/{year}/{quarter}/statusStatut soumission ARCEP

Authentification: Admin only (rôle compliance)

Celery Task

ChampValeur
Task namergz.arcep.quarterly
ScheduleCron 0 2 1 4,7,10,1 * (01/04, 01/07, 01/10, 01/01 à 02:00 UTC)
Queuergz.reports
Timeout1800s (30 minutes)
Retry5x (backup email si API ARCEP down)

Code Celery esquisse:

python
@app.task(name='rgz.arcep.quarterly', bind=True)
def generate_arcep_quarterly(self):
    """
    Génère dossier ARCEP trimestriel complet
    """
    quarter, year = _get_current_quarter()

    try:
        # 1. Récupérer données période
        period_start = _quarter_start_date(quarter, year)
        period_end = _quarter_end_date(quarter, year)

        # 2. Générer 4 PDF en parallèle
        tasks = [
            _generate_arcep_summary_pdf.delay(quarter, year),
            _generate_arcep_security_pdf.delay(quarter, year),
            _generate_arcep_incidents_pdf.delay(quarter, year),
            _generate_arcep_sites_pdf.delay(quarter, year)
        ]

        pdf_paths = [task.get() for task in tasks]

        # 3. Créer dossier + SHA-256
        dossier_path = f"/arcep/{year}/q{quarter}/"
        os.makedirs(dossier_path, exist_ok=True)
        for pdf in pdf_paths:
            shutil.copy(pdf, dossier_path)

        dossier_hash = _compute_dossier_sha256(dossier_path)

        # 4. Enregistrer dans DB
        submission = ArcepQuarterlySubmission(
            year=year,
            quarter=quarter,
            submission_date=datetime.utcnow(),
            dossier_path=dossier_path,
            submission_hash_sha256=dossier_hash,
            submission_status='draft'
        )
        db.add(submission)
        db.commit()

        # 5. Soumettre ARCEP portal (ou email backup)
        try:
            arcep_ref = _submit_to_arcep_portal(submission)
            submission.submission_status = 'submitted'
            submission.arcep_reference_number = arcep_ref
        except requests.HTTPError:
            logger.error("ARCEP portal unavailable, sending email backup")
            _email_arcep_backup(submission)
            submission.submission_status = 'email_backup'

        db.commit()

        return {'status': 'success', 'submission_id': str(submission.id)}

    except Exception as e:
        logger.error(f"ARCEP generation failed: {e}")
        self.retry(exc=e, countdown=3600)  # Retry in 1h

Commandes Utiles

bash
# Déclencher génération manuelle Q1 2026
docker-compose exec rgz-api celery -A app.celery_app call rgz.arcep.quarterly

# Vérifier statut soumission ARCEP
curl -H "Authorization: Bearer {admin_token}" \
  "http://api-rgz.duckdns.org/api/v1/reports/arcep/2026/q1/status"

# Télécharger dossier ARCEP complet
curl -H "Authorization: Bearer {admin_token}" \
  "http://api-rgz.duckdns.org/api/v1/reports/arcep/2026/q1/pdf" \
  -o arcep_q1_2026.zip && unzip arcep_q1_2026.zip

# Vérifier intégrité dossier
sha256sum /arcep/2026/q1/arcep_q1_2026_summary.pdf
# Comparer avec valeur DB: SELECT submission_hash_sha256 FROM arcep_quarterly_submissions WHERE year=2026 AND quarter=1

# Logs soumission ARCEP
docker-compose logs rgz-beat | grep "arcep"

# Archiver anciens dossiers (> 10 ans légalement pas nécessaire mais conservé)
find /arcep -maxdepth 2 -type d -mtime +3650 -name "q*" | head -5

Implémentation TODO

  • [ ] Schéma DB arcep_quarterly_submissions (year, quarter, status, hash, arcep_ref)
  • [ ] Tâche Celery rgz.arcep.quarterly dans app/tasks/arcep.py
  • [ ] Templates Jinja2 4 PDF (summary, security, incidents, sites)
  • [ ] Integration OAuth2 client pour ARCEP portal API
  • [ ] Fonctions de génération PDF: _generate_arcep_*_pdf()
  • [ ] Validation données (abonnés, volumes, incidents)
  • [ ] Endpoints API GET/POST /api/v1/reports/arcep*
  • [ ] Backup email si ARCEP portal down
  • [ ] Signature électronique e-signing (e-signature.bj ou PreSigno)
  • [ ] Tests: données Q1/Q2/Q3/Q4 synthétiques
  • [ ] Monitoring: alerte si soumission pas envoyée avant deadline
  • [ ] Audit trail #48 : tracer chaque submission
  • [ ] Documentation: processus légal ARCEP, deadlines, contacts

Dernière mise à jour: 2026-02-21

PROJET MOSAÏQUE — 81 outils, 22 conteneurs, 500+ revendeurs WiFi Zone