diff --git a/docs/changelog/2026-09-15-c163-democracia-unificada.md b/docs/changelog/2026-09-15-c163-democracia-unificada.md new file mode 100644 index 00000000..ced6ad6c --- /dev/null +++ b/docs/changelog/2026-09-15-c163-democracia-unificada.md @@ -0,0 +1 @@ +**C163 — demografia em uma única tabela (2026-09-15):** a seção 7 tinha dois visuais para o mesmo tipo de dado (grade de stats do artefato + tabela da pesquisa); agora é **uma única tabela Indicador × Dado × Fonte**, com "base Teqo" na coluna Fonte para o artefato IBGE e a data para os itens pesquisados (URLs preservadas na nota de fontes do bloco e na seção "Fontes e limites"). O item de pesquisa "População total" saiu por duplicar a linha "População" da base. Unit 3118/3118, `tsc`/eslint verdes; PDF de Itamaraju regerado (9 páginas). diff --git a/scripts/lib/cityReportBlocks.mjs b/scripts/lib/cityReportBlocks.mjs index 20f93e75..b693d145 100644 --- a/scripts/lib/cityReportBlocks.mjs +++ b/scripts/lib/cityReportBlocks.mjs @@ -795,34 +795,47 @@ const buildDemographicsSection = ({ snapshot, research }) => { }, ] } - const blocks = [ + const baseRows = [ + { indicator: 'População', value: formatInteger(demographics.population) }, + { indicator: '0–17 anos', value: formatInteger(demographics.ageBands['0-17']) }, + { indicator: '18–29 anos', value: formatInteger(demographics.ageBands['18-29']) }, + { indicator: '30–59 anos', value: formatInteger(demographics.ageBands['30-59']) }, + { indicator: '60+ anos', value: formatInteger(demographics.ageBands['60+']) }, + { indicator: 'Mulheres', value: formatPercent(demographics.sexShareFemale) }, { - kind: 'stats', + indicator: 'Idade mediana', + value: + demographics.medianAge === null ? '—' : `${formatInteger(demographics.medianAge)} anos`, + }, + ].map((row) => ({ ...row, source: 'base Teqo' })) + const researchRows = (research.demography ?? []).map((item) => ({ + indicator: item.topic, + value: item.detail, + source: formatDateBr(item.sourceDate), + })) + + return [ + { + kind: 'table', title: 'Demografia (IBGE Censo 2022)', - rows: [ - { label: 'População', value: formatInteger(demographics.population) }, - { label: '0–17 anos', value: formatInteger(demographics.ageBands['0-17']) }, - { label: '18–29 anos', value: formatInteger(demographics.ageBands['18-29']) }, - { label: '30–59 anos', value: formatInteger(demographics.ageBands['30-59']) }, - { label: '60+ anos', value: formatInteger(demographics.ageBands['60+']) }, - { label: 'Mulheres', value: formatPercent(demographics.sexShareFemale) }, - { - label: 'Idade mediana', - value: - demographics.medianAge === null ? '—' : `${formatInteger(demographics.medianAge)} anos`, - }, + columns: [ + { key: 'indicator', label: 'Indicador', width: 22 }, + { key: 'value', label: 'Dado', width: 61 }, + { key: 'source', label: 'Fonte', width: 17 }, + ], + rows: [...baseRows, ...researchRows], + note: '“base Teqo” = artefato IBGE Censo 2022 commitado; datas = pesquisa web datada, com URL na seção de fontes.', + sources: [ + sourceTeqo('IBGE Censo 2022 (artefato commitado)', null), + ...(research.demography ?? []).map((item) => ({ + kind: 'web', + label: item.topic, + url: item.sourceUrl, + date: item.sourceDate, + })), ], - 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 }) => diff --git a/tests/unit/cityReportBlocks.unit.spec.ts b/tests/unit/cityReportBlocks.unit.spec.ts index 33486ad4..016c127d 100644 --- a/tests/unit/cityReportBlocks.unit.spec.ts +++ b/tests/unit/cityReportBlocks.unit.spec.ts @@ -358,11 +358,16 @@ describe('buildCityReport', () => { }) 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 demografia = report.sections.find((section) => section.id === 'demografia')!.blocks[0] + const rows = demografia.rows as unknown as TableRow[] + expect(rows[0].indicator).toBe('População') + expect(rows[0].source).toBe('base Teqo') + expect(rows.some((row) => row.indicator === 'Cor/raça' && row.source === '10/09/2026')).toBe( + true, + ) + expect( + demografia.sources!.some((source) => source.url === 'https://exemplo.test/cor-raca'), + ).toBe(true) 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]