kpi-dashboard-design

Par wshobson · agents

Concevez des tableaux de bord KPI efficaces grâce à la sélection de métriques, aux bonnes pratiques de visualisation et aux patterns de surveillance en temps réel. Utilisez cette skill pour construire un tableau de bord de métriques SaaS destiné aux dirigeants (suivi du MRR, du churn et des ratios LTV/CAC) ; concevoir un centre d'opérations avec la santé des services en direct et le débit des requêtes ; créer une vue d'analyse de rétention par cohorte pour une équipe produit ; ou déboguer un tableau de bord dont les métriques se contredisent en raison d'une méthodologie de calcul incohérente.

npx skills add https://github.com/wshobson/agents --skill kpi-dashboard-design

Conception de Tableau de Bord KPI

Patterns complets pour concevoir des tableaux de bord Key Performance Indicator (KPI) efficaces qui orientent les décisions métier.

Quand utiliser cette compétence

  • Concevoir des tableaux de bord exécutifs
  • Sélectionner des KPI significatifs
  • Construire des affichages de monitoring en temps réel
  • Créer des vues de métriques spécifiques au département
  • Améliorer les mises en page de tableaux de bord existants
  • Établir une gouvernance des métriques

Concepts clés

1. Framework KPI

Niveau Focus Fréquence de mise à jour Audience
Stratégique Objectifs long terme Mensuel/Trimestriel Cadres dirigeants
Tactique Objectifs département Hebdomadaire/Mensuel Managers
Opérationnel Quotidien Temps réel/Quotidien Équipes

2. KPI SMART

Spécifique : Définition claire
Mesurable : Quantifiable
Atteignable : Cibles réalistes
Pertinent : Aligné sur les objectifs
Temporellement défini : Période définie

3. Hiérarchie du tableau de bord

├── Résumé exécutif (1 page)
│   ├── 4-6 KPI titres
│   ├── Indicateurs de tendance
│   └── Alertes clés
├── Vues par département
│   ├── Tableau de bord Ventes
│   ├── Tableau de bord Marketing
│   ├── Tableau de bord Opérations
│   └── Tableau de bord Finance
└── Détails approfondis
    ├── Métriques individuelles
    └── Analyse des causes

KPI courants par département

KPI Ventes

Métriques de revenu:
  - Monthly Recurring Revenue (MRR)
  - Annual Recurring Revenue (ARR)
  - Average Revenue Per User (ARPU)
  - Taux de croissance du revenu

Métriques de pipeline:
  - Valeur du pipeline commercial
  - Taux de gain
  - Taille moyenne des contrats
  - Durée du cycle de vente

Métriques d'activité:
  - Appels/E-mails par représentant
  - Démos programmées
  - Propositions envoyées
  - Taux de clôture

KPI Marketing

Acquisition:
  - Cost Per Acquisition (CPA)
  - Customer Acquisition Cost (CAC)
  - Volume de prospects
  - Marketing Qualified Leads (MQL)

Engagement:
  - Trafic du site web
  - Taux de conversion
  - Taux d'ouverture/clic d'e-mail
  - Engagement social

ROI:
  - ROI marketing
  - Performance des campagnes
  - Attribution par canal
  - Période de rentabilisation CAC

KPI Produit

Utilisation:
  - Daily/Monthly Active Users (DAU/MAU)
  - Durée de session
  - Taux d'adoption des fonctionnalités
  - Adhérence (DAU/MAU)

Qualité:
  - Net Promoter Score (NPS)
  - Customer Satisfaction (CSAT)
  - Nombre de bugs/problèmes
  - Temps de résolution

Croissance:
  - Taux de croissance des utilisateurs
  - Taux d'activation
  - Taux de rétention
  - Taux de churn

KPI Finance

Rentabilité:
  - Marge brute
  - Marge bénéficiaire nette
  - EBITDA
  - Marge opérationnelle

