Ir para o conteúdo

Nota (PT-BR): este documento é uma referência normativa de engenharia e permanece temporariamente em inglês para preservar precisão técnica. A visão geral em português está em Decision Engine. A tradução completa está no backlog de documentação.

Contrato de dados de decisioning

Status: normativo para novas implementações de inteligência e alocação de capital. Baseline: 2026-08-20. Escopo: fatos, qualidade, cenários, evidências e snapshots consumidos pelo motor determinístico. As decisões de arquitetura complementares estão em architecture-decisions.md; fontes e SLAs estão em source-matrix.md.

Este contrato implementa quatro regras inegociáveis:

  1. dinheiro e métricas de decisão são calculados em PostgreSQL, nunca por LLM nem pelo navegador;
  2. null significa desconhecido e nunca é convertido silenciosamente em zero;
  3. conversão, refund/chargeback e rebill amadurecem em relógios independentes;
  4. uma recomendação é somente leitura até uma aprovação humana registrada.

Os termos DEVE, NÃO DEVE, DEVERIA e PODE são normativos.

1. Escopo e planos

O modelo é separado em seis planos. Uma tabela pode materializar mais de um plano apenas se preservar a imutabilidade e a proveniência de cada fato.

Plano Responsabilidade Pode ser escrito por
Fatos mídia, tracking, eventos de rede, ledger, saldos, FX e compromissos adapters, webhooks, jobs e RPCs transacionais
Qualidade completude, frescor, dedupe, reconciliação, atribuição e maturidade SQL determinístico
Quant unit economics, amostra, risco, capital e alocação funções/views SQL versionadas
Decisão regras, findings, recomendações, cooldown e aprovação humana avaliador SQL/RPC e usuário autorizado
IA explicação tipada limitada aos evidence IDs recebidos gateway server-side
Aprendizado outcomes de coortes maduras e taxonomias versionadas jobs após gates de maturidade

Dados externos entram como raw imutável, são normalizados e só depois podem gerar fatos canônicos. Raw, fato normalizado, evidência e decisão NÃO DEVEM ser colapsados em uma única linha mutável.

fonte -> raw imutável -> normalização versionada -> fato canônico
      -> gates de qualidade -> evidence snapshot -> cálculo SQL
      -> recomendação -> decisão humana -> ação operacional fora do motor

2. Identidade e granularidade

Todo objeto tenant-owned DEVE carregar workspace_id. Chaves estrangeiras entre objetos tenant-owned DEVEM impedir referências entre workspaces. A workspace ativa é escolhida explicitamente e validada no servidor; escolher a primeira membership não satisfaz este contrato.

Campos comuns mínimos de um fato canônico:

Campo Tipo lógico Regra
id UUID gerado no servidor, imutável
workspace_id UUID obrigatório e coberto por RLS forçada
source_system enum/text valor controlado da matriz de fontes
source_account_id text identificador externo da conta; null só com reason code
source_record_id text ID externo ou chave canônica reproduzível
source_event_id text ID do evento quando a fonte o fornece
raw_record_id UUID referência ao raw; obrigatório para integrações automáticas
normalization_version text versão imutável do parser/mapeamento
event_at timestamptz relógio econômico ou operacional da fonte
observed_at timestamptz instante em que a fonte representava o valor
ingested_at timestamptz instante de recebimento pela Brandvelia
dedupe_key text chave determinística, com unique por workspace/fonte
payload_hash text SHA-256 de representação canônica sem secrets
quality_status enum VALID, PROVISIONAL, UNKNOWN ou INVALID
quality_reasons text[] códigos controlados, nunca apenas texto livre

source_record_id não é substituído por nome de campanha, nome de oferta ou qualquer outra dimensão mutável. Nomes são atributos; IDs são identidade.

3. Símbolos de capital

Todos os termos abaixo pertencem a uma única workspace_id, allocation_run_id, as_of, horizon_end e allocation_currency. É proibido misturar moedas ou instantes no mesmo cálculo.

