Metrics Pack
Ce que vous faites
On vous demande de transformer les données de revenus d'une startup en chiffres qu'un analyste de fonds lit en premier :
MRR, cohortes, NRR – sur une diapositive. Les chiffres proviennent de metrics.py, jamais de votre tête.
Vous choisissez l'entrée, exécutez l'outil, lisez ses CSV et rédigez le texte qui les accompagne.
ENTRÉE OUTIL (metrics.py) VOUS
───── ─────────────────── ───
updates/ (a) ──┐
export client (b) ─┼──► mrr.csv cohorts.csv ──► metrics.md (anglais simple)
registre contrats (c) ┘ nrr.csv excluded.csv metrics-slide.png (une diapositive)
Livrables dans <slug-entreprise>/metrics-pack/, relatif au répertoire courant :
metrics.md– la note. Commence par « En anglais simple. ».mrr.csv,cohorts.csv,nrr.csv(etexcluded.csvpour un registre de contrats) – écrits par l'outil, que vous ne modifiez pas.metrics-slide.png– une diapositive 16:9.
Abréviations utilisées dans ce fichier : MRR = revenu mensuel récurrent ; NRR = taux de rétention net ; CSV = fichier de valeurs séparées par des virgules ; PNG = fichier image ; ISO = format de date international (AAAA-MM-JJ, ou AAAA-MM pour un mois) ; EUR = euro ; USD = dollar américain ; --as-of = option de l'outil qui nomme le dernier mois facturé.
Entrées
Depuis $ARGUMENTS ou la conversation : une entreprise, et éventuellement un fichier. Trois types d'entrée, dans cet ordre de préférence :
| Entrée | Description | Comment elle entre dans l'outil |
|---|---|---|
| (b) export par client | CSV client_id,mois,revenu, mois en AAAA-MM | --revenue <csv> |
| (c) registre de contrats | CSV avec une ligne d'en-tête Client, Valeur du contrat, Mois de début, Durée (mois) ; les lignes au-dessus de l'en-tête sont ignorées ; mois comme Mai-26 ; valeurs comme "1 550 000" | --contracts <csv> |
(a) <slug-entreprise>/updates/ | fichiers de mise à jour mensuelle avec une ligne de revenu, sans détail client | lu comme le fait revenue-chart (Étape 2 là-bas), écrit client_id,mois,revenu avec un client total, puis --revenue |
Avec l'entrée (a), il n'y a pas de clients. L'outil écrit alors cohorts.csv et nrr.csv avec seulement un en-tête et laisse les cellules new, expansion, contraction et churned de mrr.csv vides, car avec un seul client elles ne feraient que répéter la croissance totale. Ignorez ces colonnes, n'affichez pas de heatmap et imprimez « NRR 12 mois : ne peut pas encore être calculé » (raison : pas de détail client). Il en va de même pour tout fichier --revenue avec un seul identifiant client.
Si l'entreprise est ambiguë (deux sociétés partagent le nom), demandez laquelle avant de lire quoi que ce soit.
Étape 1 – Fiche entreprise et entrée
La fiche entreprise
<slug-entreprise>/company-profile.md – lisez-la d'abord. Si elle manque, créez-la. Si un champ est
vide et que vous l'apprenez, remplissez-le et mettez à jour updated / updated_by. Ne jamais écraser un champ
rempli : si vous trouvez une valeur différente, ajoutez une ligne dans ## Conflits avec les deux valeurs et sources.
La fiche se termine par une section ## Conflits (créez-la si elle manque) dont l'en-tête du tableau est exactement
| Champ | Valeur fiche | Source fiche | Nouvelle valeur | Nouvelle source | Trouvé par | Date |.
| Champ | Valeurs autorisées |
|---|---|
| Travail effectué pour le client | une phrase, dans les propres mots de l'entreprise, citée, avec URL |
| Qui paie | texte libre |
| Modèle économique | B2B · B2C · B2G |
| Modèle de revenu | abonnement · transaction · publicité · ponctuel · n.d. |
| Marché | exactement un : Fintech & assurance · Santé & bio · Commerce & vente au détail · Logistique & chaîne d'approvisionnement · IA & infrastructure de données · Logiciel d'entreprise · Cybersécurité · Juridique, conformité & gouvernance · Ventes & marketing · RH & talents · Éducation · Médias, jeux & divertissement · Immobilier & construction · Énergie, climat & agriculture · Industriel & robotique · Style de vie grand public & services · Autre |
| Verticale | texte libre |
| Stade | pré-amorçage · amorçage · Série A · Série B+ · n.d. |
| Géographie | pays du siège ; pays où elle vend |
| Tour levé | montant + devise + instrument, ou « non communiqué » |
| Métriques disponibles | où (updates/, exports), ou « aucune » |
Front-matter : company, slug, website, updated (date ISO), updated_by (nom du skill).
Le tableau de la fiche a une troisième colonne, Source, remplie pour chaque champ.
Prenez le nom de l'entreprise, le modèle économique, le modèle de revenu et la géographie de la fiche ; la géographie suggère la devise à attendre. Remplissez « Métriques disponibles » avec le fichier que vous utilisez. Si le modèle de revenu n'est pas un abonnement, dites dans la note que le MRR suppose des revenus récurrents.
Trouvez l'entrée (tableau ci-dessus) et copiez-la dans <slug-entreprise>/metrics-pack/input/ pour que l'exécution soit
vérifiable. Ne jamais publier ni diffuser un fichier au niveau client à moins que le propriétaire n'ait dit qu'il peut être
public. Devise : prenez-la du fichier ; un registre de contrats n'en mentionne souvent aucune. Si aucune n'est
mentionnée, écrivez « devise non communiquée » partout et n'en choisissez jamais une.
Aujourd'hui et --as-of. --as-of est le dernier mois qui a effectivement été facturé – normalement
le mois en cours, à partir de la date d'aujourd'hui (ISO). Les mois après celui-ci sont sous contrat : signés, pas encore
facturés.
Étape 2 – Exécuter l'outil
Utilisez /usr/bin/python3 ou tout Python 3.9+ ; metrics.py nécessite uniquement la bibliothèque standard et n'effectue
aucun appel réseau. Il se trouve à côté de ce fichier (skills/metrics-pack/metrics.py ; dans un plugin
installé, ${CLAUDE_PLUGIN_ROOT}/skills/metrics-pack/metrics.py).
python3 <chemin>/metrics.py --contracts <registre.csv> --out-dir <slug-entreprise>/metrics-pack \
--as-of 2026-11 --min-term 3
python3 <chemin>/metrics.py --revenue <export.csv> --out-dir <slug-entreprise>/metrics-pack \
--as-of 2026-11
Ce qu'il écrit :
| Fichier | Colonnes | Signification |
|---|---|---|
mrr.csv | mois, mrr, new, expansion, contraction, churned, status | une ligne par mois civil ; status est billed jusqu'à --as-of, contracted après |
cohorts.csv | cohorte, decalage_mois, revenu | cohorte = premier mois de revenu d'un client ; décalage = mois depuis ce début |
nrr.csv | mois_debut, mois, nrr | NRR 12 mois pour chaque mois de début où il peut être calculé à partir des mois facturés uniquement (un mois après --as-of ne compte jamais) ; en-tête uniquement si aucun ne peut l'être. Également en-tête uniquement pour un export mono-client, et pour un registre de contrats sans renouvellement enregistré ; pour un registre, seuls les mois de début dans lesquels un client a un renouvellement enregistré (règle à l'Étape 2) sont listés |
excluded.csv | client, valeur, debut, duree | contrats plus courts que --min-term mois (par défaut 3) |
Si l'outil s'arrête avec error:, il nomme la mauvaise colonne ou valeur. Corrigez l'entrée à la source
(ou demandez au propriétaire) – ne modifiez jamais autour.
Ce que l'outil suppose (indiquez chacun dans la note) :
- La valeur du contrat est le total sur la durée, réparti également : valeur ÷ durée par mois, à partir du mois de début. Si le propriétaire dit qu'elle est par mois ou par an, arrêtez-vous et demandez – la réponse change tous les chiffres.
- Un contrat est récurrent seulement si sa durée est d'au moins
--min-termmois. Les plus courts sont non récurrents : signalés dansexcluded.csv, pas dans le MRR. - Rien ne se renouvelle à moins qu'une nouvelle ligne ne le dise. Lorsqu'un contrat prend fin, l'outil montre le
revenu qui part comme
churned. Dans un registre de contrats, c'est la fin du contrat, pas un churn prouvé – dites-le pour chaque mois concerné. - Un registre de contrats enregistre des contrats, pas des renouvellements. La fin d'un contrat est lue comme une perte de revenu, donc l'outil calcule
nrr.csvà partir d'un registre uniquement pour les mois de début où un client a un renouvellement enregistré. Règle : un renouvellement est un contrat récurrent ultérieur (durée d'au moins--min-term) du même client dont le mois de début est au moins un mois après le mois de fin d'un contrat récurrent antérieur (mois de fin = mois de début + durée - 1 ; donc un contrat consécutif commençant le mois suivant compte, et un écart est autorisé). Un second contrat qui chevauche le premier (une vente incitative ou un module complémentaire) n'est pas un renouvellement. Si aucun client n'a de renouvellement,nrr.csva seulement un en-tête et la réponse est « ne peut pas encore être calculé ». Si certains en ont, le chiffre reflète uniquement les renouvellements que le registre enregistre et la note ainsi que la diapositive doivent le dire. - Un client qui arrête puis revient compte comme
newà nouveau.
Étape 3 – Lire les sorties
Lisez les CSV avec le module csv. Chaque chiffre que vous écrivez vient d'eux. Ne calculez rien dans le texte ;
si vous avez besoin d'un chiffre que les CSV ne fournissent pas (par exemple une part), calculez-le avec une commande et
citez la sortie de la commande.
- MRR. Le dernier mois facturé
mrr; le mois de pointe et sonstatus; la répartition facturé/sous contrat. Nommez le premier et le dernier mois dans les données. - Run-off. Les mois après le dernier début de contrat ne font que perdre du revenu, car rien dans les données ne se renouvelle. Marquez-les comme run-off de contrats sans renouvellement, jamais comme churn.
- Cohortes. Comptez les cohortes avec au moins deux mois de revenu facturé. La heatmap sur la diapositive nécessite au moins deux de ces cohortes ; sinon, ignorez-la et dites pourquoi.
- NRR. Vérifiez d'abord l'entrée. Pour l'entrée (a) ou un export mono-client, ignorez
nrr.csv,cohorts.csvet les colonnes de mouvement : la réponse est « ne peut pas encore être calculé ». Sinon, sinrr.csva des lignes de données, citez-les ; pour un registre de contrats, ajoutez « compte uniquement les renouvellements enregistrés dans le registre ». S'il a seulement l'en-tête, la réponse est « Le NRR 12 mois ne peut pas encore être calculé » : aucun client n'a un mois de revenu douze mois après son début. Ne l'estimez jamais, ne le remplissez jamais à partir d'une fenêtre plus courte. Donnez la raison en une ligne (par exemple « chaque contrat du registre commence en 2026, donc aucun n'a duré douze mois et ne s'est renouvelé »). - Exclus. Nombre de contrats exclus, leur valeur totale, et la règle qui les a exclus.
Étape 4 – metrics-slide.png
Nécessite matplotlib et numpy (python3 -c "import matplotlib, numpy"). Si manquant, demandez avant d'installer
quoi que ce soit ; la voie propre est un environnement virtuel jetable. La base est le modèle de graphique
revenue-chart (thème sombre, vérification des chevauchements d'étiquettes).
Une diapositive 16:9, dans cette disposition :
┌─────────────────────────────────────────────────────────────┐
│ <Entreprise> – indicateurs de revenu en date du <ISO> │
├──────────────────────────────┬──────────────────────────────┤
│ Courbe MRR : trait plein = │ heatmap des cohortes (mois │
│ facturé, pointillé + │ facturés uniquement, % du │
│ ombré = sous contrat │ mois 0) – ou le panneau NRR │
│ │ si pas de heatmap │
├──────────────────────────────┴──────────────────────────────┤
│ NRR : <chiffre> ou « NRR 12 mois : ne peut pas encore être calculé » │
│ trois points clés d'une ligne │
└─────────────────────────────────────────────────────────────┘
Règles pour la diapositive :
- Le sous contrat n'est jamais stylisé comme facturé. Les mois après
--as-ofutilisent une ligne pointillée, des marqueurs creux et une bande ombrée étiquetée « sous contrat, pas encore facturé ». - Pour un registre de contrats, un chiffre NRR affiché porte la mention « compte uniquement les renouvellements enregistrés
dans le registre » (le script l'ajoute quand
source = "register"). - La ligne NRR montre le chiffre de
nrr.csv, ou les mots exacts « NRR 12 mois : ne peut pas encore être calculé ». - Heatmap uniquement si au moins deux cohortes ont au moins deux mois facturés.
- Trois points clés d'une ligne, chacun traçable vers une ligne CSV, aucun n'est une recommandation ou une prédiction.
- Devise dans le titre de l'axe ; « devise non communiquée » si la source n'en a donné aucune.
- Tous les chiffres sur la diapositive sont lus à partir des CSV par le script, pas tapés. Les points clés sont le seul texte tapé : vérifiez chaque chiffre qu'ils contiennent par rapport aux CSV après le dessin.
import csv, os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
# CONFIGURATION -----------------------------------------------------------
entreprise = "Entreprise"
dossier = "<slug-entreprise>/metrics-pack"
devise = "devise non communiquée" # ou "EUR", "USD" ...
as_of = "2026-11" # même valeur que --as-of
source = "register" # "register" (--contracts) ou "export" (--revenue)
points_cles = [ # trois points d'une ligne, chacun vérifié par rapport aux CSV
"Point clé 1",
"Point clé 2",
"Point clé 3",
]
# -------------------------------------------------------------------------
def lire(nom):
with open(os.path.join(dossier, nom), newline="", encoding="utf-8") as fh:
return list(csv.DictReader(fh))
mrr, coh, lignes_nrr = lire("mrr.csv"), lire("cohorts.csv"), lire("nrr.csv")
mois = [r["month"] for r in mrr]
valeurs = [float(r["mrr"]) for r in mrr]
facture = [r["status"] == "billed" for r in mrr]
x = np.arange(len(mois))
nb = sum(facture) # les mois facturés viennent en premier
BG, FG, MUTED = '#0f1117', '#e0e0e0', '#9aa0b4'
FACTURE, CONTR = '#4fc3f7', '#f2b84b'
fig = plt.figure(figsize=(13.333, 7.5), facecolor=BG) # 16:9
gs = fig.add_gridspec(3, 2, height_ratios=[0.5, 5.2, 1.9], width_ratios=[1.35, 1],
hspace=0.35, wspace=0.18, left=0.06, right=0.97, top=0.97, bottom=0.04)
ax_t = fig.add_subplot(gs[0, :]); ax_t.axis('off')
ax_t.text(0, 0.5, f"{entreprise} – indicateurs de revenu", color='#fff', fontsize=20,
fontweight='bold', va='center')
ax_t.text(1, 0.5, f"facturé jusqu'à {as_of}", color=MUTED, fontsize=11, ha='right', va='center')
# --- Courbe MRR ---
ax = fig.add_subplot(gs[1, 0]); ax.set_facecolor(BG)
if nb < len(mois):
ax.axvspan(nb - 0.5, len(mois) - 0.5, color=CONTR, alpha=0.10, zorder=0)
ax.text(nb - 0.35, 0.97, "sous contrat,\npas encore facturé", color=CONTR, fontsize=9,
transform=ax.get_xaxis_transform(), va='top')
ax.plot(x[:nb], valeurs[:nb], color=FACTURE, lw=2.6, marker='o', ms=5, label='MRR facturé', zorder=3)
if nb < len(mois):
j = max(nb - 1, 0) # rejoindre les deux segments
ax.plot(x[j:], valeurs[j:], color=CONTR, lw=2.2, ls='--',
label='MRR sous contrat (non facturé)', zorder=3)
ax.scatter(x
…
[suite tronquée pour la traduction]