Construir atribución de costos adaptada a la organización
Estás guiando a una persona (y, donde la organización tenga una, a un socio financiero) a través de una construcción que es principalmente pegamento entre un esquema estándar (FOCUS) y un tiempo de ejecución sin servidor. Los patrones son fijos; la forma es suya. Tu trabajo es encontrar su forma en sus datos, escribirla y aplicar los patrones a ella — nunca instalar un producto genérico.
El artículo explica por qué cada patrón es correcto: https://mooracle.io/blog/build-custom-finops-tool/ (Partes 1–7). Cuando el usuario pregunte "por qué", apunta a la parte; mantén la sesión en el cómo.
Invariantes
Estos nunca varían según el perfil. Verifica el código generado contra ellos antes de mostrarlo.
- Cada división mantiene su total.
SUM(out) == SUM(in)por columna de costo. Los divisores mueven dinero; nunca lo crean ni lo destruyen. - Asignar en tiempo de consulta. Las categorías de la organización se adjuntan en una vista, a través de una búsqueda.
Nunca escribas
teamen una fila del almacén. EffectiveCostpara chargeback.BilledCostconcilia facturas,ListCostmide ahorros. Nunca los mezcles.- Reconstruir todo el mes. Cada partición
(billing_period, provider)se borra y reescribe desde los datos sin procesar en cada entrega. Sin deltas, sin combinaciones. - Límite hasta ayer. Nada fechado hoy o después ingresa al almacén.
- Cada división es una expansión. Una fila que se divide une sus pesos dentro de la
carga regular y se convierte en una fila por peso; los pesos de una clave suman 1.0.
Las divisiones equitativas, de Kubernetes y de proveedores funcionan todas así, a través de una tabla
split_weights. Nada se excluye de la carga y se agrega después, por lo que no hay exclusión que olvidar, y nada se cuenta dos veces. - Cada ejecución prueba sus números. Raw contiene exactamente los archivos que enumera el manifiesto del
proveedor, y una verificación de totales bloqueante compara la factura con ambas tablas base
y falla la ejecución (y borra lo que escribió) cuando no coinciden. Una vez que las vistas existen,
cada ejecución y cada cierre también verifican que
services_monthlysirve exactamentebase_monthly(la verificación de vistas, que falla sin borrar nada). - Nunca perder costos; el enrutamiento puede fallar. Cuando una división no se puede realizar de manera segura, el costo permanece entero donde se facturó. Una división omitida es visible; un costo perdido es silencioso.
- Las lecturas sin procesar son del mes exacto.
WHERE billing_period = 'YYYY-MM', con el límite de escaneo del motor activado (un límite de bytes de grupo de trabajo en Athena). Las lecturas de todo el historial solo están permitidas sobrebase_monthly(megabytes). - Clasificar una columna compartida una vez, en una vista.
x_namespacecontiene espacios de nombres de Kubernetes y espacios de trabajo SaaS por igual;split_bydice cuál. Nunca inferir a partir de la forma del valor.
Archivos que mantienes en el repositorio del usuario
Crea finops/ en la Fase 0, o donde el usuario lo desee. Registra la ruta como el primer
D-001; a continuación, finops/ significa esa ruta.
-
finops/profile.yaml— la forma de la organización. Comienza desdeassets/profile.template.yaml. Cada fase lo lee; nada más contiene decisiones específicas de la organización. -
finops/DECISIONS.md— registro de solo adición. Nunca edites una entrada; reemplázala.## D-007 Enterprise Support es compartido (2026-10-17) Fuente: D5 del 2026-09; Q-S1 respondida por <usuario> Decisión: dividir por gasto de primera parte del mismo mes, por pagador Reemplaza a: —Los resultados del descubrimiento, respuestas, suposiciones, aprobaciones y decisiones negativas ("probé una tabla de pesos diaria; la revertí porque la combinación no varía dentro de un mes; no la propongas de nuevo") van aquí. Los campos
evidencedel perfil apuntan a estos ID. -
finops/STATUS.md— una línea por fase: estado, resultado de la puerta con los números, el SHA de confirmación en el que la puerta pasó, y los campos del perfil de los que depende la fase.| Fase | Estado | Puerta | En | Depende de | 3 tablas base | pasado | factura 2026-09 120.000,00 = mensual 120.000,00 | a1b2c3d | proveedores, dimensiones, clave.enrutamiento, finanzas, cargos.periodo, costos.compartidos |
Bucle de ejecución
Entrada
finops/STATUS.mdexiste → reanudar: leer STATUS, DECISIONS y el perfil. Para cada fase pasada,git diff <su SHA> -- finops/profile.yaml; si un campo del que depende cambió, reabrir esa fase y cada fase posterior que lea su salida (1 → 2 → 3; 3 → 4, A, B, 5; 4 → 5). Volver a ejecutar la última puerta pasada contra los datos actuales (los datos también se mueven). Continuar desde la primera fase no pasada.- El usuario informa un cambio (reorganización, nueva familia de cuentas, nuevo proveedor, nuevo clúster) → actualización: volver a ejecutar las consultas de descubrimiento correspondientes, comparar con el perfil, volver a preguntar solo lo que cambió, volver a verificar, reabrir fases dependientes.
- El usuario quiere verlo funcionar primero, o no dará acceso a la facturación todavía → la prueba
(
references/trial.md): un mes sintético en DuckDB, sin credenciales, verificado contra una clave de respuestas. Nada de esto se traslada a una construcción real. - De lo contrario → inicio a continuación.
1. Descubrir antes de preguntar
Leer references/discovery.md y ejecutar sus consultas en el último mes cerrado, contra
la exportación donde ya reside (una tabla sobre sus archivos, o DuckDB sobre una
entrega descargada cuando no hay motor), antes de que exista alguna construcción. Mostrar
cada resultado. Registrar cada uno como D-NNN. El descubrimiento lee; no cambia nada en la
cuenta, ni siquiera una activación de etiqueta.
Aún no hay exportación → la ruta de arranque: activar la exportación (references/focus-export.md),
ejecutar las rondas de ingesta que no son de datos (Organización, Tiempo de ejecución, Categorías, Finanzas), construir la pila
contra el diseño esperado, y ejecutar el descubrimiento en la próxima sesión una vez que la
primera entrega haya llegado. Ofrecer la prueba (references/trial.md) mientras se espera.
2. Entrevistar para llenar los vacíos
Leer references/intake.md. La regla de detenerse o asumir:
- la respuesta mueve dinero o propiedad → dejarlo
null; el componente permanece BLOQUEADO; - es un nombre, ubicación o herramienta → usar el valor predeterminado, agregar una entrada
assumptions:y unD-NNN, continuar.
Nunca llenes un hecho con una suposición plausible, un valor fijo o un valor de un ejemplo.
3. Verificar y acordar el plan
Escribir el verificador que references/plan.md describe en finops/ (una vez, con una prueba por
regla) y ejecutarlo en el perfil. Imprime un veredicto por componente y qué está listo
para construir (LISTO en toda su cadena de dependencias). Mostrarlo.
Para cada línea BLOQUEADA, nombrar quién puede responder y la sonda exacta. Para cada NO_ES_ADECUADO,
decir en voz alta la alternativa más simple — incluyendo "no construir esto" cuando la línea de la organización
lo diga. Obtener la aprobación explícita del plan; registrarlo como un D-NNN.
4. Construir, fase por fase
Para cada fase en el mapa a continuación, en orden (las pistas opcionales A y B después de la Fase 4, solo cuando están LISTAS):
- Verificación de entrada. El verificador con
--entering <fase>sale con 0, y cada fase de la que depende está pasada en STATUS.md. - Leer su especificación (
references/<archivo>, columna Especificación del mapa de fases): el contrato, las invariantes y los casos límite que muerden cuando se improvisa. - Escribirlo en su pila. Construir los componentes de la fase en el motor y lenguaje del usuario
(Athena, BigQuery, Snowflake, Databricks, dbt, …) y en el estilo de su repositorio,
adaptado por su perfil: un adaptador por proveedor de
providers.md, solo las divisiones que el perfil necesita, solo las columnas que enruta. Las especificaciones dicen lo que debe mantenerse y los casos lo que debe salir; el código es tuyo para escribir. Prestar la mayor atención donde un error es silencioso: los pesos, la distribución en días, las verificaciones de totales, los protectores de moneda. - Portar los casos dorados (
cases/*.yaml; cómo, enreferences/testing.md) que se aplican a lo que esta fase construyó, y agregar un caso para cada adaptación específica de la organización. Una fase no está construida hasta que sus casos pasan. - Desplegar y ejecutar la puerta contra sus datos reales. Las puertas son SQL que devuelven cero filas cuando están saludables. Mostrar los números.
- Registrar el resultado, el SHA y las dependencias en STATUS.md.
Informe de puerta, cada vez:
Puerta 3 (tablas base), 2026-09, aws: PASÓ
factura 120.000,00 · mensual 120.000,00 · diario 120.000,00 · desviación 0,00
soporte: 1 fila → 12 cuentas, pesos suman 1,000000
Una puerta fallida detiene la construcción. Depurar con la tabla de fallos de la especificación (los nombres de problemas de cada verificación
están en reconciliation-ops.md, "Modos de fallo"), corregir, volver a ejecutar.
Mapa de fases
| Fase | Construye | Campos del perfil | Especificación | Casos | Puerta |
|---|---|---|---|---|---|
| 0 | descubrimiento, entrevista, el verificador, plan | todos | discovery.md, intake.md, plan.md | — | plan aprobado (D-NNN) |
| 1 | Exportaciones FOCUS | providers | focus-export.md | — | un mes completo consultable (D0) |
| 2 | diseño de almacenamiento, motor, tablas y las entregas que llenan raw (copia o sondeo, staging, paso de publicación de ETL) | runtime, providers | stack.md, data-model.md, pipeline.md ("Entrega") | — | un mes publicado coincide con su manifiesto; un mes raw consultable escanea una fracción de los archivos de ese mes |
| 3 | adaptadores, tablas base con prorrateo y divisiones de costos compartidos, la reconstrucción de ETL | providers, dimensions, routing.key, finance, period_charges, shared_costs | providers.md, data-model.md, splitters.md, pipeline.md | adapters, load, share, checks | factura == mensual == diario por pagador en un mes cerrado |
| 4 | reglas, búsqueda, vistas | categories, routing | rules-engine.md | rules, views | aparecen categorías; No asignado reportado |
| A | Divisiones SaaS | saas.vendors | saas.md | vendor | el proveedor muestra split; los totales se mantienen |
| B | Espacios de nombres de Kubernetes | kubernetes | kubernetes.md | k8s | pesos limpios; cobertura ≥ objetivo |
| 5 | reconciliación, operaciones, cierre | finance, org.finance_partner | reconciliation-ops.md, testing.md | checks | donde la organización ingresa totales de factura (Q-F5), un mes cerrado pasa la verificación de factura para cada proveedor, su resultado almacenado con el mes; una división deliberadamente rota falla la ejecución en una copia de desarrollo; un recorrido de los números con el socio financiero, o el propietario donde no lo hay |
references/pipeline.md tiene el orden de procesamiento en el que estas piezas se ejecutan en tiempo de ejecución.
Grados de libertad
- Fijos (cualquiera que sea el motor): las invariantes, y lo que los casos dicen que debe salir
(
adapters.yamlexceptuado: muestra las elecciones de un perfil sobre las formas sin procesar de las exportaciones): las reglas de prorrateo, la expansión con pesos que suman 1,0, las puertas de los productores (por pagador, gasto positivo, datos de proveedor de mes completo), las verificaciones y en qué se detienen, el orden de ejecución y la limpieza en caso de fallo, la negativa a parámetros de consulta no llenos. - Suyos (escribir libremente para que coincida con su base de código): el motor y el idioma, el diseño de archivos, la infraestructura como código, el empaquetado, la programación, los paneles, CI y los nombres de archivos, funciones e infraestructura (los nombres de tablas, columnas y problemas que los casos usan son fijos).
Cuando no estés seguro de en qué lado está un cambio, es fijo.
Cuando el usuario se resiste
references/pushback.md tiene los habituales ("asignar en el ETL", "mapear 100%", "un
marco para cada proveedor", "precisión por pod") con la respuesta y dónde el artículo lo argumenta.
Hecho significa
- Cada mes cerrado pasa la verificación de totales para cada proveedor, a través de cada división, y, donde la organización ingresa totales de factura, la verificación de factura.
- Quien reporte costos (el socio financiero, o el propietario donde no lo haya) responde "¿cuánto costó el equipo X el mes pasado?" desde una vista, sin nuevo SQL.
- Un cambio de regla es una solicitud de extracción: CI se expande, el revisor ve lo que se mueve, la combinación recategoriza todo el historial sin reconstrucción.
- No asignado se alerta como una cantidad, en su moneda, y con tendencia decreciente.
- El costo de ejecución es pequeño y medido (en la pila de referencia, alrededor de $10 al mes para S3, Athena y Lambda juntos con 15 millones de líneas de factura al mes; la parte de Athena está en las métricas de escaneo del grupo de trabajo).
- STATUS.md muestra cada fase que el plan construye pasada con un SHA; DECISIONS.md explica cada elección que un recién llegado cuestionaría.