mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 15:13:07 +00:00
Terceira variante da família de dossiês: recorte pela área canônica do acervo (SPEECH_TOPICS), com o pipeline C186/C187 generalizado para a forma subject via THEME_UNIT (generalização aditiva; C186/C187 byte-idênticos). - resolveSpeechTopic no dono speechFacets (fail-closed, sem slug inventado) - themeSnapshot + extract-theme-snapshot (THEME_REPORT_CONFIRM=1, read-only) - build-dossie-solla-tema (pack medido, PDF A4 + .md + boletim 1 página) - skill/comando/2 subagentes (researcher por era + redator) + specs - cenas theme-only no descritor (acervo universo/amostra, abrangência, lacunas, hierarquia de fontes, pills do boletim) certificadas pelo designer Closes #1143
124 lines
3.8 KiB
TypeScript
124 lines
3.8 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { randomUUID } from 'node:crypto'
|
|
|
|
import type { Payload } from 'payload'
|
|
import { getPayload } from 'payload'
|
|
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
|
|
|
|
import config from '@/payload.config'
|
|
|
|
import { composeThemeSnapshot } from '../../scripts/themeSnapshot.mjs'
|
|
import { installCampaignFixtures } from '../helpers/campaignFixtures'
|
|
|
|
// C190: the theme/area snapshot recorte IS the acervo tag (`Speech.topics`), so
|
|
// there is no nominal bridge. Pins the read projection (no PII), the topic
|
|
// filter and the explicit gap when the recorte is empty.
|
|
|
|
let payload: Payload
|
|
const createdSpeechIds: number[] = []
|
|
const campaignFixtures = installCampaignFixtures({
|
|
getPayload: () => payload,
|
|
setPayload: (nextPayload) => {
|
|
payload = nextPayload
|
|
},
|
|
})
|
|
|
|
const identity = {
|
|
slug: 'educacao',
|
|
value: 'educacao',
|
|
label: 'Educação',
|
|
taxonomyNote: 'Taxonomia do acervo',
|
|
}
|
|
|
|
describe('theme snapshot (C190)', () => {
|
|
beforeAll(async () => {
|
|
payload = await getPayload({ config: await config })
|
|
})
|
|
|
|
afterAll(async () => {
|
|
for (const id of createdSpeechIds) {
|
|
try {
|
|
await payload.delete({ collection: 'speech', id, depth: 0, overrideAccess: true })
|
|
} catch {
|
|
// already deleted
|
|
}
|
|
}
|
|
})
|
|
|
|
it(
|
|
'projects the topic-matched speeches without PII and records the recorte',
|
|
{
|
|
timeout: 30_000,
|
|
},
|
|
async () => {
|
|
const fixtures = campaignFixtures()
|
|
const candidate = await fixtures.createCampaignUser('candidate')
|
|
|
|
const speech = await payload.create({
|
|
collection: 'speech',
|
|
data: {
|
|
sourceKey: `test-theme-${randomUUID()}`,
|
|
speechAt: '2024-05-10T17:28',
|
|
type: 'DISCURSO',
|
|
phase: 'Discurso',
|
|
summary: 'Defesa do financiamento da educação básica.',
|
|
officialTranscript: 'O SR. JORGE SOLLA (Bloco/PT - BA) - Sr. Presidente...',
|
|
keywords: ['escola', 'educação'],
|
|
topics: ['educacao'],
|
|
classifiedBy: 'gazetteer',
|
|
},
|
|
depth: 0,
|
|
overrideAccess: true,
|
|
})
|
|
createdSpeechIds.push(speech.id)
|
|
|
|
const actor = { ...candidate, collection: 'campaignUser' as const }
|
|
const snapshot = await composeThemeSnapshot({
|
|
payload,
|
|
actor,
|
|
identity,
|
|
readAt: '2026-09-18T11:00:00.000Z',
|
|
codeSha: 'test-sha',
|
|
database: 'localhost:5432/teqo_test',
|
|
})
|
|
|
|
expect(snapshot.meta.kind).toBe('theme')
|
|
expect(snapshot.theme).toMatchObject({
|
|
slug: 'educacao',
|
|
value: 'educacao',
|
|
label: 'Educação',
|
|
})
|
|
expect(snapshot.theme.topics).toEqual(['educacao'])
|
|
expect(snapshot.speeches.rows.some((row: { id: number }) => row.id === speech.id)).toBe(true)
|
|
const row = snapshot.speeches.rows.find(
|
|
(candidateRow: { id: number }) => candidateRow.id === speech.id,
|
|
)!
|
|
expect('officialTranscript' in row).toBe(false)
|
|
expect('mentionExcerpt' in row).toBe(false)
|
|
expect(JSON.stringify(snapshot)).not.toContain('@')
|
|
},
|
|
)
|
|
|
|
it('declares an explicit gap when the theme has no speech', async () => {
|
|
const fixtures = campaignFixtures()
|
|
const candidate = await fixtures.createCampaignUser('candidate')
|
|
const actor = { ...candidate, collection: 'campaignUser' as const }
|
|
|
|
const snapshot = await composeThemeSnapshot({
|
|
payload,
|
|
actor,
|
|
identity: {
|
|
slug: 'pessoa-deficiencia',
|
|
value: 'pessoa-deficiencia',
|
|
label: 'Pessoa com Deficiência',
|
|
taxonomyNote: 'Taxonomia do acervo',
|
|
},
|
|
readAt: '2026-09-18T11:00:00.000Z',
|
|
})
|
|
|
|
expect(snapshot.speeches.rows).toEqual([])
|
|
expect(snapshot.speeches.totalCount).toBe(0)
|
|
expect(snapshot.gaps).toContainEqual(expect.objectContaining({ id: 'acervo_sem_falas' }))
|
|
})
|
|
})
|