feat(C163): vídeo e transcrição no acervo de falas

This commit is contained in:
Francisco Solla 2026-09-15 09:44:04 -03:00
parent 0f0f9a6eab
commit e8f85f6a83
5 changed files with 29 additions and 12 deletions

View file

@ -178,8 +178,10 @@ cacheado em `data/relatorios-cidade/<base>.emendas.json` para replay.
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`.
do acervo com o nº de municípios), mais **Vídeo** (YouTube/VOD da Câmara) e
**Transcrição** (PDF do Diário) quando existirem · `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.

View file

@ -0,0 +1 @@
**C163 — vídeo e transcrição no acervo de falas (2026-09-15):** a fala de Itamaraju **tem vídeo** (YouTube `2cX_gKkJH7Q` e VOD da Câmara) — o relatório é que só projetava o texto oficial, por isso o link ia para o PDF. O extrator passou a selecionar `youtubeUrl`/`vodPlaybackUrl` e a tabela de falas ganhou as colunas **Vídeo** (YouTube, com fallback para o VOD da Câmara) e **Transcrição** (PDF do Diário), ambas clicáveis quando existem e "—" quando não. No acervo (997 falas): 802 com YouTube, 924 com VOD e 965 com texto oficial. Larguras da tabela ajustadas (data em uma linha só). Unit 3118/3118, `tsc`/eslint verdes; snapshot de Itamaraju reextraído e PDF regerado (9 páginas).

View file

@ -63,6 +63,8 @@ const loadSpeeches = async (payload, actor, municipalityID, municipalityName) =>
phase: true,
summary: true,
officialTextUrl: true,
youtubeUrl: true,
vodPlaybackUrl: true,
mentionedMunicipalities: true,
},
user: actor,
@ -88,6 +90,8 @@ const loadSpeeches = async (payload, actor, municipalityID, municipalityName) =>
phase: doc.phase ?? null,
summary: doc.summary ?? null,
officialTextUrl: doc.officialTextUrl ?? null,
youtubeUrl: doc.youtubeUrl ?? null,
vodPlaybackUrl: doc.vodPlaybackUrl ?? null,
mentionExcerpt: mentionSegment?.text ?? null,
mentionedMunicipalityCount: Array.isArray(doc.mentionedMunicipalities)
? doc.mentionedMunicipalities.length

View file

@ -873,11 +873,12 @@ const buildSpeechesSection = ({ snapshot }) => {
kind: 'table',
title: 'Acervo de falas',
columns: [
{ key: 'date', label: 'Data', width: 8 },
{ key: 'phase', label: 'Fase', width: 11 },
{ key: 'description', label: 'O que é', width: 37 },
{ key: 'mention', label: `Menção a ${cityLabel}`, width: 32 },
{ key: 'link', label: 'Link', width: 12 },
{ key: 'date', label: 'Data', width: 9 },
{ key: 'phase', label: 'Fase', width: 10 },
{ key: 'description', label: 'O que é', width: 32 },
{ key: 'mention', label: `Menção a ${cityLabel}`, width: 27 },
{ key: 'video', label: 'Vídeo', width: 11 },
{ key: 'transcript', label: 'Transcrição', width: 11 },
],
rows: speeches.rows.map((row) => ({
date: formatDateBr(row.speechAt),
@ -888,10 +889,11 @@ const buildSpeechesSection = ({ snapshot }) => {
: row.mentionedMunicipalityCount
? `Marcada no acervo — nome não localizado nos trechos (a fala cita ${formatInteger(row.mentionedMunicipalityCount)} municípios).`
: 'Marcada no acervo — trecho não localizado.',
link: row.officialTextUrl ?? '—',
video: row.youtubeUrl ?? row.vodPlaybackUrl ?? '—',
transcript: row.officialTextUrl ?? '—',
})),
note: [
'“O que é” = sumário oficial da Câmara; “Menção” = passagem que cita o município ou, quando o nome não aparece nos trechos, a marcação do acervo (C153/C155) com o nº de municípios da fala.',
'“O que é” = sumário oficial da Câmara; “Menção” = passagem que cita o município ou, quando o nome não aparece nos trechos, a marcação do acervo (C153/C155) com o nº de municípios da fala; “Vídeo” e “Transcrição” = links oficiais da Câmara quando existem.',
speeches.totalCount > speeches.rows.length
? `Mostrando ${speeches.rows.length} de ${speeches.totalCount} falas.`
: null,

View file

@ -276,6 +276,8 @@ const snapshot = {
phase: 'Discurso em plenário',
summary: 'Resumo da fala',
officialTextUrl: 'https://camara.test/discurso',
youtubeUrl: 'https://youtube.test/video',
vodPlaybackUrl: 'https://vod.test/video.mp4',
mentionExcerpt: 'Trecho da fala que cita Feira de Santana',
mentionedMunicipalityCount: 3,
},
@ -286,6 +288,8 @@ const snapshot = {
phase: 'Pequeno Expediente',
summary: 'Saúde indígena na Bahia',
officialTextUrl: 'https://camara.test/discurso-2',
youtubeUrl: null,
vodPlaybackUrl: null,
mentionExcerpt: null,
mentionedMunicipalityCount: 25,
},
@ -433,15 +437,19 @@ describe('buildCityReport', () => {
'Fase',
'O que é',
`Menção a ${snapshot.municipality.name}`,
'Link',
'Vídeo',
'Transcrição',
])
expect(falas.columns!.find((column) => column.key === 'description')!.width).toBe(37)
expect(falas.columns!.find((column) => column.key === 'mention')!.width).toBe(32)
expect(falas.columns!.find((column) => column.key === 'description')!.width).toBe(32)
expect(falas.columns!.find((column) => column.key === 'mention')!.width).toBe(27)
expect(rows[0].description).toBe('Resumo da fala')
expect(rows[0].mention).toContain('cita Feira de Santana')
expect(rows[0].video).toBe('https://youtube.test/video')
expect(rows[0].transcript).toBe('https://camara.test/discurso')
expect(rows[1].description).toBe('Saúde indígena na Bahia')
expect(rows[1].mention).toContain('nome não localizado nos trechos')
expect(rows[1].mention).toContain('25 municípios')
expect(rows[1].video).toBe('—')
})
it('renders the persona approach section and its sources', () => {