Artefato oficial por unidade (município e ZE de Salvador): TSE 2026 presidente por seção + Solla 2026 e a fatia de 2022 da análise 2º turno; cada número validado contra o baseline TSE e os totais da BA pinados (pnpm build:second-round). O relatório ganha a seção "2º turno no município (Lula × Flávio)" com X₁/X₂ pela mesma fórmula do /potencial, o papel determinístico da cidade (prioridade/mobilizacao/defesa/expansao) e o novo item de pesquisa segundo_turno (movimento local com fonte); a seção fica fora da página 1 porque é determinística e a página 1 falha fechada por medição.
30 KiB
| name | description |
|---|---|
| relatorio-cidade | Gera o relatório de cidade pré-viagem (PDF A4 + .md) a partir da base Teqo read-only + pesquisa web datada + emendas oficiais; aceita um município ou um lote separado por vírgula. |
Relatório de cidade pré-viagem (C163)
Entrega um PDF A4 datado + companion .md por município, cruzando a base de
produção do Teqo (read-only, snapshot do momento) com pesquisa web (data +
URL por item) e emendas lidas da fonte oficial em tempo de geração. Página 1
= resumo de uma olhada; páginas 2+ = aprofundamento; tudo sem fonte vira
lacuna explícita — nunca inferência.
Quando usar
- O candidato/CG pede "o relatório de " antes de uma viagem — ou de
várias numa invocação (
/relatorio-cidade Ilheus, Itacare, Una). - Quem executa são o orquestrador (agente principal) + um sub-agente
researcher por cidade + os dois scripts (
extractno homeserver,buildlocal). O PDF sai para leitura de bolso; o detalhe das etapas está em "Pipeline (etapas)".
Missão do 2º turno (Lula × Flávio — 25/10/2026)
A partir de 08/10/2026 o relatório não é só pré-viagem de mandato: cada visita também serve à eleição de Lula no 2º turno. O produto carrega o recorte oficial do 1º turno de 2026 na seção "2º turno no município (Lula × Flávio)" (nº 2, logo depois da conta eleitoral), com:
- Números oficiais por unidade (município ou ZE de Salvador, na mesma
geografia do catálogo): Lula 2022→2026, adversário (Bolsonaro→Flávio), Solla
1313, comparecimento, abstenção, brancos+nulos e o cenário X₁/X₂ do
/potencial— a MESMA fórmula da página pública, nunca uma segunda conta. Fonte: artefato commitadosrc/lib/electionAggregates/bahia-second-round-2026.json(loadersrc/lib/bahiaSecondRound.ts), gerado porpnpm build:second-rounda partir do TSE 2026 por seção + da análise 2º turno (analise-eleicoes-2026/nota-solla-2turno), com os totais da BA pinados. - Papel da cidade (determinístico,
src/lib/secondRoundRole.ts):prioridade(Lula caiu, Solla cresceu com base ≥200 votos — agenda conjunta e contato pessoal),mobilizacao(mesmo quadro, base menor),defesa(os dois caíram — comparecimento e contenção, sem vitrine),expansao(Lula não caiu — presença). - Movimento local (pesquisa): o researcher preenche o item de checklist
segundo_turno(atos, palanques, posições de lideranças locais, o que o 2º turno move na cidade), sempre comsourceUrl+sourceDate; sem fonte vira lacuna explícita.
Doutrina (de PERSONA.md e da nota): interior = conversão (presença e
proteção); Salvador = comparecimento (mobilização); persuasão em eleição geral
tem efeito médio ~0 — o papel de Solla é mobilizar, proteger e dar voz, não
converter. X₁/X₂ são votos em jogo, não transferência garantida; o
cenário de 2º turno é hipotético e a seção diz isso. Operação: redutos/vitrine
(onde Solla é a marca), cinturão do PT local, ZE de Salvador 19/15/9/12
(mobilização) e contenção em 16/4.
Regras duras da seção:
- os números oficiais (Lula, Flávio, Solla, X₁/X₂) vêm do artefato — o researcher não os pesquisa; ele pesquisa só o movimento local.
- não infira comportamento individual (falácia ecológica) nem prometa conversão; a seção informa onde mobilizar/proteger, com a incerteza declarada.
- a seção não entra na página 1: a página 1 é medida e falha fechada, e conteúdo determinístico não encolhe no fallback de texto; o dado de 2º turno vive na seção própria.
Lote (várias cidades)
A invocação aceita um município (caso de sempre) ou vários, separados por vírgula — a lista é o argumento, não um novo comando:
/relatorio-cidade Feira de Santana # N=1 — caminho atual, sem regressão
/relatorio-cidade Ilheus, Itacare, Una # lote
Parsing (orquestrador, antes de qualquer pesquisa):
- separa por
,; aplicatrim; descarta vazio; - cada token é aceito como slug canônico (
isMunicipalitySlug) ou como nome (fold acento-insensívelresolveMunicipalityName;null= nome desconhecido → token inválido) →municipalityCatalogEntriesForCity; - 0 entradas = token inválido; >1 entrada = ambíguo (ex.:
Salvadorresolve para 19 zonassalvador-ze-N— peça o slug da zona explícito); - dedupe após a resolução, preservando a ordem — duas grafias do mesmo
município (
Ilheus, ilheus) geram um artefato, não dois; - token inválido/ambíguo vira falha isolada com o motivo no summary final — nunca aborta o lote nem inventa slug.
Uma cidade = um par PDF+MD (<slug>-<YYYY-MM-DD>); sem índice/PDF agregado.
Pipeline (etapas)
- Orquestrador (agente principal). Parseia a lista (seção "Lote"), resolve os
slugs no catálogo (
src/lib/municipalityCatalog.ts, 435 unidades; Salvador =salvador-ze-N) e dispara um researcher por cidade em paralelo (Task). Não invente slug: confirme no catálogo (pnpm exec tsx -eou a lista em/campanha/municipios). Nunca retém pesquisa web nem o corpo de umresearch.json— só os recibos. - Researcher (sub-agente, ×N, em paralelo).
.opencode/agent/relatorio-cidade.md— o único passo pesado de contexto. Faz a pesquisa web datada e escrevedata/relatorios-cidade/<slug>.research.json: cada item do checklist comsourceUrl+sourceDate; sem fonte, não escreva o item (ele vira lacuna). Notícias: janela ≤90 dias. Devolve apenas o recibo curto (seção "Recibo do researcher") — nunca o corpo. Não roda ssh nem build. - Extração read-only no homeserver — serializada. Um município por vez: o
checkout
~/teqo-reporté compartilhado e ssh concorrente colide. Receita completa no runbookdocs/ops/teqo-1313-deploy.md§"C163 — relatório de cidade":
Depoisssh homeserver source ~/.nvm/nvm.sh cd ~/teqo-report && git fetch origin main && git checkout main && git pull --ff-only [ -d node_modules/tsx ] || pnpm install --prod=false set -a; source ~/stack/teqo-1313.env; set +a export DATABASE_URL="${DATABASE_URL/@postgres:5432/@127.0.0.1:5433}" export CITY_REPORT_CONFIRM=1 NODE_OPTIONS="--no-deprecation --import=tsx/esm --import=./scripts/seed-loader.mjs" \ node scripts/extract-city-report-snapshot.mjs \ --municipality=<slug> --out=data/relatorios-cidade/<slug>.snapshot.jsonscp homeserver:~/teqo-report/data/relatorios-cidade/<slug>.snapshot.json data/relatorios-cidade/. Criedata/relatorios-cidade/logs/e redirecione a saída paralogs/<slug>.extract.log 2>&1; retenha só o status + o caminho — o log não entra no contexto do orquestrador. O~/teqo-reporté checkout de scratch — não use~/teqo-deploy(deploy em andamento no mesmo host). O SHA do extrator e do builder devem ser o mesmo. - Render local (por cidade):
Saídas:NODE_OPTIONS="--no-deprecation --import=tsx/esm" node scripts/build-city-report.mjs \ --snapshot=data/relatorios-cidade/<slug>.snapshot.json \ --research=data/relatorios-cidade/<slug>.research.json \ --out-dir=docs/research/relatorios-cidadedocs/research/relatorios-cidade/<slug>-<YYYY-MM-DD>.pdf+.md(companion revisável em diff). Intermediários gitignored emdata/relatorios-cidade/(HTML, JSONs de emendas,logs/). Também redirecione a saída do builder paralogs/<slug>.build.log. Flags úteis:--emendas=<json>(replay sem rede),--author=<nome>(defaultJORGE SOLLA),--generated-at=<ISO>(reprodutibilidade),CITY_REPORT_STRICT=1(falha o run se houver lacuna de pesquisa — para conferência, não para entrega). - Summary final. Uma linha por entrada, na ordem da lista:
entrada · status (ok|failed) · PDF/MD (quando ok) · motivo (quando falha)—entradaé o slug resolvido ou, quando o token falha antes de resolver, o próprio token cru. Falha de uma cidade não cancela as demais (sucesso parcial explícito); a cidade que falha é pulada nas etapas seguintes.
Recibo do researcher
O sub-agente por cidade devolve só este recibo curto (≤ ~15 linhas) — nunca
o corpo do research.json (items[].answer/details, news[].summary,
approach, leaders, …):
{
"slug": "ilheus",
"status": "ok", // ou "failed"
"researchPath": "data/relatorios-cidade/ilheus.research.json",
"researchedAt": "2026-09-16T10:00:00.000Z",
"itemCount": 6, // itens do checklist com fonte
"gapCount": 3, // == gaps.length (checklist + blocos de pesquisa)
"gaps": ["prefeito", "vice", "disputa_local"],
"newsCount90d": 4, // notícias na janela ≤90 dias
"weakSourceCount": 1, // item cujo único apoio é fonte de região/polo
"failureReason": "…" // opcional (só quando status = failed)
}
itemCount + gapCount cobrem o checklist fixo; gapCount conta também os
blocos de pesquisa sem fonte. status: "failed" carrega o motivo e não escreve
corpo parcial. O orquestrador agrega os recibos no summary final — é o único dado
de pesquisa que cruza para ele.
Contrato dos JSONs
research.json:
{
"municipalitySlug": "feira-de-santana", // tem de bater com o snapshot
"researchedAt": "2026-09-15T10:00:00.000Z", // obrigatório
"items": [
{
"id": "prefeito", // id do checklist (abaixo)
"answer": "Nome (Partido), situação",
"summary": "opcional — versão curta, completa e auto-contida do answer, redigida para caber na página 1 (C188)",
"details": "opcional",
"sourceUrl": "https://…", // obrigatório em item publicado
"sourceDate": "2026-09-10", // obrigatório
"extraSources": [ // opcional; cada uma exige url+date
{ "label": "Polo regional", "url": "https://…", "date": "2026-09-01" }
],
"consultedAt": "2026-09-15T09:00:00.000Z" // opcional
}
],
"news": [
{
"title": "…", "outlet": "…",
"publishedAt": "2026-09-01T00:00:00.000Z", // janela ≤90 dias do researchedAt
"url": "https://…", "summary": "…"
}
],
"approach": [ // sugestões das personas; tema+texto+fonte
{
"persona": "Ciência política", // ou "Coordenação de campanha"
"topic": "Saúde regional (polo)",
"suggestion": "…",
"sourceUrl": "https://…", "sourceDate": "2026-04-07"
}
],
"preCandidates": [ // prováveis candidatos do campo do prefeito em 2026
{
"name": "…", "office": "Deputado federal", "party": "PSDB",
"support": "Campo do prefeito …",
"sourceUrl": "https://…", "sourceDate": "2026-04-18"
}
],
"leaders": [ // últimos prefeitos/vices, vereadores mais votados
{
"name": "…", "role": "Ex-prefeito", "party": "PSDB", "period": "2017–2024",
"sourceUrl": "https://…", "sourceDate": "2024-08-14"
}
],
"leaderAgenda": [ // pauta provável por liderança (rede + mesmo campo)
{
"name": "…",
"field": "Aliado (prefeito) | Aliado (federação PT/PCdoB) | Rede da campanha (base) | …",
"topics": "Saúde (hospital municipal)", // pauta provável (hipótese)
"hook": "Gancho recente publicado (o que a fonte diz)", // topics OU hook obrigatório
"sourceUrl": "https://…", "sourceDate": "2026-08-31"
}
],
"demography": [ // cor/raça e poder aquisitivo (Censo 2022)
{
"topic": "Cor/raça (Censo 2022)", "detail": "Parda 73,1% …",
"sourceUrl": "https://sidra.ibge.gov.br/tabela/9605", "sourceDate": "2026-09-15"
}
],
"economy": [ // PIB, emprego, atividades (IBGE/RAIS e imprensa)
{ "topic": "PIB e perfil", "detail": "…", "sourceUrl": "https://…", "sourceDate": "…" }
],
"transport": [ // rodovias, aeroporto, portos, ferrovia
{ "topic": "Rodovia federal (BR-101)", "detail": "…", "sourceUrl": "https://…", "sourceDate": "…" }
],
"opposition": [ // o que a oposição fez de errado na região (fato com fonte)
{ "topic": "Emenda sob investigação (PF)", "detail": "…", "sourceUrl": "https://…", "sourceDate": "…" }
],
"alliances": [ // dobradinhas: com quem somar na cidade
{ "topic": "Dobradinha estadual (PT)", "detail": "…", "sourceUrl": "https://…", "sourceDate": "…" }
],
"investments": [ // investimentos/obras de Solla, do estado e do federal (município e região)
{ "topic": "Obra federal — saúde", "detail": "…", "sourceUrl": "https://…", "sourceDate": "…" }
],
"emendasIndicators": [ // indícios de emenda, um por autor (página 1; esfera explícita)
{
"author": "Zé Neto", // obrigatório
"sphere": "municipio", // obrigatório: municipio | regiao | polo
"value": "R$ 1 mi", // opcional (como a fonte informa)
"purpose": "Ambulância do TFD", // opcional (o que beneficia)
"year": "2026", // opcional
"sourceUrl": "https://…", "sourceDate": "2026-08-24"
}
],
"gaps": [{ "id": "…", "label": "…", "reason": "…" }] // lacunas que o agente já sabe
}
Checklist (ids): prefeito, vice, relacao_campo, vereadores,
disputa_local, segundo_turno, quem_investe, noticias, imprensa_local,
emendas_web.
Item sem sourceUrl/sourceDate é convertido em lacuna pelo validador; item
ausente também. approach, preCandidates, leaders, leaderAgenda e
emendasIndicators são
listas de pesquisa: cada entrada sem fonte vira lacuna (abordagem_sem_fonte,
precandidato_sem_fonte, lideranca_sem_fonte, agenda_lideranca_sem_fonte,
emenda_indicio_sem_fonte; emendasIndicators ainda exige author e sphere
— sem eles, emenda_indicio_incompleto) e
não entra no PDF. Campos de texto livre (detail, hook, suggestion,
support, answer) aceitam os tokens de fonte inline {{fonte}} /
{{fonte:N}}, resolvidos para (fonte) no ponto exato da citação (ver "Links
clicáveis / fonte inline" adiante).
segundo_turno — movimento local da disputa Lula × Flávio: atos, palanques,
posições de lideranças locais e o que o 2º turno move na cidade, com URL+data.
Os números oficiais do bloco 2º turno (Lula/Flávio/Solla, X₁/X₂) vêm do
artefato (pnpm build:second-round) — não os pesquise nem os estime; a
pesquisa é só o movimento local. Sem fonte, vira lacuna explícita na seção.
leaderAgenda — pauta provável das lideranças (rede + mesmo campo): para
cada liderança que importa na visita, registre o que ela tende a priorizar
(Saúde, Educação, Infraestrutura…) e o gancho recente publicado que ancora a
leitura, sempre com URL+data. name é obrigatório; exige topics ou hook;
sem fonte, não entra. Priorize (a) a rede da campanha (as lideranças na base
Teqo, com supportStatus) e (b) o mesmo campo de Solla — é onde há margem
de conversa. É hipótese a validar na conversa, não declaração do líder: o
bloco "Pauta das lideranças (pesquisa)" (seção 3) sai com essa ressalva. Liderança
de rede sem rastro público não vira item: fica como tarefa de ativação
(cadastrar/atualizar supportStatus), não como pauta inventada.
emendas_web — indícios de emenda (município, região ou polo): quando a
fonte oficial não atribui emenda ao município, pesquise artigos, falas e
indicações de emenda para (a) o município, (b) a região/Território de
Identidade e (c) a maior cidade próxima (polo regional — cidades pequenas usam
serviços do polo). Registre a síntese em emendas_web (answer/details +
extraSources) e, principalmente, cada indício como uma linha de
emendasIndicators — author, sphere (municipio/regiao/polo), valor,
finalidade, ano e fonte+data. É essa lista estruturada que a página 1 imprime
(autor — o que beneficia · valor · esfera, com link); o texto corrido só entra
quando não há lista (fallback). Sem uma das linhas com fonte, o indício vira
lacuna (emenda_indicio_sem_fonte) — nunca some em silêncio. O PDF mostra o
bloco como "Emendas — indícios web" na página 1 e as URLs na seção de fontes.
Nunca somar indício de região/polo como emenda da cidade.
Demografia, economia e transporte (pesquisa): demography, economy e
transport são listas de {topic, detail, sourceUrl, sourceDate} (sem fonte →
lacuna). Pesquise: cor/raça e rendimento (Censo 2022 — SIDRA 9605 e 10295;
IBGE Cidades para salário médio); economia (PIB e PIB per capita — IBGE
Cidades; composição setorial e emprego formal — RAIS/CAGED e perfis regionais;
café, pecuária e comércio no caso do Extremo Sul); transporte (rodovias
federais/estaduais que cortam o município e obras novas/reformadas — DNIT,
Seinfra; aeroporto mais próximo em operação e o do polo, com situação; portos e
ferrovia da região — FIOL/Porto Sul, deixando claro quando não serve
diretamente a cidade). O enquadramento é "onde falta × onde o estado e a União
têm acertado" — o candidato é da base.
Frente de oposição (pesquisa): opposition é uma lista de
{topic, detail, sourceUrl, sourceDate} (sem fonte → lacuna) com fatos
publicados e datados sobre o que a oposição fez de errado na região — PF/MP em
emendas, obras paradas, anúncio sem entrega, escândalos do campo adversário.
Regras: (1) cada linha é fato publicado, nunca inferência de culpa — atribua
à fonte ("segundo a PF / o jornal") e diferencie acusação de condenação; (2)
priorize o local (operadores da oposição no município/região, ex.: emendas
investigadas) sobre o ataque frontal ao líder estadual; (3) marque temas de
mão dupla (casos que também citam o campo da base) para a coordenação não
entrar desprevenida; (4) não recomende ataque pessoal nem onde a marca do
adversário domina — o relatório é insumo ancorado, não roteiro de ataque. O item
entra na seção 2 (junto aos concorrentes) e a seção 14 lista as URLs.
Dobradinhas e investimentos (pesquisa): alliances é a lista de
{topic, detail, sourceUrl, sourceDate} com com quem Solla soma na cidade
(estadual/federal, chapa e lideranças que transferem voto) — insumo direto da
approach, exibido como bloco "Dobradinhas (pesquisa)" na seção 3 (rede).
investments lista investimentos e obras de Solla, do governo do estado e do
federal no município e na região, com fase/valor quando a fonte informar
(empenho ≠ pagamento), exibido como bloco "Investimentos e obras (pesquisa)"
na seção 4 (conjuntura). Sem fonte, cada item vira lacuna; nenhuma das duas
listas é obrigatória.
Emendas oficiais: sem --emendas, o builder consulta o Portal da
Transparência (PORTAL_TRANSPARENCIA_API_KEY no ambiente; sem chave → lacuna).
A API oficial não filtra por município (só UF/Nacional/Múltiplo na
localidadeDoGasto); o builder casa pela localidade com o nome do município e,
sem linha atribuível, degrada para lacuna (Issue #1025). O resultado fica
cacheado em data/relatorios-cidade/<base>.emendas.json para replay.
Conteúdo do relatório
- Página 1 (uma olhada): identificação/prioridade/classe/nível, conta
eleitoral 2022 (votos, % do próprio voto, rank/435) + expectativa de votos
(cenário central com pessimista/otimista) — a meta de cadeira e a cobertura
de pledges saíram da página 1; quem é quem (prefeito, vice, relação com o
campo, lideranças, dobradinhas, vereadores), o que Solla entregou (emendas
oficiais com fase, acervo de falas, notícias ≤90 dias), emendas — indícios
web quando a fonte oficial não atribui ao município (uma linha por autor:
o que beneficia · valor · esfera explícita
município/região/polo, com link), O que anunciar agora (a relação local com fonte + os compromissos que a campanha cumpre) com a advertência de defeso, riscos e pontos sem leitura. O painel fixo "O que NÃO anunciar" saiu da página 1 (copy invariante; o guardrail de defeso permanece na nota do painel de anúncio). Os textos livres da pesquisa nunca são cortados no meio (C188): a página 1 imprime osummarydo item quando existe; sem ele, imprime oanswerintegral e, se a página não couber, o builder re-renderiza apontando o aprofundamento — sem "…". Por isso, escrevasummarycurto e auto-contido para os itens que aparecem na página 1 (prefeito,vice,relacao_campo,vereadores,disputa_local,quem_investe,emendas_web): é a única forma de a página 1 manter o texto que você redigiu. As listas mostrame mais N; o texto integral de cada item fica no aprofundamento (ver "respostas integrais" em "Fontes e limites"). - Seções 2+:
1. Conta eleitoral·2. 2º turno no município (Lula × Flávio)— números oficiais do artefato (Lula/adversário/Solla, comparecimento, abstenção, brancos+nulos e X₁/X₂), o papel da cidade (prioridade/mobilizacao/defesa/expansao) e o movimento local pesquisado (segundo_turno), com o cenário rotulado como hipotético ·3. Concorrentes no município (federal e estadual)— top 5 por votos de 2022 na base TSE, com série 2014/2018/2022 e os prováveis candidatos do campo do prefeito (pesquisa), além da frente de oposição quando pesquisada (fatos com fonte) ·4. Rede e lideranças— inclui as lideranças locais pesquisadas (ex-prefeitos/vices, vereadores mais votados, com partido) e a Pauta das lideranças (pesquisa): pauta provável + gancho recente por liderança da rede/mesmo campo, marcada como hipótese ·5. Conjuntura·6. Sinais·7. Demandas e visitas·8. Demografia— IBGE Censo 2022 do artefato + complemento pesquisado (cor/raça e poder aquisitivo) ·9. Atividade econômica (pesquisa)·10. Transporte e conexões (pesquisa)·11. Acervo de falas— cada fala com "O que é" (sumário oficial) e "Menção ao município" (passagem que cita a cidade ou, se o nome não aparece nos trechos, a marcação do acervo com o nº de municípios), mais Vídeo (YouTube no trecho da fala —youtubeExcerptStartSeconds= época do trecho (excerptTMs) − início da sessão, calibrado contra o vídeo real: o da sessão de 13/03/2018 começa ~37s depois doeventStartAt, então 645s viramt=608s(YOUTUBE_VIDEO_OFFSET_SECONDS); ospeechAtda API é o horário do slot, não o início real; fallback para o VOD da Câmara, que é o clipe do próprio trecho) e Transcrição (PDF do Diário) quando existirem ·12. Notícias e imprensa·13. Panorama regional·14. Abordagem sugerida (personas)·15. Fontes e limites— lista as fontes e, antes delas, a tabela "Pesquisa — respostas integrais" com o texto completo de cada item do checklist (o que a página 1 capou). - Acervo por região e tema: como cidade pequena quase nunca tem fala
própria, a seção 10 mostra três recortes não sobrepostos: falas do
município (quando existem), menções à região (demais municípios do mesmo
Território de Identidade) e falas por tema regional (temas-chave do
interior). Região/polo nunca é somado como fala da cidade — o bloco diz
isso explicitamente. Fonte:
speeches.regionespeeches.topicsdo snapshot (extraídos read-only no homeserver; ausentes nos snapshots antigos → seção degrada para a busca por município sem quebrar). Cada recorte diz por que a fala entrou: a tabela do município mostra "Menção a "; a da região mostra "Município da região citado"; a de tema mostra a coluna "Tema" — porque essas falas foram selecionadas por tema, não por menção ao município/região (não rotular como "marcada com o município" o que só foi escolhido por tema). - Links clicáveis / fonte inline: todo URL no PDF é um link (
<a href>): células de tabela, fontes por linha e a seção de fontes. Nos textos de pesquisa (oposição, dobradinhas, investimentos, pauta das lideranças, abordagem, demografia…), o(fonte)com hyperlink entra inline no próprio texto, logo após o trecho que cita o fato verificado — não numa coluna. Um mesmo texto pode citar mais de uma fonte: use{{fonte}}para a fonte principal (sourceUrl) e{{fonte:2}},{{fonte:3}}… para cada entrada deextraSources; o renderer resolve os tokens em(fonte)/(fonte N)e o companion.mdvira[(fonte)](url). Se o texto não tiver token, o builder acrescenta a(s) fonte(s) no fim do trecho. A coluna Fonte fica só com a data e o link completo continua listado abaixo da tabela (> Fontes:). - Numeração dinâmica e omissão de seções vazias: o builder numera as seções
depois de montá-las (o
idé estável; o número é posicional). As seções Sinais recentes e Demandas e visitas são omitidas quando não há dado na base (mostrar um callout de "nada aqui" só gastaria página); as seções de pesquisa (opposition,alliances,investments, demografia, economia, transporte) mantêm a lacuna explícita quando o item falta. Nunca referencie seção por número fixo — use o nome/id. - Tabela de notícias com larguras fixas:
Data 9 · Veículo 14 · Título 49 · Link 28(%). As larguras explícitas tornam a tabelatable-fixed(a URL longa/crua quebra em vez de inflar a coluna) e dão ao Título — o texto mais longo — a maior fatia. Sem largura, a URL sem espaços domina o layout automático e espreme/estoura a tabela.
Guardrails de produto (não negociáveis)
- Sem fonte, não publica: afirmação não trivial sem data+URL não entra.
- Empenho ≠ pagamento (defeso/ano eleitoral): o bloco de emendas mostra a fase (empenhada, liquidada, paga, restos) e a advertência de defeso acompanha o painel "O que anunciar agora".
- Leitura relativa: % do próprio voto, rank, LQ — nunca % estadual absoluto.
- PII mínima: contatos completos (telefone/e-mail) nunca entram; nomes de lideranças entram porque o produto pede "quem é quem".
- Completo para o candidato: estimativas e nível N0–N4 entram no PDF, sem marca de restrição; o que não sai é para o palanque.
- Indício regional não é emenda da cidade: emendas do município, da região ou do polo entram rotuladas na evidência web e nunca somadas; a fonte oficial ausente continua lacuna explícita.
- Abordagem é análise ancorada: as sugestões das personas (governo do PT na
região, projetos futuros, prioridades locais) saem do
approachcom fonte por item — sem fonte, não entra; o PDF deixa claro que é análise, não fato novo. - Frente de oposição é fato, não ataque:
oppositionsó recebe fato publicado com URL + data; o PDF não afirma culpa, não infere dolo e não orienta ataque pessoal. Prioriza o local (operadores na região) sobre o líder estadual e sinaliza temas de mão dupla; onde a marca do adversário domina, o insumo é para contraste, não para confronto frontal. - 2º turno é cenário, não promessa: X₁/X₂ são votos em jogo (fórmula do
/potencial), nunca transferência garantida; a seção é determinística (TSE oficial no artefato) — o researcher pesquisa só o movimento local e o PDF declara a incerteza. Não se infere comportamento individual (falácia ecológica) nem se propõe persuasão onde a evidência diz efeito ~0: o papel de Solla é mobilizar, proteger e dar voz. - Falha isolada no lote: cidade que falha (token inválido/ambíguo, pesquisa sem fonte suficiente, snapshot×research mismatch) não cancela as demais; o summary final diz explicitamente o que saiu e o que falhou (sucesso parcial visível).
- Artefato gitignored: o repo é público; o PDF/MD com dado interno nunca é commitado (só a skill/scripts/changelog).
Troubleshooting
- Seção 2º turno saiu como lacuna: a unidade não está no artefato oficial —
rode
pnpm build:second-round -- --analysis-dir=<pasta da análise 2º turno>(o comando valida totais e versões) e verifiquemunicipalityCatalog× artefato (o testetests/unit/bahiaSecondRound.unit.spec.tscobre o catálogo). - Movimento local "Não pesquisado.": o researcher não preencheu o item
segundo_turno; é lacuna de pesquisa, não falha do build — repita a pesquisa com fonte datada. - Página 1 estourou: o builder aborta com
scrollHeight > útile o erro é açãoável. A página 1 já aplica tetos de conteúdo aos textos da pesquisa (PAGE_ONE_*_MAXemscripts/lib/cityReportBlocks.mjs) e preserva o texto integral no aprofundamento; se ainda estourar, aperte o teto da pesquisa ou corte copy do resumo — nunca mexa no layout para "espremer". O bloco 2º turno vive na seção própria (nunca na página 1) justamente porque é determinístico e não encolhe no fallback. tsxausente no homeserver:pnpm install --prod=false.- API de emendas 429/erro: o builder degrada para lacuna com URL+motivo; reexecute depois (o cache só é escrito com resultado utilizável).
- Emenda sem atribuição oficial: é o comportamento esperado (a API não
expõe o município). Pesquise os indícios web (município/região/polo) no item
emendas_web; sem indício, a lacuna fica explícita — nunca zero silencioso. - Chromium: vem do
@playwright/test; se faltar binário,pnpm exec playwright install chromium. - Snapshot de outro município: o builder recusa (
municipalitySlug≠ snapshot) — regenere, nunca edite o JSON à mão para "casar". - Token inválido/ambíguo no lote: vira linha
failedno summary com o motivo;Salvadoré ambíguo (19 zonas) e exige o slug da zona (salvador-ze-N) — nunca expanda nem adivinhe. - Extração ssh em paralelo: não faça — o
~/teqo-reporté compartilhado (git fetch/checkout/pull+pnpm install); serialize a extração por cidade.
Referências
- Intenção:
docs/plans/relatorio-cidade-viagem.md; impl:docs/plans/relatorio-cidade-viagem-impl.md. - Lote + sub-agentes:
docs/plans/ops118-relatorio-cidade-lote-municipios-sub-agentes.mde-impl.md. - Precedente de PDF:
scripts/build-solla-ceuci-salvador-report.mjs(C157). - Runbook:
docs/ops/teqo-1313-deploy.md§C163.