1600 factures fictives réparties sur 9 centres, plusieurs catégories et statuts de paiement — modélisées en schéma en étoile pour croiser rapidement tous les axes d'analyse.
Modélisation dimensionnelle, requêtes SQL avec fenêtrage et CTE, contrôles qualité en Python, conception d'indicateurs de délai et de retard.
| ID | Date | Centre | Catégorie | Fournisseur | Montant | Statut | Délai (j) |
|---|
Une table de faits « Factures » reliée à quatre dimensions, adaptée à une exploitation Power BI / SQL — chaque filtre du dashboard ci-dessus correspond à une jointure sur une dimension.
Voici quelques extraits de code utilisés pour la conception de ce projet.
Question métier : quels fournisseurs cumulent un volume élevé et des retards fréquents, donc prioritaires pour une revue contractuelle ?
SELECT
d.nom_fournisseur,
COUNT(*) AS nb_factures,
SUM(f.montant) AS montant_total,
ROUND(100.0 * SUM(CASE WHEN f.statut = 'En retard' THEN 1
ELSE 0 END) / COUNT(*), 1) AS taux_retard_pct,
ROUND(AVG(f.delai_paiement), 1) AS delai_moyen_j
FROM fact_factures f
JOIN dim_fournisseur d ON d.id_fournisseur = f.id_fournisseur
WHERE f.date >= DATEADD(month, -12, CURRENT_DATE)
GROUP BY d.nom_fournisseur
HAVING COUNT(*) >= 15
ORDER BY taux_retard_pct DESC, montant_total DESC
LIMIT 10;
Question métier : quelles catégories de dépenses accélèrent ou ralentissent d'un mois à l'autre ?
WITH montant_mensuel AS (
SELECT
DATE_TRUNC('month', f.date) AS mois,
c.nom_categorie,
SUM(f.montant) AS montant
FROM fact_factures f
JOIN dim_categorie c ON c.id_categorie = f.id_categorie
GROUP BY 1, 2
)
SELECT
mois,
nom_categorie,
montant,
montant - LAG(montant) OVER (
PARTITION BY nom_categorie ORDER BY mois) AS variation_chf,
ROUND(100.0 * (montant - LAG(montant) OVER (
PARTITION BY nom_categorie ORDER BY mois))
/ NULLIF(LAG(montant) OVER (
PARTITION BY nom_categorie ORDER BY mois), 0), 1) AS variation_pct
FROM montant_mensuel
ORDER BY nom_categorie, mois;
Contrôles automatisés exécutés avant chargement dans le Data Warehouse (doublons, valeurs invalides, références orphelines).
import pandas as pd
df = pd.read_sql("SELECT * FROM fact_factures", conn)
# Controles de qualite
doublons = df[df.duplicated(subset=["id_facture"])]
montants_invalides = df[df["montant"] <= 0]
factures_orphelines = df[~df["id_fournisseur"].isin(dim_fournisseur["id_fournisseur"])]
# Detection des retards par fournisseur
df["est_en_retard"] = df["statut"].eq("En retard")
retard_par_fournisseur = (
df.groupby("id_fournisseur")
.agg(nb_factures=("id_facture", "count"),
taux_retard=("est_en_retard", "mean"),
montant_total=("montant", "sum"))
.query("nb_factures >= 15")
.sort_values("taux_retard", ascending=False)
)
print(f"{len(doublons)} doublons, {len(montants_invalides)} montants invalides, "
f"{len(factures_orphelines)} factures orphelines detectees.")