Símbolo Campo canônico Definição
S0 settled_cash saldo de caixa liquidado, reconciliado e disponível no início lógico do allocation run (as_of); exclui accrued, pending, held, forecast e recebíveis
Cp prior_commitments compromissos prévios ainda não liquidados que já são autorizados ou inevitáveis dentro do horizonte, sem duplicar saídas já refletidas em S0
L liquidity_floor reserva de liquidez exigida pela policy version: operação, refund/chargeback, impostos e piso mínimo de caixa, em componentes não sobrepostos
Ca allocatable_capital capital alocável conservador depois de compromissos e reserva
I requested_investment nova alocação solicitada pelo cenário/decisão; ao ser aprovada vira reserva ou compromisso e deixa de estar livre

Fórmulas normativas:

Ca = greatest(0, S0 - Cp - L)
0 <= I <= Ca
retained_cash = Ca - I

Regras adicionais:

  • S0, Cp, L, Ca e I DEVEM ter a mesma moeda.
  • Saldos em outra moeda não entram em S0 até estarem liquidados na moeda de alocação. Uma conversão futura ou uma cotação de mercado não é caixa em USD.
  • Comissões ClickBank accrued, return allowance, payout scheduled, valores held e rebills esperados não entram em S0.
  • Budget total ou diário é um teto operacional, não automaticamente Cp. Somente a parte já aprovada, reservada ou incorrida e não paga é compromisso.
  • Gasto Meta já incorrido, ainda não debitado da conta financeira, entra em Cp; o mesmo gasto não pode também reduzir S0.
  • L é produzido por componentes identificáveis e uma policy_version; não é um percentual livre digitado no dashboard.
  • Ca = 0 é um resultado válido. Caixa retido é uma alocação válida.
  • Falta de S0, Cp ou L produz Ca = null, nunca 0.

O SQL equivalente DEVE preservar o desconhecido:

case
  when s0 is null or cp is null or liquidity_floor is null then null
  else greatest(0::numeric, s0 - cp - liquidity_floor)
end

4. Dinheiro, moeda e precisão

4.1 Tipo canônico

Na API e em snapshots JSON, dinheiro é um objeto e o decimal é string:

{
  "amount": "1250.430000",
  "currency": "USD"
}

No PostgreSQL:

  • valores monetários novos: numeric(20,6);
  • taxas e razões: numeric(24,12);
  • moeda: código ISO 4217 em maiúsculas, inicialmente USD e BRL;
  • TypeScript: decimal string na fronteira; number não é contrato financeiro;
  • NaN, Infinity, float SQL e soma de moedas diferentes são inválidos.

A escala adicional preserva micros de plataformas e rateios. Arredondamento só ocorre na liquidação exigida pela fonte ou na apresentação; o modo e a escala DEVEM ser explícitos (HALF_EVEN por padrão). O valor bruto recebido permanece no raw.

4.2 Sinal e natureza

amount canônico é não negativo. O sentido é explícito:

direction = INFLOW | OUTFLOW
economic_type = REVENUE | REFUND | CHARGEBACK | FEE | AD_SPEND |
                OPEX | PAYOUT | TRANSFER | FX | ADJUSTMENT

Uma transferência entre contas produz duas pernas ligadas por transfer_id e não é receita nem despesa. Refund e chargeback apontam para a coorte/transação original. Correções são novas entradas de ajuste; fatos financeiros importados e reconciliados não são atualizados in-place.

4.3 Conversão cambial

Uma conversão DEVE registrar:

Campo Regra
base_currency / quote_currency par explícito; por exemplo USD/BRL
base_amount / quote_amount valores efetivamente trocados
market_rate benchmark observado, opcional com reason code
actual_rate quote_amount / base_amount, obrigatório para lucro realizado
fx_fee e fx_fee_currency separados do principal
provider, external_ref comprovante da operação
quoted_at, executed_at, settled_at relógios distintos

market_rate serve para benchmark e planejamento; nunca substitui actual_rate no lucro realizado. Métricas em moeda gerencial DEVEM expor a fonte e o timestamp da taxa. Não se soma USD e BRL antes da conversão por uma regra versionada.

5. Relógios e janelas

Datas são fatos, não metadados intercambiáveis.

