mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-12 22:13:05 +00:00
feat(C163): link do vídeo do acervo na minutagem do trecho
This commit is contained in:
parent
e8f85f6a83
commit
ebf484c984
5 changed files with 26 additions and 3 deletions
|
|
@ -178,7 +178,9 @@ 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), mais **Vídeo** (YouTube/VOD da Câmara) e
|
||||
do acervo com o nº de municípios), mais **Vídeo** (YouTube já na minutagem
|
||||
do trecho — `youtubeOffsetSeconds` = época do trecho − início da sessão;
|
||||
fallback para o VOD da Câmara, que é o clipe do próprio trecho) 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`.
|
||||
|
|
|
|||
1
docs/changelog/2026-09-15-c163-falas-minutagem.md
Normal file
1
docs/changelog/2026-09-15-c163-falas-minutagem.md
Normal file
|
|
@ -0,0 +1 @@
|
|||
**C163 — vídeo do acervo já na minutagem do trecho (2026-09-15):** o link do YouTube no acervo de falas agora abre no momento em que o deputado fala. O extrator passou a projetar `youtubeOffsetSeconds` usando o dono `excerptOffsetSeconds` (`src/lib/speechVod.ts` — época do trecho menos o início da sessão, ambos em hora de Brasília) e o builder acrescenta `&t=Ns` (ou `?t=Ns`) à URL do YouTube; quando não há offset conhecido, o link fica sem timestamp, e o VOD (clipe do próprio trecho) segue como fallback sem timestamp. Na fala de Itamaraju (641): offset de 645s (10min45s) — `youtube.com/watch?v=2cX_gKkJH7Q&t=645s`. Unit 3118/3118, `tsc`/eslint verdes; snapshot reextraído e PDF regerado.
|
||||
|
|
@ -14,6 +14,7 @@ import { FEDERAL_DEPUTY_OFFICE, HISTORICAL_SERIES_YEARS } from '../src/lib/elect
|
|||
import { getMunicipalityCatalogEntry } from '../src/lib/municipalityCatalog.ts'
|
||||
import { getMunicipalityVoteRank } from '../src/lib/municipalityVoteRank.ts'
|
||||
import { normalizeForSearch } from '../src/lib/speechSearch.ts'
|
||||
import { excerptOffsetSeconds } from '../src/lib/speechVod.ts'
|
||||
import { campaignRoleLabels } from '../src/utilities/campaignUserProfile.ts'
|
||||
import { loadMunicipalityDossierData } from '../src/utilities/municipality/municipalityDossierData.ts'
|
||||
import {
|
||||
|
|
@ -65,6 +66,8 @@ const loadSpeeches = async (payload, actor, municipalityID, municipalityName) =>
|
|||
officialTextUrl: true,
|
||||
youtubeUrl: true,
|
||||
vodPlaybackUrl: true,
|
||||
excerptTMs: true,
|
||||
eventStartAt: true,
|
||||
mentionedMunicipalities: true,
|
||||
},
|
||||
user: actor,
|
||||
|
|
@ -92,6 +95,8 @@ const loadSpeeches = async (payload, actor, municipalityID, municipalityName) =>
|
|||
officialTextUrl: doc.officialTextUrl ?? null,
|
||||
youtubeUrl: doc.youtubeUrl ?? null,
|
||||
vodPlaybackUrl: doc.vodPlaybackUrl ?? null,
|
||||
/** Offset of the excerpt inside the session video (YouTube start). */
|
||||
youtubeOffsetSeconds: excerptOffsetSeconds(doc.excerptTMs, doc.eventStartAt),
|
||||
mentionExcerpt: mentionSegment?.text ?? null,
|
||||
mentionedMunicipalityCount: Array.isArray(doc.mentionedMunicipalities)
|
||||
? doc.mentionedMunicipalities.length
|
||||
|
|
|
|||
|
|
@ -852,6 +852,18 @@ const buildTransportSection = ({ research }) =>
|
|||
items: research.transport ?? [],
|
||||
})
|
||||
|
||||
/**
|
||||
* YouTube link starts where the excerpt sits in the session video
|
||||
* (`youtubeOffsetSeconds`, from the Câmara excerpt epoch − session start).
|
||||
* The VOD clip is the excerpt itself, so it needs no timestamp.
|
||||
*/
|
||||
const withYoutubeTimestamp = (url, offsetSeconds) => {
|
||||
if (!url) return null
|
||||
if (!Number.isFinite(offsetSeconds) || offsetSeconds <= 0) return url
|
||||
const separator = url.includes('?') ? '&' : '?'
|
||||
return `${url}${separator}t=${Math.floor(offsetSeconds)}s`
|
||||
}
|
||||
|
||||
const buildSpeechesSection = ({ snapshot }) => {
|
||||
const { speeches, municipality } = snapshot
|
||||
if (!speeches.rows.length) {
|
||||
|
|
@ -889,7 +901,9 @@ 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.',
|
||||
video: row.youtubeUrl ?? row.vodPlaybackUrl ?? '—',
|
||||
video: row.youtubeUrl
|
||||
? withYoutubeTimestamp(row.youtubeUrl, row.youtubeOffsetSeconds)
|
||||
: (row.vodPlaybackUrl ?? '—'),
|
||||
transcript: row.officialTextUrl ?? '—',
|
||||
})),
|
||||
note: [
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ const snapshot = {
|
|||
officialTextUrl: 'https://camara.test/discurso',
|
||||
youtubeUrl: 'https://youtube.test/video',
|
||||
vodPlaybackUrl: 'https://vod.test/video.mp4',
|
||||
youtubeOffsetSeconds: 125,
|
||||
mentionExcerpt: 'Trecho da fala que cita Feira de Santana',
|
||||
mentionedMunicipalityCount: 3,
|
||||
},
|
||||
|
|
@ -444,7 +445,7 @@ describe('buildCityReport', () => {
|
|||
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].video).toBe('https://youtube.test/video?t=125s')
|
||||
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')
|
||||
|
|
|
|||
Loading…
Reference in a new issue