# Design do ETL Autbank/monbank para o Core Monetarie

Data: 2026-06-25
Branch: `etl-autbank`
Base empírica: `etl/study/2026-06-25-monbank-acc-deep-study.md` (não duplicar; este documento é o COMO, o estudo é o O QUE).

## 1. Objetivo e princípios

Migrar 100% dos dados verdadeiros da base `monbank_acc` (legado Autbank, instituição única, BRL) para o Core Monetarie (SCD), com fidelidade contábil ao centavo, partida dobrada COSIF e ledger TigerBeetle. Nada de inferência, nada de número inventado; toda divergência é sinalizada, não maquiada.

Princípios de design:
- Fonte única da verdade contábil: o ETL NÃO reimplementa regra bancária; reusa os contratos do Core (`OpenAccount`, `Wallet`, `PostingEngine`). Se a regra COSIF muda no Core, o ETL acompanha de graça.
- Idempotente e re-executável: rodar duas vezes não duplica nem corrompe; permite reprocessar quando chegarem os dados faltantes ("seguir agora, integrar depois").
- Determinístico: ordem cronológica por carteira preserva continuidade de saldo.
- Porta de saída com batimento ao centavo: se não fecha, aborta e reporta.

## 2. Decisões do dono (registradas)

- D1 Divergências (70 carteiras, R$ 1.271.695,40): adotar `wallet.balance` como verdade e lançar ajuste de abertura conciliador; QUARENTENAR só os casos duros (4 resets de R$ 1 mi, 2 negativas, 3 bloqueios sem lastro) para assinatura.
- D2 Escopo: migrar TODAS as 1.582 contas (158 vivas como ativas; 1.424 ENC como fechadas/inativas; não auto-ativar).
- D3 Histórico: REPLAY COSIF COMPLETO dos ~499k movimentos (partida dobrada + backfill de saldo COSIF por data).
- D4 Numeração: emitir nova numeração Monetarie; guardar a legada (cod_cliente, agência 19, número, DV) como referência externa.
- D6 Auth: portar hash Argon2id; base toda em `pending_activation` com tokens expirados, então reemitir ativação no destino (sem auto-ativar).
- D7 Seed atual de homologação: PODE SER DESTRUÍDO. O ETL vira a fonte da verdade para a entidade SCD; não há convivência com o seed sintético.
- Dados faltantes: seguir agora, com gancho de reprocessamento.

## 3. Arquitetura

Mix task no Core: `Mix.Tasks.Monetarie.EtlAutbank` (modelada no esqueleto provado `Mix.Tasks.Monetarie.SeedDictAccounts`), em `core/backend/lib/mix/tasks/`.

- Leitura da origem: conexão Postgrex SOMENTE LEITURA ao container `monbank-etl-src` (`127.0.0.1:5544`, base `monbank_acc`), configurável por env (`AUTBANK_SOURCE_URL`). Nenhuma escrita na origem.
- Escrita no destino: pelos contratos do Core dentro de `Repo.transaction`/`Ecto.Multi`.
- Flags da task: `--dry-run` (transforma e valida sem gravar), `--apply`, `--only=customers|ledger|opening`, `--entity-ispb=46026562`, `--limit=N` (amostra), `--reset-entity` (destrói o seed atual da entidade antes de carregar, conforme D7), `--report=path`.
- Lotes e desempenho: por carteira (291 ativas têm volume; a maior ~19.655 lançamentos). Processar conta a conta; commit por conta para isolar falhas; concorrência limitada.

## 4. Componentes e fluxo

Fase 0 (pré): garantir entidade SCD (`Entities.Entity` ISPB 46026562) e plano COSIF semeado (`scd_cosif_plan.exs`), templates de postagem (`030_posting_templates.exs` + novos da Seção 6). Se `--reset-entity`, apagar dados da entidade no Core (contas/users/journal/TB) de forma controlada.

Fase 1 (titular + conta + carteira), por linha de `account_information`/`accounts`/`account_wallets`/`users`/`addresses` (todos 1:1):
1. Dedupe do cliente por (document) via `users` (32 documentos repetidos -> 1 cliente, N contas).
2. `OpenAccount.open_account/2`: cria `User` (tax_id, name, email, user_type PF/PJ, password_hash Argon2id portado, `pending_activation`), `Account` (numeração nova Monetarie, status de ENC/ATI/BLQ, cosif_account_code PF/PJ) e carteira TigerBeetle.
3. `Member` (KYC): endereço (CEP left-pad 8, número 0=S/N), is_pep=false (re-screening pendente), datas reais de `data.autbank.data_cadastro`. Campos poluídos (renda) NÃO migrados.
4. Referência externa: gravar cod_cliente, agência 19, número/DV legados, `data.migration.batch_id/source_row` em campo de referência/metadata para auditoria e reprocessamento.