Liquidité:
  - Ratio courant
  - Ratio rapide
  - Flux de trésorerie
  - Fonds de roulement

Efficacité:
  - Revenu par employé
  - Ratio des dépenses opérationnelles
  - Days Sales Outstanding
  - Rotation des stocks

Patterns de mise en page du tableau de bord

Pattern 1 : Résumé exécutif

┌─────────────────────────────────────────────────────────────┐
│  TABLEAU DE BORD EXÉCUTIF                  [Plage ▼]  │
├─────────────┬─────────────┬─────────────┬─────────────────┤
│   REVENU    │   PROFIT    │  CLIENTS    │    SCORE NPS    │
│   2,4 M €   │    450 K €  │    12 450   │       72        │
│   ▲ 12%     │    ▲ 8%     │    ▲ 15%    │     ▲ 5 pts     │
├─────────────┴─────────────┴─────────────┴─────────────────┤
│                                                             │
│  Tendance du revenu            │  Revenu par produit         │
│  ┌───────────────────────┐     │  ┌──────────────────┐   │
│  │    /\    /\          │     │  │ ████████ 45%     │   │
│  │   /  \  /  \    /\   │     │  │ ██████   32%     │   │
│  │  /    \/    \  /  \  │     │  │ ████     18%     │   │
│  │ /            \/    \ │     │  │ ██        5%     │   │
│  └───────────────────────┘     │  └──────────────────┘   │
│                                                             │
├─────────────────────────────────────────────────────────────┤
│  🔴 Alerte : Taux de churn dépassé le seuil (> 5%)          │
│  🟡 Avertissement : Volume de tickets support 20% au-dessus  │
└─────────────────────────────────────────────────────────────┘

Pattern 2 : Tableau de bord métriques SaaS

┌─────────────────────────────────────────────────────────────┐
│  MÉTRIQUES SAAS                    Jan 2024  [Mensuel ▼]    │
├──────────────────────┬──────────────────────────────────────┤
│  ┌────────────────┐  │  CROISSANCE MRR                      │
│  │      MRR       │  │  ┌────────────────────────────────┐  │
│  │    125 000 €   │  │  │                          /──   │  │
│  │     ▲ 8%       │  │  │                    /────/      │  │
│  └────────────────┘  │  │              /────/            │  │
│  ┌────────────────┐  │  │        /────/                  │  │
│  │      ARR       │  │  │   /────/                       │  │
│  │   1 500 000 €  │  │  └────────────────────────────────┘  │
│  │     ▲ 15%      │  │  J  F  M  A  M  J  J  A  S  O  N  D  │
│  └────────────────┘  │                                      │
├──────────────────────┼──────────────────────────────────────┤
│  ÉCONOMIE UNITAIRE   │  RÉTENTION PAR COHORTE               │
│                      │                                      │
│  CAC:     450 €      │  Mois 1: ████████████████████ 100%   │
│  LTV:     2 700 €    │  Mois 3: █████████████████    85%    │
│  LTV/CAC: 6,0x       │  Mois 6: ████████████████     80%    │
│                      │  Mois 12: ██████████████      72%    │
│  Rentabilisation:    │                                      │
│  4 mois              │                                      │
├──────────────────────┴──────────────────────────────────────┤
│  ANALYSE DU CHURN                                           │
│  ┌──────────┬──────────┬──────────┬──────────────────────┐ │
│  │ Churn    │ Churn    │ Churn    │ Expansion            │ │
│  │ brut     │ net      │ logo    │                       │ │
│  │ 4,2%     │ 1,8%     │ 3,1%     │ 2,4%                 │ │
│  └──────────┴──────────┴──────────┴──────────────────────┘ │
└─────────────────────────────────────────────────────────────┘

Pattern 3 : Opérations temps réel