Relógio Significado
event_at quando o clique, impressão, compra, refund, gasto ou compromisso ocorreu na fonte
source_updated_at quando a fonte corrigiu a versão, se disponível
observed_at instante ao qual um snapshot de saldo, insight ou cotação se refere
ingested_at quando a Brandvelia persistiu o raw
effective_at competência contábil/econômica
settled_at quando houve liquidação financeira
available_at quando o saldo ficou realmente utilizável
reconciled_at quando o fato entrou em uma reconciliação válida
as_of cutoff fechado do snapshot de decisão
matured_at instante calculado para cada tipo de maturidade

Todos os instantes são timestamptz em UTC. Também são preservados source_timezone e source_business_date. Um dia Meta usa o timezone da conta de anúncios; um evento ClickBank usa o timestamp da transação; relatórios executivos podem depois projetar ambos em um calendário gerencial versionado.

Regras:

  • ingested_at vem do servidor e não pode ser enviado pelo cliente.
  • as_of <= now() e fatos com ingested_at > as_of não entram no snapshot.
  • atraso é ingested_at - observed_at; frescor é as_of - observed_at.
  • timestamp ausente não recebe now() como substituto. O valor fica null com reason code, exceto ingested_at.
  • evento mais de 5 minutos no futuro em relação à ingestão é PROVISIONAL por clock skew; acima de 24 horas é INVALID até correção.
  • todo range usa limite [from, to). Datas inclusivas de uma API são normalizadas explicitamente pelo adapter.

6. Maturidades independentes

Cada coorte de aquisição mantém três estados separados. Não existe um campo canônico is_mature.

maturity_kind   = CONVERSION | REFUND | REBILL
maturity_status = MATURE | IMMATURE | UNKNOWN | NOT_APPLICABLE | INVALID
Maturidade Início do relógio Torna-se MATURE quando
conversão click.event_at/início da coorte encerrou a janela de conversão da policy, tracking está fresco e eventos foram reconciliados até o cutoff
refund evento de sale/bill da coorte encerrou a janela de refund e chargeback aplicável e a reconciliação cobre a janela
rebill sale inicial ou ciclo anterior os ciclos observáveis definidos na policy terminaram e seus eventos foram reconciliados

Cada registro inclui cohort_key, kind, window_start, window_end, matured_at, policy_version, quality_status e evidence_ids. Atraso da fonte estende o cutoff de observação; não antecipa maturidade.

Uso por fase:

Uso Requisito mínimo
PREFLIGHT cenários sourced; maturidades históricas podem ser UNKNOWN
TEST tracking, caixa e compromisso VALID; economics podem ser PROVISIONAL com cap de risco
VALIDATE maturidade de conversão MATURE; refund fica separado e exige reserva conservadora se ainda IMMATURE
SCALE conversão e refund MATURE/VALID; rebill só entra no economics se também MATURE/VALID
aprendizado/calibração somente outcomes cujas maturidades necessárias são MATURE/VALID

Oferta sem recorrência tem rebill NOT_APPLICABLE, não MATURE. Oferta recorrente sem histórico tem rebill UNKNOWN ou IMMATURE; expected rebill é zero peso em caixa e não é convertido em fato realizado.

7. null, zero e reason codes

Todo campo nullable usado em decisioning DEVE possuir um campo irmão <field>_null_reason. Códigos permitidos:

Código Uso
NOT_OBSERVED a fonte existe, mas não publicou/observou o valor
SOURCE_UNAVAILABLE integração ou credencial indisponível
NOT_APPLICABLE o conceito não se aplica, como rebill não recorrente
NOT_YET_MATURE janela ainda aberta
NOT_RECONCILED observado, mas ainda não reconciliado
ZERO_DENOMINATOR razão não definida por denominador zero
LEGACY_MISSING legado não capturou o campo obrigatório
REDACTED removido por privacidade/retenção

Falha de parse, moeda inválida, conflito de dedupe ou timestamp impossível não são reason codes de ausência: o registro vai para quarentena com gate INVALID. Zero é aceito somente quando a fonte observou zero ou uma fórmula válida resultou em zero.

Exemplos:

spend = "0.000000", reason = null         # Meta confirmou zero
spend = null, reason = SOURCE_UNAVAILABLE # não houve coleta
roas  = null, reason = ZERO_DENOMINATOR   # spend válido igual a zero
rebill = null, reason = NOT_YET_MATURE    # não inventar previsão