Fase 2 (saldo de abertura), por carteira:
- Lançar `wallet.balance` (autoridade) como saldo de abertura via evento `OPENING_BALANCE` (Seção 6), com `entry_date` = data de abertura, preservando a identidade `balance = available + blocked` (bloqueio como sub-saldo, não somado a disponível).
- Carteiras em quarentena (casos duros) NÃO recebem abertura automática.

Fase 3 (replay do razão), por carteira em ordem `created_at, id`:
- Para cada `transactions` ligada a `transfer_transactions` (240.197): `PostingEngine.post_event(event_type, valor_centavos, reference_id, reference_type, entry_date: created_at, tigerbeetle_transfer_id: ...)`, com `event_type` resolvido por `details.history_code` (driver) com fallback em `display_name`.
- Para cada `transactions` legada (`AutbankLegacyTransaction`, 18.560): mapear por `display_name`/`type` para o `event_type` equivalente; metadados degradados (pai ausente), posição cronológica preservada; `reference_id` = id do movimento.
- Idempotência nativa: `reference_id` único em `cosif_journal_entries (reference_type, reference_id, entry_date)`; reexecução faz `on_conflict :nothing`.

Fase 4 (backfill de saldo COSIF diário): após o replay, gerar `cosif_account_balances` por data conforme a movimentação (competência por `entry_date`).

Fase 5 (batimento ao centavo): Seção 7.

## 5. Conversão de dinheiro (exata)

- Origem `numeric(10,2)` reais. Destino: base_units (BRL x 10.000) no TigerBeetle/`transactions`; centavos (BRL x 100) no `cosif_journal_entries`.
- Regra: `valor_centavos = round(valor_origem * 100)`; `valor_base_units = valor_centavos * 100`. Como a origem tem só 2 casas, ambas as conversões são exatas (sem arredondamento perdido). Validar via `MoneyUnit` nas bordas.

## 6. Mapa COSIF (de-para aterrado nos templates reais do Core)

Contas-chave: cliente (passivo) `2.1.1.10.00-9` Depósitos à Vista; institucional (ativo) `1.1.1.10.00-6` Disponibilidades; tarifa PIX `7.1.7.20.00-8`.

| Origem (history_code / display_name) | Sentido cliente | event_type Core | Débito / Crédito | Situação |
|---|---|---|---|---|
| 01000 Pix Recebimento | credit | `PIX_RECEIVE` | 1.1.1.10.00-6 / 2.1.1.10.00-9 | existe |
| 01012 Pix Pagamento | debit | `PIX_SEND` | 2.1.1.10.00-9 / 1.1.1.10.00-6 | existe |
| 01002/01014 Pix Devolução | credit/debit | `PIX_RETURN` (+ par envio) | 1.1.1.10.00-6 / 2.1.1.10.00-9 | existe (1 lado); confirmar lado debit |
| 00732 Crédito via TED | credit | `PAG_TED_RECEIVE` | 1.1.1.10.00-6 / 2.1.1.10.00-9 | existe |
| 00723/00724 TED (saída/dif. titularidade) | debit | `PAG_TED_SEND` | 2.1.1.10.00-9 / 1.1.1.10.00-6 | existe |
| 00505/00507 Devolução TED | credit/debit | `TED_RETURN` | a definir | NOVO (confirmar com contabilidade) |
| 00708/00709 Transferência interna | credit/debit | `INTERNAL_TRANSFER_CREDIT/DEBIT` | a definir (conta de trânsito interna) | confirmar templates internos |
| 01030 Transferência OJ | debit | mapear p/ interna ou TED | a definir | confirmar |
| Tarifa PIX / TED / Manutenção (display_name) | debit | `PIX_FEE` / `FEE` | 2.1.1.10.00-9 / 7.1.7.x | existe (PIX_FEE); TED/manut. confirmar |
| IOF (display_name) | debit | `IOF_COLLECTION` | a confirmar (tributo a recolher) | existe (confirmar par) |
| Juros ADP (display_name) | credit/debit | `INTEREST_*` | a definir | NOVO (confirmar) |
| Saldo de abertura (migração) | credit | `OPENING_BALANCE` | 1.1.1.10.00-6 / 2.1.1.10.00-9 | NOVO (par de abertura/migração) |

