|
@@ -8,9 +8,12 @@ O backend segue uma arquitetura em camadas com bootstrap por factories. Cada dom
|
|
|
index.js
|
|
index.js
|
|
|
└── CoreFactory.Iniciar()
|
|
└── CoreFactory.Iniciar()
|
|
|
├── BancoFactory → conecta ao MySQL, valida conexão
|
|
├── BancoFactory → conecta ao MySQL, valida conexão
|
|
|
- └── ServerFactory → cria o Express app, registra middlewares, sobe o servidor
|
|
|
|
|
|
|
+ └── ServerFactory → cria o Express app, registra middlewares, sobe o servidor,
|
|
|
|
|
+ │ agenda os jobs em background (src/jobs/)
|
|
|
|
|
+ ├── /webhooks/whatsapp (fora de /api, sem JWT, antes de express.json())
|
|
|
└── Roteamento.IniciarRoteamento(app)
|
|
└── Roteamento.IniciarRoteamento(app)
|
|
|
- └── Domínios: Auth · Chat · Conversations · Documents · Ingest · Search · Usuario
|
|
|
|
|
|
|
+ └── Domínios: Auth · Chat · Conversations · Documents · Ingest · Search · Usuario ·
|
|
|
|
|
+ Atendimentos · Conversas (WhatsApp) · WhatsappConexao · IxcIntegrador (+ IxcBi)
|
|
|
└── *.Rotas.js → *.Controller.js → *Service.js → Model (Objection)
|
|
└── *.Rotas.js → *.Controller.js → *Service.js → Model (Objection)
|
|
|
```
|
|
```
|
|
|
|
|
|
|
@@ -30,7 +33,7 @@ Responsáveis pelo bootstrap da aplicação. Chamadas em sequência por `index.j
|
|
|
|
|
|
|
|
A classe `Roteamento` registra todos os domínios no Express via `IniciarRoteamento(app)`. Cada domínio tem seu próprio arquivo `*.Rotas.js` que define as rotas e middlewares daquele domínio.
|
|
A classe `Roteamento` registra todos os domínios no Express via `IniciarRoteamento(app)`. Cada domínio tem seu próprio arquivo `*.Rotas.js` que define as rotas e middlewares daquele domínio.
|
|
|
|
|
|
|
|
-Todos os domínios são montados sob o prefixo `/api`, que recebe **dois middlewares globais** antes de qualquer rota: `rateLimitMiddleware` (`RateLimit.js`) e `authMiddleware` (`Validajwt.js`). Ou seja, o JWT é decodificado globalmente; os middlewares `requireUser`/`requireAdmin` por rota apenas verificam o resultado.
|
|
|
|
|
|
|
+Todos os domínios são montados sob o prefixo `/api`, que recebe **dois middlewares globais** antes de qualquer rota: `rateLimitMiddleware` (`RateLimit.js`) e `authMiddleware` (`Validajwt.js`). Ou seja, o JWT é decodificado globalmente; os middlewares `requireUser`/`requireAdmin` por rota apenas verificam o resultado. Ver [auth.md](auth.md) para o fluxo completo e os níveis de acesso.
|
|
|
|
|
|
|
|
### Controllers (`src/controllers/`)
|
|
### Controllers (`src/controllers/`)
|
|
|
|
|
|
|
@@ -48,8 +51,18 @@ Modelos Objection.js. Mapeiam as tabelas do MySQL e definem relacionamentos.
|
|
|
|---|---|
|
|
|---|---|
|
|
|
| `Usuario.model.js` | `usuarios` |
|
|
| `Usuario.model.js` | `usuarios` |
|
|
|
| `RefreshToken.model.js` | `refresh_tokens` |
|
|
| `RefreshToken.model.js` | `refresh_tokens` |
|
|
|
|
|
+| `SolicitacaoResetSenha.model.js` | `solicitacoes_reset_senha` |
|
|
|
| `Conversation.model.js` | `conversations` |
|
|
| `Conversation.model.js` | `conversations` |
|
|
|
| `Message.model.js` | `messages` |
|
|
| `Message.model.js` | `messages` |
|
|
|
|
|
+| `Atendimento.model.js` | atendimentos sincronizados do ifbot |
|
|
|
|
|
+| `AtendimentoMensagem.model.js` | mensagens de um atendimento |
|
|
|
|
|
+| `AtendimentoCliente.model.js` | vínculo atendimento ↔ cliente/contrato IXC |
|
|
|
|
|
+| `AtendimentoAvaliacao.model.js` | resultado da avaliação por LLM de um atendimento |
|
|
|
|
|
+| `AtendimentoGoldenLabel.model.js` | rótulos de referência (golden set) para calibrar a avaliação |
|
|
|
|
|
+| `AtendimentoRagIndex.model.js` | controle de ingestão do RAG de atendimentos |
|
|
|
|
|
+| `WhatsappConversa.model.js` | conversas do chat de atendente via WhatsApp Cloud API |
|
|
|
|
|
+| `WhatsappMensagem.model.js` | mensagens de uma `WhatsappConversa` |
|
|
|
|
|
+| `Integracao.model.js` | credenciais/token do integrador IXC (Alfred), cacheadas no banco |
|
|
|
|
|
|
|
|
### Middleware (`src/middleware/`)
|
|
### Middleware (`src/middleware/`)
|
|
|
|
|
|
|
@@ -69,14 +82,18 @@ Schemas **zod** usados com o middleware `Validate` (que chama `schema.parse()`).
|
|
|
|
|
|
|
|
| Arquivo | Domínio |
|
|
| Arquivo | Domínio |
|
|
|
|---|---|
|
|
|---|---|
|
|
|
|
|
+| `Auth.Schema.js` | Login, refresh, solicitação de reset de senha |
|
|
|
| `Chat.Schema.js` | Corpo do chat |
|
|
| `Chat.Schema.js` | Corpo do chat |
|
|
|
| `Ingest.Schema.js` | Corpo do ingest |
|
|
| `Ingest.Schema.js` | Corpo do ingest |
|
|
|
| `Search.Schema.js` | Corpo da busca |
|
|
| `Search.Schema.js` | Corpo da busca |
|
|
|
| `Usuario.Schema.js` | Criar, atualizar e alterar senha de usuário |
|
|
| `Usuario.Schema.js` | Criar, atualizar e alterar senha de usuário |
|
|
|
|
|
+| `Atendimento.Schema.js` | Filtros e ações sobre atendimentos/avaliações |
|
|
|
|
|
+| `Conversas.Schema.js` | Corpo das rotas do chat de atendente (WhatsApp) |
|
|
|
|
|
+| `IxcIntegrador.Schema.js` | Corpo/query das buscas no integrador IXC (clientes, contratos, OS) |
|
|
|
|
|
|
|
|
## Padrão de arquivos por domínio
|
|
## Padrão de arquivos por domínio
|
|
|
|
|
|
|
|
-Todo domínio segue a convenção de nomenclatura abaixo:
|
|
|
|
|
|
|
+Todo domínio segue a convenção de nomenclatura abaixo (passo a passo completo, com exemplo, em [conventions.md](conventions.md)):
|
|
|
|
|
|
|
|
```
|
|
```
|
|
|
src/
|
|
src/
|
|
@@ -108,15 +125,57 @@ import { db } from "#config/db.config.js";
|
|
|
|
|
|
|
|
## Domínios existentes
|
|
## Domínios existentes
|
|
|
|
|
|
|
|
|
|
+Referência completa de endpoints, bodies e retornos em [api.md](api.md).
|
|
|
|
|
+
|
|
|
| Domínio | Prefix da rota | Descrição |
|
|
| Domínio | Prefix da rota | Descrição |
|
|
|
|---|---|---|
|
|
|---|---|---|
|
|
|
| Auth | `/api/auth` | Login, logout, refresh de token |
|
|
| Auth | `/api/auth` | Login, logout, refresh de token |
|
|
|
-| Chat | `/api/chat` | Envio de mensagens ao LLM |
|
|
|
|
|
-| Conversations | `/api/conversations` | CRUD de conversas persistidas |
|
|
|
|
|
|
|
+| Chat | `/api/chat` | Envio de mensagens ao LLM (pipeline em `src/chat/`, ver abaixo) |
|
|
|
|
|
+| Conversations | `/api/conversations` | CRUD de conversas do chat persistidas |
|
|
|
| Documents | `/api/documents` | Listagem e exclusão de documentos |
|
|
| Documents | `/api/documents` | Listagem e exclusão de documentos |
|
|
|
-| Ingest | `/api/ingest` | Ingestão de conteúdo na base RAG |
|
|
|
|
|
|
|
+| Ingest | `/api/ingest` | Ingestão de conteúdo na base RAG de documentos |
|
|
|
| Search | `/api/search` | Busca semântica nos documentos |
|
|
| Search | `/api/search` | Busca semântica nos documentos |
|
|
|
| Usuario | `/api/users` | CRUD de usuários do sistema |
|
|
| Usuario | `/api/users` | CRUD de usuários do sistema |
|
|
|
|
|
+| Atendimentos | `/api/atendimentos` | Sincronização, avaliação por LLM, golden set e ranking de atendentes sobre atendimentos de suporte |
|
|
|
|
|
+| Conversas | `/api/conversas` | Chat de atendente via WhatsApp (mensagens, sugestão de resposta) |
|
|
|
|
|
+| WhatsappConexao | `/api/whatsapp` | Status da conexão com a WhatsApp Cloud API |
|
|
|
|
|
+| IxcIntegrador | `/api/ixc` | Proxy para o integrador Alfred/IXC (clientes, contratos, OS); sub-roteia `/api/ixc/bi/*` para `IxcBi.Rotas.js` (BI do Alfred) |
|
|
|
|
|
+
|
|
|
|
|
+O webhook do WhatsApp (`/webhooks/whatsapp`) não é um domínio sob `/api` — é montado direto no `ServerFactory`, fora do middleware de auth, autenticado por `hub.verify_token` (GET, verificação) e assinatura HMAC opcional (`WHATSAPP_APP_SECRET`, POST, recebimento).
|
|
|
|
|
+
|
|
|
|
|
+## Pipeline do chat (`src/chat/`)
|
|
|
|
|
+
|
|
|
|
|
+Uma mensagem recebida em `/api/chat` passa por três estágios, nessa ordem:
|
|
|
|
|
+
|
|
|
|
|
+1. **Comandos explícitos** (`commands.js`) — antes de qualquer LLM, tenta casar a mensagem com um comando estruturado que consulta o ERP (Alfred/IXC) diretamente: `/cto <busca>` (`ctoCommand.js`) e `/<pppoe>` (`loginCommand.js`, fallback genérico — por isso vem por último no array `COMMANDS`, depois de comandos mais específicos). Sem chamada de LLM, sem risco de alucinação em dado técnico.
|
|
|
|
|
+2. **Roteamento por LLM** (`chatRouter.js`) — se nenhum comando bateu, `classificarModoChat()` classifica a intenção em um de três modos: `documentos` (RAG sobre a base de conhecimento), `atendimentos` (RAG sobre atendimentos de suporte já registrados) ou `geral`. Um regex de código de protocolo (`CODIGO_PROTOCOLO_RE`) bypassa o LLM quando a mensagem já traz um protocolo explícito. O resultado é cacheado (TTL 15 min) quando não há histórico de conversa.
|
|
|
|
|
+3. **Chain de resposta** (`chatChain.js`) — monta o RAG do modo escolhido: rewrite de query (resolve follow-ups usando o histórico), HyDE opcional (gera uma passagem hipotética para melhorar o embedding de busca), busca no Qdrant, rerank via LLM e geração da resposta final — streaming SSE (`answerWithContextStream`) ou não-streaming (`answerWithContext`).
|
|
|
|
|
+
|
|
|
|
|
+Ao adicionar um novo comando explícito, siga o padrão `parse`/`resolve` de `ctoCommand.js`/`loginCommand.js` e registre-o no array `COMMANDS`.
|
|
|
|
|
+
|
|
|
|
|
+## RAG de atendimentos vs. RAG de documentos
|
|
|
|
|
+
|
|
|
|
|
+Dois pipelines RAG paralelos e independentes, com collections Qdrant separadas:
|
|
|
|
|
+
|
|
|
|
|
+- **Documentos**: ingestão manual/upload via domínio `Ingest`, collection `QDRANT_COLLECTION`.
|
|
|
|
|
+- **Atendimentos**: ingestão automática via job (`ingestAtendimentosRag.js`), a partir de conversas sincronizadas do ifbot (`syncAtendimentos.js`), collection `ATENDIMENTOS_RAG_COLLECTION`. Enriquecido com dados da avaliação por IA (score/resumo/sugestões) via `atendimentoRagService.js`. A busca vetorial nesse modo é opcional, controlada por `ATENDIMENTOS_RAG_SEARCH_ENABLED`.
|
|
|
|
|
+
|
|
|
|
|
+Atendimentos passam por avaliação automática via LLM-como-juiz (`atendimentoAvaliacaoService.js`, job `avaliarAtendimentos.js`), sem golden set formal — `AtendimentoGoldenLabel` existe para começar a calibrar isso.
|
|
|
|
|
+
|
|
|
|
|
+## Integração com o ERP (Alfred/IXC)
|
|
|
|
|
+
|
|
|
|
|
+`ixcIntegradorService.js` fala com um integrador externo (repositório separado, roda em `http://localhost:4001`) que expõe Cliente/Contrato/OS do IXC. Credenciais (`Url`/`Login`/`Senha`/`Token`) ficam na tabela `integracao` (model `Integracao`), não no `.env` — só o seed (`npm run seed:run`) lê do `.env` para provisionar a linha inicial; o token sobrevive a restart e só é invalidado rodando o seed de novo com credenciais diferentes.
|
|
|
|
|
+
|
|
|
|
|
+## Jobs em background (`src/jobs/`)
|
|
|
|
|
+
|
|
|
|
|
+Agendados pelo `ServerFactory`, intervalo configurável por env var (`0` desativa; variáveis completas em [setup.md](setup.md)):
|
|
|
|
|
+
|
|
|
|
|
+| Job | Intervalo | Função |
|
|
|
|
|
+|---|---|---|
|
|
|
|
|
+| `cleanupTokens.js` | fixo, 6h (não configurável por env) | Remove refresh tokens expirados |
|
|
|
|
|
+| `syncAtendimentos.js` | `ATENDIMENTOS_SYNC_INTERVAL_MINUTES` | Importa atendimentos novos do ifbot |
|
|
|
|
|
+| `avaliarAtendimentos.js` | `ATENDIMENTOS_AVALIACAO_INTERVAL_MINUTES` | Avalia atendimentos pendentes via LLM |
|
|
|
|
|
+| `ingestAtendimentosRag.js` | `ATENDIMENTOS_RAG_INTERVAL_MINUTES` | Ingere atendimentos avaliados no Qdrant |
|
|
|
|
|
|
|
|
## Health check
|
|
## Health check
|
|
|
|
|
|