Agregações não fazem coalesce(value, 0) antes de verificar completude. A UI exibe “desconhecido” e o reason code; não exibe $0.00.

8. Proveniência e evidência

8.1 Envelope de proveniência

Todo fato usado em uma decisão deve resolver para:

{
  "source_system": "clickbank_ins",
  "source_account_id": "account-alias",
  "source_record_id": "receipt|SALE|item",
  "source_event_id": null,
  "raw_record_id": "00000000-0000-0000-0000-000000000001",
  "normalization_version": "clickbank-ins/v1",
  "formula_version": null,
  "event_at": "2026-08-20T12:00:00Z",
  "observed_at": "2026-08-20T12:00:00Z",
  "ingested_at": "2026-08-20T12:00:04Z",
  "payload_hash": "sha256:...",
  "quality_status": "PROVISIONAL",
  "quality_reasons": ["NOT_RECONCILED"]
}

Valores derivados trocam raw_record_id por input_evidence_ids e exigem formula_id, formula_version, parâmetros e hash dos inputs. Um novo parser ou uma nova fórmula cria nova versão; não reescreve a evidência antiga.

8.2 Evidence IDs

evidence_id é UUID opaco, global e imutável, gerado no servidor. O envelope de evidência contém workspace_id, evidence_type, ponteiro para fato/snapshot, content_hash, quality_status, as_of, created_at e, para derivadas, input_evidence_ids e formula_version.

Tipos mínimos:

SOURCE_FACT | RECONCILIATION | DERIVED_METRIC | POLICY |
SCENARIO_ASSUMPTION | HUMAN_DECISION

IA recebe do servidor uma allowlist de evidence IDs e deve citar evidence:<uuid> em toda afirmação factual. ID desconhecido, de outro workspace ou fora do snapshot invalida a resposta. Evidências não contêm PII e não podem apontar apenas para uma linha mutável “atual”.

9. Gates de qualidade

Os quatro gates têm esta semântica única em toda a plataforma:

Gate Semântica Efeito
VALID schema, proveniência, dedupe, frescor, reconciliação e maturidade exigidos pelo uso foram satisfeitos pode alimentar a fase autorizada pela policy
PROVISIONAL fato existe e é estruturalmente íntegro, mas está imaturo, dentro da tolerância de atraso, em reconciliação, manualmente sourced ou no shadow legado pode aparecer e alimentar shadow/test conservador; não libera SCALE sozinho
UNKNOWN não existe observação obrigatória ou não há fonte/credencial/cobertura mantém valor null; bloqueia a decisão que depende dele
INVALID observação contraditória ou insegura: schema, moeda, relógio, tenant, hash, dedupe ou reconciliação hard falhou quarentena; bloqueia snapshot e alerta owner

Ordem determinística para um input:

INVALID     se qualquer hard check falhar
UNKNOWN     se não houver fato requerido válido ou provisório
PROVISIONAL se existir fato íntegro, mas faltar soft freshness,
            reconciliação ou maturidade requerida
VALID       somente se todos os checks requeridos passarem

Composição de inputs obrigatórios usa a precedência INVALID > UNKNOWN > PROVISIONAL > VALID. Inputs opcionais não promovem nem rebaixam o gate agregado, mas continuam visíveis. quality_reasons lista todos os motivos, não apenas o primeiro.

Cada fonte define soft_sla e hard_sla na matriz:

  • dentro do soft SLA: elegível a VALID;
  • após soft e até hard SLA: no máximo PROVISIONAL;
  • após hard SLA: INVALID se uma linha stale está sendo apresentada como atual; UNKNOWN se nenhum fato atual é selecionado.

Reconciliação não altera o raw. Divergência cria finding e rebaixa o snapshot dependente. A tolerância monetária é declarada por fonte/moeda, nunca embutida em TypeScript.

10. Contratos por domínio

10.1 Tracking first-party

O contrato mínimo do clique é:

click_id, event_id, workspace_id, session_id, landing_page_id,
campaign_id, creative_id, offer_id, event_at, ingested_at,
referrer, device, country,
utm_source, utm_medium, utm_campaign, utm_content, utm_term,
gclid, fbclid, ttclid, external_click_id, consent_state,
source_system, dedupe_key, payload_hash

