From 1e9693410bdb03a4bda884ce8bc5921d06988c2c Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 9 Aug 2026 23:34:57 -0300 Subject: [PATCH 1/2] =?UTF-8?q?docs(B185,B186,B187,B188,B189,B190):=20plan?= =?UTF-8?q?os=20de=20inten=C3=A7=C3=A3o=20=E2=80=94=20Sollinha=20gest?= =?UTF-8?q?=C3=A3o=20(lideran=C3=A7as=20pendentes,=20prioridades=20do=20mo?= =?UTF-8?q?mento,=20cobertura=20de=20atualiza=C3=A7=C3=A3o=20e=20de=20dobr?= =?UTF-8?q?adinhas)=20+=20links=20com=20apar=C3=AAncia=20de=20link=20+=20c?= =?UTF-8?q?ontexto=20por=20sess=C3=A3o=20da=20janela?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/sollinha-contexto-sessao-janela.md | 76 ++++++++++++++++ ...sollinha-liderancas-pendentes-abordagem.md | 87 +++++++++++++++++++ docs/plans/sollinha-links-como-links.md | 67 ++++++++++++++ ...inha-municipios-sem-atualizacao-recente.md | 78 +++++++++++++++++ .../sollinha-municipios-sem-dobradinha.md | 75 ++++++++++++++++ docs/plans/sollinha-prioridades-do-momento.md | 76 ++++++++++++++++ 6 files changed, 459 insertions(+) create mode 100644 docs/plans/sollinha-contexto-sessao-janela.md create mode 100644 docs/plans/sollinha-liderancas-pendentes-abordagem.md create mode 100644 docs/plans/sollinha-links-como-links.md create mode 100644 docs/plans/sollinha-municipios-sem-atualizacao-recente.md create mode 100644 docs/plans/sollinha-municipios-sem-dobradinha.md create mode 100644 docs/plans/sollinha-prioridades-do-momento.md diff --git a/docs/plans/sollinha-contexto-sessao-janela.md b/docs/plans/sollinha-contexto-sessao-janela.md new file mode 100644 index 00000000..d4e351d3 --- /dev/null +++ b/docs/plans/sollinha-contexto-sessao-janela.md @@ -0,0 +1,76 @@ +# Sollinha: contexto da conversa persiste durante a sessão da janela/tab + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #529 +Priority: P1 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: A — N/A (comportamento de estado; nenhuma superfície nova) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng; navegação dos links do chat + persistência de sessão da aba; sem migration/schema/Consent + +## Intenção + +O Sollinha foi pensado para trabalhar **em colaboração** com o usuário, mas hoje o contexto da conversa se perde: se o usuário clica num link que o próprio Sollinha sugeriu (ex.: detalhe de um município), a página recarrega por completo e a conversa zera. A expectativa: o contexto permanece durante **toda a sessão daquela janela/tab** — inclusive ao navegar pelos links do próprio Sollinha e ao recarregar a página — e some quando a aba é fechada. É uma mudança de decisão de produto: o plano original do chat (entregue) travou "sessão nova a cada abertura"; este item é o **sucessor** dessa decisão, no escopo restrito de uma aba. + +## Persona e fluxo + +- **Persona / contexto:** assessor/coordenador em `/campanha`, conversa com o Sollinha, segue um link sugerido para ver o detalhe, e **espera voltar à mesma conversa** para continuar pedindo. +- **Job principal:** navegar dentro do app (inclusive via links do chat) sem perder o fio da conversa, durante toda a sessão da aba. +- **Fluxo desejado:** + 1. O usuário pergunta, recebe resposta com link e clica. + 2. A vista destino abre, **sem recarregar a página** (navegação interna do app). + 3. O chat continua com a conversa; o usuário reabre o painel/drawer e retoma de onde parou. + 4. Recarregar a página na mesma aba também mantém a conversa (sessão da aba). + 5. Fechar a aba → tudo some; outra aba começa limpa. +- **Anti-goals de produto:** persistência entre abas/dispositivos ou no servidor (fora de escopo); histórico/threads gerenciáveis (complexidade de v2 continua fora). + +## Objetivo e aceite + +- Clicar num link do Sollinha navega **sem recarregar a página** (navegação por cliente do app), e a conversa continua presente ao reabrir o chat. +- Recarregar a página na mesma aba restaura a conversa (mensagens e estado em andamento). +- Fechar a aba apaga tudo; outra aba inicia sem histórico. +- Nada é gravado no servidor/banco; nada cruza abas ou dispositivos. +- Se o chat estava aberto ao recarregar, ele volta aberto na mesma superfície (painel/drawer conforme o viewport). + +## Dados (intenção) + +- Dados: N/A — estado de sessão local; nenhum dado de campanha novo. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `src/components/campaign/shell/ai/CampaignAIChat.tsx` + `CampaignAISidebarContext.tsx` (o `useChat` vive no provider, montado no layout `(app)/layout.tsx`, que **sobrevive** à navegação por cliente — a perda acontece porque os links do markdown são âncoras comuns (``) que disparam navegação completa). Direções prováveis, a critério do executor: (1) links do chat navegando por rota do app (Next `Link`/interceptação de clique), preservando o layout; (2) persistência da conversa no storage de sessão da aba (ex.: `sessionStorage`), restaurada ao montar — cobre também recarga manual. `src/lib/sollinhaChatPanelWidth.ts` é precedente de persistência local do chat (guards `typeof window`, try/catch). +- **Precedente a olhar:** `docs/plans/ai-chat-sollinha.md` (decisão original "sem persistência" — `in-prod`, não editar; este item a sucede por decisão de produto); `docs/plans/migrar-chat-painel-drawer-resize.md` (B167 — estados do painel/drawer); `docs/plans/largura-padrao-chat-sollinha.md` (B166 — storage local do chat). +- **Risco de acoplamento:** mesma área de render tocada por B187 (links com aparência de link) — os dois mudam o render de mensagens; devem ser feitos sem conflito (ou em sequência). O `useChat` tem lifecycle próprio (limpar estado ao fechar aba é nativo do storage de sessão). + +## Dependências + +- Nenhuma dura. Nota: links **parecerem** links é B187 (separado; aparece junto na prática). + +## Fora de escopo + +- Histórico entre abas, dispositivos ou logins; threads nomeadas; persistência no servidor/banco. +- Exportar/limpar conversa manualmente (a aba limpa sozinha). +- Sobreviver ao fechamento e reabertura da aba (persistência "até apagar os dados" é outra decisão de produto). + +## Rabbit holes de produto + +- **Virar histórico infinito.** Se alguém "só completar", a conversa cresce sem limite e o custo/ruído sobe. **Corte:** guardrail simples de tamanho no storage de sessão (mensagens mais antigas descartadas sem pedir), e nada além da sessão da aba. +- **Chat abrindo sozinho.** Restaurar o estado aberto não pode fazer o chat saltar na tela sem o usuário ter deixado aberto. **Corte:** restaura junto apenas se estava aberto no momento da recarga. +- **Conflito com B166/B167.** Largura e superfície (painel/drawer) têm regras próprias. **Corte:** este item só restaura conversa + estado aberto; largura e migração de superfície continuam como estão. + +## Questões em aberto (produto) + +- **Restaurar também o estado "aberto"?** **Opções:** (A) só as mensagens — o chat reabre fechado; (B) mensagens + estado aberto (se estava aberto, volta aberto). **Recomendação:** (B) — é o que faz "continuar trabalhando" funcionar após recarga; a superfície segue o viewport (regra B167). _(assumido — validar)_ +- **Rascunho digitado no input:** persistir junto? **Opções:** (A) sim; (B) só mensagens trocadas. **Recomendação:** (B) — mensagens são o contrato de contexto; rascunho é bônus que adiciona estados estranhos (pergunta parcial no meio de digitação). _(assumido — validar)_ +- **Limite de contexto:** quantas mensagens/máximo de bytes na sessão? **Recomendação:** guardrail de tamanho (ex.: últimas ~50 mensagens ou teto de bytes), sem UI de aviso. _(assumido — validar)_ + +## Referências + +- `src/components/campaign/shell/ai/CampaignAISidebarContext.tsx` — provider do `useChat` (estado atual) +- `src/components/campaign/shell/ai/CampaignAIChat.tsx` — render dos links do markdown +- `src/app/(campaign)/campanha/(app)/layout.tsx` — onde o provider vive (sobrevive à navegação por cliente) +- `src/lib/sollinhaChatPanelWidth.ts` — precedente de storage local do chat (B166) +- `docs/plans/ai-chat-sollinha.md` — decisão original "sem persistência" (imutável, `in-prod`; este item a sucede) +- `docs/plans/sollinha-links-como-links.md` (B187) — irmão no mesmo render diff --git a/docs/plans/sollinha-liderancas-pendentes-abordagem.md b/docs/plans/sollinha-liderancas-pendentes-abordagem.md new file mode 100644 index 00000000..818ddd76 --- /dev/null +++ b/docs/plans/sollinha-liderancas-pendentes-abordagem.md @@ -0,0 +1,87 @@ +# Sollinha: lideranças pendentes de abordagem + assessores responsáveis (gestão) + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #524 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: A — N/A (resposta em texto no chat existente; nenhuma superfície nova) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng; uma tool nova read-only + testes; sem migration/collection/Consent + +## Intenção + +O Sollinha hoje responde perguntas de leitura **por entidade** ("quais lideranças temos em X?", "como está o município Y?"), mas não consegue responder perguntas de **acompanhamento de campanha** que cruzam dados: "Quais lideranças ainda precisamos abordar no Vale do Jiquiriça?", "Quais são os assessores das lideranças que ainda precisam ser abordadas em Salvador?". O usuário quer que o Sollinha monte essas listas com confiança — critério de "pendente" explícito na resposta e responsáveis à vista para agir. + +## Persona e fluxo + +- **Persona / contexto:** coordenador ou assessor em `/campanha`, no chat, planejando o trabalho de campo de um território; quer saber o que **falta fazer** e **com quem**. +- **Job principal:** descobrir, em linguagem natural, quais lideranças de um território ainda precisam ser abordadas e quem são os assessores responsáveis por elas. +- **Fluxo desejado:** + 1. "Quais lideranças ainda precisamos abordar no Vale do Jiquiriça?" → o Sollinha filtra pelo território de identidade (`municipality.region`), aplica o critério "pendente de abordagem", responde lista (nome, municípios, status de apoio, última atualização) **declarando o critério usado** e oferece links de navegação (precedente B162). + 2. "Quais são os assessores das lideranças que ainda precisam ser abordadas em Salvador?" → mesma leitura, agrupada por assessor responsável, com link para cada assessor/liderança. + 3. O assessor pede o recorte do próprio escopo e usa a lista para acionar o campo; o coordenador pede por região/cidade. +- **Anti-goals de produto:** não virar estatística/dashboard no chat; não incluir dados eleitorais (leader lockdown B180); não virar write tool (abordagem registrada pelo chat fica fora). + +## Objetivo e aceite + +- O Sollinha responde "quais lideranças ainda precisam ser abordadas" por **território de identidade** (ex.: Vale do Jiquiriçá), **cidade** (ex.: Salvador — os 19 ZE juntos) ou **município**, com a resposta declarando o critério aplicado ("status 'a abordar'/'em disputa' ou sem compromisso de votos"). +- Quando perguntado, agrupa ou lista os **assessores responsáveis** das lideranças pendentes (campo "Assessores responsáveis" da liderança) com link. +- Liderança com status engajado **e** compromisso de votos recente não aparece na lista. +- A resposta inclui links de navegação (liderança/município/assessor) via a tool de links existente. +- **Escopo por papel:** assessor enxerga apenas os municípios que administra (RBAC atual); leader recebe resposta de acesso negado, fail-closed, sem nenhum dado. +- Tipografia de nomes tolerante: "Vale do Jiquiriça" (sem acento) casa com "Vale do Jiquiriçá". +- **Filtro "sem assessor" (C3):** com o filtro ativo, a lista traz apenas as lideranças pendentes que **não têm assessor responsável** — as órfãs de responsabilidade, que o coordenador precisa atribuir antes de cobrar. +- **Modo "municípios sem liderança" (C4):** quando perguntado "quais municípios não têm liderança cadastrada?", a tool responde com os municípios do escopo sem **nenhuma** liderança vinculada — buraco de registro, não de abordagem. + +## Dados (intenção) + +- **Vou apresentar dados?** Sim — listas de lideranças com recência/status, como superfície deste item (no chat). +- **Decisões desbloqueadas:** coordenador decide em qual território atacar a seguir; assessor decide quais lideranças do seu escopo abordar primeiro; coordenador direciona assessores pelas listas agrupadas por responsável. +- **Forma:** *adiada ao plano de implementação*. Restrições de produto: leitura relativa/local (território ou escopo do usuário), nunca % estadual; lista, não mapa; o critério de pendência **sempre** visível na resposta. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** nova tool em `src/utilities/ai/tools/` (mesma família de `getLeaderships`, `getMunicipalityOverview`, `getDobradinhas`), registrada no `index.ts`; reuso de `buildCampaignLinks` para os links; campos: `leadership.supportStatus`/`updatedAt`/`advisors`, `votePledge` (declaredAt), `municipality.region`/`city`. +- **Precedente a olhar:** `docs/plans/sollinha-tool-urls-navegacao.md` (B162 — links na resposta); `docs/plans/sollinha-tools-eleitorais-leader-lockdown.md` (B180 — padrão de gate fail-closed por papel); `docs/plans/ai-chat-sollinha.md` (entrega original, `in-prod` — não editar). +- **Risco de acoplamento:** tools eleitorais são staff-only (B180); o recorte por `region` depende do texto canônico do território; não duplicar o sufixo de status já usado em `getLeaderships`. + +## Dependências + +- Nenhuma dura. Soft: B186 (prioridades) reusa a semântica de "atualização recente" definida aqui. + +## Fora de escopo + +- Write tools (marcar abordagem/atualizar liderança via chat) — decisão de produto separada, não pedida. +- Estatísticas agregadas/percentuais por território — dashboard, não chat. +- Incluir dados eleitorais na lista — leader lockdown B180 continua valendo. +- Alertas proativos (o Sollinha avisar sem ser perguntado). + +## Rabbit holes de produto + +- **Definição de "pendente" ambígua.** Se alguém "só completar", vira um filtro de status sem critério declarado. **Corte:** o critério é fixo por default (status ≠ engajado **ou** sem compromisso), sempre exibido na resposta; o usuário pode estreitar com filtros, nunca expandir para "todos os dados". +- **Lista gigante.** 435 municípios × lideranças → resposta impossível. **Corte:** top N por padrão com total, escalonável por território/página. + +## Semântica compartilhada da família (B185/B186/B189) + +Este item é a **fonte** das definições que B186 (prioridades) e B189 (cobertura de atualizações) reusam, para a família não redefinir o mesmo conceito três vezes: + +- **"Atualização recente"** = data da última `municipalityUpdate` (createdAt); município **sem nenhuma** atualização conta como "nunca atualizado" (estagnação máxima). Limiar padrão proposto: 30 dias, ajustável por filtro/pergunta. +- **Escopo** = o do usuário: assessor vê só os municípios que administra; coordenador vê o estado, filtrável por região/cidade. +- **"Pendente de abordagem"** = status ≠ `engajado` **ou** sem `votePledge` no município (critério declarado na resposta). + +## Questões em aberto (produto) + +- **O que define "ainda precisa ser abordada"?** **Opções:** (a) `supportStatus` ≠ `engajado` (campo mantido pelo staff); (b) sem `votePledge` (compromisso de votos) no município; (c) sem atualização recente (ex.: >30 dias); (d) combinação configurável. **Recomendação:** (a) + (b) — status é a verdade do staff e o compromisso é o resultado da abordagem; o critério aplicado aparece na resposta e pode ser estreitado por filtros. _(assumido — validar)_ +- **"Assessores das lideranças" = quem?** **Opções:** (A) `leadership.advisors` ("Assessores responsáveis" da liderança); (B) `municipality.advisors` (assessores do município); (C) os dois. **Recomendação:** (A) — é o vínculo direto pessoa→liderança; quando útil, o nome do assessor do município vem como contexto. _(assumido — validar)_ +- **Escopo "Salvador" e "Vale do Jiquiriça":** interpretar "Salvador" como os 19 ZE juntos (cidade) e território pelo campo região? **Recomendação:** sim, com a tool aceitando região | cidade | município como filtros. _(assumido — validar)_ +- **C4 (municípios sem liderança) merece item próprio?** **Opções:** (A) fold-in aqui, como modo de saída (recomendado no menu do gate); (B) item próprio. **Recomendação:** (A) — mesma tool, mesma família de dado (cadastro de lideranças), aceite único "saber o que falta no território". _(assumido — validar)_ + +## Referências + +- `src/utilities/ai/tools/getLeaderships.ts` — precedente de tool de lideranças (estende o surface) +- `src/utilities/ai/tools/getMunicipalityOverview.ts` — contagens e visão por município +- `src/utilities/ai/tools/buildCampaignLinks.ts` — links na resposta (B162) +- `src/collections/Leadership.ts` — campos `supportStatus`, `advisors`, `updatedAt` +- `docs/plans/ai-chat-sollinha.md` — arquitetura do chat (imutável, `in-prod`) diff --git a/docs/plans/sollinha-links-como-links.md b/docs/plans/sollinha-links-como-links.md new file mode 100644 index 00000000..6a487572 --- /dev/null +++ b/docs/plans/sollinha-links-como-links.md @@ -0,0 +1,67 @@ +# Sollinha: links com aparência de link nas respostas + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #528 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: B — encaixe no bubble do chat (estilo de links nas respostas do assistente) +Canvas UI: /home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-18/canvases/plan-b187-ui-draft.canvas.tsx +Appetite: ~0,5 dia eng; estilo de render de markdown + testes e2e de aparência; sem migration/schema + +## Intenção + +Quando o Sollinha oferece um link (ex.: "[Ilhéus](/campanha/municipios/ilheus)", via B162), ele não **parece** um link: sai igual a texto comum. O usuário quer que todo link nas respostas seja visualmente um link — no mínimo sublinhado e com cor de destaque, como um link normal em texto — e que continue clicável para navegar (ver B188). + +## Persona e fluxo + +- **Persona / contexto:** assessor/coordenador lendo a resposta do Sollinha no painel/drawer; precisa **perceber** que há um link sem passar o mouse. +- **Job principal:** identificar de relance onde a resposta tem atalhos clicáveis e usá-los com confiança. +- **Fluxo desejado:** a resposta chega com o link visivelmente destacado (sublinhado + cor) → o usuário reconhece → clica e navega (a navegação sem perder contexto é o item B188; aqui só a aparência). +- **Anti-goals de produto:** não redesenhar o bubble/chat; não trocar o sistema de markdown; não estilizar apenas links internos — qualquer link conta. + +## Objetivo e aceite + +- Todo link emitido pelo Sollinha numa resposta aparece **sublinhado e com cor de destaque** (primária do tema), distinguível do texto corrido, no tema claro **e** escuro. +- Estados de **hover e foco visíveis** (acessibilidade: foco por teclado deve aparecer). +- O resto da formatação de markdown (títulos, listas, tabelas) permanece como está. +- Links externos (se um dia existirem) abrem em nova aba; links internos `/campanha/...` continuam navegando no app. + +## Dados (intenção) + +- Dados: N/A — aparência apenas; nenhuma decisão de dado/agregação. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** `src/components/campaign/shell/ai/CampaignAIChat.tsx` — a renderização das mensagens do assistente usa `ReactMarkdown` com classes `prose` que **não têm efeito** (o plugin `@tailwindcss/typography` não está instalado no projeto — confirmado no CSS entry `src/app/(frontend)/styles.css`); os `` do markdown ficam sem estilo. Ajustar o render de links (underline + cor primária + hover/focus, claro/escuro) mantendo o restante do markdown intacto. +- **Precedente a olhar:** `docs/plans/sollinha-tool-urls-navegacao.md` (B162 — como os links entram na resposta); `docs/plans/sollinha-contexto-sessao-janela.md` (B188 — o comportamento de clique/estado do chat é vizinho). +- **Risco de acoplamento:** mesma área de render tocada por B188 (navegação/clique) — pequeno, mas os dois itens devem ser feitos sem pisar um no outro (ou em sequência). + +## Dependências + +- Nenhuma dura. Nota: navegação preservando contexto é B188 (separado). + +## Fora de escopo + +- Mudar comportamento de navegação dos links (isso é B188). +- Estilo de mensagens do usuário (não há markdown nelas hoje). +- Instalar plugin de tipografia para o app inteiro — o escopo é o chat. + +## Rabbit holes de produto + +- **"Ficou bonito demais".** Se alguém "só completar", vira um polimento geral do bubble. **Corte:** só a aparência de links; nada de mudar padding/bubble/cores de fundo. +- **Alvo do link errado.** Abrir link interno em aba nova quebra a colaboração com o chat (contexto some). **Corte:** interno navega no app (comportamento atual), externo em aba nova; a navegação sem perder contexto é do B188. + +## Questões em aberto (produto) + +- **Cor do link:** cor primária do tema ou outra de destaque no bubble? **Recomendação:** primária do tema (é a "cor de link" padrão do app) com contraste garantido no claro e escuro. _(assumido — validar)_ +- **Sublinhado em hover só ou sempre visível?** **Recomendação:** sublinhado **sempre** visível (o usuário pediu "pelo menos sublinhado"), com hover reforçando (muda cor/intensidade). _(assumido — validar)_ + +## Referências + +- Canvas UI (gate): `/home/fsolla/.cursor/projects/home-fsolla-cursor-worktrees-teqo-plans-plan-issue-18/canvases/plan-b187-ui-draft.canvas.tsx` +- `src/components/campaign/shell/ai/CampaignAIChat.tsx` — render de markdown das respostas +- `src/app/(frontend)/styles.css` — entry CSS (prose sem plugin) +- `docs/plans/sollinha-tool-urls-navegacao.md` (B162) — origem dos links nas respostas +- `docs/plans/sollinha-contexto-sessao-janela.md` (B188) — irmão no mesmo render diff --git a/docs/plans/sollinha-municipios-sem-atualizacao-recente.md b/docs/plans/sollinha-municipios-sem-atualizacao-recente.md new file mode 100644 index 00000000..14e49e66 --- /dev/null +++ b/docs/plans/sollinha-municipios-sem-atualizacao-recente.md @@ -0,0 +1,78 @@ +# Sollinha: municípios sem atualização recente (cobertura do acompanhamento) + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #526 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: A — N/A (resposta em texto no chat existente; nenhuma superfície nova) +Canvas UI: N/A — sem UI +Appetite: ~0,5–1 dia eng; uma tool nova read-only + testes; sem migration/collection/Consent + +## Intenção + +O registro de acompanhamento é tão importante quanto a ação em campo: um município sem atualização recente é um município que ninguém está olhando — ou que está sendo negligenciado em silêncio. O Sollinha deve responder "quais municípios estão há mais de X dias sem atualização (ou nunca atualizados)?" de forma **exaustiva** (todos do escopo além do limiar, com contagem), para o coordenador cobrar disciplina de registro e o assessor auditar o próprio portfólio. Complementa B186: lá o ranque de prioridades com motivo; aqui a **cobertura** — o que está ficando para trás no registro, independente de prioridade. + +## Persona e fluxo + +- **Persona / contexto:** coordenador em `/campanha` conferindo a disciplina de acompanhamento do time; assessor checando se algum município do seu escopo está "apagado". +- **Job principal:** saber, em uma pergunta, quais municípios estão sem atualização há mais de X dias — contados, listados e agrupáveis por região/assessor. +- **Fluxo desejado:** + 1. "Quais municípios estão sem atualização há mais de 30 dias?" → o Sollinha varre o escopo do usuário, aplica o limiar (default 30, ajustável na pergunta) e responde com contagem + lista ordenada do mais velho ao mais recente, cada item com "há N dias" (ou "nunca atualizado" no topo). + 2. O usuário refina: "e no Vale do Jiquiriçá?", "agrupa por assessor", "só os que nunca atualizaram". + 3. Liderança nunca vê nada: resposta de acesso negado, fail-closed. +- **Anti-goals de produto:** não virar feed de alertas/push; não ranquear por gravidade (isso é B186); não propor "o que fazer" — só o fato com o limiar declarado. + +## Objetivo e aceite + +- O Sollinha responde "quais municípios estão sem atualização há mais de X dias" no **escopo do usuário**, com **contagem total** e um item por município com "há N dias". +- Municípios **nunca atualizados** aparecem no topo, marcados como "nunca atualizado" (estagnação máxima). +- O **limiar é declarado na resposta** ("sem atualização há 30+ dias") e ajustável na pergunta ("há mais de 15 dias?"). +- Filtros: região, cidade; agrupável por assessor responsável (`municipality.advisors`) quando pedido. +- Links de navegação para o detalhe de cada município (precedente B162). +- Assessor vê apenas o próprio escopo; leader recebe acesso negado (fail-closed, sem dados). + +## Dados (intenção) + +- **Vou apresentar dados?** Sim — lista de cobertura com recência, como superfície deste item (no chat). +- **Decisões desbloqueadas:** coordenador decide cobrar o assessor do município/região; assessor decide reativar o registro de um município esquecido. +- **Forma:** *adiada ao plano de implementação*. Restrições de produto: lista exaustiva com contagem (não top N — cobertura é "tudo além do limiar"), ordenação pelo mais velho primeiro; limiar e definição sempre visíveis na resposta. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** nova tool em `src/utilities/ai/tools/` (família de `getMunicipalityOverview`/`getDobradinhas`), registrada no `index.ts`; fonte: `municipalityUpdate` (createdAt) × `municipality` (region/city/advisors); reuso de `buildCampaignLinks`. +- **Precedente a olhar:** `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185 — fonte da semântica compartilhada "atualização recente" da família); `docs/plans/sollinha-prioridades-do-momento.md` (B186 — irmão, usa o mesmo sinal com outro propósito); `docs/plans/sollinha-tools-eleitorais-leader-lockdown.md` (B180 — gate fail-closed por papel). +- **Risco de acoplamento:** a semântica de "atualização recente" é da família (B185) — este item reusa; a leitura usa só `municipalityUpdate`, sem tocar tools eleitorais (lockdown B180 intacto). + +## Dependências + +- Soft: B185 (semântica compartilhada de "atualização recente" — reusar, não redefinir). B186 é irmão (mesmo sinal, outro propósito), sem ordem obrigatória. + +## Fora de escopo + +- Ranking de prioridades com motivo (isso é B186). +- Alertas proativos/push quando um município "expira" sem atualização. +- Relacionar a lista com pendências de lideranças (isso é B185) ou com dados eleitorais (staff-only B180). +- Dashboard/mapa de cobertura. + +## Rabbit holes de produto + +- **Virar feed de alertas.** Se alguém "só completar", nasce um painel de monitoramento em tempo real. **Corte:** pergunta→resposta; sem persistência de estado, sem notificações. +- **Limiar invisível.** Responder sem declarar o limiar faz a lista parecer arbitrária. **Corte:** "há 30+ dias" (ou o limiar pedido) sempre na primeira linha da resposta. +- **"Nunca atualizado" se perde.** Municípios recém-criados/sem registro sumiriam da leitura. **Corte:** contam como estagnação máxima e vão ao topo, rotulados. + +## Questões em aberto (produto) + +- **Limiar padrão:** 15, 30 ou 60 dias? **Recomendação:** **30** — cadência típica de giro/atualização de campo, ajustável por pergunta ("há mais de 15 dias?"). _(assumido — validar)_ +- **"Sem atualização" mede só `municipalityUpdate`?** **Opções:** (a) só atualizações (recomendação da família B185); (b) qualquer edição do município (`updatedAt`). **Recomendação:** (a) — edição de config/estratégia não é acompanhamento de campo; manter a família coerente. _(assumido — validar)_ +- **Salvador na resposta:** listar os 19 ZE separados ou agrupar por cidade? **Recomendação:** agrupar por cidade por padrão (ex.: "Salvador: 7 das 19 zonas sem atualização 30+ dias"), com opção de detalhar por ZE. _(assumido — validar)_ + +## Referências + +- `src/collections/MunicipalityUpdate.ts` — artefato de acompanhamento (createdAt, polarity, urgent) +- `src/utilities/ai/tools/getMunicipalityOverview.ts` — precedente de leitura por município +- `src/utilities/ai/tools/buildCampaignLinks.ts` — links na resposta (B162) +- `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185) — semântica compartilhada da família +- `docs/plans/sollinha-prioridades-do-momento.md` (B186) — irmão (mesmo sinal, propósito diverso) +- `docs/plans/ai-chat-sollinha.md` — arquitetura do chat (imutável, `in-prod`) diff --git a/docs/plans/sollinha-municipios-sem-dobradinha.md b/docs/plans/sollinha-municipios-sem-dobradinha.md new file mode 100644 index 00000000..c038884a --- /dev/null +++ b/docs/plans/sollinha-municipios-sem-dobradinha.md @@ -0,0 +1,75 @@ +# Sollinha: municípios sem dobradinha (cobertura de parcerias) + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #527 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: A — N/A (resposta em texto no chat existente; nenhuma superfície nova) +Canvas UI: N/A — sem UI +Appetite: ~0,5–1 dia eng; uma tool nova read-only + testes; sem migration/collection/Consent + +## Intenção + +O pedido original de gestão cobre três domínios — municípios, lideranças e **dobradinhas** — e o Sollinha ainda não enxerga a cobertura de parcerias: "em quais municípios ainda não temos dobradinha?" Um município sem dobradinha é uma frentes de mobilização a explorar (o deputado estadual parceiro puxa voto para a chapa); saber onde a parceria **não existe** é o primeiro passo para decidir onde buscá-la. O mesmo vale no sentido inverso: dobradinhas cadastradas **sem município vinculado** são parceiros órfãos que o coordenador pode estar esquecendo de usar. + +## Persona e fluxo + +- **Persona / contexto:** coordenador em `/campanha`, no chat, planejando onde ativar/formalizar dobradinhas; assessor conferindo o próprio escopo. +- **Job principal:** saber, em linguagem natural, quais municípios do escopo estão sem dobradinha (ou quais dobradinhas estão órfãs), com contagem e links para agir. +- **Fluxo desejado:** + 1. "Em quais municípios ainda não temos dobradinha?" → o Sollinha cruza o catálogo de municípios do escopo com os vínculos `municipality.stateDeputies` e responde lista + contagem, com link para cada município. + 2. "Quais dobradinhas estão sem município?" → responde os parceiros órfãos, com link. + 3. O usuário refina por região/cidade; liderança nunca vê nada (fail-closed). +- **Anti-goals de produto:** não qualificar/ranquear dobradinhas existentes; não sugerir deputados estaduais-alvo para abordar (depende de dado de candidato estadual que não está garantido no escopo do app); não virar consulta eleitoral. + +## Objetivo e aceite + +- O Sollinha responde "quais municípios estão sem dobradinha" no **escopo do usuário**, com **contagem** e um item por município, ordenável por região/cidade. +- Responde "quais dobradinhas estão sem município vinculado" (órfãs), com contagem e links. +- Salvador aparece **agrupada por cidade** por padrão (19 ZE como uma unidade), com opção de detalhar por ZE. +- Links de navegação para o detalhe de cada município/dobradinha (precedente B162). +- Assessor vê apenas o próprio escopo; leader recebe acesso negado (fail-closed, sem dados). + +## Dados (intenção) + +- **Vou apresentar dados?** Sim — lista de cobertura de parcerias, como superfície deste item (no chat). +- **Decisões desbloqueadas:** coordenador decide onde buscar/formalizar dobradinha; assessor identifica parceria faltando no seu território. +- **Forma:** *adiada ao plano de implementação*. Restrições de produto: lista exaustiva com contagem (cobertura, não top N); leitura relativa ao escopo do usuário; sem % estadual. + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** nova tool em `src/utilities/ai/tools/` (família de `getDobradinhas`), registrada no `index.ts`; fonte: `municipality.stateDeputies` (hasMany) e a coleção `stateDeputy` (municípios vinculados); reuso de `buildCampaignLinks` (destinations `municipality`/`dobradinha`). +- **Precedente a olhar:** `src/utilities/ai/tools/getDobradinhas.ts` (leitura atual de dobradinhas por município); `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185 — fonte da semântica compartilhada da família); `docs/plans/sollinha-tools-eleitorais-leader-lockdown.md` (B180 — gate fail-closed por papel). +- **Risco de acoplamento:** dobradinhas são dados de campanha (staff-only por RBAC) — gate por papel igual à família; não tocar dados eleitorais (lockdown B180 intacto). + +## Dependências + +- Soft: B185/B186/B189 (família de gestão — mesma convenção de escopo e gate por papel; sem ordem obrigatória). + +## Fora de escopo + +- Sugerir **quais** deputados estaduais abordar em municípios sem dobradinha (exigiria catálogo de candidatos estaduais — não garantido no escopo de dados atual; registrar como candidato futuro se o dado existir). +- Avaliar qualidade/potencial das dobradinhas existentes. +- Dashboard/mapa de cobertura de parcerias. + +## Rabbit holes de produto + +- **Virar consulta de candidatos estaduais.** Se alguém "só completar", o item cresce para "monte a chapa ideal por município". **Corte:** só a cobertura (tem/não tem), sem ranking de quem abordar. +- **Salvador vira 19 linhas.** Listar os ZE separados polui a leitura de cobertura. **Corte:** agrupar por cidade; detalhar por ZE só quando pedido. +- **Confundir "sem dobradinha" com "sem dado".** Município sem vínculo pode ser dado faltando no cadastro. **Corte:** a resposta declara que a leitura é sobre o cadastro atual ("não há dobradinha vinculada no sistema"). + +## Questões em aberto (produto) + +- **Dobradinhas órfãs entram no mesmo aceite?** **Opções:** (A) sim — mesma tool, dois modos de saída (recomendado no menu do gate); (B) só municípios sem dobradinha. **Recomendação:** (A) — o parceiro órfão é a mesma lacuna vista pelo outro lado. _(assumido — validar)_ +- **Salvador agrupada por cidade?** **Opções:** (A) agrupar (recomendado); (B) 19 ZE individualizados por padrão. **Recomendação:** (A) — cobertura é leitura por unidade operacional (cidade), com drill-down opcional. _(assumido — validar)_ +- **Incluir "sem liderança" e "sem dobradinha" num mesmo modo de cobertura do município?** **Opções:** (A) não — domínios separados (liderança é B185, dobradinha é este item); (B) sim, um modo "raios-X do município". **Recomendação:** (A) — cada lacuna tem dono e aceite próprios; combo vira dashboard (anti-goal). _(assumido — validar)_ + +## Referências + +- `src/utilities/ai/tools/getDobradinhas.ts` — leitura atual de dobradinhas (precedente) +- `src/collections/StateDeputy.ts` + `Municipality.ts` (campo `stateDeputies`) — vínculos +- `src/utilities/ai/tools/buildCampaignLinks.ts` — links na resposta (B162) +- `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185) — família de gestão (escopo/gate) +- `docs/plans/ai-chat-sollinha.md` — arquitetura do chat (imutável, `in-prod`) diff --git a/docs/plans/sollinha-prioridades-do-momento.md b/docs/plans/sollinha-prioridades-do-momento.md new file mode 100644 index 00000000..0df047f6 --- /dev/null +++ b/docs/plans/sollinha-prioridades-do-momento.md @@ -0,0 +1,76 @@ +# Sollinha: prioridades do momento (gestão) + +Status: plano — registrado (blocked até plano em main) +Atualizado em: 2026-08-09 +Issue: #525 +Priority: P2 +Model: composer-2.5 +Model-local: deepseek-v4-flash-high +Impeccable: A — N/A (resposta em texto no chat existente; nenhuma superfície nova) +Canvas UI: N/A — sem UI +Appetite: ~1 dia eng; uma tool nova read-only + testes; sem migration/collection/Consent + +## Intenção + +O coordenador/assessor pergunta "Quais devem ser minhas prioridades neste momento?" e espera uma resposta **acionável**, não uma tabela de dados. O Sollinha deve elencar os municípios do escopo do usuário que merecem atenção agora, com o **porquê de cada um** — tamanho/potencial, atualização recente desfavorável ou muito tempo sem atualização — e links para agir. É a capacidade irmã de B185 (lideranças pendentes): lá o "o quê falta"; aqui o "o que atacar primeiro". + +## Persona e fluxo + +- **Persona / contexto:** coordenador em `/campanha`, no chat, começando o dia ou o giro; assessor querendo saber o que no próprio escopo está pegando fogo. +- **Job principal:** em uma pergunta, receber um ranking curto de municípios priorizados, cada um com uma linha de evidência ("sem atualização há 40 dias", "atualização negativa ontem", "potencial alto e nível de engajamento baixo"). +- **Fluxo desejado:** + 1. "Quais devem ser minhas prioridades neste momento?" → o Sollinha varre o escopo do usuário (coordenador: estado, filtrável por região; assessor: só os municípios que administra), ordena por gravidade combinada e responde top N com motivo explícito por item + links (B162). + 2. O usuário pede um recorte ("e em Salvador?", "só as que estão sem atualização") e refina a mesma leitura. + 3. Liderança nunca vê nada: resposta de acesso negado, fail-closed. +- **Anti-goals de produto:** não virar painel/estatística no chat; não exibir % estadual absoluto; não incluir estimativas escondidas de liderança (assimetria de votos permanece). + +## Objetivo e aceite + +- O Sollinha responde "quais devem ser minhas prioridades neste momento?" com **top N municípios do escopo do usuário**, cada item com **uma linha de evidência** (o fator que o colocou ali: potencial, sinal desfavorável recente, estagnação). +- O critério de ordenação é declarado na resposta e o usuário pode reordenar/filtrar por um fator (ex.: "só os sem atualização"). +- Um município com atualização recente **favorável** e sem sinais negativos não aparece entre as prioridades. +- Assessor vê apenas o próprio escopo; leader recebe acesso negado (fail-closed, sem dados). +- Links de navegação para o detalhe de cada município priorizado (precedente B162). + +## Dados (intenção) + +- **Vou apresentar dados?** Sim — ranking com justificativa, como superfície deste item (no chat). +- **Decisões desbloqueadas:** coordenador decide onde investir a próxima ação de campo; assessor decide o que tratar primeiro no seu escopo. +- **Forma:** *adiada ao plano de implementação*. Restrições de produto: leitura relativa/local ao escopo do usuário (nunca % estadual absoluto); lista ranqueada com motivo, não score nu; "uma classe nunca vem sem o porquê" (mesma regra do mapa). + +## Direção no codebase (hipótese) + +- **Áreas prováveis:** nova tool em `src/utilities/ai/tools/` (família de `getMunicipalityOverview`/`getDobradinhas`), registrada no `index.ts`; fontes: `municipality` (engagementLevel, priority, expectedVotes, politicalTrend, region), `municipalityUpdate` (polarity/urgent/adversarySignal/createdAt), `votePledge` (recência de compromissos); reuso de `buildCampaignLinks`. +- **Precedente a olhar:** `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185 — define a semântica de "atualização recente"); `docs/plans/sollinha-tools-eleitorais-leader-lockdown.md` (B180 — gate fail-closed por papel); `src/lib/engagementLevel.ts` (níveis N0–N4, regras puras). +- **Risco de acoplamento:** tools eleitorais/staff são staff-only (B180); a assimetria votos declarados × estimados permanece (liderança nunca vê estimativas); não inventar um score de prioridade global persistido — o ranking é derivado na hora. + +## Dependências + +- Soft: B185 (semântica compartilhada de "atualização recente"/pendência — reusar, não redefinir). + +## Fora de escopo + +- Persistir prioridades/ranking no banco (é derivado, não dado). +- Notificações/alertas proativos fora do chat. +- Priorização de outros objetos (lideranças, atividades, demandas) num primeiro corte — município é a unidade operacional. +- Dashboard visual/mapa. + +## Rabbit holes de produto + +- **Score mágico.** Se alguém "só completar", nasce um índice composto sem explicação. **Corte:** ranking por gravidade combinada simples (sinal desfavorável > estagnação > potencial) com o motivo sempre explícito; sem número de score na resposta. +- **"Prioridade" como campo.** Já existe `municipality.priority` (alta/normal) — o ranking **deriva** daí e de sinais, não duplica o campo. **Corte:** prioridade marcada entra como um dos fatores, não como o ranking inteiro. +- **Estimativas vazando para liderança.** `expectedVotes` é staff-only. **Corte:** gate por papel igual ao B180; leader fail-closed. + +## Questões em aberto (produto) + +- **Formato do ranking:** lista única ordenada ou grupos por motivo ("sem atualização 30+ dias", "sinal negativo recente", "potencial alto")? **Recomendação:** lista única ordenada por gravidade combinada, cada item com o motivo; o usuário filtra por motivo depois. _(assumido — validar)_ +- **"Sem atualização" mede o quê?** **Opções:** (a) data da última `municipalityUpdate`; (b) `updatedAt` do município (qualquer edição); (c) compromisso de votos mais recente. **Recomendação:** (a) — **já definido como semântica compartilhada em B185** (fonte da família: "atualização recente" = última `municipalityUpdate`; sem nenhuma = "nunca atualizado", estagnação máxima); B186 reusa, não redefine. _(assumido — validar)_ +- **Potencial = o quê?** **Opções:** (A) `expectedVotes` (estimativa 2026, staff-only) com fallback nos válidos de 2022; (B) só 2022; (C) votos do candidato em 2022. **Recomendação:** (A), rotulando a fonte na evidência ("potencial alto: estimativa central X"). _(assumido — validar)_ + +## Referências + +- `src/utilities/ai/tools/getMunicipalityOverview.ts` — campos e contagens por município +- `src/utilities/ai/tools/buildCampaignLinks.ts` — links na resposta (B162) +- `src/collections/MunicipalityUpdate.ts` — sinais (polarity/urgent/adversarySignal) +- `docs/plans/sollinha-liderancas-pendentes-abordagem.md` (B185) — semântica de pendência/atualização +- `docs/plans/ai-chat-sollinha.md` — arquitetura do chat (imutável, `in-prod`) From 7576a3489e6c429c6cca9cd6934916c58ea1de15 Mon Sep 17 00:00:00 2001 From: Francisco Solla Date: Sun, 9 Aug 2026 23:37:04 -0300 Subject: [PATCH 2/2] =?UTF-8?q?docs(B185,B186,B189,B190):=20formata=C3=A7?= =?UTF-8?q?=C3=A3o=20prettier=20dos=20planos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/plans/sollinha-liderancas-pendentes-abordagem.md | 2 +- docs/plans/sollinha-municipios-sem-atualizacao-recente.md | 2 +- docs/plans/sollinha-municipios-sem-dobradinha.md | 2 +- docs/plans/sollinha-prioridades-do-momento.md | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/plans/sollinha-liderancas-pendentes-abordagem.md b/docs/plans/sollinha-liderancas-pendentes-abordagem.md index 818ddd76..fb6a9472 100644 --- a/docs/plans/sollinha-liderancas-pendentes-abordagem.md +++ b/docs/plans/sollinha-liderancas-pendentes-abordagem.md @@ -39,7 +39,7 @@ O Sollinha hoje responde perguntas de leitura **por entidade** ("quais lideranç - **Vou apresentar dados?** Sim — listas de lideranças com recência/status, como superfície deste item (no chat). - **Decisões desbloqueadas:** coordenador decide em qual território atacar a seguir; assessor decide quais lideranças do seu escopo abordar primeiro; coordenador direciona assessores pelas listas agrupadas por responsável. -- **Forma:** *adiada ao plano de implementação*. Restrições de produto: leitura relativa/local (território ou escopo do usuário), nunca % estadual; lista, não mapa; o critério de pendência **sempre** visível na resposta. +- **Forma:** _adiada ao plano de implementação_. Restrições de produto: leitura relativa/local (território ou escopo do usuário), nunca % estadual; lista, não mapa; o critério de pendência **sempre** visível na resposta. ## Direção no codebase (hipótese) diff --git a/docs/plans/sollinha-municipios-sem-atualizacao-recente.md b/docs/plans/sollinha-municipios-sem-atualizacao-recente.md index 14e49e66..61135388 100644 --- a/docs/plans/sollinha-municipios-sem-atualizacao-recente.md +++ b/docs/plans/sollinha-municipios-sem-atualizacao-recente.md @@ -37,7 +37,7 @@ O registro de acompanhamento é tão importante quanto a ação em campo: um mun - **Vou apresentar dados?** Sim — lista de cobertura com recência, como superfície deste item (no chat). - **Decisões desbloqueadas:** coordenador decide cobrar o assessor do município/região; assessor decide reativar o registro de um município esquecido. -- **Forma:** *adiada ao plano de implementação*. Restrições de produto: lista exaustiva com contagem (não top N — cobertura é "tudo além do limiar"), ordenação pelo mais velho primeiro; limiar e definição sempre visíveis na resposta. +- **Forma:** _adiada ao plano de implementação_. Restrições de produto: lista exaustiva com contagem (não top N — cobertura é "tudo além do limiar"), ordenação pelo mais velho primeiro; limiar e definição sempre visíveis na resposta. ## Direção no codebase (hipótese) diff --git a/docs/plans/sollinha-municipios-sem-dobradinha.md b/docs/plans/sollinha-municipios-sem-dobradinha.md index c038884a..4d35ee1a 100644 --- a/docs/plans/sollinha-municipios-sem-dobradinha.md +++ b/docs/plans/sollinha-municipios-sem-dobradinha.md @@ -36,7 +36,7 @@ O pedido original de gestão cobre três domínios — municípios, lideranças - **Vou apresentar dados?** Sim — lista de cobertura de parcerias, como superfície deste item (no chat). - **Decisões desbloqueadas:** coordenador decide onde buscar/formalizar dobradinha; assessor identifica parceria faltando no seu território. -- **Forma:** *adiada ao plano de implementação*. Restrições de produto: lista exaustiva com contagem (cobertura, não top N); leitura relativa ao escopo do usuário; sem % estadual. +- **Forma:** _adiada ao plano de implementação_. Restrições de produto: lista exaustiva com contagem (cobertura, não top N); leitura relativa ao escopo do usuário; sem % estadual. ## Direção no codebase (hipótese) diff --git a/docs/plans/sollinha-prioridades-do-momento.md b/docs/plans/sollinha-prioridades-do-momento.md index 0df047f6..3b4bcc25 100644 --- a/docs/plans/sollinha-prioridades-do-momento.md +++ b/docs/plans/sollinha-prioridades-do-momento.md @@ -36,7 +36,7 @@ O coordenador/assessor pergunta "Quais devem ser minhas prioridades neste moment - **Vou apresentar dados?** Sim — ranking com justificativa, como superfície deste item (no chat). - **Decisões desbloqueadas:** coordenador decide onde investir a próxima ação de campo; assessor decide o que tratar primeiro no seu escopo. -- **Forma:** *adiada ao plano de implementação*. Restrições de produto: leitura relativa/local ao escopo do usuário (nunca % estadual absoluto); lista ranqueada com motivo, não score nu; "uma classe nunca vem sem o porquê" (mesma regra do mapa). +- **Forma:** _adiada ao plano de implementação_. Restrições de produto: leitura relativa/local ao escopo do usuário (nunca % estadual absoluto); lista ranqueada com motivo, não score nu; "uma classe nunca vem sem o porquê" (mesma regra do mapa). ## Direção no codebase (hipótese)