Metrics Pack
Lo que estás haciendo
Se te ha pedido convertir los datos de ingresos de una startup en los números que un analista de fondos lee primero:
MRR, cohortes, NRR – en una sola diapositiva. Los números provienen de metrics.py, nunca de tu cabeza.
Tú eliges la entrada, ejecutas el helper, lees sus CSV y escribes las palabras alrededor de ellos.
ENTRADA HELPER (metrics.py) TÚ
───── ─────────────────── ───
updates/ (a) ──┐
exportación cliente (b) ─┼──► mrr.csv cohorts.csv ──► metrics.md (inglés sencillo)
registro contratos (c) ┘ nrr.csv excluded.csv metrics-slide.png (una diapositiva)
Entregables en <company-slug>/metrics-pack/, relativo al directorio de trabajo actual:
metrics.md– el informe. Comienza con "In plain English."mrr.csv,cohorts.csv,nrr.csv(yexcluded.csvpara un registro de contratos) – escritos por el helper, sin que tú los modifiques.metrics-slide.png– una diapositiva 16:9.
Abreviaturas usadas en este archivo: MRR = ingresos recurrentes mensuales; NRR = retención neta de ingresos;
CSV = archivo de valores separados por comas; PNG = archivo de imagen; ISO = el formato de fecha internacional
(YYYY-MM-DD, o YYYY-MM para un mes); EUR = euro; USD = dólar estadounidense; --as-of = la opción del helper que nombra el último mes facturado.
Entradas
De $ARGUMENTS o la conversación: una empresa y, opcionalmente, un archivo. Tres tipos de entrada,
en este orden de preferencia:
| Entrada | Qué es | Cómo entra al helper |
|---|---|---|
| (b) exportación por cliente | CSV customer_id,month,revenue, mes en formato YYYY-MM | --revenue <csv> |
| (c) registro de contratos | CSV con una fila de encabezado Client, Contract value, Start month, Term (months); las líneas por encima del encabezado se omiten; meses como May-26; valores como "1,550,000" | --contracts <csv> |
(a) <company-slug>/updates/ | archivos de actualización mensual con una línea de ingresos, sin detalle del cliente | leído como lo hace revenue-chart (Paso 2 allí), escribe customer_id,month,revenue con un cliente total, luego --revenue |
Con la entrada (a) no hay clientes. El helper entonces escribe cohorts.csv y nrr.csv con
solo un encabezado y deja las celdas new, expansion, contraction y churned de mrr.csv
vacías, porque con un solo cliente solo repetirían el crecimiento total. Ignora esas columnas,
no muestres ningún mapa de calor e imprime "12-month NRR: cannot compute yet" (razón: sin detalle del cliente).
Lo mismo aplica para cualquier archivo --revenue con un único id de cliente.
Si la empresa es ambigua (dos empresas comparten el nombre), pregunta cuál antes de leer nada.
Paso 1 – Ficha de empresa y entrada
La ficha de empresa
<company-slug>/company-profile.md – léela primero. Si falta, créala. Si un campo está
vacío y lo aprendes, complétalo y actualiza updated / updated_by. Nunca sobrescribas un campo
completado: si encuentras un valor diferente, agrega una fila a ## Conflicts con ambos valores y fuentes.
La ficha termina con una sección ## Conflicts (crea una si falta) cuyo encabezado de tabla es exactamente
| Field | Card value | Card source | New value | New source | Found by | Date |.
| Campo | Valores permitidos |
|---|---|
| Trabajo realizado para el cliente | una oración, en palabras propias de la empresa, citado, con URL |
| Quién paga | texto libre |
| Modelo de negocio | B2B · B2C · B2G |
| Modelo de ingresos | suscripción · transacción · anuncios · único · n.a. |
| Mercado | exactamente uno: Fintech & insurance · Health & bio · Commerce & retail · Logistics & supply chain · AI & data infrastructure · Enterprise software · Cybersecurity · Legal, compliance & governance · Sales & marketing · HR & talent · Education · Media, gaming & entertainment · Real estate & construction · Energy, climate & agriculture · Industrial & robotics · Consumer lifestyle & services · Other |
| Vertical | texto libre |
| Etapa | pre-seed · seed · Series A · Series B+ · n.a. |
| Geografía | país de la sede; países donde vende |
| Ronda en curso | monto + moneda + instrumento, o "not stated" |
| Métricas disponibles | dónde (updates/, exportaciones), o "none" |
Frontmatter: company, slug, website, updated (fecha ISO), updated_by (nombre del skill).
La tabla de la ficha tiene una tercera columna, Source, completada para cada campo.
Toma el nombre de la empresa, el modelo de negocio, el modelo de ingresos y la geografía de la ficha; la geografía sugiere la moneda que esperar. Completa "Métricas disponibles" con el archivo que uses. Si el modelo de ingresos no es suscripción, di en el informe que MRR asume ingresos recurrentes.
Encuentra la entrada (tabla anterior) y cópiala en <company-slug>/metrics-pack/input/ para que la ejecución sea
auditable. Nunca comprometas o publiques un archivo a nivel de cliente a menos que el propietario haya dicho que puede ser
público. Moneda: tómala del archivo; un registro de contratos a menudo no indica ninguna. Si no se indica ninguna,
escribe "currency not stated" en todas partes y nunca elijas una.
Hoy y --as-of. --as-of es el último mes que realmente se ha facturado – normalmente
el mes actual, a partir de la fecha de hoy (ISO). Los meses posteriores son contratados: firmados, aún no facturados.
Paso 2 – Ejecutar el helper
Usa /usr/bin/python3 o cualquier Python 3.9+; metrics.py solo necesita la biblioteca estándar y no realiza
llamadas de red. Se encuentra al lado de este archivo (skills/metrics-pack/metrics.py; en un plugin instalado,
${CLAUDE_PLUGIN_ROOT}/skills/metrics-pack/metrics.py).
python3 <ruta>/metrics.py --contracts <register.csv> --out-dir <company-slug>/metrics-pack \
--as-of 2026-11 --min-term 3
python3 <ruta>/metrics.py --revenue <export.csv> --out-dir <company-slug>/metrics-pack \
--as-of 2026-11
Lo que escribe:
| Archivo | Columnas | Significado |
|---|---|---|
mrr.csv | month, mrr, new, expansion, contraction, churned, status | una fila por mes calendario; status es billed hasta --as-of, contracted después |
cohorts.csv | cohort, month_offset, revenue | cohorte = el primer mes de un cliente con ingresos; desplazamiento = meses desde entonces |
nrr.csv | start_month, months, nrr | NRR a 12 meses para cada mes de inicio donde se pueda calcular solo a partir de meses facturados (un mes después de --as-of nunca cuenta); solo encabezado si no se puede calcular ninguno. También solo encabezado para una exportación de un solo cliente y para un registro de contratos sin renovaciones registradas; para un registro, solo se listan los meses de inicio en los que algún cliente tiene una renovación registrada (regla en el Paso 2) |
excluded.csv | client, value, start, term | contratos más cortos que --min-term meses (por defecto 3) |
Si el helper se detiene con error:, nombra la columna o valor incorrecto. Corrige la entrada en la fuente
(o pregunta al propietario) – nunca edites alrededor de ella.
Lo que asume el helper (indica cada uno en el informe):
- El valor del contrato es el total durante el plazo, distribuido uniformemente: valor ÷ plazo por mes, desde el mes de inicio. Si el propietario dice que es por mes o por año, detente y pregunta – la respuesta cambia cada número.
- Un contrato es recurrente solo si su plazo es de al menos
--min-termmeses. Los más cortos son no recurrentes: reportados enexcluded.csv, no en MRR. - Nada se renueva a menos que una nueva fila lo indique. Cuando el plazo de un contrato termina, el helper muestra
los ingresos que se van como
churned. En un registro de contratos que es fin de contrato, no abandono probado – dilo para cada mes de ese tipo. - Un registro de contratos registra contratos, no renovaciones. El fin del contrato se lee como ingresos perdidos,
por lo que el helper calcula
nrr.csva partir de un registro solo para los meses de inicio en los que algún cliente tiene una renovación registrada. Regla: una renovación es un contrato recurrente posterior (plazo de al menos--min-termmeses) del mismo cliente cuyo mes de inicio es al menos un mes después del mes de finalización de un contrato recurrente anterior (mes de finalización = mes de inicio + plazo - 1; por lo tanto, un contrato consecutivo que comienza el mes después del final cuenta, y se permite un intervalo). Un segundo contrato que se superpone con el primero (una venta adicional o complementaria) no es una renovación. Si ningún cliente tiene una renovación,nrr.csvsolo tiene encabezado y la respuesta es "cannot compute yet". Si algunos la tienen, la cifra refleja solo las renovaciones que el registro documenta y tanto el informe como la diapositiva deben decirlo. - Un cliente que se detiene y luego regresa cuenta como
newnuevamente.
Paso 3 – Leer las salidas
Lee los CSV con el módulo csv. Cada número que escribes proviene de ellos. No calcules nada en
el texto; si necesitas un número que los CSV no tengan (por ejemplo, una proporción), calcúlalo con un comando y
cita la salida del comando.
- MRR. El
mrrdel último mes facturado; el mes máximo y sustatus; la división facturado/contratado. Nombra el primer y último mes en los datos. - Agotamiento. Los meses posteriores al último inicio de contrato solo pierden ingresos, porque nada en los datos se renueva. Márcalos como agotamiento de contratos sin renovación, nunca como abandono.
- Cohortes. Cuenta las cohortes con al menos dos meses de ingresos facturados. El mapa de calor en la diapositiva necesita al menos dos de esas cohortes; de lo contrario, omítelo y explica por qué.
- NRR. Primero verifica la entrada. Para la entrada (a) o una exportación de un solo cliente, ignora
nrr.csv,cohorts.csvy las columnas de movimiento: la respuesta es "cannot compute yet". De lo contrario, sinrr.csvtiene filas de datos, cítalas; para un registro de contratos agrega "counts only renewals recorded in the register". Si solo tiene el encabezado, la respuesta es "12-month NRR cannot be computed yet": ningún cliente tiene un mes de ingresos doce meses después de su inicio. Nunca lo estimes, nunca lo completes desde una ventana más corta. Da la razón en una línea (por ejemplo, "every contract in the register starts in 2026, so none has run twelve months and renewed"). - Excluidos. Número de contratos excluidos, su valor total y la regla que los excluyó.
Paso 4 – metrics-slide.png
Necesita matplotlib y numpy (python3 -c "import matplotlib, numpy"). Si faltan, pregunta antes de instalar
algo; la ruta limpia es un entorno virtual desechable. La base es la plantilla de gráfico en
revenue-chart (tema oscuro, verificación de colisiones de etiquetas).
Una diapositiva 16:9, en este diseño:
┌─────────────────────────────────────────────────────────────┐
│ <Empresa> – métricas de ingresos a partir del <fecha ISO> │
├──────────────────────────────┬──────────────────────────────┤
│ Línea de MRR: sólido = facturado, │ mapa de calor de cohortes (solo meses │
│ discontinua + sombreado = contratado │ facturados, % del mes 0) – o el │
│ │ panel de NRR si no hay mapa de calor │
├──────────────────────────────┴──────────────────────────────┤
│ NRR: <cifra> o "12-month NRR: cannot compute yet" │
│ tres conclusiones de una línea │
└─────────────────────────────────────────────────────────────┘
Reglas para la diapositiva:
- Contratado nunca se estiliza como facturado. Los meses posteriores a
--as-ofusan una línea discontinua, marcadores huecos y una banda sombreada etiquetada "contracted, not yet billed". - Para un registro de contratos, una cifra de NRR mostrada lleva la advertencia "counts only renewals
recorded in the register" (el script la agrega cuando
source = "register"). - La línea de NRR muestra la cifra de
nrr.csv, o las palabras exactas "12-month NRR: cannot compute yet". - Mapa de calor solo si al menos dos cohortes tienen al menos dos meses facturados.
- Tres conclusiones de una línea, cada una trazable a una fila de CSV, ninguna es una recomendación o predicción.
- Moneda en el título del eje; "currency not stated" si la fuente no proporcionó ninguna.
- Todas las cifras en la diapositiva son leídas de los CSV por el script, no escritas a mano. Las conclusiones son el único texto escrito: verifica cada número en ellas contra los CSV después de dibujar.
import csv, os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
# CONFIGURAR -------------------------------------------------------------
company = "Empresa"
folder = "<company-slug>/metrics-pack"
currency = "currency not stated" # o "EUR", "USD" ...
as_of = "2026-11" # mismo valor que --as-of
source = "register" # "register" (--contracts) o "export" (--revenue)
takeaways = [ # tres líneas, cada una verificada contra los CSV
"Conclusión 1",
"Conclusión 2",
"Conclusión 3",
]
# -------------------------------------------------------------------------
def read(name):
with open(os.path.join(folder, name), newline="", encoding="utf-8") as fh:
return list(csv.DictReader(fh))
mrr, coh, nrr_rows = read("mrr.csv"), read("cohorts.csv"), read("nrr.csv")
months = [r["month"] for r in mrr]
vals = [float(r["mrr"]) for r in mrr]
billed = [r["status"] == "billed" for r in mrr]
x = np.arange(len(months))
nb = sum(billed) # los meses facturados vienen primero
BG, FG, MUTED = '#0f1117', '#e0e0e0', '#9aa0b4'
BILLED, 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"{company} – métricas de ingresos", color='#fff', fontsize=20,
fontweight='bold', va='center')
ax_t.text(1, 0.5, f"facturado hasta {as_of}", color=MUTED, fontsize=11, ha='right', va='center')
# --- Línea de MRR ---
ax = fig.add_subplot(gs[1, 0]); ax.set_facecolor(BG)
if nb < len(months):
ax.axvspan(nb - 0.5, len(months) - 0.5, color=CONTR, alpha=0.10, zorder=0)
ax.text(nb - 0.35, 0.97, "contratado,\naún no facturado", color=CONTR, fontsize=9,
transform=ax.get_xaxis_transform(), va='top')
ax.plot(x[:nb], vals[:nb], color=BILLED, lw=2.6, marker='o', ms=5, label='MRR facturado', zorder=3)
if nb < len(months):
j = max(nb - 1, 0) # unir los dos segmentos
ax.plot(x[j:], vals[j:], color=CONTR, lw=2.2, ls='--',
label='MRR contratado (no facturado)', zorder=3)
ax.scatter(x