mirror of
https://github.com/fsolla/teqo.git
synced 2026-11-13 06:35:21 +00:00
Some checks are pending
Deploy / preflight (push) Waiting to run
Deploy / verify (push) Blocked by required conditions
Deploy / deploy staging (teqo-staging) (push) Blocked by required conditions
Deploy / requeue when main advanced (push) Blocked by required conditions
Deploy / deploy production (teqo-1313) (push) Blocked by required conditions
Collection archivePhoto ganha o grupo catalog (legenda/descrição/cena/texto visível/temas/pessoas/município/origem/catalogedAt), curatedFields (o que a assessoria edita nunca é sobrescrito), takenOn e searchText com índice GIN trgm (migration aditiva add_archive_photo_catalog); hook beforeValidate deriva o índice e marca curadoria só em edição humana; pipeline transacional baixa os bytes privados, prepara JPEG <=1024px com sharp e mescla o VLM local com o gazetteer/catálogo curado (pessoas só do TEXTO, nunca de rosto); CLI pnpm archive:catalog (plan/--apply/--verify, recibo honesto, idempotente por catalogedAt) com engine OpenAI-compatible na rede privada atrás de guarda de host (ARCHIVE_VISION_ALLOW_REMOTE é o escape); client HTTP com retry/timeout e parser zod fail-closed; resolver de município extraído para owner compartilhado com o catálogo de peças delegando; unit/int novos e runbook staging-primeiro.
76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
/**
|
|
* Pure text normalization for the speech catalog (C153). The segment hook
|
|
* stores `normalizeForSearch(text)` in `speechSegment.searchText` and the
|
|
* search UI (C154) normalizes the query with the SAME function before the
|
|
* `contains` query, so accent/case variants match the trigram index.
|
|
*/
|
|
|
|
export const TRANSCRIPT_TEXT_MAX_LENGTH = Number.MAX_SAFE_INTEGER
|
|
|
|
/** NFD-strips accents, lowercases and collapses whitespace. */
|
|
export const normalizeForSearch = (value: string): string =>
|
|
value
|
|
.normalize('NFD')
|
|
.replace(/[\u0300-\u036f]/g, '')
|
|
.toLowerCase()
|
|
.replace(/\s+/g, ' ')
|
|
.trim()
|
|
|
|
/** Escapes a literal for a RegExp source; the whole-word matchers share it. */
|
|
export const escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
|
|
type SpeechSearchableRecord = {
|
|
/** Normalized segment text persisted by the segment hook (C153). */
|
|
searchText?: string | null
|
|
keywords?: readonly string[] | null
|
|
}
|
|
|
|
/**
|
|
* C180 — pure mirror of a SINGLE textual `where` branch
|
|
* (`speechListFilters.buildSpeechTextBranches`): normalized `searchText` LIKE
|
|
* the normalized term, OR a raw official keyword containing the term.
|
|
* `keywords` compare case-insensitively (accent still significant) to match the
|
|
* ILIKE `%term%` Postgres runs. Adding or removing a branch there means
|
|
* changing this predicate and its test in the same commit. An empty term never
|
|
* matches (a branch with no term cannot surface a row).
|
|
*/
|
|
export const speechMatchesSearchTerm = (
|
|
speech: SpeechSearchableRecord,
|
|
term: string | undefined,
|
|
): boolean => {
|
|
const trimmed = term?.trim()
|
|
if (!trimmed) return false
|
|
if (normalizeForSearch(speech.searchText ?? '').includes(normalizeForSearch(trimmed))) return true
|
|
const lowered = trimmed.toLowerCase()
|
|
return (speech.keywords ?? []).some((keyword) => keyword.toLowerCase().includes(lowered))
|
|
}
|
|
|
|
/**
|
|
* C180 — the search query mirror: empty/missing query is an unconditional match
|
|
* (no textual filter), otherwise any of its terms. C192 reuses
|
|
* `speechMatchesSearchTerm` for the expanded theme terms.
|
|
*/
|
|
export const speechMatchesSearchQuery = (
|
|
speech: SpeechSearchableRecord,
|
|
query: string | undefined,
|
|
): boolean => {
|
|
const trimmed = query?.trim()
|
|
if (!trimmed) return true
|
|
return speechMatchesSearchTerm(speech, trimmed)
|
|
}
|
|
|
|
/**
|
|
* Dedupes values by their normalized form, preserving the first spelling.
|
|
* Used by the facet classifier before persisting people/program mentions.
|
|
*/
|
|
export const uniqueByNormalizedForm = (values: readonly string[]): string[] => {
|
|
const seen = new Set<string>()
|
|
const result: string[] = []
|
|
for (const value of values) {
|
|
const key = normalizeForSearch(value)
|
|
if (key === '' || seen.has(key)) continue
|
|
seen.add(key)
|
|
result.push(value.trim())
|
|
}
|
|
return result
|
|
}
|