Replaces the hardcoded single-extension redirect for AI->human call
escalation with a real Agent Pool + Routing Engine:
- agents/escalations/routing_rules tables (migration 0031), asterisk_call_links
gains tenant_id/current_level/required_skills_json/priority.
- services/routing_service/engine.py: level/tenant/skill filtered agent
selection with atomic (CAS) reservation, no double-booking.
- routing-service: /agents CRUD + /internal/routing/reserve-agent and
/internal/routing/release-agent.
- asterisk-bridge-service: voice_ai.request_handoff now uses the Routing
Engine automatically for any queue_code configured in
ASTERISK_QUEUE_LEVEL_MAP_JSON (all other queue_codes keep the existing
static ASTERISK_TRANSFER_TARGET_MAP_JSON behavior unchanged); new
POST /asterisk/live-calls/{call_id}/escalations entrypoint; agent is
released back to AVAILABLE and the escalation closed when the call ends.
Targets the Tele2 Kazgaz DID +77476456048 (from-tele2-kazgaz context) as the
first queue wired to real L2 routing instead of AI-only.
Known gap (documented in docs/architecture/l1-l2-routing-engine.md):
automatic no-answer retry-to-next-agent needs a small, separately reviewed
dialplan change and is left for a follow-up MR rather than guessed at blind.
Tests: services/routing_service/engine.py covered by
tests/test_routing_engine.py (selection filtering, atomic reservation,
release); existing test_asterisk_bridge_service.py and
test_routing_service_pg_counter.py suites still pass unmodified.
81 lines
3.6 KiB
SQL
81 lines
3.6 KiB
SQL
ALTER TABLE asterisk_call_links ADD COLUMN IF NOT EXISTS tenant_id TEXT;
|
|
ALTER TABLE asterisk_call_links ADD COLUMN IF NOT EXISTS current_level TEXT;
|
|
ALTER TABLE asterisk_call_links ADD COLUMN IF NOT EXISTS required_skills_json TEXT NOT NULL DEFAULT '[]';
|
|
ALTER TABLE asterisk_call_links ADD COLUMN IF NOT EXISTS priority INTEGER NOT NULL DEFAULT 3;
|
|
CREATE INDEX IF NOT EXISTS idx_asterisk_call_links_tenant_id ON asterisk_call_links(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS idx_asterisk_call_links_current_level ON asterisk_call_links(current_level);
|
|
|
|
CREATE TABLE IF NOT EXISTS agents (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
agent_id TEXT NOT NULL UNIQUE,
|
|
tenant_ids_json TEXT NOT NULL DEFAULT '[]',
|
|
extension TEXT NOT NULL,
|
|
endpoint TEXT,
|
|
display_name TEXT NOT NULL,
|
|
level TEXT NOT NULL,
|
|
skills_json TEXT NOT NULL DEFAULT '[]',
|
|
status TEXT NOT NULL DEFAULT 'OFFLINE',
|
|
current_call_id TEXT,
|
|
max_concurrent_calls INTEGER NOT NULL DEFAULT 1,
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
calls_handled_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_agents_agent_id ON agents(agent_id);
|
|
CREATE INDEX IF NOT EXISTS ix_agents_extension ON agents(extension);
|
|
CREATE INDEX IF NOT EXISTS ix_agents_level ON agents(level);
|
|
CREATE INDEX IF NOT EXISTS ix_agents_status ON agents(status);
|
|
CREATE INDEX IF NOT EXISTS ix_agents_current_call_id ON agents(current_call_id);
|
|
CREATE INDEX IF NOT EXISTS ix_agents_enabled ON agents(enabled);
|
|
CREATE INDEX IF NOT EXISTS idx_agents_level_status_enabled ON agents(level, status, enabled);
|
|
|
|
CREATE TABLE IF NOT EXISTS escalations (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
escalation_id TEXT NOT NULL UNIQUE,
|
|
call_id TEXT NOT NULL,
|
|
tenant_id TEXT,
|
|
from_level TEXT NOT NULL,
|
|
to_level TEXT NOT NULL,
|
|
reason_code TEXT NOT NULL,
|
|
required_skills_json TEXT NOT NULL DEFAULT '[]',
|
|
priority INTEGER NOT NULL DEFAULT 3,
|
|
topic TEXT,
|
|
summary TEXT,
|
|
status TEXT NOT NULL DEFAULT 'requested',
|
|
assigned_agent_id TEXT,
|
|
requested_at TEXT NOT NULL,
|
|
connected_at TEXT,
|
|
completed_at TEXT
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_escalation_id ON escalations(escalation_id);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_call_id ON escalations(call_id);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_tenant_id ON escalations(tenant_id);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_from_level ON escalations(from_level);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_to_level ON escalations(to_level);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_reason_code ON escalations(reason_code);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_status ON escalations(status);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_assigned_agent_id ON escalations(assigned_agent_id);
|
|
CREATE INDEX IF NOT EXISTS ix_escalations_requested_at ON escalations(requested_at);
|
|
CREATE INDEX IF NOT EXISTS idx_escalations_call_id_status ON escalations(call_id, status);
|
|
|
|
CREATE TABLE IF NOT EXISTS routing_rules (
|
|
id BIGSERIAL PRIMARY KEY,
|
|
rule_id TEXT NOT NULL UNIQUE,
|
|
from_level TEXT NOT NULL,
|
|
to_level TEXT NOT NULL,
|
|
reason_code TEXT,
|
|
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
priority INTEGER NOT NULL DEFAULT 3,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS ix_routing_rules_rule_id ON routing_rules(rule_id);
|
|
CREATE INDEX IF NOT EXISTS ix_routing_rules_from_level ON routing_rules(from_level);
|
|
CREATE INDEX IF NOT EXISTS ix_routing_rules_to_level ON routing_rules(to_level);
|
|
CREATE INDEX IF NOT EXISTS ix_routing_rules_reason_code ON routing_rules(reason_code);
|
|
CREATE INDEX IF NOT EXISTS ix_routing_rules_enabled ON routing_rules(enabled);
|