Pacote de Métricas
O que você está fazendo
Você foi solicitado a transformar os dados de receita de uma startup nos números que um analista de fundos lê primeiro:
MRR, coortes, NRR – em um único slide. Os números vêm de metrics.py, nunca da sua cabeça.
Você escolhe a entrada, executa o helper, lê seus CSVs e escreve as palavras ao redor deles.
ENTRADA HELPER (metrics.py) VOCÊ
─────── ─────────────────── ───
updates/ (a) ──┐
exportação cliente (b) ──┼──► mrr.csv cohorts.csv ──► metrics.md (inglês claro)
registro contratos (c) ──┘ nrr.csv excluded.csv metrics-slide.png (um slide)
Entregáveis em <company-slug>/metrics-pack/, relativo ao diretório de trabalho atual:
metrics.md– a nota. Abre com "In plain English."mrr.csv,cohorts.csv,nrr.csv(eexcluded.csvpara um registro de contratos) – escritos pelo helper, intocados por você.metrics-slide.png– um slide 16:9.
Abreviações usadas neste arquivo: MRR = receita recorrente mensal; NRR = retenção de receita líquida;
CSV = arquivo de valores separados por vírgula; PNG = arquivo de imagem; ISO = o formato de data internacional
(YYYY-MM-DD, ou YYYY-MM para um mês); EUR = euro; USD = dólar americano; --as-of = a opção do
helper que nomeia o último mês faturado.
Entradas
De $ARGUMENTS ou da conversa: uma empresa, e opcionalmente um arquivo. Três tipos de entrada,
nesta ordem de preferência:
| Entrada | O que é | Como entra no helper |
|---|---|---|
| (b) exportação por cliente | CSV customer_id,month,revenue, mês como YYYY-MM | --revenue <csv> |
| (c) registro de contratos | CSV com uma linha de cabeçalho Client, Contract value, Start month, Term (months); linhas acima do cabeçalho são ignoradas; meses como May-26; valores como "1,550,000" | --contracts <csv> |
(a) <company-slug>/updates/ | arquivos de atualização mensal com uma linha de receita, sem detalhe de cliente | lido como revenue-chart faz (Passo 2 lá), escreve customer_id,month,revenue com um cliente total, então --revenue |
Com entrada (a) não há clientes. O helper então escreve cohorts.csv e nrr.csv com
apenas um cabeçalho e deixa as células new, expansion, contraction e churned de mrr.csv
vazias, porque com um cliente elas apenas reafirmariam o crescimento total. Ignore essas colunas,
não mostre um mapa de calor, e imprima "12-month NRR: cannot compute yet" (motivo: nenhum detalhe de cliente).
O mesmo vale para qualquer arquivo --revenue com um único id de cliente.
Se a empresa for ambígua (duas empresas compartilham o nome), pergunte qual delas antes de ler qualquer coisa.
Passo 1 – Ficha da empresa e entrada
A ficha da empresa
<company-slug>/company-profile.md – leia-a primeiro. Se estiver faltando, crie-a. Se um campo estiver
vazio e você aprender, preencha-o e atualize updated / updated_by. Nunca sobrescreva um campo
preenchido: se encontrar um valor diferente, adicione uma linha em ## Conflicts com ambos os valores e fontes.
A ficha termina com uma seção ## Conflicts (crie-a se faltar) cujo cabeçalho da tabela é exatamente
| Field | Card value | Card source | New value | New source | Found by | Date |.
| Campo | Valores permitidos |
|---|---|
| Trabalho feito para o cliente | uma frase, nas próprias palavras da empresa, com URL |
| Quem paga | texto livre |
| Modelo de negócio | B2B · B2C · B2G |
| Modelo de receita | assinatura · transação · anúncios · pontual · n.a. |
| Mercado | exatamente um: Fintech & seguros · Saúde & bio · Comércio & varejo · Logística & cadeia de suprimentos · IA & infraestrutura de dados · Software empresarial · Cibersegurança · Jurídico, compliance & governança · Vendas & marketing · RH & talentos · Educação · Mídia, jogos & entretenimento · Imobiliário & construção · Energia, clima & agricultura · Industrial & robótica · Estilo de vida do consumidor & serviços · Outros |
| Vertical | texto livre |
| Estágio | pré-seed · seed · Série A · Série B+ · n.a. |
| Geografia | país sede; países onde vende |
| Rodada sendo levantada | valor + moeda + instrumento, ou "não informado" |
| Métricas disponíveis | onde (updates/, exportações), ou "nenhuma" |
Frontmatter: company, slug, website, updated (data ISO), updated_by (nome do skill).
A tabela da ficha tem uma terceira coluna, Source, preenchida para cada campo.
Pegue o nome da empresa, modelo de negócio, modelo de receita e geografia da ficha; geografia sugere a moeda esperada. Preencha "Metrics available" com o arquivo que você usar. Se o modelo de receita não for assinatura, diga na nota que MRR assume receita recorrente.
Encontre a entrada (tabela acima) e copie-a para <company-slug>/metrics-pack/input/ para que a
execução seja auditável. Nunca publique ou envie um arquivo em nível de cliente a menos que o
dono tenha dito que pode ser público. Moeda: pegue-a do arquivo; um registro de contratos
frequentemente não informa nenhuma. Se nenhuma for informada, escreva "moeda não informada" em
todos os lugares e nunca escolha uma.
Hoje e --as-of. --as-of é o último mês que foi realmente faturado – normalmente
o mês atual, a partir da data de hoje (ISO). Meses após ele são contratados: assinados, ainda não
faturados.
Passo 2 – Execute o helper
Use /usr/bin/python3 ou qualquer Python 3.9+; metrics.py precisa apenas da biblioteca padrão e não faz
chamadas de rede. Ele fica ao lado deste arquivo (skills/metrics-pack/metrics.py; em um plugin
instalado, ${CLAUDE_PLUGIN_ROOT}/skills/metrics-pack/metrics.py).
python3 <caminho>/metrics.py --contracts <register.csv> --out-dir <company-slug>/metrics-pack \
--as-of 2026-11 --min-term 3
python3 <caminho>/metrics.py --revenue <export.csv> --out-dir <company-slug>/metrics-pack \
--as-of 2026-11
O que ele escreve:
| Arquivo | Colunas | Significado |
|---|---|---|
mrr.csv | month, mrr, new, expansion, contraction, churned, status | uma linha por mês calendário; status é billed até --as-of, contracted após |
cohorts.csv | cohort, month_offset, revenue | coorte = primeiro mês de um cliente com receita; offset = meses desde então |
nrr.csv | start_month, months, nrr | NRR de 12 meses para cada mês inicial onde pode ser calculado a partir de meses faturados apenas (um mês após --as-of nunca conta); apenas cabeçalho se nenhum puder. Também apenas cabeçalho para uma exportação de único cliente, e para um registro de contratos sem renovação registrada; para um registro, apenas meses iniciais em que algum cliente tem uma renovação registrada (regra no Passo 2) são listados |
excluded.csv | client, value, start, term | contratos mais curtos que --min-term meses (padrão 3) |
Se o helper parar com error:, ele nomeia a coluna ou valor ruim. Corrija a entrada na fonte
(ou pergunte ao dono) – nunca edite ao redor dela.
O que o helper assume (diga cada um na nota):
- O valor do contrato é o total ao longo do prazo, distribuído uniformemente: valor ÷ prazo por mês, a partir do mês inicial. Se o dono disser que é por mês ou por ano, pare e pergunte – a resposta muda cada número.
- Um contrato é recorrente apenas se seu prazo for de pelo menos
--min-termmeses. Mais curtos são não recorrentes: relatados emexcluded.csv, não no MRR. - Nada renova a menos que uma nova linha diga. Quando o prazo de um contrato termina, o helper mostra a
receita saindo como
churned. Em um registro de contratos, isso é fim do contrato, não churn comprovado – diga isso para cada mês desse tipo. - Um registro de contratos registra contratos, não renovações. O fim do contrato é lido como perda de receita,
então o helper calcula
nrr.csvde um registro apenas para meses iniciais em que algum cliente tem uma renovação registrada. Regra: uma renovação é um contrato recorrente posterior (prazo pelo menos--min-termmeses) do mesmo cliente cujo mês inicial é pelo menos um mês após o mês final de um contrato recorrente anterior (mês final = mês inicial + prazo - 1; então um contrato consecutivo iniciando no mês após o final conta, e uma lacuna é permitida). Um segundo contrato que sobrepõe o primeiro (um upsell ou complemento) não é uma renovação. Se nenhum cliente tiver uma renovação,nrr.csvtem apenas cabeçalho e a resposta é "cannot compute yet". Se alguns tiverem, o valor reflete apenas as renovações que o registro registra e a nota e o slide devem dizer isso. - Um cliente que para e depois retorna conta como
newnovamente.
Passo 3 – Leia as saídas
Leia os CSVs com o módulo csv. Cada número que você escreve vem deles. Não compute nada em
prosa; se precisar de um número que os CSVs não tenham (por exemplo, uma proporção), compute-o com um comando e
cite a saída do comando.
- MRR. O
mrrdo último mês faturado; o mês de pico e seustatus; a divisão faturado-para-contratado. Nomeie o primeiro e o último mês nos dados. - Execução. Meses após o último início de contrato apenas perdem receita, porque nada nos dados renova. Marque-os como execução de contratos sem renovação, nunca como churn.
- Coortes. Conte coortes com pelo menos dois meses de receita faturada. O mapa de calor no slide precisa de pelo menos duas dessas coortes; caso contrário, pule e diga por quê.
- NRR. Primeiro verifique a entrada. Para entrada (a) ou uma exportação de único cliente, ignore
nrr.csv,cohorts.csve as colunas de movimento: a resposta é "cannot compute yet". Caso contrário, senrr.csvtiver linhas de dados, cite-as; para um registro de contratos adicione "conta apenas renovações registradas no registro". Se tiver apenas o cabeçalho, a resposta é "12-month NRR cannot be computed yet": nenhum cliente tem um mês de receita doze meses após seu início. Nunca estime, nunca preencha a partir de uma janela mais curta. Dê o motivo em uma linha (por exemplo "todo contrato no registro começa em 2026, então nenhum rodou doze meses e renovou"). - Excluídos. Número de contratos excluídos, seu valor total, e a regra que os excluiu.
Passo 4 – metrics-slide.png
Precisa de matplotlib e numpy (python3 -c "import matplotlib, numpy"). Se faltarem, pergunte antes de
instalar qualquer coisa; a rota limpa é um ambiente virtual descartável. A base é o modelo de
gráfico em revenue-chart (tema escuro, verificação de colisão de rótulos).
Um slide 16:9, neste layout:
┌─────────────────────────────────────────────────────────────┐
│ <Company> – métricas de receita a partir de <data ISO> │
├──────────────────────────────┬──────────────────────────────┤
│ Linha MRR: sólido = faturado, │ mapa de calor de coorte (meses │
│ tracejado + sombreado = contratado │ faturdos apenas, % do mês 0) – ou o │
│ │ painel NRR se sem mapa de calor │
├──────────────────────────────┴──────────────────────────────┤
│ NRR: <valor> ou "12-month NRR: cannot compute yet" │
│ três conclusões de uma linha │
└─────────────────────────────────────────────────────────────┘
Regras para o slide:
- Contratado nunca é estilizado como faturado. Meses após
--as-ofusam uma linha tracejada, marcadores ocos e uma faixa sombreada rotulada "contratado, ainda não faturado". - Para um registro de contratos, um valor NRR mostrado traz a ressalva "conta apenas renovações registradas
no registro" (o script adiciona quando
source = "register"). - A linha NRR mostra o valor de
nrr.csv, ou as palavras exatas "12-month NRR: cannot compute yet". - Mapa de calor apenas se pelo menos duas coortes tiverem pelo menos dois meses faturados.
- Três conclusões de uma linha, cada uma rastreável a uma linha CSV, nenhuma uma recomendação ou previsão.
- Moeda no título do eixo; "moeda não informada" se a fonte não deu nenhuma.
- Todos os valores no slide são lidos dos CSVs pelo script, não digitados. As conclusões são o único texto digitado: verifique cada número nelas contra os CSVs após desenhar.
import csv, os
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
# CONFIGURE ---------------------------------------------------------------
company = "Company"
folder = "<company-slug>/metrics-pack"
currency = "currency not stated" # ou "EUR", "USD" ...
as_of = "2026-11" # mesmo valor de --as-of
source = "register" # "register" (--contracts) ou "export" (--revenue)
takeaways = [ # três conclusões de uma linha, cada uma verificada contra os CSVs
"Takeaway 1",
"Takeaway 2",
"Takeaway 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) # meses faturados vêm primeiro
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 receita", color='#fff', fontsize=20,
fontweight='bold', va='center')
ax_t.text(1, 0.5, f"faturado através de {as_of}", color=MUTED, fontsize=11, ha='right', va='center')
# --- Linha 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,\
ainda não faturado", 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 faturado', zorder=3)
if nb < len(months):
j = max(nb - 1, 0) # une os dois segmentos
ax.plot(x[j:], vals[j:], color=CONTR, lw=2.2, ls='--',
label='MRR contratado (não faturado)', zorder=3)
ax.scatter(x
…
[truncado para tradução]