┌─────────────────────────────────────────────────────────────┐
│  CENTRE OPÉRATIONS                      En direct ● 10:42:15 │
├────────────────────────────┬────────────────────────────────┤
│  SANTÉ SYSTÈME             │  STATUT DES SERVICES           │
│  ┌──────────────────────┐  │                                │
│  │   CPU    MEM    DISK │  │  ● API Gateway      Sain       │
│  │   45%    72%    58%  │  │  ● User Service     Sain       │
│  │   ███    ████   ███  │  │  ● Payment Service  Dégradé    │
│  │   ███    ████   ███  │  │  ● Database         Sain       │
│  │   ███    ████   ███  │  │  ● Cache            Sain       │
│  └──────────────────────┘  │                                │
├────────────────────────────┼────────────────────────────────┤
│  DÉBIT DE REQUÊTES         │  TAUX D'ERREUR                 │
│  ┌──────────────────────┐  │  ┌──────────────────────────┐  │
│  │ ▁▂▃▄▅▆▇█▇▆▅▄▃▂▁▂▃▄▅ │  │  │ ▁▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁  │  │
│  └──────────────────────┘  │  └──────────────────────────┘  │
│  Actuel : 12 450 req/s     │  Actuel : 0,02%                │
│  Pic : 18 200 req/s        │  Seuil : 1,0%                  │
├────────────────────────────┴────────────────────────────────┤
│  ALERTES RÉCENTES                                           │
│  10:40  🟡 Latence élevée sur payment-service (p99 > 500ms) │
│  10:35  🟢 Résolu : Pool de connexions DB rétabli           │
│  10:22  🔴 Circuit breaker du service de paiement activé    │
└─────────────────────────────────────────────────────────────┘

Patterns d'implémentation

SQL pour les calculs KPI

-- Monthly Recurring Revenue (MRR)
WITH mrr_calculation AS (
    SELECT
        DATE_TRUNC('month', billing_date) AS month,
        SUM(
            CASE subscription_interval
                WHEN 'monthly' THEN amount
                WHEN 'yearly' THEN amount / 12
                WHEN 'quarterly' THEN amount / 3
            END
        ) AS mrr
    FROM subscriptions
    WHERE status = 'active'
    GROUP BY DATE_TRUNC('month', billing_date)
)
SELECT
    month,
    mrr,
    LAG(mrr) OVER (ORDER BY month) AS prev_mrr,
    (mrr - LAG(mrr) OVER (ORDER BY month)) / LAG(mrr) OVER (ORDER BY month) * 100 AS growth_pct
FROM mrr_calculation;

-- Cohort Retention
WITH cohorts AS (
    SELECT
        user_id,
        DATE_TRUNC('month', created_at) AS cohort_month
    FROM users
),
activity AS (
    SELECT
        user_id,
        DATE_TRUNC('month', event_date) AS activity_month
    FROM user_events
    WHERE event_type = 'active_session'
)
SELECT
    c.cohort_month,
    EXTRACT(MONTH FROM age(a.activity_month, c.cohort_month)) AS months_since_signup,
    COUNT(DISTINCT a.user_id) AS active_users,
    COUNT(DISTINCT a.user_id)::FLOAT / COUNT(DISTINCT c.user_id) * 100 AS retention_rate
FROM cohorts c
LEFT JOIN activity a ON c.user_id = a.user_id
    AND a.activity_month >= c.cohort_month
GROUP BY c.cohort_month, EXTRACT(MONTH FROM age(a.activity_month, c.cohort_month))
ORDER BY c.cohort_month, months_since_signup;

-- Customer Acquisition Cost (CAC)
SELECT
    DATE_TRUNC('month', acquired_date) AS month,
    SUM(marketing_spend) / NULLIF(COUNT(new_customers), 0) AS cac,
    SUM(marketing_spend) AS total_spend,
    COUNT(new_customers) AS customers_acquired
