Guia visual da arquitetura: como a demanda circula entre as colônias, como o conhecimento se acumula e como os textos viram resumos.
Cores: azul é a trilha da demanda, âmbar é o conhecimento, verde é a saída pública, cinza é o núcleo, roxo são os fluxos paralelos de lugar e empresa.
Um ciclo concluído vira um caso. Os casos iguais se somam em um perfil. O perfil vira um dossiê explicado. A próxima demanda nasce com esse caminho na mão.
Cada caixa é um módulo independente. As setas são eventos no barramento. Cada colônia mostra o que publica.
Entender as quatro partes de qualquer colônia é o que permite ler todos os diagramas deste guia.
Assina tipos no barramento, guarda um cursor por tipo e faz replay do que perdeu no boot.
Tabelas no schema da colônia, por exemplo d24.*. Nenhuma outra colônia escreve ali.
A saída vai para o log append-only. É assim que o resto do sistema fica sabendo do que aconteceu.
Para mostrar algo, mantém uma cópia local de leitura. Nunca consulta o schema da vizinha.
Falhou no meio? O evento vai para a DLQ, o cursor não avança e o replay reprocessa. Nada se perde.
Evento demanda.normalizada, publicado pela D-1b e consumido por D-2, D-3, D-7 e D-12.
{
"demanda_id": "a1b2c3d4-...",
"titulo": "Falta de água na Rua das Flores",
"descricao_limpa": "Falta d'água na Rua das Flores",
"confianca_normalizacao": 0.95
}
Leitura de baixo para cima: o ciclo concluído vira caso, o caso vira caminho, o caminho acelera a próxima demanda.
O sistema não cola textos soltos. Ele transforma cada atualização em campos tipados e é desses campos que nasce o resumo.
"Liguei hoje para a Secretaria de Obras e abri o protocolo SO-2026/4127. A vistoria ficou para a semana que vem."
Texto livre, em linguagem natural. É assim que a atualização chega pelo app.
Relato em áudio segue o mesmo caminho: a D-1b transcreve e a atualização é estruturada do mesmo jeito.
Ao publicar, o conselheiro escolhe o tipo da atualização. O sistema valida os campos obrigatórios daquele tipo.
| Tipo | Campos extraídos |
|---|---|
| contato_realizado | canal = telefone data_contato = 12/03 |
| protocolo_aberto | órgão = Secretaria de Obras protocolo = SO-2026/4127 |
| prazo_registrado | data_estimada = 20/04 |
O caso recebe os campos, não o texto inteiro:
Telefone, nome de pessoa e outros dados pessoais são removidos na sanitização antes de qualquer persistência ou publicação.
O caso também guarda a data de abertura, o prazo e o desfecho. É isso que permite calcular a mediana da página 8.
O que acontece entre o relato e o resumo, passo a passo. Nenhum passo entende o significado do texto.
| Conjunto | Tokens (exemplo simplificado) |
|---|---|
| A | buraco · rua · flores · carro |
| B | buraco · rua · flores · fundo |
Interseção: 3 tokens. União: 5 tokens.
3 ÷ 5 = 0,60. No limiar, os dois trechos são tratados como o mesmo ponto.
Os bigramas entram no mesmo cálculo. "buraco grande" e "grande buraco" têm as mesmas palavras, mas pares diferentes.
No código: mvp-api · src/duplicidade/texto-similaridade.ts (normalizarTexto, tokenizar e similaridadeJaccard). O limiar de 0,60 vive em src/demanda/d-12-deteccao-duplicidade/d12.constants.ts.
Contar é agrupar: quantas vezes cada canal, órgão e gargalo aparece no conjunto de casos.
Mediana é ordenar e pegar o meio:
[22, 14, 12, 5, 45, 14, 6, 8, 18, 15]
→ [5, 6, 8, 12, 14, 14, 15, 18, 22, 45]
→ (14 + 14) ÷ 2 = 14
Com 10 valores (par), a mediana é a média dos dois do meio. O caso de 45 dias não puxa o resultado como puxaria a média.
O que ele é, o que a nossa API envia, o que volta e o que o usuário faz com isso.
O endpoint recebe {campo, texto} e chama o LanguageTool com language=pt-BR.
O texto do campo vai inteiro para o motor. Nada é salvo: a operação é stateless e não bloqueia o envio do formulário.
{
"matches": [{
"message": "Use a crase...",
"offset": 12,
"length": 7,
"replacements": [ "..." ],
"rule": { "id": "PT_CRASE" }
}]
}
A API traduz para o contrato do app: trecho, mensagem, tipo e substituições. O usuário aplica uma, várias ou nenhuma.
Remover telefone, CPF e e-mail do texto é casamento de padrão (regex), não compreensão. Roda antes de persistir ou publicar.
O canal "telefone" do caso é outra coisa: vem de uma lista fechada escolhida no formulário, não de leitura automática do texto.
A D-6b percorre as atualizações publicadas e monta uma linha por marco, até o desfecho. Método resumo_v1.
Ciclo de {dias} dias, {n} atualizações. {data} {tipo}: {trecho sanitizado} ... Desfecho: {resumo_final}
Ciclo de 38 dias, 5 atualizações. 12/03 Contato por telefone com o órgão responsável. 14/03 Protocolo SO-2026/4127 aberto na Secretaria de Obras. 28/03 Entrave de gravidade média: vistoria atrasou. 02/04 Prazo estimado para 20/04. 19/04 Demanda concluída.
Atualizado a cada publicação e fechado na conclusão. Limite de 6000 caracteres.
A D-12 recebe os relatos do agregado, remove repetições e soma os detalhes diferentes. Método resumo_v1.
A mesma heurística que a D-12 usa para detectar duplicatas.
3 relatos registrados entre 12/03/2026 e 13/03/2026. Ponto em comum: buraco na Rua A, próximo ao número 120. Detalhes somados: profundidade, tempo de existência, desvio de veículos e prejuízo ao ônibus.
Limite de 3000 caracteres. Republicado quando o agregado ganha um membro novo.
A D-24 agrega os casos por município e subcategoria, calcula os indicadores e monta o texto explicado. Método caminho_v1.
| # | Abertura | Canal | Prazo | Gargalo |
|---|---|---|---|---|
| 1 | 02/02 | telefone | 22 dias | vistoria lenta |
| 2 | 09/02 | app | 14 dias | — |
| 3 | 15/02 | telefone | 12 dias | vistoria lenta |
| 4 | 24/02 | telefone | 5 dias | — |
| 5 | 03/03 | presencial | 45 dias | material em falta |
| 6 | 11/03 | telefone | 14 dias | falta de protocolo |
| 7 | 18/03 | app | 6 dias | — |
| 8 | 27/03 | telefone | 8 dias | vistoria lenta |
| 9 | 05/04 | app | 18 dias | falta de protocolo |
| 10 | 12/04 | telefone | 15 dias | vistoria lenta |
10 demandas de "Buracos na via" concluídas em Ituiutaba, entre 02/02/2026 e 12/04/2026. Canal mais usado: telefone (6), depois app (3) e presencial (1). Órgão acionado: Secretaria de Obras. Prazo mediano: 14 dias (mínimo 5, máximo 45). Gargalos recorrentes: vistoria lenta (4) e falta de protocolo (2). Documentos mais pedidos: foto do local (7) e endereço completo (5).
O dossiê aparece no acompanhamento público, no relatório exportado e no workspace do conselheiro no momento em que ele assume a demanda.
O conselheiro decide: segue o caminho, adapta ou faz diferente. O que ele registrar vira o caso 11 e atualiza o perfil.
As dúvidas que qualquer pessoa nova no projeto vai ter, respondidas sem rodeio.
Os termos usados neste guia, na ordem em que aparecem no projeto.
Quem publica, quem consome e o que cada evento carrega. Toda colônia nova registra seus eventos no Registry antes de publicar.
| Evento | Publica | Consome | O que carrega |
|---|---|---|---|
| demanda.recebida | D-1a | D-1b, D-1c | Texto bruto, coordenadas, mídias, consentimentos e a categoria escolhida pelo cidadão. |
| demanda.normalizada | D-1b | D-2, D-3, D-7, D-12 | Título inferido, descrição limpa, confiança da normalização e termos suspeitos da denylist. |
| anexo.processado | D-1c | D-1d, D-7 | Hash, tipo, tamanho, classificação NSFW e de pessoa, evidência pública. |
| moderacao.decidida | D-1d | D-1c, D-7 | Decisão humana (aprovar, bloquear ou remover), motivo e trilha auditável. |
| demanda.georreferenciada | D-2 | D-3, D-4, D-7, D-24 | UC de menor nível, cadeia completa de UCs, método e confiança da resolução. |
| demanda.categorizada | D-3 | D-4, D-7, D-12, D-24 | Categoria, nível de precedência, score horizontal, confiança e alternativas. |
| demanda.ranqueada | D-4 | D-5, D-7 | Score final e posição da demanda no ranking da unidade cívica. |
| ranking.atualizado | D-4 | D-5, D-7 | Topo do ranking reconstruído, quando a primeira posição muda. |
| agenda.item_disponível | D-5 | D-6a | Demanda pronta para receber conselheiro, com posição no backlog. |
| conselheiro.sorteado | D-6a | D-5, D-6b | Atribuição da demanda ao conselheiro sorteado. |
| conselheiro.atualização_publicada | D-6b | D-7, D-24 | Texto final do conselheiro mais os campos estruturados do tipo da atualização. |
| demanda.concluída | D-6b | D-5, D-7, D-24 | Fecho do ciclo e dias até a conclusão. |
| conselheiro.ciclo_concluído | D-6b | D-6a, D-7, D-24 | Motivo do encerramento, datas, duração e resumo final em linguagem neutra. |
| duplicidade.agregada | D-12 | D-4, D-5, D-6a, D-6b, D-7, D-24 | Representante, membros unidos e confirmações que formaram o agregado. |
| caminho.atualizado | D-24 | D-6b, D-7 | Perfil do caminho e dossiê explicado de um município e subcategoria. |
| demanda.resumo_agregado_atualizado | D-12 | D-7 | Resumo público dos relatos do agregado, com fontes e versão de método. |
| demanda.resumo_ciclo_atualizado | D-6b | D-7 | Resumo público do ciclo, atualização por atualização, até o desfecho. |