# Discovery-brief: переделка миграции проектов + распределения лидов

**Дата:** 2026-05-20 · **Режим:** FEATURE (discovery-interview) · **Статус:** зафиксировано заказчиком, реализация НЕ начата.

## Проблема

Два связанных изменения в логике создания/миграции проектов:

1. Экспорт проекта Лидерра → портал поставщика crm.bp-gr.ru сейчас неполный и отложенный (каркас при создании, параметры — только ночью).
2. Алгоритм распределения входящих лидов между клиентами не имеет потолка получателей — один номер может уйти 20 клиентам, владелец номера «сходит с ума».

## Архитектура (как есть)

- Клиент (tenant) создаёт/правит проект в ЛК → `ProjectService` запускает `SyncSupplierProjectJob` (очередь) — ставит на портал **каркас** (лимит 0, дни — вся неделя, регионы пусто).
- Ночной `SyncSupplierProjectsJob` (крон 20:30 МСК, `app/routes/console.php:52`) сверяет квоты/дни/регионы и дописывает на портал через `FailoverProjectChannel` (ярус1 AJAX → ярус2 форма → ярус3 ручная очередь).
- Входящий лид → `RouteSupplierLeadJob` → `LeadRouter::matchEligibleProjects` → Deal-копия каждому eligible клиенту.

## Зафиксированные требования

### Канал экспорта — два режима

- **R1. Режим «Онлайн».** Создание/изменение проекта в ЛК → перенос поставщику сразу, с полными параметрами (лимит/дни/регионы), не каркасом.
- **R2. Режим «Пакетный»** (текущий ночной) — оставить, но время **20:30 → 18:00 МСК**. Снижает нагрузку при многократных правках одного проекта за день.
- **R3.** Выбор режима — переключатель в админке.
- Мотив: онлайн нужен для быстрой отработки/тестирования миграции; пакетный — для прод-нагрузки.

### Маппинг формы проекта (подтверждено живым тестом на портале)

- **R5.** Слать **один** `save` с тремя флагами `srcrt+srcbl+srcmt` — портал сам создаёт 3 проекта (B1/B2/B3). Сейчас код шлёт 3 раздельных save. Меньше нагрузки.
- **R6.** Лимит делит **сам портал поровну** (проверено: лимит 15 → проекты по 5). Убрать наш ручной split в `SupplierQuotaAllocator::distributeForPlatform`.
- **R7.** `tag` = **название региона** клиента (не `_lidpotok`). При 2+ регионах — **отдельный save на каждый регион** (1 регион → 3 проекта, 2 → 6). Тег региона приходит обратно в лиде (`raw_payload['tag']`) → **протянуть в `deals`** (поле тег = регион, для дальнейшей работы со сделками).

### Алгоритм распределения лидов (полностью пересмотрен — группировка ВЫКИНУТА)

Решения, принятые в диалоге (нюансы заказчика: заказ ≠ поставка; платим за фактически поступившие лиды; лимит — жёсткий потолок, недобор допустим):

- **Заказ у поставщика** = `max( наибольший_лимит , ceil(Σ всех лимитов / 3) )`.
  - `ceil(Σ/3)` — ёмкость шаринга (один лид продаётся максимум 3 раза).
  - `наибольший_лимит` — крупнейший клиент должен иметь достаточно разных лидов, чтобы добрать.
  - Заказ = потолок запроса; придёт ≤; платим за фактически поступившие.
- **Распределение лида** = 3 случайным клиентам из тех, у кого остаток лимита > 0 (`получено_сегодня < лимит`).
  - cap=3 — защита владельца номера;
  - выбор только из недобравших → лимит-потолок не превышается;
  - недобор допустим (поставщик шлёт сколько хочет).
- **Группировка клиентов НЕ нужна** — рандом из недобравших сам обеспечивает cap=3 + соблюдение лимита + максимизацию шаринга.

### Примеры расчёта заказа (verified в диалоге)

| Клиенты | Σ | наиб. лимит | ceil(Σ/3) | Заказ |
|---|---|---|---|---|
| 5, 5, 10, 20 | 40 | 20 | 14 | **20** |
| 15×5 + 10 (16 клиентов) | 85 | 10 | 29 | **29** |
| 3×15 | 45 | 15 | 15 | **15** |
| 3×15 + 30 | 75 | 30 | 25 | **30** |
| 4×10 | 40 | 10 | 14 | **14** |

## Что НЕ так в текущей реализации (пины)

- **Заказ:** `SupplierQuotaAllocator::allocate` (`app/app/Services/Supplier/SupplierQuotaAllocator.php:55`) суммирует `Σ daily_limit` + делит на B1/B2/B3 (`:73`). Надо: формула `max(наиб, ceil(Σ/3))`, split убрать (портал делит сам).
- **Распределение/cap:** `LeadRouter::matchEligibleProjects` (`app/app/Services/LeadRouter.php:46`) возвращает всех eligible; `RouteSupplierLeadJob` (`app/app/Jobs/RouteSupplierLeadJob.php:115`) создаёт копию каждому — нет cap=3, нет рандома.
- **Время крона:** `app/routes/console.php:52` — 20:30, надо 18:00.
- **Экспорт по одному флагу:** `SupplierPortalClient::toPayload` (`app/app/Services/Supplier/SupplierPortalClient.php:422`) шлёт один src-флаг; `SyncSupplierProjectJob` (`app/app/Jobs/SyncSupplierProjectJob.php:64`) — раздельные save по платформам.

## Открытые под-вопросы (для brainstorming перед реализацией)

1. Scope переключателя режима — глобально (SaaS) или per-tenant?
2. Поведение онлайн-режима при недоступном портале — эскалация в ярус-3 очередь, как сейчас?
3. Тег при «вся РФ» (регион не выбран) — пустой?
4. Имя «Конкурент 1» на портал не уходит (в name едет номер донора) — нужно ли тянуть человекочитаемое имя?
5. Ключ конкуренции клиентов за поток (источник+регион+день) — как именно сопоставляется регион.

## Следующий шаг

Эпик (новые режимы экспорта + переписка квот/маршрутизации + админка). Реализацию начинать через `brainstorming` (закрыть под-вопросы 1–5) → `writing-plans` → TDD.

## Прочее (сессия 2026-05-20)

- Webhook-канал чинили (secret <32 после re-seed → 404; восстановлен).
- CSV reconcile здоров.
- Тестовые проекты на портале от живого теста R5: id `12742042/12742043/12742044` (`*_LIDERRA_TEST_DELETE_ME`) — заказчик удалит сам.
