Files
portal/app/config/autopodbor.php
T
Дмитрий a028af4bc8
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
feat(autopodbor): сторож валидности телефонов на шаге 2 (реестр Россвязи + ДаДата)
Экстрактор кода страницы ловил любые 11 цифр (7/8 + телефонная группировка) без
проверки, что это настоящий российский номер — со страниц-агрегаторов пролезал мусор
с несуществующими кодами (227/303/453/613/761) и подписывался «городской».

PhoneValidityGate между CandidateBuilder и SourceAggregator:
- слой 1 (бесплатно, всегда): реестр Россвязи (phone_ranges) — нет в реестре → выкидываем;
- слой 2 (платно, за флагом autopodbor.phone_dadata_enabled, по умолчанию ВЫКЛ):
  DaDataValidityLayer перепроверяет прошедшие реестр номера, снимает qc 2/7,
  под дневным бюджетом + кэш вердикта; платим только за выживших (мусор отсекается ДО ДаДаты).
- fail-open при пустом реестре / сбое БД (иначе внешний catch в SiteOpener съел бы все номера);
- короткие/uncertain номера не трогаем (нельзя сверить).

Закрыты ОБА пути шага 2: обычный (RealCompetitorAgent) и глубокий (SiteOpener).
Миграций нет — реестр уже на проде (453k диапазонов).

Тесты: PhoneValidityGateTest (7), DaDataValidityLayerTest (6), SiteOpenerValidityTest (1);
весь набор автоподбора 519 зелёных, Pint + Larastan чисто.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 10:34:19 +03:00

142 lines
9.4 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
return [
/*
|--------------------------------------------------------------------------
| Живой движок поиска конкурентов (шаг 1)
|--------------------------------------------------------------------------
|
| true → findCompetitors использует НАСТОЯЩИЙ движок (ниша → поиск 2ГИС/Яндекс →
| резолв → сборка). false → демо-заглушка FakeCompetitorAgent. По умолчанию ВЫКЛ —
| включается осознанно (локально/за тумблером), т.к. ходит в живые сервисы.
|
*/
'real_find' => env('AUTOPODBOR_REAL_FIND', false),
/*
|--------------------------------------------------------------------------
| Авто-повтор поиска при ПУСТОМ сборе
|--------------------------------------------------------------------------
|
| Внешние источники (2ГИС/Яндекс/EXA) иногда моргают и отдают пусто. Чтобы
| клиент не видел «ничего не нашлось» из-за секундного сбоя — при пустом сборе
| повторяем поиск N доп. раз. 0 → без повторов. Реально пустая ниша (все попытки
| пусты) идёт штатным путём «empty» без списания.
|
*/
'search_retry_on_empty' => (int) env('AUTOPODBOR_SEARCH_RETRY_ON_EMPTY', 2),
/*
|--------------------------------------------------------------------------
| Путь к бинарнику node для Playwright-скриптов
|--------------------------------------------------------------------------
|
| Из-под PHP-процесса (очередь, artisan) PATH может отличаться от интерактивного
| шелла. Если 'node' по PATH не находится — пропишите абсолютный путь в .env:
| AUTOPODBOR_NODE_BIN="C:\Program Files\nodejs\node.exe"
|
*/
'node_bin' => env('AUTOPODBOR_NODE_BIN', 'node'),
/*
|--------------------------------------------------------------------------
| Глубокий шаг 2 (движок «глобального сбора источников»)
|--------------------------------------------------------------------------
|
| true → studyCompetitor сам доищет след (EXA → площадки → якорный фильтр →
| 2ГИС по имени → Яндекс Playwright). false → старый путь по site_url +
| directory_urls из шага 1. По умолчанию ВЫКЛ — включается осознанно.
|
*/
'deep_study' => env('AUTOPODBOR_DEEP_STUDY', false),
/*
|--------------------------------------------------------------------------
| Разворот по телефонам (сайты проектов застройщика)
|--------------------------------------------------------------------------
|
| true → на шаге 2 по каждому личному телефону фирмы делаем обратный поиск в
| 2ГИС и вскрываем связанные карточки (проекты холдинга): забираем их сайты и
| телефоны, цепочкой. Общие номера (у многих фирм) пропускаем. Требует deep_study.
| По умолчанию ВЫКЛ — включается осознанно (ходит в живой 2ГИС).
|
*/
'phone_reverse' => env('AUTOPODBOR_PHONE_REVERSE', false),
/*
|--------------------------------------------------------------------------
| Разворот по телефонам в Яндекс.Картах (рядом с 2ГИС)
|--------------------------------------------------------------------------
|
| true → на шаге 2 номера фирмы разворачиваем ещё и в Яндексе (для застройщиков,
| которые только в Яндексе). Каждая карточка-кандидат открывается и номер на ней
| проверяется (Яндекс отдаёт рыхлый список). Требует phone_reverse. По умолчанию ВЫКЛ.
|
*/
'phone_reverse_yandex' => env('AUTOPODBOR_PHONE_REVERSE_YANDEX', false),
/*
|--------------------------------------------------------------------------
| Параллельный разворот: 2ГИС и Яндекс одновременно
|--------------------------------------------------------------------------
|
| true → 2ГИС-разворот и Яндекс-разворот запускаются РАЗОМ (двумя процессами,
| Concurrency), итог по времени = max(2ГИС, Яндекс), а не сумма. Оба источника
| отрабатывают полностью — ничего не режется. false → последовательный композит.
| Безопасно: два разных провайдера, каждый одно-поточный, виртуалка держит потолок.
| Требует phone_reverse_yandex. По умолчанию ВЫКЛ.
|
*/
'phone_reverse_parallel' => env('AUTOPODBOR_PHONE_REVERSE_PARALLEL', false),
/*
|--------------------------------------------------------------------------
| Свой рендер-канал (self-render) вместо платного xf4
|--------------------------------------------------------------------------
|
| true → 2ГИС+Яндекс тянутся через свой рендер-сервис на виртуалке (SelfRenderClient
| + прокси; 2ГИС отдаёт initialState → TwoGisJsonReader). false → текущий путь xf4.
| Мгновенный откат тумблером. Требует services.self_render.endpoint/.key (в .env).
| Гранулярные тумблеры (для безопасной раскатки) наследуют master, если не заданы явно:
| self_render_2gis — только 2ГИС на свой рендер;
| self_render_yandex — только Яндекс на свой рендер+прокси.
| По умолчанию ВСЁ ВЫКЛ.
|
*/
'self_render' => env('AUTOPODBOR_SELF_RENDER', false),
'self_render_2gis' => env('AUTOPODBOR_SELF_RENDER_2GIS', env('AUTOPODBOR_SELF_RENDER', false)),
'self_render_yandex' => env('AUTOPODBOR_SELF_RENDER_YANDEX', env('AUTOPODBOR_SELF_RENDER', false)),
/*
|--------------------------------------------------------------------------
| Кэш отрисованных карточек 2ГИС/Яндекс (снизить нагрузку и ускорить)
|--------------------------------------------------------------------------
|
| true → загрузчик карточек оборачивается CachingPageFetcher: отрисованные карточки
| (2ГИС `…/firm/…`, Яндекс `…/maps/org/…`) кладутся в Redis по ключу URL на N дней и
| повторно берутся из кэша — без обращений к 2ГИС/Яндекс/прокси. Кэш ОБЩИЙ для шага 1
| (поиск) и шага 2 (изучение) и для повторных прогонов. Поиск (`…/search/…`) и ленты —
| всегда свежие (мимо кэша), поэтому повторный сбор всё равно находит новые фирмы.
| Пустой ответ не кэшируется (сбой не отравит кэш). По умолчанию ВЫКЛ — откат тумблером.
|
*/
'render_cache' => env('AUTOPODBOR_RENDER_CACHE', false),
'render_cache_ttl_days' => (int) env('AUTOPODBOR_RENDER_CACHE_TTL_DAYS', 5),
/*
|--------------------------------------------------------------------------
| Второй слой отсева телефонов через ДаДату (после реестра Россвязи)
|--------------------------------------------------------------------------
|
| Пойманные при «вскрытии» кода страницы номера всегда чистятся реестром Россвязи
| (бесплатно): номера с несуществующим кодом → выкидываются. Этот флаг добавляет
| ВТОРОЙ, ПЛАТНЫЙ слой — ДаДата clean/phone (~0,6 ₽/номер) перепроверяет уже прошедшие
| реестр номера и снимает подтверждённый мусор/иностранцев (qc 2/7). Платим только за
| реальных выживших (мусор отсекается бесплатно ДО ДаДаты), под дневным бюджетом
| services.dadata.daily_cap_rub, с кэшем вердикта. По умолчанию ВЫКЛ — включается
| осознанно (тратит деньги). Требует services.dadata.api_key/secret.
|
*/
'phone_dadata_enabled' => env('AUTOPODBOR_PHONE_DADATA_ENABLED', false),
];