feat: no-answer retry, agent status machine, escalation events (ТЗ Phase 2) #8

Merged
arystanbek merged 1 commits from feature/escalation-no-answer-retry-and-agent-state into main 2026-08-30 09:19:59 +00:00
Owner

Phase 2 маршрутизации звонков (после MR!4). Закрывает AC-08 (retry при неответе оператора), AC-16/17 (RINGING/TALKING/AFTER_CALL_WORK), AC-19 (timeline по каждому переходу) + каталог событий §25. Без правок дайлплана — AMI-слушатель начал читать нативные DialEnd/Hangup вместо фильтрации только по UserEvent. Миграция 0033 (postgres+sqlite). Юнит-тесты: tests/test_escalation_retry.py + расширен tests/test_routing_engine.py.

Phase 2 маршрутизации звонков (после MR!4). Закрывает AC-08 (retry при неответе оператора), AC-16/17 (RINGING/TALKING/AFTER_CALL_WORK), AC-19 (timeline по каждому переходу) + каталог событий §25. Без правок дайлплана — AMI-слушатель начал читать нативные DialEnd/Hangup вместо фильтрации только по UserEvent. Миграция 0033 (postgres+sqlite). Юнит-тесты: tests/test_escalation_retry.py + расширен tests/test_routing_engine.py.
arystanbek added 1 commit 2026-08-30 09:19:38 +00:00
Phase 2 of the L1->L2 routing engine (Phase 1: MR!4).

- ami_loop() now also captures native AMI DialEnd/Hangup frames (not
  only UserEvent), needed to detect that an escalated agent did not
  answer. No dialplan change required - Redirect already routes the
  client channel into an existing Dial()-based transfer context, so
  Asterisk emits these events on its own; the listener just wasn't
  reading them before.
- retry_escalation_no_answer(): on NOANSWER/BUSY/CANCEL/CHANUNAVAIL/
  CONGESTION, releases the non-answering agent, excludes it, and
  reserves+redirects to the next available agent via the routing
  engine's existing exclude_agent_ids support. Exhausted pool marks
  the escalation failed and leaves the call with the AI instead of
  dropping the client (ТЗ §32).
- Agent status now actually moves through
  RESERVED -> RINGING -> TALKING -> AFTER_CALL_WORK -> AVAILABLE
  instead of staying stuck on RESERVED for the whole call; a new
  acw_sweep_loop background thread (same pattern as the existing
  failed_retry_loop) times out AFTER_CALL_WORK back to AVAILABLE.
- escalations gains attempt_count/real_agent_id/attempted_agent_ids_json
  (migration 0033); fixes a latent bug where assigned_agent_id stored
  the SIP extension instead of the real agent_id despite routing-service
  already returning it in RoutingAgentReserveOut.
- Every transition now records an interaction timeline entry and
  publishes the ТЗ §25 event catalog (AgentReserved/AgentRinging/
  AgentNoAnswer/AgentConnected/TransferCompleted/TransferFailed)
  through the existing emit_voice_event/EventOutboxRow idempotent path.

Not in this MR (see plan): SLA config, Callback, L3 (needs real
technical agents from the business), metrics.
arystanbek merged commit 66652845d8 into main 2026-08-30 09:19:59 +00:00
Sign in to join this conversation.
No Reviewers
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: digiops/call-center#8