teqo-backup/tests/unit/speechExcerpt.unit.spec.ts

149 lines
4.4 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
buildSpeechExcerpt,
speechExcerptTerms,
type SpeechExcerptSegment,
} from '@/lib/speechExcerpt'
const seg = (startSeconds: number, endSeconds: number, text: string): SpeechExcerptSegment => ({
startSeconds,
endSeconds,
text,
})
describe('speechExcerptTerms', () => {
it('normaliza acento/caixa e quebra em caracteres não alfanuméricos (sem curinga no LIKE)', () => {
expect(speechExcerptTerms('100% saúde_pública')).toEqual(['100', 'saude', 'publica'])
})
it('descarta termos com menos de 3 caracteres', () => {
expect(speechExcerptTerms('a de oxi')).toEqual(['oxi'])
expect(speechExcerptTerms('de a o')).toEqual([])
})
it('deduplica preservando a ordem', () => {
expect(speechExcerptTerms('SUS sus SUS saúde')).toEqual(['sus', 'saude'])
})
it('limita a 6 termos', () => {
expect(speechExcerptTerms('alfa beta gama delta epsilon zeta eta theta')).toEqual([
'alfa',
'beta',
'gama',
'delta',
'epsilon',
'zeta',
])
})
})
describe('buildSpeechExcerpt', () => {
it('escolhe a âncora de maior cobertura e expande até o fim do trecho disponível', () => {
const excerpt = buildSpeechExcerpt(
[
seg(0, 3, 'O hospital do subúrbio'),
seg(3, 8, 'precisa de atenção'),
seg(8, 14, 'e de investimento'),
],
['hospital', 'suburbio'],
)
expect(excerpt).toEqual({
startSeconds: 0,
endSeconds: 14,
text: 'O hospital do subúrbio precisa de atenção e de investimento',
matchedTerms: ['hospital', 'suburbio'],
})
})
it('cobre termos espalhados em segmentos consecutivos', () => {
const excerpt = buildSpeechExcerpt(
[seg(0, 6, 'Vamos falar do hospital'), seg(6, 12, 'do subúrbio e da saúde')],
['hospital', 'suburbio'],
)
expect(excerpt).toMatchObject({ startSeconds: 0, endSeconds: 12 })
})
it('expande além do alvo até cobrir termos distantes dentro do teto de 60s', () => {
const segments = [
seg(0, 5, 'hospital'),
seg(5, 25, 'contexto do mandato'),
seg(25, 45, 'mais contexto'),
seg(45, 55, 'ainda contexto'),
seg(55, 60, 'suburbio'),
]
for (const targetSeconds of [20, 45]) {
expect(
buildSpeechExcerpt(segments, ['hospital', 'suburbio'], { targetSeconds }),
).toMatchObject({ startSeconds: 0, endSeconds: 60 })
}
})
it('não concatena por cima de um gap maior que 5s', () => {
expect(
buildSpeechExcerpt(
[seg(0, 5, 'hospital'), seg(25, 30, 'suburbio')],
['hospital', 'suburbio'],
),
).toBeNull()
})
it('devolve null quando nenhum segmento cobre o tema', () => {
expect(buildSpeechExcerpt([seg(0, 5, 'economia e emprego')], ['hospital'])).toBeNull()
})
it('devolve null sem segmentos ou sem termos', () => {
expect(buildSpeechExcerpt([], ['hospital'])).toBeNull()
expect(buildSpeechExcerpt([seg(0, 5, 'hospital')], [])).toBeNull()
})
it('as variantes curta e longa produzem janelas diferentes', () => {
const segments = [
seg(0, 10, 'hospital'),
seg(10, 20, 'primeira parte'),
seg(20, 30, 'segunda parte'),
seg(30, 40, 'terceira parte'),
seg(40, 50, 'quarta parte'),
]
expect(buildSpeechExcerpt(segments, ['hospital'], { targetSeconds: 20 })).toMatchObject({
startSeconds: 0,
endSeconds: 20,
})
expect(buildSpeechExcerpt(segments, ['hospital'], { targetSeconds: 45 })).toMatchObject({
startSeconds: 0,
endSeconds: 50,
})
})
it('respeita o teto de 60s na expansão', () => {
const excerpt = buildSpeechExcerpt(
[seg(0, 30, 'hospital'), seg(30, 60, 'meio'), seg(60, 90, 'fim')],
['hospital'],
{ targetSeconds: 45 },
)
expect(excerpt).toMatchObject({ startSeconds: 0, endSeconds: 60 })
})
it('expande para trás quando a âncora sozinha não atinge o piso de contexto', () => {
const excerpt = buildSpeechExcerpt(
[seg(0, 20, 'história da Bahia'), seg(20, 22, 'hospital')],
['hospital'],
)
expect(excerpt).toMatchObject({ startSeconds: 0, endSeconds: 22 })
})
it('devolve um segmento único maior que o teto inteiro (nunca corta no meio)', () => {
const excerpt = buildSpeechExcerpt(
[seg(0, 90, 'hospital e subúrbio')],
['hospital', 'suburbio'],
)
expect(excerpt).toMatchObject({ startSeconds: 0, endSeconds: 90 })
})
})