# Routing Маршрутизация звонков состоит из двух уровней: 1. **Routing Rules** — priority-based правила с regex-матчингом (first-match-wins) 2. **Balancing Mode** — стратегия выбора ноды внутри правила или глобально ## Routing Rules Правила хранятся в `data/routes.json` и проверяются в порядке `Priority` (меньше = раньше). Первое совпавшее правило определяет маршрутизацию. Если правило не сработало (нода мертва / пул пуст) — переход к следующему. Если ни одно правило не совпало — глобальный `PickNode()`. ### Match Conditions (AND-логика) Все непустые поля должны совпасть одновременно. Пустые поля — не проверяются. | Поле | Тип | Описание | |------|-----|----------| | `match_caller_id` | regex | Caller ID звонящего | | `match_destination` | regex | Номер назначения | | `match_ingress_trunk` | regex | ID входящего транка | | `match_days` | string | Дни недели: `"1-5"` (пн-пт), `"1,3,5"` (пн,ср,пт), `"6"` (сб). Воскресенье = 0 или 7 | | `match_time_start` | `"HH:MM"` | Начало временного диапазона | | `match_time_end` | `"HH:MM"` | Конец временного диапазона | ### Action Types | Тип | Поле | Описание | |-----|------|----------| | `node` | `action_node_id` | Маршрутизация на конкретную ноду. Если нода dead/stale/lethal — fallthrough к следующему правилу | | `pool` | `action_pool_ids` | Выбор лучшей ноды из заданного списка (по текущему режиму балансировки). Пустой/нездоровый пул — fallthrough | | `auto` | — | Fallthrough к глобальному `PickNode()` с учётом режима балансировки | ### Пример правила ```json { "id": "rule-001", "name": "VIP caller to dedicated pool", "enabled": true, "priority": 10, "match_caller_id": "^7495", "match_destination": "", "match_ingress_trunk": "", "match_days": "1-5", "match_time_start": "09:00", "match_time_end": "18:00", "action_type": "pool", "action_pool_ids": ["pbx-01", "pbx-02"], "description": "VIP номера 7495 в бизнес-часы на выделенные ноды" } ``` ## Balancing Modes Режим балансировки определяет, **как** выбирается нода из множества healthy-нод (после отсева lethal). Применяется как в глобальном `PickNode()`, так и внутри `pool`-правил. ### best Выбирает ноду с **максимальным score** из кэша. O(1) — `bestScore` предрассчитывается при каждой метрике. - Всегда одна и та же нода при одинаковых метриках — максимально нагружает лучшую ноду - Хорошо для тестирования и сценариев где нужна предсказуемость ### weighted_random (по умолчанию) Выбирает ноду **пропорционально score**: чем выше score, тем выше вероятность быть выбранной. O(n) — сканирует все ноды. Алгоритм: 1. Отсеивает disabled, stale, lethal (score < 0) 2. Суммирует score всех кандидатов (`totalScore`) 3. `pick = rand(0, totalScore)`, идёт по кандидатам, вычитая score 4. Если `totalScore <= 0` — равновероятный выбор - Распределяет нагрузку пропорционально готовности - Предсказуемость ниже, но утилизация выше — менее загруженные ноды получают пропорционально меньше звонков ### Переключение режима **Через API:** ``` PUT /api/route/mode Authorization: Bearer {"mode": "best"} ``` **Через конфиг:** ```json { "balance_mode": "weighted_random" } ``` Допустимые значения: `"best"`, `"weighted_random"`. API-эндпоинт сразу применяет режим и сохраняет в `config.json`. ## Ответ /api/route В ответе маршрутизации появилось поле `matched_rule` — имя сработавшего правила (или `"none"` если правило не применилось): ```json { "node_id": "pbx-03", "score": 88, "sip_gateway": "sip:pbx03.lan:5060", "matched_rule": "VIP to dedicated" } ```