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
24 lines
887 B
TypeScript
24 lines
887 B
TypeScript
/**
|
|
* Retry/backoff dos AI jobs (agente.md secao 108): "retry, exponential
|
|
* backoff, dead-letter strategy, limitar tentativas" — nunca retry
|
|
* infinito.
|
|
*/
|
|
const BASE_DELAY_MS = 30_000; // 30s
|
|
const MAX_DELAY_MS = 30 * 60_000; // 30min
|
|
|
|
/** Backoff exponencial com teto — 2^attempt * base, nunca passa do teto. */
|
|
export function computeBackoffDelayMs(attemptCount: number): number {
|
|
const delay = BASE_DELAY_MS * 2 ** Math.max(0, attemptCount - 1);
|
|
return Math.min(delay, MAX_DELAY_MS);
|
|
}
|
|
|
|
export function computeNextScheduledAt(attemptCount: number, now: Date = new Date()): Date {
|
|
return new Date(now.getTime() + computeBackoffDelayMs(attemptCount));
|
|
}
|
|
|
|
/** Secao 108: "limitar tentativas" — dead-letter (FAILED terminal) quando
|
|
* esgotar. */
|
|
export function isDeadLetter(attemptCount: number, maxAttempts: number): boolean {
|
|
return attemptCount >= maxAttempts;
|
|
}
|