IRAI · Intraday Risk Appetite Index
Os 5 gráficos da página de detalhe migraram de Recharts para
TradingView lightweight-charts v5, reconstruídos por engenharia reversa
do bundle de produção. Este documento registra a entrega e reconcilia o backlog
de correções — revisado por fable-reasoner e codex,
que corrigiram três status otimistas demais.
Hoje o localhost mostra V1 e a produção mostra V2 sob o mesmo
rótulo “DINÂMICO (KALMAN)”: o polling primário (App.jsx:645)
pede version=both, que o engine trata como V1 estático, enquanto o
firebase_sync pede v2.
O painel de pesos Kalman recém-migrado exibe pesos estáticos no local. Resolver a Fase 2 não é limpeza — é convergir o local com o que a produção já entrega.
Parte 1 — O que foi entregue
A fonte da produção foi perdida — só existe o bundle minificado. Cada
componente foi extraído por engenharia reversa (displayName →
forwardRef → corpo) numa migração strangler-fig: Recharts e
lightweight-charts coexistiram até o último chart sair, com build, lint, deploy
e revisão do Codex a cada etapa.
| # | Chart | Componente | Commit |
|---|---|---|---|
| 3 | Pair-spread z-score (piloto) | TVPairwiseZScoreChart | b47e3b1 |
| 4 | Probabilidade P(↑) dinâmica | TVProbabilityChart | 87d7fd9 |
| 5 | Divergência-preço z-score | TVPriceDivergeZScoreChart | 1f43499 |
| 6 | Pesos dinâmicos (Kalman) | TVKalmanWeightsChart | 552f64f |
| 7 | Movimento do índice (NWE) | TVNweChart | 9cc6066 · 2be2ecf |
computeNWE passou a expor nwe_*_price, mantendo os campos % que ainda alimentam header, gauge e badges.Parte 2 — O plano anterior ainda é pertinente?
A migração é ortogonal ao backlog: mexe na apresentação e não resolve nenhum P0 de sinal. As duas revisões independentes rebaixaram três status que eu havia marcado com otimismo — as Fases 3 e 4 são bugs vivos hoje, não “a verificar”. Status conferidos contra o código nesta data:
| Fase | Tema | Status | Evidência |
|---|---|---|---|
| 1 | Schema divergence_config |
Parcial | Migração idempotente existe, mas init_db() não a chama e o loader mantém except Exception: rows=[] — “0 models” segue silencioso em instalação limpa |
| 2 | Contrato version=both |
Pendente | Divergência V1/V2 entre ambientes (ver callout) — impacto maior que “limpeza de contrato” |
| 3 | Ghost bars / pré-mercado | P0 vivo | Só a detecção de ghost foi corrigida; win_return=0 nunca é restaurado. No v2 o retorno falso alimenta o Kalman por ~180 barras M5 antes da abertura B3 |
| 4 | Persistência Kalman monotônica | Corrupção viva | INSERT OR REPLACE sem guard de timestamp; irai_current itera datas antigas em v2 e sobrescreve o estado live |
| 5 | NWE causal (backend) | Pendente | get_center do overview soma sobre range(n) → lookahead de 1 barra no slope dos cards |
| 6 | API não-bloqueante | Pendente | Sem single-flight nem threadpool em main.py |
| 7 | Contrato Firebase completo | Pendente | history_closes nunca chega em produção; falta is_b3. Mas accuracy já viaja em summaries[target] → B1 não depende desta fase |
| 8.1 | Acurácia no detalhe | Fix trivial | seriesInfo.accuracy é lido mas nunca setado → sempre fallback 80. summary.accuracy já está nos dois payloads |
| 8.2 | Corridas de request | Pendente | fetchSeries sem AbortController/sequence-id |
| 10 | Modularização App.jsx |
Parcial | 5 charts extraídos; App.jsx ainda ~1190 linhas (gauge, computeNWE, cliente de dados inline) |
Duas dependências novas criadas pela migração: os sinais inertes do
TVNweChart (markers pair/z, GEX) precisam do backend emitir eventos
discretos por barra (Fases 2/7); e a paridade do chart em produção depende da
Fase 7 (contrato Firebase).
Parte 3 — Ordem de execução recomendada
A ordem original (fechar o frontend antes do quantitativo) foi rejeitada por
ambas as revisões: adiar a correção deixa a corrupção de estado Kalman ativa
em produção, e trocar a UI para v2 (B2) sem antes corrigir o
pré-mercado expõe o caminho bugado que o version=both hoje
esconde ao cair no V1.
recharts do package.json + build final. Varrer código morto — sem tocar o flatten weight_${…} (o chart Kalman consome).save_kalman_state + persist_state=False para replay histórico. Independente de tudo.summary.accuracy para seriesInfo nos dois ramos de fetchSeries. Não depende da Fase 7.win_return=0 no pré-mercado antes de mudar para version=v2. Só então o local converge com a produção.history_closes + is_b3 + schema_version. Paridade NWE/eixo local vs. produção; pré-req do TVNweChart hospedado.j ≤ i e ao lookback de 95; idealmente calcular o NWE no backend. Vem antes das melhorias visuais.computeNWE causal + paridade %-vs-preço) → validação visual do chart de movimento → touch/mobile (canvas ≠ SVG) → eixo BRT via tickMarkFormatter → sync de crosshair → modularizar App.jsx.(target,date,version), trabalho síncrono para threadpool, invalidação seletiva de cache. Isolada, pode ficar por último.
Regra transversal (workflow do projeto): toda mudança de sinal/contrato
(Fases 2–5, B1, B2) entra com regressão permanente antes da correção.
Hoje tests/ só cobre z-score/pair — mínimo novo:
test_engine_premarket, test_engine_kalman_state,
test_api_contract, test_nwe_causality.
Riscos
kalman_state + timestamps antes do guard, para distinguir correção de mudança de continuidade.App.jsx sem os testes de C1 arrisca regressão de timezone/NWE.tickMarkFormatter precisa aplicar o −6h só para ativos B3; globais não recebem shift.