Le vrai problème n'est pas l'API
Orange Money fournit une API REST pour les partenaires. Obtenir les transactions n'est pas le défi. Le défi, c'est ce qui vient après :
- Idempotence : La même transaction peut apparaître deux fois dans deux appels API successifs. Sans déduplication, vos agrégats sont faux.
- Qualité : 2 à 5% des transactions ont des montants nuls, des statuts invalides, ou des timestamps manquants. Sans contrôle qualité, votre Gold Layer est contaminé.
- Conformité CDP: Les numéros de téléphone sont des données personnelles au sens de l'Article 4 de la loi sénégalaise. Les stocker en clair dans un Data Lake, c'est une non-conformité immédiate.
- Volume : 5 000+ transactions/jour pour un commerçant moyen, 500 000+ pour une banque. Sans partitionnement, vos requêtes Gold mettent 30 secondes au lieu de 2.
L'Architecture Medallion : Pourquoi trois couches ?
L'architecture Medallion (Bronze → Silver → Gold), popularisée par Databricks, n'est pas un buzzword. C'est une réponse directe aux quatre problèmes ci-dessus.
┌──────────────────────────────────────────────┐
│ ORANGE MONEY API │
│ (ou Mock Generator pour le dev) │
└─────────────────┬────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 🥉 BRONZE — raw_transactions │
│ • Append-only, jamais modifié │
│ • Tokenisation PII à l'ingestion (CDP) │
│ • Format : Delta Lake │
└─────────────────┬────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 🥈 SILVER — normalized_transactions │
│ • Déduplication (ROW_NUMBER) │
│ • 7 règles de qualité automatiques │
│ • Enrichissement : buckets, heures de pointe │
└─────────────────┬────────────────────────────┘
│
▼
┌──────────────────────────────────────────────┐
│ 🥇 GOLD — daily_summary, fraud_patterns │
│ • KPIs prêts pour dashboards │
│ • Détection d'anomalies │
│ • Partitionné, aggregé, documenté │
└──────────────────────────────────────────────┘Bronze : L'ingestion qui protège les données personnelles
La couche Bronze reçoit les transactions brutes de l'API Orange Money. Mais avant d'écrire la moindre ligne dans Delta Lake, une étape critique se produit : la tokenisation des numéros de téléphone.
# Tokenisation CDP à l'ingestion — Art. 44
class PhoneTokenizer:
"""HMAC-SHA256 pour conformité CDP."""
def tokenize(self, phone: str) -> str:
return hmac.new(
self.secret, phone.encode(), hashlib.sha256
).hexdigest()[:16]
# Avant écriture dans Bronze
if self._tokenizer:
tx.sender_phone = self._tokenizer.tokenize(tx.sender_phone)
tx.recipient_phone = self._tokenizer.tokenize(tx.recipient_phone)Pourquoi c'est important : la CDP sénégalaise (Article 44) exige la pseudonymisation des données personnelles. En tokenisant à l'ingestion — avant que les données n'atteignent le stockage — vous éliminez le risque de PII en clair dans votre Data Lake. Même un accès non autorisé à la table Bronze ne révèle aucun numéro de téléphone réel.
| Colonne | Rôle |
|---|---|
| _ingested_at | Horodatage d'ingestion (UTC) |
| _source_file | Fichier source ou endpoint API |
| _batch_id | Identifiant de lot pour la traçabilité |
Silver : La normalisation qui fait la différence
C'est ici que la plupart des projets échouent. On passe de Bronze à Gold directement — et on obtient des dashboards qui affichent des totaux inexacts à cause de doublons et de données corrompues.
1. Déduplication
# Déduplication par transaction_id
window = Window.partitionBy("transaction_id") \
.orderBy(F.col("_ingested_at").desc())
deduped = bronze \
.withColumn("_row_num", F.row_number().over(window)) \
.filter(F.col("_row_num") == 1) \
.drop("_row_num")2. Contrôles qualité
QUALITY_RULES = {
"amount_positive": F.col("amount") > 0,
"amount_reasonable": F.col("amount") < 50_000_000,
"valid_status": F.col("status").isin(
"SUCCESS", "PENDING", "FAILED", "REVERSED"
),
"has_sender": F.col("sender_phone").isNotNull(),
"fee_non_negative": F.col("fee") >= 0,
}Les lignes qui échouent ne sont pas rejetées — elles sont conservées avec le flag FAIL, ce qui permet un audit sans perte de données.
3. Enrichissement
| Colonne enrichie | Exemple | Utilité |
|---|---|---|
| _date | 2025-01-15 | Partitionnement |
| _hour | 14 | Analyse horaire |
| _is_peak_hour | True | Heures de pointe (9h-12h, 15h-18h) |
| _is_weekend | False | Patterns week-end vs semaine |
| _amount_bucket | MEDIUM | Segmentation (MICRO → XLARGE) |
| _is_merchant_txn | True | Transaction commerçant vs P2P |
| _processing_latency_seconds | 3 | Délai initiation → complétion |
Gold : Les agrégats qui parlent aux décideurs
daily_summary — Le pouls quotidien
-- Volume par région et canal (7 derniers jours)
SELECT
_date, region,
SUM(transaction_count) AS total_txns,
SUM(total_volume_xof) AS volume_fcfa,
ROUND(AVG(success_rate), 1) AS success_pct
FROM orange_money.gold.daily_summary
WHERE _date >= CURRENT_DATE() - INTERVAL 7 DAYS
GROUP BY _date, region
ORDER BY _date DESC, volume_fcfa DESC;Détection de fraude
La couche Gold exécute quatre patterns de détection d'anomalies à chaque exécution :
| Pattern | Règle | Exemple |
|---|---|---|
| VELOCITY_ANOMALY | > 20 transactions/jour par émetteur | Compte émet 45 transferts en une journée |
| AMOUNT_SPIKE | Transaction > 5× la moyenne 7 jours | Transaction de 500K FCFA (moy: 45K) |
| RAPID_TRANSFER | Transferts multiples même destinataire < 10 min | 3 transferts de 50K en 4 minutes |
| OFF_HOURS_LARGE | Transaction > 100K hors heures de pointe | Paiement de 200K à 3h du matin |
Infrastructure-as-Code : Terraform pour la reproductibilité
# Azure Databricks Workspace (Premium — Unity Catalog)
resource "azurerm_databricks_workspace" "this" {
name = "orange-money-databricks"
sku = "premium"
}
# ADLS Gen2 — un container par couche Medallion
resource "azurerm_storage_container" "bronze" { ... }
resource "azurerm_storage_container" "silver" { ... }
resource "azurerm_storage_container" "gold" { ... }Mode Mock : Développer sans credentials API
from src.ingestion import OrangeMoneyClient
# Aucun credential requis — mode mock par défaut
client = OrangeMoneyClient()
for page in client.fetch_transactions(
datetime(2025, 1, 15), datetime(2025, 1, 17)
):
for tx in page.transactions:
print(f"{tx.amount:,.0f} FCFA — {tx.region}")Le générateur respecte :
- Distribution régionale : Dakar ~45%, Thiès ~15%
- Heures de pointe : 9h-12h et 15h-18h GMT
- Canaux : USSD 65%, APP 25%, WEB 5%, AGENT 5%
- Montants : Clusters autour de 500, 1 000, 2 000, 5 000, 10 000 FCFA
Ce que cette architecture apporte concrètement
| Avant | Après |
|---|---|
| Scripts Python ad-hoc par employé | Pipeline unique, versionné, testé |
| Numéros de téléphone en clair dans les logs | Tokenisation HMAC-SHA256 à l'ingestion |
| Doublons non détectés → KPIs erronés | Déduplication déterministe dans Silver |
| 2-5% de données corrompues | Quality flag sur chaque ligne, audit trail |
| Dashboard = 30 secondes | Partitionnement → 2 secondes |
| Pas de détection de fraude | 4 patterns d'anomalies automatiques |
| Déploiement manuel | terraform apply → infra complète |
Code source
L'intégralité du code est open-source (MIT) :
Le dépôt contient le pipeline Python complet (Bronze → Silver → Gold), les modèles dbt alternatifs (SQL), les modules Terraform, quatre notebooks Databricks exécutables, une suite de tests complète et une CI/CD GitHub Actions.