Construa atribuição de custos moldada como a organização
Você está guiando uma pessoa (e, quando a organização tem uma, um parceiro de finanças) por uma construção que é principalmente cola entre um esquema padrão (FOCUS) e um runtime serverless. Os padrões são fixos; a forma é deles. Seu trabalho é encontrar a forma deles nos seus dados, escrevê-la e aplicar os padrões a ela — nunca instalar um produto genérico.
O artigo explica por que cada padrão é correto: https://mooracle.io/blog/build-custom-finops-tool/ (Partes 1–7). Quando o usuário perguntar "por quê", aponte para a parte; mantenha a sessão no "como".
Invariantes
Estes nunca variam por perfil. Verifique o código gerado contra eles antes de mostrá-lo.
- Cada divisão mantém seu total.
SUM(out) == SUM(in)por coluna de custo. Os divisores movem dinheiro; nunca o criam ou destroem. - Aloque no momento da consulta. As categorias da organização são anexadas em uma view, por meio de uma
consulta. Nunca escreva
teamem uma linha do warehouse. EffectiveCostpara chargeback.BilledCostconcilia faturas,ListCostmede economias. Nunca os misture.- Reconstrua o mês inteiro. Cada partição
(billing_period, provider)é limpa e reescrita a partir dos dados brutos em cada entrega. Sem deltas, sem merges. - Limite até ontem. Nada datado de hoje ou posterior entra no warehouse.
- Cada divisão é uma única expansão. Uma linha que é dividida junta seus pesos dentro do
carregamento regular e se torna uma linha por peso; os pesos de uma chave somam 1,0. Divisões
fair-share, Kubernetes e de fornecedores funcionam todas assim, por meio de uma única tabela
split_weights. Nada é excluído do carregamento e adicionado depois, portanto não há exclusão a ser esquecida, e nada é contado duas vezes. - Toda execução prova seus números. Raw contém exatamente os arquivos que o manifesto do
provedor lista, e uma verificação bloqueante de totais compara a fatura com ambas as tabelas base
e falha a execução (e limpa o que escreveu) quando discordam. Depois que as views existem,
cada execução e cada fechamento também verificam se
services_monthlyserve exatamentebase_monthly(a verificação das views, que falha sem limpar nada). - Nunca perca custos; o roteamento pode falhar. Quando uma divisão não pode ser feita com segurança, o custo permanece inteiro onde foi faturado. Uma divisão ignorada é visível; um custo perdido é silencioso.
- Leituras brutas são do mês exato.
WHERE billing_period = 'YYYY-MM', com o limite de varredura do mecanismo ativado (um limite de bytes do workgroup no Athena). Leituras de todo o histórico são permitidas apenas sobrebase_monthly(megabytes). - Classifique uma coluna compartilhada uma vez, em uma view.
x_namespacecontém workspaces Kubernetes e SaaS igualmente;split_bydiz qual. Nunca infira a partir da forma do valor.
Arquivos que você mantém no repositório do usuário
Crie finops/ na Fase 0, ou onde o usuário quiser. Registre o caminho como o primeiro
D-001; abaixo, finops/ significa esse caminho.
-
finops/profile.yaml— a forma da organização. Comece a partir deassets/profile.template.yaml. Cada fase o lê; nada mais contém decisões específicas da organização. -
finops/DECISIONS.md— log somente de acréscimo. Nunca edite uma entrada; substitua-a.## D-007 Enterprise Support é compartilhado (2026-10-17) Fonte: D5 em 2026-09; Q-S1 respondido por <usuário> Decisão: dividir pelo gasto próprio do mesmo mês, por pagador Substitui: —Resultados de descoberta, respostas, suposições, aprovações e decisões negativas ("tentei uma tabela de pesos diários; reverti porque a mistura não varia dentro de um mês; não proponha novamente") vão todos aqui. Os campos
evidencedo perfil apontam para esses ids. -
finops/STATUS.md— uma linha por fase: estado, resultado do gate com os números, o commit SHA em que o gate passou e os campos do perfil dos quais a fase depende.| Fase | Estado | Gate | Em | Depende de | | 3 tabelas base | passou | fatura 2026-09 120.000,00 = mensal 120.000,00 | a1b2c3d | providers, dimensions, routing.key, finance, period_charges, shared_costs |
Loop de execução
Entrando
finops/STATUS.mdexiste → retomar: leia STATUS, DECISIONS e o perfil. Para cada fase que passou,git diff <seu SHA> -- finops/profile.yaml; se um campo do qual ela depende mudou, reabra essa fase e toda fase posterior que lê sua saída (1 → 2 → 3; 3 → 4, A, B, 5; 4 → 5). Re-execute o último gate que passou com os dados atuais (os dados também mudam). Continue da primeira fase que não passou.- O usuário relata uma mudança (reorganização, nova família de contas, novo fornecedor, novo cluster) → atualizar: re-execute as consultas de descoberta correspondentes, compare com o perfil, pergunte novamente apenas o que mudou, verifique novamente, reabra fases dependentes.
- O usuário quer ver funcionando primeiro, ou não dará acesso à fatura ainda → o teste
(
references/trial.md): um mês sintético no DuckDB, sem credenciais, verificado contra um gabarito de respostas. Nada disso é transferido para uma construção real. - Caso contrário → iniciar abaixo.
1. Descubra antes de perguntar
Leia references/discovery.md e execute suas consultas no último mês fechado, contra a
exportação onde ela já está (uma tabela sobre seus arquivos, ou DuckDB sobre uma entrega
baixada quando não há mecanismo), antes de qualquer construção existir. Mostre
cada resultado. Registre cada um como um D-NNN. A descoberta lê; ela não muda nada na
conta, nem mesmo a ativação de uma tag.
Ainda não há exportação → o caminho de bootstrap: ative a exportação (references/focus-export.md),
execute as rodadas de admissão sem dados (Organização, Runtime, Categorias, Finanças),
construa a pilha contra o layout esperado e execute a descoberta na próxima sessão assim que a
primeira entrega chegar. Ofereça o teste (references/trial.md) enquanto espera.
2. Entreviste para preencher as lacunas
Leia references/intake.md. A regra de parada ou suposição:
- a resposta move dinheiro ou propriedade → deixe
null; o componente permanece BLOQUEADO; - é um nome, local ou ferramenta → use o padrão, adicione uma entrada
assumptions:e umD-NNN, prossiga.
Nunca preencha um fato com um palpite plausível, um valor fixo ou um valor de um exemplo.
3. Verifique e aprove o plano
Escreva o verificador que references/plan.md descreve em finops/ (uma vez, com um teste por
regra) e execute-o no perfil. Ele imprime um veredito por componente e o que está pronto
para construir (PRONTO em toda a cadeia de dependências). Mostre-o.
Para cada linha BLOQUEADA, nomeie quem pode responder e a pergunta exata. Para cada NÃO_APLICÁVEL,
diga a alternativa mais simples em voz alta — incluindo "não construa isso" quando a linha
organizacional assim o disser. Obtenha aprovação explícita do plano; registre-a como um D-NNN.
4. Construa, fase por fase
Para cada fase no mapa abaixo, em ordem (as trilhas opcionais A e B após a Fase 4, apenas quando PRONTO):
- Verificação de entrada. O verificador com
--entering <fase>sai com 0, e toda fase da qual ela depende está como passada em STATUS.md. - Leia sua especificação (
references/<arquivo>, coluna Especificação do mapa de fases): o contrato, os invariantes e os casos de borda que mordem quando improvisados. - Escreva na pilha deles. Construa os componentes da fase no mecanismo e idioma do usuário
(Athena, BigQuery, Snowflake, Databricks, dbt, …) e no estilo do repositório deles,
moldado pelo perfil deles: um adaptador por provedor de
providers.md, apenas as divisões que o perfil precisa, apenas as colunas que ele roteia. As especificações dizem o que deve ser válido e os casos o que deve sair; o código é seu para escrever. Tome o máximo cuidado onde um erro é silencioso: os pesos, a distribuição ao longo dos dias, as verificações de totais, as proteções de moeda. - Porte os casos dourados (
cases/*.yaml; como, emreferences/testing.md) que se aplicam ao que esta fase construiu e adicione um caso para cada adaptação específica da organização. Uma fase não está construída até que seus casos passem. - Implante e execute o gate contra os dados reais deles. Gates são SQL que retornam zero linhas quando saudáveis. Mostre os números.
- Registre o resultado, o SHA e as dependências em STATUS.md.
Relatório do gate, toda vez:
Gate 3 (tabelas base), 2026-09, aws: APROVADO
fatura 120.000,00 · mensal 120.000,00 · diário 120.000,00 · deriva 0,00
suporte: 1 linha → 12 contas, pesos soma 1,000000
Um gate com falha interrompe a construção. Depure com a tabela de falhas da especificação (os nomes
de problema de cada verificação estão em reconciliation-ops.md, "Modos de falha"), corrija, re-execute.
Mapa de fases
| Fase | Constrói | Campos do perfil | Especificação | Casos | Gate |
|---|---|---|---|---|---|
| 0 | descoberta, entrevista, o verificador, plano | todos | discovery.md, intake.md, plan.md | — | plano aprovado (D-NNN) |
| 1 | Exportações FOCUS | providers | focus-export.md | — | um mês inteiro consultável (D0) |
| 2 | layout de armazenamento, mecanismo, tabelas e as entregas que preenchem raw (cópia ou poller, staging, etapa de publicação do ETL) | runtime, providers | stack.md, data-model.md, pipeline.md ("Entrega") | — | um mês publicado corresponde ao seu manifesto; uma consulta raw de um mês varre uma fração dos arquivos desse mês |
| 3 | adaptadores, tabelas base com rateio e divisões de custos compartilhados, a reconstrução do ETL | providers, dimensions, routing.key, finance, period_charges, shared_costs | providers.md, data-model.md, splitters.md, pipeline.md | adapters, load, share, checks | fatura == mensal == diário por pagador em um mês fechado |
| 4 | regras, lookup, views | categories, routing | rules-engine.md | rules, views | categorias aparecem; Não Alocado reportado |
| A | Divisões SaaS | saas.vendors | saas.md | vendor | o fornecedor mostra split; totais se mantêm |
| B | Namespaces Kubernetes | kubernetes | kubernetes.md | k8s | pesos limpos; cobertura ≥ alvo |
| 5 | reconciliação, operações, fechamento | finance, org.finance_partner | reconciliation-ops.md, testing.md | checks | onde a organização insere totais de fatura (Q-F5), um mês fechado passa a verificação de fatura para cada provedor, seu resultado armazenado com o mês; uma divisão deliberadamente quebrada falha a execução em uma cópia de desenvolvimento; uma caminhada pelos números com o parceiro de finanças, ou o proprietário quando não houver |
references/pipeline.md tem a ordem de processamento que essas peças executam em tempo de execução.
Graus de liberdade
- Fixo (seja qual for o mecanismo): os invariantes e o que os casos dizem que deve sair
(
adapters.yamlexceto: mostra as escolhas de um perfil nas formas brutas das exportações): as regras de rateio, a expansão com pesos que somam 1,0, os gates dos produtores (por pagador, gasto positivo, dados de fornecedor do mês inteiro), as verificações e no que elas param, a ordem de execução e limpeza em caso de falha, recusar parâmetros de consulta não preenchidos. - Deles (escreva livremente para corresponder ao código deles): o mecanismo e idioma, layout de arquivo, infraestrutura como código, empacotamento, agendamento, dashboards, CI e os nomes de arquivos, funções e infraestrutura (os nomes de tabela, coluna e problema que os casos usam são fixos).
Quando você não tiver certeza de qual lado uma mudança está, é fixo.
Quando o usuário resiste
references/pushback.md tem os usuais ("alocar no ETL", "mapear 100%", "um
framework para cada fornecedor", "precisão por pod") com a resposta e onde o
artigo argumenta.
Pronto significa
- Cada mês fechado passa na verificação de totais para cada provedor, através de cada divisão, e, onde a organização insere totais de fatura, na verificação de fatura.
- Quem reporta custo (o parceiro de finanças, ou o proprietário quando não houver) responde "quanto custou o time X no mês passado?" a partir de uma view, sem novo SQL.
- Uma mudança de regra é um pull request: CI expande, o revisor vê o que se move, o merge recategoriza todo o histórico sem reconstrução.
- Não Alocado é alertado como um valor, em sua moeda, e com tendência de queda.
- O custo de execução é pequeno e medido (na pilha de referência, cerca de $10 por mês para S3, Athena e Lambda juntos para 15 milhões de linhas de fatura por mês; a parte do Athena está nas métricas de varredura do workgroup).
- STATUS.md mostra toda fase que o plano constrói passou com um SHA; DECISIONS.md explica cada escolha que um recém-chegado questionaria.