teqo-backup/src/lib/institutionNameAliases.ts
Francisco Solla cb5854d17e feat(C187): dossiê Solla por instituição + boletim modelo (skill + builder) (Closes #1135)
Generaliza aditivamente o pipeline C186 (default município; specs verdes) via o
descritor scripts/lib/dossieUnit.mjs (municipality|institution), extrai o emit
A4/fit-guard para scripts/lib/buildPdf.mjs e a receita read-only para
scripts/lib/readOnlyExtract.mjs.

- Catálogo fail-closed src/lib/institutionCatalog.ts + institutionNameAliases.ts
  (seed UFBA/Correios/Enfermagem; escape one-off --slug/--name; dedup).
- Skill/command/subagente /dossie-solla-instituicao; eras A/B/C reusadas.
- Abrangência instituição × setor × rede nunca somada; era sem fonte vira página
  de lacuna explícita; lista capada declara "e mais N" (regra C188 no dono).
- Boletim modelo 1×A4 com fase junto ao valor, sem fontes, sem CTA; herda apenas
  fatos com fonte do dossiê (incl. falas do acervo read-only).
- Sem schema/migration/DB write; artefatos gitignored.
2026-09-17 19:25:13 -03:00

49 lines
1.8 KiB
TypeScript

import {
type InstitutionCatalogEntry,
institutionCatalog,
institutionSpellings,
} from '@/lib/institutionCatalog'
import { normalizeSearchPhrase } from '@/lib/wordStartFilter'
/**
* Institution-name resolution for the institutional dossiê (C187). Mirrors the
* `resolveMunicipalityName` fold: accent/case/punctuation-insensitive lookup
* over the catalog's canonical name + aliases. Returns null when unknown — the
* caller keeps the fail-closed policy (skill resolves aliases, never invents a
* slug). A fold shared by two different entries is treated as ambiguous (null),
* so the operator must pass the canonical slug.
*/
const entriesBySearchValue = new Map<string, Set<InstitutionCatalogEntry>>()
const register = (spelling: string, entry: InstitutionCatalogEntry) => {
const key = normalizeSearchPhrase(spelling)
if (!key) return
const bucket = entriesBySearchValue.get(key)
if (bucket) bucket.add(entry)
else entriesBySearchValue.set(key, new Set([entry]))
}
for (const entry of institutionCatalog) {
register(entry.slug, entry)
for (const spelling of institutionSpellings(entry)) register(spelling, entry)
}
/**
* Resolve a token (slug, canonical name or alias) to its catalog entry. Returns
* null when unknown or when the fold is ambiguous.
*/
export const resolveInstitutionEntry = (
value: string | undefined | null,
): InstitutionCatalogEntry | null => {
if (typeof value !== 'string') return null
const trimmed = value.trim()
if (!trimmed) return null
const bucket = entriesBySearchValue.get(normalizeSearchPhrase(trimmed))
if (!bucket || bucket.size !== 1) return null
return [...bucket][0] ?? null
}
/** Canonical name for a token, or null when unknown/ambiguous. */
export const resolveInstitutionName = (value: string | undefined | null): string | null =>
resolveInstitutionEntry(value)?.name ?? null