FROM (
    SELECT
        DATE_TRUNC('month', u.created_at) AS acquired_date,
        u.id AS new_customers,
        m.spend AS marketing_spend
    FROM users u
    JOIN marketing_spend m ON DATE_TRUNC('month', u.created_at) = m.month
    WHERE u.source = 'marketing'
) acquisition
GROUP BY DATE_TRUNC('month', acquired_date);

Code Python Dashboard (Streamlit)

import streamlit as st
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go

st.set_page_config(page_title="KPI Dashboard", layout="wide")

# Header with date filter
col1, col2 = st.columns([3, 1])
with col1:
    st.title("Executive Dashboard")
with col2:
    date_range = st.selectbox(
        "Period",
        ["Last 7 Days", "Last 30 Days", "Last Quarter", "YTD"]
    )

# KPI Cards
def metric_card(label, value, delta, prefix="", suffix=""):
    delta_color = "green" if delta >= 0 else "red"
    delta_arrow = "▲" if delta >= 0 else "▼"
    st.metric(
        label=label,
        value=f"{prefix}{value:,.0f}{suffix}",
        delta=f"{delta_arrow} {abs(delta):.1f}%"
    )

col1, col2, col3, col4 = st.columns(4)
with col1:
    metric_card("Revenue", 2400000, 12.5, prefix="$")
with col2:
    metric_card("Customers", 12450, 15.2)
with col3:
    metric_card("NPS Score", 72, 5.0)
with col4:
    metric_card("Churn Rate", 4.2, -0.8, suffix="%")

# Charts
col1, col2 = st.columns(2)

with col1:
    st.subheader("Revenue Trend")
    revenue_data = pd.DataFrame({
        'Month': pd.date_range('2024-01-01', periods=12, freq='M'),
        'Revenue': [180000, 195000, 210000, 225000, 240000, 255000,
                    270000, 285000, 300000, 315000, 330000, 345000]
    })
    fig = px.line(revenue_data, x='Month', y='Revenue',
                  line_shape='spline', markers=True)
    fig.update_layout(height=300)
    st.plotly_chart(fig, use_container_width=True)

with col2:
    st.subheader("Revenue by Product")
    product_data = pd.DataFrame({
        'Product': ['Enterprise', 'Professional', 'Starter', 'Other'],
        'Revenue': [45, 32, 18, 5]
    })
    fig = px.pie(product_data, values='Revenue', names='Product',
                 hole=0.4)
    fig.update_layout(height=300)
    st.plotly_chart(fig, use_container_width=True)

# Cohort Heatmap
st.subheader("Cohort Retention")
cohort_data = pd.DataFrame({
    'Cohort': ['Jan', 'Feb', 'Mar', 'Apr', 'May'],
    'M0': [100, 100, 100, 100, 100],
    'M1': [85, 87, 84, 86, 88],
    'M2': [78, 80, 76, 79, None],
    'M3': [72, 74, 70, None, None],
    'M4': [68, 70, None, None, None],
})
fig = go.Figure(data=go.Heatmap(
    z=cohort_data.iloc[:, 1:].values,
    x=['M0', 'M1', 'M2', 'M3', 'M4'],
    y=cohort_data['Cohort'],
    colorscale='Blues',
    text=cohort_data.iloc[:, 1:].values,
    texttemplate='%{text}%',
    textfont={"size": 12},
))
fig.update_layout(height=250)
st.plotly_chart(fig, use_container_width=True)

# Alerts Section
st.subheader("Alerts")
alerts = [
    {"level": "error", "message": "Churn rate exceeded threshold (>5%)"},
    {"level": "warning", "message": "Support ticket volume 20% above average"},
]
for alert in alerts:
    if alert["level"] == "error":
        st.error(f"🔴 {alert['message']}")
    elif alert["level"] == "warning":
        st.warning(f"🟡 {alert['message']}")

Bonnes pratiques

À faire

  • Limiter à 5-7 KPI - Se concentrer sur ce qui compte
  • Afficher le contexte - Comparaisons, tendances, cibles
  • Utiliser des couleurs cohérentes - Rouge=mauvais, vert=bon
  • Activer le zoom - Du résumé au détail
  • Mettre à jour correctement - Correspondre à la fréquence des métriques