click_id é aleatório, não contém PII e atravessa landing, HopLink extclid, ClickBank INS e atribuição. event_id deduplica retries; click_id não deve ser reutilizado para eventos diferentes. UTMs são dimensões informativas, não identidade canônica. Atribuição por nome fuzzy só pode ser PROVISIONAL.

10.2 ClickBank

O evento raw INS é persistido antes da normalização. O fato normalizado preserva receipt, transaction_type, item, payout da afiliada, moeda, timestamps, tracking codes, conta, vendor e ponteiros de atribuição. Dedupe segue a matriz de fontes e a gravação raw -> conversion -> ledger -> audit/outbox deve ser atômica ou retomável por estado, sem produzir duas entradas financeiras.

SALE/BILL, RFND e CGBK continuam ligados à mesma aquisição/coorte. O QuickStats é controle agregado oficial, não substituto silencioso de transações detalhadas. Divergência acima da tolerância rebaixa os períodos afetados.

Marketplace payout, Gravity, marketplace EPC, score e opinião de IA são sinais de pre-flight. Eles nunca se tornam prova de lucro próprio.

10.3 Meta Ads, piloto read-only

Meta é o primeiro canal oficial de mídia. O adapter só lê:

  • catálogo mínimo de account/campaign/ad set/ad com IDs e status;
  • insights diários por nível e objeto;
  • spend, impressions, clicks, outbound_clicks, landing_page_views e actions normalizadas por action_type, quando a API efetivamente retornar o campo;
  • moeda, timezone, attribution setting/window e versão da API.

Meta não é autoridade de vendas ClickBank. Actions de conversão da Meta permanecem métricas de plataforma e não são somadas ao ledger de receita. Nenhum endpoint de escrita, ads_management, mudança de budget, pausa ou publicação pertence ao piloto.

10.4 Caixa, saldos e compromissos

Um balance snapshot registra por conta:

financial_account_id, provider, account_currency, as_of,
available_amount, pending_amount, held_amount, accrued_amount,
source_record_id, statement_id, reconciled_at, quality_status

Somente available_amount liquidado e reconciliado é candidato a S0. pending, held e accrued são preservados e excluídos.

Um compromisso registra:

commitment_id, kind, counterparty, external_ref, amount, currency,
incurred_at, due_at, status, reserved_amount, settled_amount,
campaign_id, approval_id, source_system, evidence_ids

Estados: DRAFT, AUTHORIZED, INCURRED, PARTIALLY_SETTLED, SETTLED, CANCELLED, DISPUTED. Apenas AUTHORIZED, INCURRED, PARTIALLY_SETTLED e a parcela conservadora de DISPUTED entram em Cp. DRAFT não entra; SETTLED já está em caixa/ledger; CANCELLED vale zero.

10.5 Cenários e snapshots

Um scenario set contém exatamente LOW, BASE e HIGH, cada um com os mesmos campos econômicos. Cada input é um fato com evidence ID ou uma assumption explícita com owner, rationale, unidade, source URL/document, observed_at e expires_at. Aprovação não transforma assumption em fato.

Um decision snapshot é imutável e contém:

snapshot_id, workspace_id, as_of, horizon_end, allocation_currency,
source_watermarks, scenario_set_version, policy_version, formula_versions,
input_evidence_ids, input_hash, gate_status,
S0, Cp, L, Ca, requested_I, retained_cash,
conversion_maturity, refund_maturity, rebill_maturity

Reexecutar com os mesmos inputs e versões deve produzir o mesmo input_hash e resultado.

11. Exemplo de allocation snapshot

{
  "as_of": "2026-08-20T15:00:00Z",
  "allocation_currency": "USD",
  "S0": { "amount": "1000.000000", "currency": "USD" },
  "Cp": { "amount": "150.000000", "currency": "USD" },
  "L": { "amount": "300.000000", "currency": "USD" },
  "Ca": { "amount": "550.000000", "currency": "USD" },
  "I": { "amount": "200.000000", "currency": "USD" },
  "retained_cash": { "amount": "350.000000", "currency": "USD" },
  "maturity": {
    "conversion": "MATURE",
    "refund": "IMMATURE",
    "rebill": "NOT_APPLICABLE"
  },
  "gate_status": "PROVISIONAL",
  "quality_reasons": ["REFUND_NOT_YET_MATURE"],
  "evidence_ids": [
    "00000000-0000-0000-0000-000000000101",
    "00000000-0000-0000-0000-000000000102"
  ]
}

