Fase 7: CDR/métricas/relatórios, reconciliação, dashboard e compliance

- Reconciliação de tentativas órfãs após restart (reconciliation.ts),
  rodando a cada 60s.
- Vínculo real agente<->chamada (agent-call-binding.ts): claim atômico de
  agente disponível via FOR UPDATE SKIP LOCKED, DialAttempt.agentId
  populado no connect, ciclo AVAILABLE -> IN_CALL -> WRAP_UP -> AVAILABLE.
- Corrige abandonRate (EWMA) nunca atualizado pelo campaign-worker real —
  agora o fluxo QUEUED -> connect-or-abandon atualiza as estatísticas de
  fato usadas pelo predictive engine.
- Novo módulo de relatórios: /api/reports/calls (+export CSV), /metrics
  (TME/TMA/abandono), /agents/:id.
- Novo módulo de dashboard: /api/dashboard, /calls-by-hour,
  /campaigns/:id (Postgres + snapshot EWMA do Redis).
- Novo módulo de compliance: ComplianceSettings configurável +
  /api/compliance/settings e /indicators com contadores reais.
- Validado end-to-end contra containers reais (campanha de teste em modo
  simulação): agentId no connect, ciclo de estado do agente e abandonRate
  todos confirmados corrigidos com dados reais, não só no harness isolado.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoVkLx1KsvtT1C88dRS3QW
This commit is contained in:
2026-08-27 16:09:22 -03:00
parent 66cc2058fd
commit 0a8b830e2c
22 changed files with 1072 additions and 34 deletions

59
TODO.md
View File

@@ -220,14 +220,57 @@ simulação de NO_ANSWER (retry agendado) e ANSWERED (AGENT_CONNECTED, EWMA
atualizada ao vivo no Redis) → parada sem derrubar chamadas em andamento.
## Fase 7 — CDR, métricas e relatórios
- [ ] Modelo consolidado de chamadas (CDR+CEL+AMI+queue_log)
- [ ] Reconciliação de estados órfãos após restart
- [ ] TME / TMA (definições documentadas, cálculo correto)
- [ ] Relatório de Chamadas (filtros, paginação server-side, export CSV)
- [ ] Relatório de Agentes (tempos, pausas detalhadas)
- [ ] Dashboard geral (cards + gráficos reais)
- [ ] Dashboard do discador (por campanha, tempo real)
- [ ] Compliance de Chamadas (parâmetros configuráveis, alertas, contadores)
- [x] Modelo consolidado de chamadas `DialAttempt` já concentra os timestamps
reais (started/ringing/answered/queued/agentConnected/ended) e
hangupCause; CDR/CEL do Asterisk seguem gravados via `cdr_adaptive_odbc`/
`cel_odbc` na base `asterisk` (não duplicados no domínio da app). Não há
correlação automática CDR↔DialAttempt para chamadas reais (não
simuladas) além do timeout de segurança — ver nota em
docs/PREDICTIVE_DIALER.md.
- [x] Reconciliação de estados órfãos após restart — `reconciliation.ts`
(`reconcileOrphanedAttempts`), rodando a cada 60s via `main.ts`; força
`DialAttempt`s presos há >10min para `FAILED`/`RECONCILED_ORPHAN` e
devolve o lead para `READY`. Testado por unit tests; não exercitado
neste ciclo via kill real do worker (fica para Fase 10).
- [x] TME / TMA — cálculo real em `reports.service.ts#calculateMetrics` e
`dashboard.service.ts#overview/campaignDashboard`, a partir de
`queuedAt`/`agentConnectedAt`/`endedAt`. Validado end-to-end: campanha
de teste "Campanha F7" retornou `tmeSeconds: 0.023`, `tmaSeconds: 136.6`
batendo com os timestamps reais da chamada simulada conectada.
- [x] Relatório de Chamadas — `GET /api/reports/calls` (filtros
campaignId/agentId/dispositionId/phone/state/from/to, paginação
server-side) e `GET /api/reports/calls/export` (CSV via
`csv-stringify`, teto de 100k linhas). Testado contra as 10 tentativas
reais da campanha de teste — paginação e filtros funcionando.
- [x] Relatório de Agentes — `GET /api/reports/agents/:agentId`, tempo real
por estado a partir de `agent_state_events`/`agent_pause_events`.
Validado: agente de teste retornou `timeByStateSeconds` com
LOGGED_IN/AVAILABLE/IN_CALL/WRAP_UP batendo com o ciclo real observado.
- [x] Dashboard geral — `GET /api/dashboard` e `/api/dashboard/calls-by-hour`,
cards/gráficos 100% derivados de `dial_attempts`/`agent_state_events`
reais (sem números inventados, seção 72). Validado com dados reais.
- [x] Dashboard do discador — `GET /api/dashboard/campaigns/:id`, combina
contagens Postgres com snapshot EWMA do Redis
(`dialer:stats:{campaignId}`). Validado: `abandonRate` e
`pacingFactor` refletiram corretamente o abandono real ocorrido no
teste.
- [x] Compliance de Chamadas — `ComplianceSettings` (singleton configurável:
limiar de chamada curta, limites de tentativas por número/dia/mês,
threshold de alto volume) + `GET/PATCH /api/compliance/settings` e
`GET /api/compliance/indicators` (contadores reais de chamadas
hoje/mês, chamadas curtas, abandonadas, números acima do limite
diário, alertas). Validado com dados reais da campanha de teste.
**Teste E2E executado (2026-08-27):** criados trunk/fila/ramal/agente/usuário/
campanha de teste ("Campanha F7"), 10 leads importados via CSV, campanha
rodada em `DIALER_SIMULATION=true`. Confirmado em produção real (não só no
harness isolado): `DialAttempt.agentId` populado corretamente ao conectar
(bug da Fase 7 corrigido em `agent-call-binding.ts`); ciclo completo de
`AgentStateEvent` `AVAILABLE → IN_CALL → WRAP_UP → AVAILABLE`; `abandonRate`
no Redis atualizado de `0` para `0.05` após abandono real (bug do EWMA não
conectado ao `campaign-worker.ts` corrigido); todos os endpoints de
relatórios/dashboard/compliance retornando dados reais e coerentes com o
banco. Todos os fixtures de teste foram removidos/desativados ao final.
## Fase 8 — Frontend completo
- [ ] Bootstrap Next.js + Tailwind + shadcn/ui + TanStack Query + WS client