À ne pas faire

  • Ne pas afficher de métriques de vanité - Se concentrer sur les données exploitables
  • Ne pas surcharger - L'espace blanc aide la compréhension
  • Ne pas utiliser de graphiques 3D - Ils déforment la perception
  • Ne pas cacher la méthodologie - Documenter les calculs
  • Ne pas ignorer le mobile - Assurer un design réactif

Dépannage

MRR affiché sur le tableau de bord contredit le chiffre de la finance

La cause la plus courante est le traitement incohérent des plans annuels. La finance peut faire une prorata sur un taux quotidien tandis que le tableau de bord normalise au mois. Aligner sur une formule unique et la documenter directement sur la carte KPI :

-- Formule explicite affichée dans l'infobulle / dictionnaire des données
-- Plans annuels : diviser la valeur totale du contrat par 12
-- Plans trimestriels : diviser par 3
-- Plans mensuels : utiliser tel quel
CASE subscription_interval
    WHEN 'monthly'   THEN amount
    WHEN 'quarterly' THEN amount / 3.0
    WHEN 'yearly'    THEN amount / 12.0
END AS normalized_mrr

Le tableau de bord affiche du vert mais l'équipe produit signale des plaintes d'utilisateurs

Le tableau de bord suit probablement la disponibilité du système (indicateur retardé) mais pas les métriques de qualité perçues par l'utilisateur. Ajouter des métriques de qualité perçue par le client aux côté des métriques d'infrastructure :

Infrastructure (vert) Perçues par l'utilisateur (ajouter)
Uptime API 99,9% Temps de chargement page P95
Taux d'erreur 0,1% Taux de complétude des tâches
Profondeur de queue normale Volume de tickets de support

La cohorte de rétention semble plate — pas de variation entre cohorts

Vérifier que la requête de cohorte partitionne correctement par mois de création. Un bug courant est utiliser created_at::date au lieu de DATE_TRUNC('month', created_at), ce qui groupe par jour et crée des cohorts trop petites pour montrer les tendances :

-- Mauvais : trop granulaire, cohorts trop petites
DATE_TRUNC('day', created_at) AS cohort_date

-- Correct : cohorts mensuelles
DATE_TRUNC('month', created_at) AS cohort_month

Le tableau de bord temps réel surcharge la base de données

Un tableau de bord en direct s'actualisant toutes les 10 secondes avec du SQL de cohorte complexe dégraderas la performance des requêtes de production. Séparer les charges de travail OLAP d'OLTP en écrivant des métriques préagrégées dans une table de résumé via un travail programmé, et avoir le tableau de bord lire à partir de celle-ci :

# Programmé toutes les 5 minutes via cron/Celery
def refresh_mrr_summary():
    conn.execute("""
        INSERT INTO kpi_snapshot (metric, value, snapshot_at)
        SELECT 'mrr', SUM(...), NOW()
        FROM subscriptions WHERE status = 'active'
        ON CONFLICT (metric) DO UPDATE SET value = EXCLUDED.value
    """)

Les seuils d'alerte se déclenchent constamment, l'équipe les ignore

Les seuils statiques définis une fois et jamais révisés causent une fatigue aux alertes. Utiliser des seuils dynamiques basés sur des moyennes mobiles pour que les alertes se déclenchent uniquement quand la métrique s'écarte significativement de sa propre base :

# Alerte si la valeur actuelle est > 2 écarts-types de la moyenne mobile 30 jours
def is_anomalous(current: float, history: list[float]) -> bool:
    mean = statistics.mean(history)
    stdev = statistics.stdev(history)
    return abs(current - mean) > 2 * stdev

Compétences connexes

  • data-storytelling - Transformer les découvertes du tableau de bord en narratifs qui orientent les décisions exécutives

Skills similaires