import { runSimulation } from './simulation-harness'; // Reproduz o cenário exato pedido nas seções 66 e 91 do prompt mestre: // 20 agentes, 10 CPS, 30% answer rate, 10s answer delay, 180s TMA. // Verifica: CPS nunca excedido, concorrência nunca excedida, e que o motor // não oscila descontroladamente (estabiliza). const BASE_CONFIG = { agentCount: 20, maxCps: 10, answerRate: 0.3, answerDelaySeconds: 10, talkTimeSeconds: 180, targetAbandonRate: 0.03, maxWaitForAgentSeconds: 30, durationSeconds: 900, // 15 minutos simulados maxConcurrentCalls: 200, }; describe('runSimulation — cenário de aceite (agente.md seções 66/91)', () => { it('nunca origina mais que o CPS configurado em nenhum segundo', () => { const result = runSimulation(BASE_CONFIG); expect(result.maxCallsInAnySecondWindow).toBeLessThanOrEqual(BASE_CONFIG.maxCps); }); it('nunca excede o máximo de chamadas simultâneas configurado', () => { const tightConfig = { ...BASE_CONFIG, maxConcurrentCalls: 20 }; const result = runSimulation(tightConfig); expect(result.maxObservedConcurrency).toBeLessThanOrEqual(tightConfig.maxConcurrentCalls); }); it('o pacing estabiliza dentro dos limites (não diverge, não oscila para os extremos)', () => { const result = runSimulation(BASE_CONFIG); const lastQuarter = result.ticks.slice(-Math.floor(BASE_CONFIG.durationSeconds / 4)); const pacingValues = lastQuarter.map((t) => t.pacingFactor); for (const p of pacingValues) { expect(p).toBeGreaterThanOrEqual(0.5); expect(p).toBeLessThanOrEqual(3); } // Este cenário (seção 66) é propositalmente desbalanceado: CPS=10 // permite ~27x mais discagem do que 20 agentes com TMA=180s conseguem // sustentar (~0,37 chamadas/s), e o answerDelay é um valor fixo (sem // jitter), então os primeiros agentes ficam livres em lote sincronizado // e o ciclo se repete a cada ~180s. Nesse regime é esperado um resíduo // de oscilação — o que a seção 66 proíbe é bater nos EXTREMOS // configurados (pacingMin/pacingMax) repetidamente, não qualquer // variação. Antes das correções desta rodada, o pacing batia no teto // (3.0) e no piso (0.5) alternadamente a cada ciclo (spread=2.5, a // faixa inteira); agora fica contido a uma banda intermediária. const spread = Math.max(...pacingValues) - Math.min(...pacingValues); expect(spread).toBeLessThan(1.5); // Nunca mais bate no teto configurado (3.0) — só bateria antes da correção. expect(Math.max(...pacingValues)).toBeLessThan(3); }); it('produz alguma chamada originada e alguma atendida (o motor efetivamente disca)', () => { const result = runSimulation(BASE_CONFIG); expect(result.totalOriginated).toBeGreaterThan(0); expect(result.totalAnswered).toBeGreaterThan(0); }); it('reduz o pacing quando os agentes são escassos (menos agentes -> pacing final menor)', () => { const manyAgents = runSimulation({ ...BASE_CONFIG, agentCount: 20 }); const fewAgents = runSimulation({ ...BASE_CONFIG, agentCount: 3 }); expect(fewAgents.finalPacingFactor).toBeLessThanOrEqual(manyAgents.finalPacingFactor); }); it('é determinístico para a mesma seed (reprodutibilidade do teste)', () => { const a = runSimulation({ ...BASE_CONFIG, seed: 7 }); const b = runSimulation({ ...BASE_CONFIG, seed: 7 }); expect(a.totalOriginated).toBe(b.totalOriginated); expect(a.totalAnswered).toBe(b.totalAnswered); }); });