mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
feat(C163): demografia complementar, atividade econômica e transporte no relatório
This commit is contained in:
parent
576fda7f2c
commit
32a1a46fad
6 changed files with 209 additions and 21 deletions
|
|
@ -107,6 +107,18 @@ URL por item) e emendas lidas da fonte oficial **em tempo de geração**. Págin
|
|||
"sourceUrl": "https://…", "sourceDate": "2024-08-14"
|
||||
}
|
||||
],
|
||||
"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": "…" }
|
||||
],
|
||||
"gaps": [{ "id": "…", "label": "…", "reason": "…" }] // lacunas que o agente já sabe
|
||||
}
|
||||
```
|
||||
|
|
@ -127,6 +139,18 @@ mais de uma fonte. O PDF mostra o item como "Emendas — indícios web" na pági
|
|||
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.
|
||||
|
||||
**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
|
||||
|
|
@ -148,12 +172,14 @@ cacheado em `data/relatorios-cidade/<base>.emendas.json` para replay.
|
|||
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) · `3. Rede e
|
||||
lideranças` — inclui as lideranças locais pesquisadas (ex-prefeitos/vices,
|
||||
vereadores mais votados) · `4. Conjuntura` · `5. Sinais` · `6. Demandas e
|
||||
visitas` · `7. Demografia` · `8. 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)
|
||||
· `9. Notícias e imprensa` · `10. Panorama regional` · `11. Abordagem
|
||||
sugerida (personas)` · `12. Fontes e limites`.
|
||||
vereadores mais votados, com partido) · `4. Conjuntura` · `5. Sinais` · `6.
|
||||
Demandas e visitas` · `7. Demografia` — IBGE Censo 2022 do artefato +
|
||||
**complemento pesquisado** (cor/raça e poder aquisitivo) · `8. Atividade
|
||||
econômica (pesquisa)` · `9. Transporte e conexões (pesquisa)` · `10. 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) · `11. Notícias e imprensa` · `12. Panorama
|
||||
regional` · `13. Abordagem sugerida (personas)` · `14. Fontes e limites`.
|
||||
- **Links clicáveis:** todo URL no PDF é um link (`<a href>`): células de
|
||||
tabela, fontes por linha e a seção de fontes.
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
**C163 — demografia complementar, economia e transporte no relatório (2026-09-15):** a seção de Demografia ganhou o complemento pesquisado de **cor/raça e poder aquisitivo** (Censo 2022; Itamaraju: parda 73,1%, branca 13,7%, preta 11,1%, indígena 2,0%; renda domiciliar per capita média R$ 997,27 e mediana R$ 725,00) e entraram duas seções novas: **"8. Atividade econômica (pesquisa)"** (PIB e per capita, composição setorial, emprego formal e atividades — IBGE Cidades, RAIS/perfis regionais; Itamaraju: PIB per capita R$ 23.412,56 em 2023, serviços 38,2%, agropecuária 27,7%, administração pública 23,7%, indústria 10,3%) e **"9. Transporte e conexões (pesquisa)"** (rodovias que cortam o município e obras novas/reformadas, aeroporto mais próximo em operação e o do polo, ferrovia/portos da região — FIOL/Porto Sul com a ressalva de não servir diretamente a cidade). O enquadramento é "onde falta × onde o estado e a União têm acertado", com URL por item. Contrato: `demography`, `economy` e `transport` (`{topic, detail, sourceUrl, sourceDate}`) validados — sem fonte vira lacuna —, listados nas fontes e documentados na skill; seções renumeradas para 14. Unit 3118/3118, `tsc`/eslint verdes; PDF de Itamaraju em 9 páginas.
|
||||
|
|
@ -747,7 +747,43 @@ const buildDemandsVisitsSection = ({ snapshot }) => {
|
|||
return blocks
|
||||
}
|
||||
|
||||
const buildDemographicsSection = ({ snapshot }) => {
|
||||
const buildSourcedListBlocks = ({ title, note, items }) => {
|
||||
if (!items.length) {
|
||||
return [
|
||||
{
|
||||
kind: 'callout',
|
||||
tone: 'gap',
|
||||
title,
|
||||
body: ['Nenhum item pesquisado com fonte para este município.', GAP_COPY],
|
||||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
{
|
||||
kind: 'table',
|
||||
title,
|
||||
columns: [
|
||||
{ key: 'topic', label: 'Tema', width: 22 },
|
||||
{ key: 'detail', label: 'Dado', width: 62 },
|
||||
{ key: 'source', label: 'Fonte', width: 16 },
|
||||
],
|
||||
rows: items.map((item) => ({
|
||||
topic: item.topic,
|
||||
detail: item.detail,
|
||||
source: formatDateBr(item.sourceDate),
|
||||
})),
|
||||
note,
|
||||
sources: items.map((item) => ({
|
||||
kind: 'web',
|
||||
label: item.topic,
|
||||
url: item.sourceUrl,
|
||||
date: item.sourceDate,
|
||||
})),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const buildDemographicsSection = ({ snapshot, research }) => {
|
||||
const demographics = snapshot.demographics
|
||||
if (!demographics) {
|
||||
return [
|
||||
|
|
@ -759,10 +795,10 @@ const buildDemographicsSection = ({ snapshot }) => {
|
|||
},
|
||||
]
|
||||
}
|
||||
return [
|
||||
const blocks = [
|
||||
{
|
||||
kind: 'stats',
|
||||
title: 'Demografia',
|
||||
title: 'Demografia (IBGE Censo 2022)',
|
||||
rows: [
|
||||
{ label: 'População', value: formatInteger(demographics.population) },
|
||||
{ label: '0–17 anos', value: formatInteger(demographics.ageBands['0-17']) },
|
||||
|
|
@ -779,8 +815,30 @@ const buildDemographicsSection = ({ snapshot }) => {
|
|||
sources: [sourceTeqo('IBGE Censo 2022 (artefato commitado)', null)],
|
||||
},
|
||||
]
|
||||
blocks.push(
|
||||
...buildSourcedListBlocks({
|
||||
title: 'Demografia complementar (pesquisa)',
|
||||
note: 'Cor/raça e poder aquisitivo (IBGE Censo 2022) — leitura para agenda e linguagem na cidade; cada item com URL.',
|
||||
items: research.demography ?? [],
|
||||
}),
|
||||
)
|
||||
return blocks
|
||||
}
|
||||
|
||||
const buildEconomySection = ({ research }) =>
|
||||
buildSourcedListBlocks({
|
||||
title: 'Atividade econômica (pesquisa)',
|
||||
note: 'PIB, emprego e principais atividades (IBGE/CAGED e imprensa local) — onde a cidade gera renda e onde depende de fora; cada item com URL.',
|
||||
items: research.economy ?? [],
|
||||
})
|
||||
|
||||
const buildTransportSection = ({ research }) =>
|
||||
buildSourcedListBlocks({
|
||||
title: 'Transporte e conexões (pesquisa)',
|
||||
note: 'Rodovias (novas/reformadas), aeroporto mais próximo, portos e ferrovia: como a cidade se conecta à Bahia e o que está em obra/planejado — cada item com URL.',
|
||||
items: research.transport ?? [],
|
||||
})
|
||||
|
||||
const buildSpeechesSection = ({ snapshot }) => {
|
||||
const { speeches, municipality } = snapshot
|
||||
if (!speeches.rows.length) {
|
||||
|
|
@ -996,6 +1054,16 @@ const buildSourcesSection = ({ snapshot, research, emendas }) => {
|
|||
date: item.sourceDate,
|
||||
})
|
||||
}
|
||||
for (const listKey of ['demography', 'economy', 'transport']) {
|
||||
for (const item of research[listKey] ?? []) {
|
||||
items.push({
|
||||
kind: 'web',
|
||||
label: `${listKey === 'demography' ? 'Demografia' : listKey === 'economy' ? 'Economia' : 'Transporte'} — ${item.topic}`,
|
||||
url: item.sourceUrl,
|
||||
date: item.sourceDate,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
|
|
@ -1069,26 +1137,40 @@ export const buildCityReport = ({ snapshot, research, emendas, generatedAt = new
|
|||
title: '6. Demandas e visitas',
|
||||
blocks: buildDemandsVisitsSection({ snapshot }),
|
||||
},
|
||||
{ id: 'demografia', title: '7. Demografia', blocks: buildDemographicsSection({ snapshot }) },
|
||||
{ id: 'falas', title: '8. Acervo de falas', blocks: buildSpeechesSection({ snapshot }) },
|
||||
{
|
||||
id: 'demografia',
|
||||
title: '7. Demografia',
|
||||
blocks: buildDemographicsSection({ snapshot, research }),
|
||||
},
|
||||
{
|
||||
id: 'economia',
|
||||
title: '8. Atividade econômica (pesquisa)',
|
||||
blocks: buildEconomySection({ research }),
|
||||
},
|
||||
{
|
||||
id: 'transporte',
|
||||
title: '9. Transporte e conexões (pesquisa)',
|
||||
blocks: buildTransportSection({ research }),
|
||||
},
|
||||
{ id: 'falas', title: '10. Acervo de falas', blocks: buildSpeechesSection({ snapshot }) },
|
||||
{
|
||||
id: 'noticias',
|
||||
title: '9. Notícias internas e imprensa local',
|
||||
title: '11. Notícias internas e imprensa local',
|
||||
blocks: buildNewsSection({ research }),
|
||||
},
|
||||
{
|
||||
id: 'regiao',
|
||||
title: '10. Panorama regional (Território de Identidade)',
|
||||
title: '12. Panorama regional (Território de Identidade)',
|
||||
blocks: buildRegionSection({ snapshot }),
|
||||
},
|
||||
{
|
||||
id: 'abordagem',
|
||||
title: '11. Abordagem sugerida (pesquisa)',
|
||||
title: '13. Abordagem sugerida (pesquisa)',
|
||||
blocks: buildApproachSection({ research }),
|
||||
},
|
||||
{
|
||||
id: 'fontes',
|
||||
title: '12. Fontes e limites',
|
||||
title: '14. Fontes e limites',
|
||||
blocks: buildSourcesSection({ snapshot, research, emendas }),
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -267,6 +267,39 @@ export const normalizeResearchInput = (raw, { now = new Date() } = {}) => {
|
|||
})
|
||||
}
|
||||
|
||||
const sourcedLists = { demography: [], economy: [], transport: [] }
|
||||
for (const key of Object.keys(sourcedLists)) {
|
||||
for (const entry of Array.isArray(raw[key]) ? raw[key] : []) {
|
||||
const topic = isNonEmptyString(entry?.topic) ? entry.topic.trim() : null
|
||||
const detail = isNonEmptyString(entry?.detail) ? entry.detail.trim() : null
|
||||
const sourceUrl = isNonEmptyString(entry?.sourceUrl) ? entry.sourceUrl.trim() : null
|
||||
const sourceDate = isValidDate(entry?.sourceDate) ? entry.sourceDate : null
|
||||
if (!topic || !detail) {
|
||||
gaps.push({
|
||||
id: `${key}_incompleto`,
|
||||
label: topic,
|
||||
reason: 'Item sem tema ou dado — descartado.',
|
||||
})
|
||||
continue
|
||||
}
|
||||
if (!sourceUrl || !sourceDate) {
|
||||
gaps.push({
|
||||
id: `${key}_sem_fonte`,
|
||||
label: topic,
|
||||
reason: 'Item sem fonte: URL e data são obrigatórias.',
|
||||
})
|
||||
continue
|
||||
}
|
||||
sourcedLists[key].push({
|
||||
topic,
|
||||
detail,
|
||||
sourceUrl,
|
||||
sourceDate,
|
||||
consultedAt: isValidDate(entry.consultedAt) ? entry.consultedAt : null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
municipalitySlug: raw.municipalitySlug.trim(),
|
||||
researchedAt: researchedAt.toISOString(),
|
||||
|
|
@ -275,6 +308,9 @@ export const normalizeResearchInput = (raw, { now = new Date() } = {}) => {
|
|||
approach,
|
||||
preCandidates,
|
||||
leaders,
|
||||
demography: sourcedLists.demography,
|
||||
economy: sourcedLists.economy,
|
||||
transport: sourcedLists.transport,
|
||||
gaps,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,30 @@ const research = normalizeResearchInput(
|
|||
sourceDate: '2026-03-05',
|
||||
},
|
||||
],
|
||||
demography: [
|
||||
{
|
||||
topic: 'Cor/raça',
|
||||
detail: 'Parda 62%, branca 25%.',
|
||||
sourceUrl: 'https://exemplo.test/cor-raca',
|
||||
sourceDate: '2026-09-10',
|
||||
},
|
||||
],
|
||||
economy: [
|
||||
{
|
||||
topic: 'PIB',
|
||||
detail: 'Serviços e agropecuária lideram.',
|
||||
sourceUrl: 'https://exemplo.test/pib',
|
||||
sourceDate: '2026-09-10',
|
||||
},
|
||||
],
|
||||
transport: [
|
||||
{
|
||||
topic: 'Rodovias',
|
||||
detail: 'BR-101 corta o município; trecho em obra.',
|
||||
sourceUrl: 'https://exemplo.test/br101',
|
||||
sourceDate: '2026-09-10',
|
||||
},
|
||||
],
|
||||
gaps: [],
|
||||
},
|
||||
{ now: generatedAt },
|
||||
|
|
@ -323,12 +347,29 @@ const asReport = (value: unknown) => value as ReportShape
|
|||
describe('buildCityReport', () => {
|
||||
const report = asReport(buildCityReport({ snapshot, research, emendas, generatedAt }))
|
||||
|
||||
it('builds the 12 deepening sections in the product order', () => {
|
||||
expect(report.sections).toHaveLength(12)
|
||||
it('builds the 14 deepening sections in the product order', () => {
|
||||
expect(report.sections).toHaveLength(14)
|
||||
expect(report.sections[0].title).toMatch(/Conta eleitoral completa/)
|
||||
expect(report.sections[1].title).toMatch(/Concorrentes no município/)
|
||||
expect(report.sections[10].title).toMatch(/Abordagem sugerida/)
|
||||
expect(report.sections[11].title).toMatch(/Fontes e limites/)
|
||||
expect(report.sections[7].title).toMatch(/Atividade econômica/)
|
||||
expect(report.sections[8].title).toMatch(/Transporte e conexões/)
|
||||
expect(report.sections[12].title).toMatch(/Abordagem sugerida/)
|
||||
expect(report.sections[13].title).toMatch(/Fontes e limites/)
|
||||
})
|
||||
|
||||
it('adds the researched demography, economy and transport data', () => {
|
||||
const demografia = report.sections.find((section) => section.id === 'demografia')!
|
||||
const complementary = demografia.blocks.find(
|
||||
(block) => block.title === 'Demografia complementar (pesquisa)',
|
||||
)!
|
||||
expect((complementary.rows as unknown as TableRow[])[0].topic).toBe('Cor/raça')
|
||||
const economia = report.sections.find((section) => section.id === 'economia')!.blocks[0]
|
||||
expect((economia.rows as unknown as TableRow[])[0].detail).toContain('Serviços')
|
||||
const transporte = report.sections.find((section) => section.id === 'transporte')!.blocks[0]
|
||||
expect((transporte.rows as unknown as TableRow[])[0].detail).toContain('BR-101')
|
||||
expect(transporte.sources![0]).toEqual(
|
||||
expect.objectContaining({ url: 'https://exemplo.test/br101' }),
|
||||
)
|
||||
})
|
||||
|
||||
it('lists the main competitors with their vote series and the pre-candidates', () => {
|
||||
|
|
|
|||
|
|
@ -114,8 +114,10 @@ describe('renderReportMd', () => {
|
|||
it('carries the same sections as tables and headings', () => {
|
||||
expect(md).toContain('## 1. Conta eleitoral completa')
|
||||
expect(md).toContain('## 2. Concorrentes no município (federal e estadual)')
|
||||
expect(md).toContain('## 11. Abordagem sugerida (pesquisa)')
|
||||
expect(md).toContain('## 12. Fontes e limites')
|
||||
expect(md).toContain('## 8. Atividade econômica (pesquisa)')
|
||||
expect(md).toContain('## 9. Transporte e conexões (pesquisa)')
|
||||
expect(md).toContain('## 13. Abordagem sugerida (pesquisa)')
|
||||
expect(md).toContain('## 14. Fontes e limites')
|
||||
expect(md).toContain('| Ano | Votos |')
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue