Commit Graph

3 Commits

Author SHA1 Message Date
Дмитрий d4ccb5ac95 fix,смс: оператор и часовой пояс номера — из бесплатного справочника Россвязи
Модуль спрашивал и оператора, и регион ТОЛЬКО у платной ДаДаты, а без её
полезного ответа выходил молча. На бою это убило две трети модуля: рассылки
«по своей базе» и «списком руками» не отправляли ни одного сообщения —
причина skipped_unknown_operator, живой прогон 03.08.2026.

Теперь порядок такой: сперва ДаДата — только она знает про перенос номера к
другому оператору, — затем справочник phone_ranges, который дозаполняет
оставшееся пустым и никогда не затирает уже известное. Тем же справочником
страхуется приём лидов от поставщика.

Починено в трёх местах: обогащение своей базы номеров, догон снимка рассылки
и предпросмотр. Предпросмотр врал клиенту «ждут уточнения региона» про
номера, которые справочник знает наизусть, и смету на них не показывал.
Исчерпанный дневной лимит ДаДаты больше не обрывает догон целиком: бесплатный
справочник спрашивается всё равно, и остаток списка не ждёт следующего часа.

Сторож считает ЗАПРОСЫ: предпросмотр списка на 20 000 номеров, спрашивая
справочник по одному номеру, давал 20 044 запроса и 22 секунды на одно
нажатие кнопки. Пачками — 64 запроса и 4,6 секунды.

Тесты писались первыми и проверены красными; сторож пачечного запроса
проверен вырезанием. Модуль СМС 399 из 399, статанализ 0.
2026-08-03 12:05:13 +03:00
Дмитрий d61b552532 feat(смс-клиент): ДаДата даёт регион и оператора всем номерам, снимок дозревает
Этап 3 «Время и цена», Task 5. Строка листа 3.3 закрыта целиком.

Номер, про который мы ничего не знаем, перестал быть тупиком: теперь его судьбу решает
ДаДата — и решает ОДНИМ платным вопросом, потому что пояс и оператор приезжают в одном
ответе. Входов обогащения три, и все три ведут в одно и то же место.

Своя база: джоб обогащения контактов кладёт пояс рядом с оператором и догоняет номера,
загруженные до Этапа 3, — берёт контакт, у которого пусто хоть что-то одно. Без догона
такой номер после выката не получил бы СМС никогда.

Рассылка: новый EnrichClientSmsSnapshotRegionJob спрашивает про строки снимка без пояса и
пишет туда и пояс, и оператора. У номеров, вписанных руками, оператора не было вовсе —
это и есть закрытие старой жалобы В-69. Ставится из контроллера сразу после снимка и
только если такие строки есть; узнав регион, сам будит рассылку, чтобы созревшие номера
не ждали четверть часа зря. Будит ТОЛЬКО ждущую утра: у остановленной клиентом рассылки
статус другой, и повторный запуск затёр бы ей «остановлена» на «готово».

Авто-СМС: сделка без региона спрашивает ДаДату по номеру лида (В-100) — иначе заведённый
руками лид не получил бы СМС и после этой задачи. За номер платим один раз: узнанный пояс
несётся в самом отложенном задании, а «ответила, но пояса нет» помечается отметкой; заново
спрашиваем только если вопрос не состоялся — сбой сети или выбранный дневной лимит (В-104).

Дозревание: команда добора ставит окончательную причину строкам, которые ждут региона
дольше суток, и будит рассылку, чтобы та дописала причину в журнал и закончилась — деньги
размораживаются. В журнал пишет джоб отправки, он это и так умеет для всех пропущенных;
второго места, пишущего в журнал, не завёл (В-107).

Кого НЕ спрашиваем (В-105): строку, у которой пояс есть, а оператор пуст. Она и так уйдёт
универсальным каналом, вопрос был бы ради экономии на канале, а ДаДата — наши деньги:
рассылка на 20 000 номеров это до 12 000 ₽ при ещё не назначенном потолке расходов (В-90).
Платим только там, где без ответа сообщение не уйдёт вовсе.

Прогоны: СМС 217/217 (было 202, +15 новых), приём лидов 17/17, фронт 1663 зелёных,
phpstan 0, pint чисто. Миграций нет — колонки пояса завёл ещё Task 2.

Вырезанием проверено пятью вырезами: убрал запись пояса — покраснел «узнав регион, номер
уходит»; убрал суточный срок — покраснел «не узнали за сутки»; убрал вторую половину
условия пробуждения — покраснел денежный тест про заморозку; убрал вопрос ДаДате в
авто-СМС — покраснели три теста третьего входа; убрал догон старых номеров базы —
покраснел «догоняет уже загруженный номер».

Живьём на локальной базе (19:19 МСК, ДаДата заглушена, живой ключ не тронут): вписанные
руками номера узнали пояс и оператора, московский ушёл сразу, камчатский отложен до
10 утра его времени; номер, про который ДаДата молчит, через сутки получил причину и
рассылка стала «готово»; сделка без региона получила авто-СМС по ответу ДаДаты, а сделка,
про которую ДаДата молчит, ждёт дальше и в журнал не пишет. Стенд возвращён как был.

🪤 Урок В-106: повторный Http::fake() прежнюю заглушку не заменяет — отвечает первая, и
тест «а теперь ДаДата отвечает» молча проверяет старый ответ.

🟢 Запрет на выкат ветки СНЯТ (был из-за В-93): ни один источник больше не отправляет ноль.
Выкатывать всё равно рано — Этап 3 не закончен, цена считается по-старому.
2026-07-28 19:28:14 +03:00
Дмитрий bc573a99eb feat(смс-клиент): своя база — загрузка Excel, пример файла, оператор через ДаДату, отброшенные не молча
Доработки экрана «Моя база» клиентской СМС-рассылки по просьбе владельца:

- Кривые номера больше не выбрасываются молча: uploadContacts/uploadContactsFile
  возвращают rejected + rejected_samples, экран показывает «Добавлено: N,
  не распознали: M (например: …) — проверьте формат».
- Таблица базы — только «Номер» и «Оператор» (колонку «Имя» убрал).
- Загрузка базы Excel-файлом: сервис ClientSmsPhoneFileReader (phpspreadsheet)
  читает первую колонку, пропускает заголовок, номера-как-числа не уходят в
  научную нотацию; endpoint POST /api/sms/contacts/file; на экране — выбор файла
  и кнопка «Загрузить файл».
- «Скачать пример файла»: ClientSmsBaseExampleWriter + GET /api/sms/contacts/example
  — готовый .xlsx с колонкой «Телефон» и образцами номеров.
- Оператор через ДаДату: EnrichClientSmsContactsOperatorJob обогащает добавленные
  номера (DaDataPhoneClient.provider) в фоне, под tenant-контекстом (RLS), с
  защитой дневного бюджета (DaDataBudgetGuard); сбой ДаДаты не роняет прогон,
  оператор остаётся пустым; уже известного оператора не перезапрашиваем.

Тесты: бэкенд ClientSms 130/130, фронт СМС-спеки 63/63 — зелёные; pint чист;
портал пересобран. Синтетические номера 7999… в тестах.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-26 21:11:13 +03:00