Regra de fallback: se um `history_code`/`display_name` não tem template, o ETL NÃO chuta; aborta a conta e lista no relatório de exceções para definição contábil. Cobertura é validada antes do `--apply` (a Seção 7 conta os event_types presentes na origem e exige template ativo para cada).

## 7. Batimento ao centavo (porta de saída, obrigatório)

Após carga, validar e abortar/reportar se qualquer um falhar:
1. Por conta: saldo TigerBeetle (`credits_posted - debits_posted - debits_pending`, em base_units) == `wallet.balance` da origem (x10.000). Exceto quarentena.
2. Balancete COSIF da entidade fecha: soma de débitos == soma de créditos por data e no total.
3. Identidade preservada: `available + blocked == balance` em todas as contas migradas.
4. Contagens: contas migradas + quarentena == 1.582; users == 1.533; lançamentos COSIF == (258.757 - movimentos de contas em quarentena) + aberturas; 0 órfãos.
5. Cobertura de template: todo `event_type` exigido pela origem tem template ativo (0 faltando).
6. Relatório de exceções: quarentena (4+2+3), templates faltantes, documentos com DV inválido, contas ENC, casos sem `data_cadastro`.

## 8. Quarentena (casos duros, sem inventar)

Não entram automaticamente; vão para `etl/out/quarentena-YYYY-MM-DD.csv` (gitignored) com motivo:
- 4 contas PJ com reset redondo (saldo 0 x razão -100k..-400k = R$ 1.000.000,00).
- 2 carteiras com saldo negativo sem lançamento.
- 3 carteiras com bloqueio maior que o saldo, available negativo, sem lançamento.
Decisão por caso fica com o dono; o ETL só carrega após sinalização explícita (flag `--include-quarantine=<id,...>`).

## 9. Destruição do seed atual (D7)

`--reset-entity` apaga, de forma transacional e logada, os dados da entidade SCD no Core (users/accounts/journal/TB/daily_summaries/cosif_balances) antes do load, para o ETL ser a única fonte. Protegido por env explícita (`ALLOW_ENTITY_RESET=true`) e restrito ao `entity_id` alvo. Nunca toca outras entidades.

## 10. Relatório final (entregável, pt-br humano)

Ao fim do `--apply` (e no `--dry-run`), gerar relatório em escrita humana, pt-br acentuado, sem travessão de IA, explicando:
- O que foi migrado (contas, clientes, lançamentos) com os números.
- Os batimentos (saldo TB x origem, balancete COSIF, identidade de saldo) com resultado ao centavo.
- O que ficou em quarentena e por quê.
- O que falta pedir ao cliente (Seção 11).
- A situação das contas e do seed (que o seed sintético foi destruído e substituído pelos dados reais).

## 11. O que falta pedir ao cliente (lista de pendências)

1. Export da tabela `autbank_legacy_transactions` (detalhe dos 18.560 lançamentos legados e lastro dos 59 saldos sem movimento).
2. Export da tabela `tenants` (nome/CNPJ/ISPB da instituição de origem).
3. Chaves PIX / EndToEndId / txid originais (100% ausentes; sem isso não há rastreabilidade DICT retroativa).
4. KYC real: renda/faturamento confiáveis (origem poluída), triagem PEP, campos PF/PJ faltantes, comprovantes.
5. Definição contábil dos eventos sem template (devolução TED, transferência interna, juros, abertura).
6. Decisão sobre os casos em quarentena (4 resets, 2 negativas, 3 bloqueios).

## 12. Testes (TDD)

- Conversão de dinheiro: propriedade `base_units == origem * 1.000.000 / 100` exata para amostras reais; bordas (0, máx observado, negativos).
- Mapa COSIF: cada `event_type` exigido resolve para débito/crédito existentes; partida fecha.
- Replay determinístico: dada uma carteira-fixture, o saldo TB final == `wallet.balance`.
- Idempotência: rodar duas vezes não duplica journal nem move saldo a mais.
- Batimento: harness que reproduz o estudo (1.512 batem, 70 não) e prova que pós-carga o batimento fecha exceto quarentena.
- Quarentena: os 9 casos duros são detectados e NÃO carregados sem flag.

## 13. Fora de escopo (YAGNI)

- Não migrar colunas 100% nulas/poluídas (scopes, PIN, renda, etc.).
- Não recuperar chave PIX (impossível pelo dump).
- Não reabrir contas encerradas.
- Não criar a tabela `tenants`/`autbank_legacy_transactions` fictícia.
- Não tocar outras entidades/seed que não a alvo.