O exemplo não autoriza SCALE: o caixa é conhecido, mas refund ainda é provisional. A policy pode autorizar um TEST limitado e reservar risco em L.

12. Lacunas verificadas no estado atual

As lacunas abaixo são constatações do código em 2026-08-20, não autorização para compatibilidade silenciosa.

Área Estado atual Gap para este contrato Gate inicial
dinheiro legado usa transactions.amount numeric(12,2)/TypeScript number; a migration 20260821130000_brandvelia_decision_truth_foundation.sql introduz ledger numeric(20,6) e direção falta migrar fronteiras para decimal string e tornar o novo ledger a autoridade PROVISIONAL
relógios transactions têm só tx_date; visits só clicked_at faltam event/observed/ingested/effective/settled/available separados PROVISIONAL ou UNKNOWN
null dashboards usam coalesce(..., 0) ausência pode aparecer como zero; faltam reason codes INVALID para decisão de capital dependente
proveniência metadata, external_ref e raw legados; a foundation adiciona sync runs e hashes em vários fatos faltam envelope uniforme, normalization/formula versions e ligação a evidence IDs PROVISIONAL
ClickBank INS raw, conversion, dedup e ledger existem escrita é multi-etapa não atômica; dedupe e ledger não têm constraint composta tenant-aware; PII é copiada PROVISIONAL até reconciliação
QuickStats sync diário/manual compara totais USD janela fixa curta, single-workspace no cron, tolerância em TypeScript e sem watermark formal PROVISIONAL
tracking click_id, UTMs e click IDs existem GET com side effect, sem event_id, consent, source clocks ou proveniência; atribuição pode usar nome fuzzy PROVISIONAL
Meta Ads CSV genérico; a foundation cria accounts, mappings e performance facts read-only adapter/API, credenciais, actions detalhadas, ingestão e reconciliação inexistem UNKNOWN
caixa contas legadas e schema de balance snapshots na foundation não há ingestor, reconciliação, available_at nem fatos operacionais de saldo UNKNOWN
FX colunas legadas e fx_rate_snapshots na foundation server action atual não envia FX do formulário; faltam ingestor, comprovante/settlement e reconciliação UNKNOWN
compromissos schema capital_commitments na foundation faltam serviço, approval evidence, dedupe externo, histórico de estado e cálculo de Cp sem dupla contagem UNKNOWN
capital settings e reserve inputs na foundation não há allocation run nem cálculo/versionamento de S0, Cp, L, Ca e I UNKNOWN
maturidade foundation cria três *_matures_at, mas um único maturity_status e um score agregado faltam estados/gates independentes de conversion/refund/rebill UNKNOWN
evidência audit log genérico não há evidence IDs, allowlist para IA, content hash ou snapshot imutável UNKNOWN
cenários experiments não são scenarios financeiros não há LOW/BASE/HIGH versionado nem aprovação UNKNOWN

Até essas lacunas serem fechadas, os dashboards legados continuam informativos, mas não são autorização para escala ou movimentação de capital.

13. Critérios de aceite do contrato

Uma implementação está conforme somente se testes automatizados demonstrarem:

  1. replays de tracking, INS, Meta e imports não duplicam fatos nem ledger;
  2. falta de fonte produz null + reason, não zero;
  3. nenhuma soma cruza moedas sem FX evidence;
  4. Ca fica null se S0, Cp ou L estiver desconhecido;
  5. pending/held/accrued/forecast/rebill esperado nunca entra em S0;
  6. mudar uma maturidade não altera as outras duas;
  7. fatos posteriores ao as_of não entram no snapshot;
  8. a mesma combinação de inputs/versions gera o mesmo hash e resultado;
  9. evidence ID de outro workspace ou fora da allowlist é rejeitado;
  10. gate agregado respeita INVALID > UNKNOWN > PROVISIONAL > VALID;
  11. Meta opera com ads_read e testes impedem qualquer chamada de escrita;
  12. recommendation não altera campanha, budget, dinheiro ou fase sem decisão humana registrada.