Sub-fase B do modulo de IA: novo servico apps/ai-worker (poll + FOR UPDATE SKIP LOCKED) processa AIJob de TRANSCRIPTION/ANALYSIS disparados automaticamente apos uma gravacao ficar disponivel, respeitando a cascata de privacidade Tenant>Queue>Campaign e o entitlement do Plan. Transcricao separa o WAV estereo em 2 canais (parser proprio, sem ffmpeg) e transcreve cada perna independente; analise sempre redige dados sensiveis antes de sair pro provider e valida o resultado contra o schema antes de persistir. CRUD de AIPromptTemplate/AIPromptVersion em apps/api. Testado ponta a ponta contra o worker real em Docker e Postgres real com RLS (cascata de privacidade em 3 cenarios, WAV sintetico real no object storage, claim/retry/dead-letter reais) — chamada de rede contra OpenAI/Anthropic continua nunca exercitada (mesma restricao de rede desde o Provider Layer). Detalhes em docs/AI_PIPELINE.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01X1HxY46WGU4G1zmVDNKcWw
39 lines
1.7 KiB
TypeScript
39 lines
1.7 KiB
TypeScript
/**
|
|
* Resolução de privacidade de IA em cascata (agente.md secao 122):
|
|
* Campaign > Queue > Tenant (o nível mais específico configurado vence).
|
|
* Tenant é o único nível obrigatório (nunca null no banco, default
|
|
* AI_OFF) — os outros dois só existem como *override* opcional.
|
|
*
|
|
* Simplificação conhecida: `Campaign` guarda a intenção como dois
|
|
* booleanos independentes (`aiTranscriptionEnabled`/`aiAnalysisEnabled`,
|
|
* já existiam desde a fase Campaigns, secao 63) em vez do enum de 3
|
|
* níveis usado em `Tenant`/`Queue` — não há como uma campanha forçar
|
|
* AI_OFF explicitamente quando o tenant/fila já tem um nível mais
|
|
* permissivo (só pode fazer *opt-in*, nunca *opt-out* explícito). Dado
|
|
* que "opt-in" é a direção mais segura por padrão, aceitável por agora;
|
|
* documentado em docs/AI_PIPELINE.md.
|
|
*/
|
|
export type AIPrivacyLevelName = "AI_OFF" | "TRANSCRIPTION_ONLY" | "TRANSCRIPTION_AND_ANALYSIS";
|
|
|
|
export interface ResolvePrivacyParams {
|
|
tenantLevel: AIPrivacyLevelName;
|
|
queueLevel?: AIPrivacyLevelName | null;
|
|
campaignTranscriptionEnabled?: boolean;
|
|
campaignAnalysisEnabled?: boolean;
|
|
}
|
|
|
|
export function resolvePrivacyLevel(params: ResolvePrivacyParams): AIPrivacyLevelName {
|
|
if (params.campaignAnalysisEnabled) return "TRANSCRIPTION_AND_ANALYSIS";
|
|
if (params.campaignTranscriptionEnabled) return "TRANSCRIPTION_ONLY";
|
|
if (params.queueLevel) return params.queueLevel;
|
|
return params.tenantLevel;
|
|
}
|
|
|
|
export function allowsTranscription(level: AIPrivacyLevelName): boolean {
|
|
return level === "TRANSCRIPTION_ONLY" || level === "TRANSCRIPTION_AND_ANALYSIS";
|
|
}
|
|
|
|
export function allowsAnalysis(level: AIPrivacyLevelName): boolean {
|
|
return level === "TRANSCRIPTION_AND_ANALYSIS";
|
|
}
|