Commit Graph

669 Commits

Author SHA1 Message Date
Дмитрий 29b90c5759 feat(autopodbor): удаление предложения показывает пул связанных строк
Клик «Удалить» у предложения открывает окно со всеми предложениями,
найденными в том же месте (совпал url карточки/сайта в «где нашли»), с
галочками. По умолчанию отмечена только нажатая строка; клиент выбирает,
какие связанные строки удалить. «В работе» — прежнее окно. Массовое
удаление предложений — подтверждение со списком выбранных.

Только показ и порядок «сначала выбери, потом удали»; БД/бэкенд не тронуты.
Тесты: пул по url, строгое совпадение, дефолт-галочка, удаление отмеченных.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:54:50 +03:00
Дмитрий ab6d1b59a1 docs(autopodbor): дизайн кэша отрисованных карточек (5 дней) + хендофф
Решение владельца: общий Redis-кэш карточек 2ГИС/Яндекс на 5 дней, единый для шага 1
(поиск) и шага 2 (изучение) и повторных прогонов. Кэшируем ТОЛЬКО карточки (/firm/,
/maps/org/); поиск (/search/) и ленты Яндекса — всегда свежие (добывание новых фирм не
ограничиваем). Кэш ограничивает свежесть только у уже найденных карточек — редко меняются.

Замер на САПС (счётчик скачиваний): 12 повторных открытий / ~54с внутри одного сбора
(карточка Метрополиса тянулась 3× — ingest+поиск+разворот); повторный сбор в течение 5 дней
даёт ~200с+ и резкое падение нагрузки на 2ГИС/Яндекс/прокси.

План реализации — через TDD за флагом AUTOPODBOR_RENDER_CACHE (ВЫКЛ по умолчанию), TTL
AUTOPODBOR_RENDER_CACHE_TTL_DAYS=5. Плюс промт-хендофф для следующей сессии.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 10:07:47 +03:00
Дмитрий 0131ecfc47 fix(autopodbor): якорь по данным клиента на шаге 2 + параллельный разворот
Качество. Привязанные клиентом карточки 2ГИС/Яндекс теперь используются как
«земля» и якорь холдинга (раньше выбрасывались). Слепой поиск по имени берётся
только при наличии якоря; AnchorGate: пустой якорь → drop (было keep-всё — корень
шума). Без сайта чужие фирмы по широкому имени («Притяжение» → стоматология,
салоны, эротический магазин) больше не попадают в карточку конкурента. Доказано на
живых данных (Сибагропромстрой): свежий полный сбор — 0 салонов, только номера
застройщика.

Скорость (за флагом AUTOPODBOR_PHONE_REVERSE_PARALLEL, ВЫКЛ по умолчанию).
ParallelPhoneReverse крутит 2ГИС- и Яндекс-разворот ОДНОВРЕМЕННО (итог по времени
= max, не сумма; оба источника отрабатывают полностью — ничего не режется).
Сверка последовательно vs параллельно: данные один в один, потерь нет. Таймаут
подпроцесса 600с (драйвер process иначе берёт дефолт Symfony 60с и убивал разворот).

Тесты: AnchorGate (пустой якорь → drop; телефон-якорь режет чужое), якорь
привязанной карточки режет салоны, ParallelPhoneReverse (слияние на sync-драйвере).
Весь Autopodbor 464/464, Pint чист.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 08:54:48 +03:00
Дмитрий 113a3f5aca docs(autopodbor): итог сессии — виртуалка живёт, ускорение шага 1 и шага 2
Дописан блок «УСКОРЕНИЕ 06.07» в ранбук фазы 8: числа, что откатано и почему, где узкое место шага 2, следующий рычаг. Ссылка на коммит 9022b15b.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 05:02:11 +03:00
Дмитрий 9022b15b42 perf: ускорение автоподбора — рендер-канал и шаг 2
render-service: умное ожидание вместо фикс-пауз. 2ГИС карточка 7.2 -> 1.6с, поиск 17 -> 5с, Яндекс-карточка 6.5 -> 2.8с. Качество проверено вживую.
шаг 2: таймаут открытия сайта 45 -> 15с, убран лишний повтор, карточки 2ГИС пачкой htmlBatch. Полнота сохранена, скорость выросла.
доки: ранбук выката рендер-виртуалки, фаза 8.

Ветка worktree-avtopodbor, не прод. Тесты 459/459, Pint чист.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-06 04:58:46 +03:00
Дмитрий 31828d339e docs(autopodbor): handoff — стройка self-render завершена (код+прокси), осталась фаза 8
Весь код канала готов (459/459 тестов), рендер-сервис проверен на живом 2ГИС,
мобильный прокси Proxy.Market куплен (Россия/Москва/Tele2, безлимит, до 04.08).
Секреты прокси + RENDER_KEY — в gitignored render-service/.env (в гит не идут).
Осталась фаза 8: поднять виртуалку + включить флаг + ручная сверка владельцем.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:56:43 +03:00
Дмитрий 1a0729a327 docs(autopodbor): статус self-render — фазы 1-7 готовы + хвост по Яндексу
План: таблица статусов фаз с коммитами (1-7 , 8 и приёмка — за владельцем).
Спека §5.3: помечен follow-up — node-скриптовые точки Яндекса (листинг шаг 1,
разворот) пока на локальном Playwright под self_render (не xf4, без прокси).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:07:26 +03:00
Дмитрий 87566d31f4 feat(autopodbor): спайк 2ГИС initialState — вердикт GO + фикстуры 3 типов
Разведка живого 2ГИС (worktree, НЕ прод): из window.initialState чисто
достаётся список фирм-в-фирму для всех 3 типов URL (листинг/карточка/
поиск-по-телефону). Главный риск проекта снят.

- Фикстуры (реальные initialState+HTML): tests/fixtures/autopodbor/2gis/
  listing (12 ломбардов), card (Ломбардико: сайт+телефон), phone (холдинг).
- Карта JSON-путей: pagination-список результатов + entity.profile (имя/
  город/рубрика/сайт/телефоны). Идти ТОЛЬКО по pagination, не по ключам.
- Вскрыт рецепт рендера museum double-tap: гейт «обновите браузер» дважды
  кодирует return_url (%20→«20», отсюда старый мусор). Обход — двойной заход.
- Findings: docs/superpowers/findings/2026-07-05-2gis-initialstate-spike.md
- План реализации (8 фаз): docs/superpowers/plans/2026-07-05-self-render-*.md
- Спека v4 обновлена: риск initialState снят, double-tap задокументирован.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:30:19 +03:00
Дмитрий 33d7dd5847 docs(autopodbor): handoff сессии «скорость → свой рендер-канал» + спека v4
Единый файл-передача: замер 25→9 мин, 3 глубокие ревизии (шаг2/разворот,
JSON=initialState-спайк, топология Б), утверждённые решения, следующий шаг
(writing-plans, первым — спайк GO/NO-GO), окружение и грабли. Переживёт компакт.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:52:47 +03:00
Дмитрий a813a0be30 docs(autopodbor): спека v4 — топология Б (виртуалка = рендер-сервис, портал не трогаем)
Третий проход поднял межсистемные стыки: деньги списываются ВНУТРИ задания
(chargeForRun → balance_transactions), значит вариант A сажал биллинг/RLS/
очередь/оркестрацию на рендер-машину. Переход на вариант Б: виртуалка = тупой
Node-сервис рендера («свой xf4»), портал зовёт по HTTP на том же шве ->html().
Деньги/база/RLS/админка/проекты/другие очереди — ОСТАЮТСЯ на портале, бит-в-бит.
Добавлено §4.2 таблица неприкосновенности остальной системы; §5.6 рендер-сервис
(не app-узел, без БД/Redis); §12 SelfRenderClient как HTTP-клиент к виртуалке;
§8 фаза 5 без роли БД. Вариант Б снимает риски биллинга/RLS/очереди варианта A.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:48:14 +03:00
Дмитрий d8c255cf4c docs(autopodbor): спека v3 — де-риск JSON спайком + wiring + план Б
Второй построчный проход + живая разведка 2ГИС вскрыли главное: допущение
«JSON = чисто и просто» НЕ подтверждено. Источник = window.initialState
(356КБ), но извлечь список фирм нетривиально (наивный обход берёт города-
спутники), а телефонов/сайтов в листинге НЕТ (только в карточках) — вскрытие
карточек остаётся. Правки: §5.2 реальный источник+риск; §8 фаза 1 = СПАЙК
с GO/NO-GO до стройки; §12 точки подмены (DeepStudyCollector жёстко завязан
на XfetchClient — развязать на интерфейс; шаг 2 последователен — reuse, но
не кратно; гранулярность флага); §13 риски+план Б (initialState неподъёмна →
план Б, xf4 не рискуем зря).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:34:27 +03:00
Дмитрий 92b38a23be docs(autopodbor): спека — закрыты стыки после построчной ревизии
Инвентаризация кода вскрыла пропуски: 2ГИС/Яндекс живут не только в шаге 1.
Добавлено: §4.1 полная карта 7 точек добычи (шаг 1 листинг/карточки, шаг 2
изучение, разворот по телефонам 2ГИС+Яндекс, резолв) + 3 типа URL 2ГИС;
§5.2 явный стык JSON↔HTML через TwoGisJsonReader с инвариантом контракта;
§5.3 прокси на ВСЕХ 4 Яндекс-точках; §5.4 трафик прокси = деньги + отсечка
картинок; §5.6 роль БД с виртуалки; §5.7 гарантия «шаг 2 и разворот не
теряются, xf4 не остаётся нигде при флаге ВКЛ»; §7 тесты на 3 типа URL +
контракт-идентичность + регресс шага 2. Недельная работа сохраняется 1:1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:25:36 +03:00
Дмитрий a2e5fdd7fd docs(autopodbor): спека «свой канал 2ГИС+Яндекс на виртуалке» (замена xf4)
Дизайн утверждён владельцем в брейншторме: рендер своим браузером с
маскировкой + ротация RU-прокси вместо платного xf4, чистый разбор 2ГИС
через перехват встроенного JSON (убирает трэш), постоянный браузер,
всё на выделенной виртуалке (не на боевом портале), за флагом self_render
с откатом на xf4. Планка приёмки — фирма-в-фирму с живым 2ГИС.
UI/кнопки/функции не трогаются. Замер: 25 мин xf4 → 9 мин свой рендер.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 17:17:11 +03:00
Дмитрий 7d6516e6b2 feat(autopodbor): журнал слияний + возврат поглощённой карточки
Слияние конкурентов больше не «чёрная дыра» (баг «САПС/Метрополис»: карточка
исчезла без следа, вернуть было нечем). Теперь каждое слияние пишет событие в
autopodbor_merge_events со СНИМКОМ поглощённых карточек (имя/сайт/телефоны/
справочники), кто и когда слил.

- Таблица autopodbor_merge_events (+RLS tenant_isolation, паттерн 1:1 с sources,
  rls-ревью OK; nullOnDelete на survivor_id/user_id — история не рушится).
- AutopodborCompetitorMerger::merge($tenantId,$ids,$name,$userId) пишет событие.
- GET /field/merge-events — история; POST /field/merge-events/{id}/restore —
  возврат поглощённых как предложения (защита от повторного возврата).
- Фронт: панель «🕘 История слияний» + «↩ Вернуть карточку».

Бэк 428/428, фронт 12/12, Pint чистый, build ок. schema.sql канон-синк всех
таблиц автоподбора — предвыкатный TODO (вся фича migration-only в этой ветке).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 13:52:43 +03:00
Дмитрий f06b336363 feat(autopodbor): кросс-ящик дублей поле↔предложения на экране поля (доведён до рабочего)
Кнопка «Найти и объединить дубли» на экране поля теперь ловит и предложения,
дублирующие карточки поля (scope=cross): счётчик учитывает их, окно помечает
«· предложение», объединение сливает предложение в карточку поля (выживает поле,
merger не трогали). Бэкенд оставляет только группы с ≥1 карточкой поля — пары
чисто-предложений на экране поля не мусорят.

Файл-статус сессии — docs/superpowers/findings/2026-07-05-avtopodbor-session-status.md.

Feature 154/154, фронт 32/32, Pint чистый, билд собран.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 11:19:38 +03:00
Дмитрий 9fce28fda8 feat(autopodbor): Яндекс-разворот по телефонам на шаге 2 (рядом с 2ГИС)
Для застройщиков, которые сидят только в Яндексе. По каждому номеру ищем в
Яндекс.Картах, но список рыхлый — поэтому КАЖДУЮ карточку открываем и оставляем
только те, где искомый номер реально есть. Это отсекает «Похожие места рядом»,
рекламу и рыхлые совпадения по бренду. Оба справочника объединяются
(CompositePhoneReverse), дубли по домену схлопываются.

Переиспользована готовая механика Яндекса: render-yandex-list.cjs (список) +
DirectoryParser::parseYandexCard (карточка). Браузерные адаптеры за интерфейсами
(YandexPhoneSearch/YandexCardReader) — логика TDD-ится на фейках; порог общей
линии считается по подтверждённым карточкам.

За отдельным тумблером AUTOPODBOR_PHONE_REVERSE_YANDEX (по умолчанию ВЫКЛ,
требует phone_reverse). Юнит 267/267, feature 150/150. План и находки —
docs/superpowers/{plans,findings}/2026-07-05-*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 09:30:27 +03:00
Дмитрий 36c3a7fe7f feat(autopodbor): разворот по телефонам на шаге 2 (сайты проектов застройщика)
По личным телефонам фирмы обратным поиском в 2ГИС вскрываем связанные карточки
холдинга (проекты застройщика) — забираем их сайты и телефоны, цепочкой «до талого».
Общий номер (у >4 фирм) пропускаем; visited-set + потолок карточек; уже известную
главную карточку не дублируем. Защита от AutoMoney: реклама отсечена парсером,
общий номер обрублен до разворота, находки не клеятся молча.

За тумблером AUTOPODBOR_PHONE_REVERSE (по умолчанию ВЫКЛ). Юнит 259/259, feature
146/146. Решение и план — в docs/superpowers/{findings,plans}/2026-07-05-*.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 07:55:03 +03:00
Дмитрий f1352083a1 docs(autopodbor): решение по сайтам проектов застройщика (ДСК) — что упустили и план
Застройщик = холдинг из проектов-ЖК, каждый со своим сайтом; шаг 2 берёт только
главную карточку. Решение: проекты = элементы конкурента, авто-открытие из 2ГИС/
сайта (нужен го владельца), не переобрабатывать уже собранную главную. Эталон —
ДСК (tenant 38, competitor 720). Открытые вопросы О-1..О-4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 06:49:16 +03:00
Дмитрий f757d8adaf docs(autopodbor): handoff сессии 04-05.07 - что сделано по #2, что открыто (#1 прогресс, баг городов, похожесть)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 03:42:41 +03:00
Дмитрий 47bc508246 docs(autopodbor): находки похожесть-погода и конкурент-из-элементов + план Часть 1
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 18:14:54 +03:00
Дмитрий 66a0f2bb67 docs(автоподбор): снимок 04.07 — движок шага 2 на ветке + приёмка 385 + UI + что осталось
Полный статус: движок перенесён (134 теста, ревью «Ready»), 385 подтверждён 1:1 с
ЭТАЛОНом живьём, корневой баг «нашлось мало» = завис queue:work (починен перезапуском),
UI (метки/регион-тайпахед/групповое создание) закоммичен ac3d6975. Осталось: приёмка
412/414/416/420, открытый вопрос дефолта «Вся РФ», перенос на прод по слову.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-04 11:13:25 +03:00
Дмитрий 2907a02d45 docs(автоподбор): план реализации переноса движка шага 2 (TDD, 12 задач)
12 задач по TDD: тумблер+is_federal, CompetitorGeo, DomainKinship (близнецы
НЕ сливать), PlatformClassifier, отпечаток+запросы EXA, AnchorGate,
FootprintSearch, SiteOpener, DeepStudyCollector (маппинг 1:1 со словарём
чипов песочницы), развилка по тумблеру, DI, приёмка на 5 против ЭТАЛОНа.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:32:17 +03:00
Дмитрий 07fc46d524 docs(автоподбор): дизайн переноса движка «глобального шага 2» в боевой шаг 2
Закон: результат боевого шага 2 строго 1:1 с песочницей. Полный движок за
тумблером autopodbor.deep_study, старый путь — запасной. Явная НЕ-цель:
сайты-близнецы НЕ сливать (красломбард.рф и kraslombard24 — две отдельные
записи). Хранение/биллинг/идемпотентность не трогаем. Приёмка — те же 5
конкурентов через боевой шаг 2 = одобренные артефакты bylo-stanet.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 22:17:19 +03:00
Дмитрий ec4a2b178c feat(автоподбор): песочница «глобального шага 2» + артефакты было/станет
Прототип нового шага 2 «Собрать источники» — отдельный скрипт scripts/sandbox-step2.php,
боевой шаг 2 НЕ тронут. Отпечаток фирмы (домен+имя+тип+город) → поиск по отпечатку через EXA
(гео-скоуп по типу фед/рег) → фильтр «точно она» по якорному телефону → глубокое вскрытие
сайтов (код/скрытые/подменный коллтрекинг/Контакты) + 2ГИС(поиск по имени, JSON) + Яндекс(Playwright)
→ все номера с пометками. Генерит HTML «было/станет» на конкурента (было из БД, станет из прогона),
ссылки кликабельные. scripts/_shot.cjs — скриншот HTML в PNG.

Артефакты bylo-stanet/ на 5 конкурентов с «Предложениями» (tenant 36). Свежие со всеми фиксами —
КрасЛомбард и Банк ДОМ.РФ; остальным нужен финальный перегон. Детали, дизайн и 4 фикса
(схема сайта, Яндекс→Playwright, 2ГИС по имени/JSON/ретрай, близнецы кир/лат) — в файле находок.

НЕ в портале, НЕ на проде. Прототип для оценки.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 21:22:13 +03:00
Дмитрий df83fa836c feat(автоподбор): Конкурентное поле — финальный проход склейки, вскрыть всё, группы дублей, телефоны, отсев рекламных номеров 2ГИС
Шаг 1 «Конкурентное поле» — чекпоинт:
- Финальный проход «Найти и объединить дубли» на поле и предложениях; клиент решает по каждой группе, ничего не склеиваем молча.
- Тихая склейка только по сайту и коду справочника; телефон и людный номер у более чем 4 фирм — на решение клиента.
- Колонка phones jsonb; телефон-дубли видны уже на «Предложениях».
- Sonar даёт только имя и тип, сайт всегда через EXA; вскрываем все карточки без гейта «есть сайт».
- Яндекс-карточки через локальный Playwright параллельно; рубрика из заголовка идёт в описание.
- Фикс рекламных номеров: 2ГИС-парсер разбирает телефоны пообъектно и выкидывает рекламные кнопки с platforms/caption чужих фирм.

Тесты: автоподбор 224/224 unit, 105/105 feature; фронт-спеки зелёные. Времянки app/scripts/diag-*/probe-* в гит не входят.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 17:34:03 +03:00
Дмитрий 65770f0e70 docs: снимок 02.07 — launch-gate ЯДРО на проде + автоподбор в воркстри + промт продолжения
Консолидированное состояние двух треков (launch-gate выкачен на liderra.ru core-only;
автоподбор работает только в воркстри) + квирки окружения/тестов + промт для
восстановления контекста после компакта.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 13:22:53 +03:00
Дмитрий 4c05af7fda docs(биллинг): статус фичи launch-gate — реализовано, на gitea, follow-up
Спека помечена РЕАЛИЗОВАНО (коммиты f78ab4af/9ff7bc8f/8a8783df), ревью opus без
критичных находок. Зафиксирован follow-up: прод-флаг BILLING_LAUNCH_REQUIRES_ACTIVE_TIERS,
DRY-сведение оставшихся копий правила, пред-существующая партиция-хрупкость тест-инфры.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 12:26:41 +03:00
Дмитрий f78ab4af3d feat(биллинг): баланс блокирует запуск проекта, а не создание — единый гейт
Создавать проекты можно всегда; баланс проверяется в момент ЗАПУСКА
(создать-и-запустить / запустить / возобновить одиночно и пачкой / автоподбор)
под замком на клиента (без гонок). Не хватает — проект остаётся на паузе с
меткой preflight_blocked_at, клиенту сообщение в рублях (сколько пополнить).
Групповой запуск «сколько влезло». Нет активного тарифа на дату → запуск
запрещён (fail-closed). Гейт реквизитов первого проекта добавлен и в автоподбор.

- LaunchBalanceGate — единый гейт вместо 3 копий preflight (ProjectController
  store/update, AutopodborController), под DB::transaction + lockForUpdate(Tenant).
- ProjectService::create($launch) + новый setActive(); bulk resume «сколько влезло».
- AutopodborProjectCreator: пачка в транзакции через общий ProjectService::create.
- Идемпотентность box/phone_type миграций автоподбора (Schema::hasColumn guard).
- Тест-инфра: afterRefreshingDatabase восстанавливает месячные партиции.

Тесты фичи 40/40 зелёные. Спека и план — docs/superpowers.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 11:07:22 +03:00
Дмитрий a88c4034b7 docs(автоподбор): снимок 02.07 — живой движок работает end-to-end + ускорение 2ГИС + промт продолжения
Зафиксировано состояние: фича Stage 1+2 собрана, живой сбор проверен (прогон #12 ~8 мин, 139 фирм 2ГИС,
14 сохранено, 300₽), коммит b3f5158e, квирки (лимит xfetch, EXA OS-env, очередь-локально, node-путь) и
промт для восстановления контекста. Детали движка — в памяти project-autopodbor-engine-live-working-2026-07-02.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 06:30:03 +03:00
Дмитрий 76c67e1fc5 feat автоподбор параллельная загрузка карточек справочника пачками по 4
Замер боевого лимита xf4.ru показал безопасный потолок 4 одновременных запроса — выше идут 429 Too Many Requests, плюс есть лимит за окно времени. Сбор до 25 карточек филиалов в XfetchDirectoryFetcher грузился по одной секунды на каждую — узкое место шага 2.

Новый интерфейс BatchPageFetcher + XfetchClient::htmlBatch грузит страницы порциями по services.xfetch.concurrency дефолт 4, параллелит внутри порции через Http::pool, ретраит пустые и 429. XfetchDirectoryFetcher берёт карточки одной пачкой если загрузчик batch-совместимый, иначе по одной. Два справочника шага 2 оставлены последовательно намеренно — иначе 2x4=8 за лимитом. Число выносится в XFETCH_CONCURRENCY без деплоя.

По TDD. Бэкенд автоподбора 258/258, Pint чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 18:52:18 +03:00
Дмитрий 82123057f5 docs(автоподбор): спека + план фичи сверки находок + файл состояния Stage 1 и промт продолжения
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:47:12 +03:00
Дмитрий 68341e5576 fix(автоподбор): дедуп ДО отсева агрегаторов + файл состояния/промт продолжения
Отсев агрегаторов шёл по СЫРЬЮ до склейки — gpt-4o-mini залетало 227 позиций
с дублями (Ломбардико ×6). Порядок в FindCompetitorsAssembler: сначала
склейка/дедуп, потом отсев — классифицируем ~90 уникальных, а не 227.

Плюс файл состояния сессии (что сделано/осталось) + промт восстановления
контекста для компакта. TDD, 234/234 зелёные. НЕ прод.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:27:26 +03:00
Дмитрий f1ab608444 feat(автоподбор): масштаб и чистота шага 1 — пул EXA, очередь-throttle, батч агрегатора, узкие рубрики, дедуп по словарю
Масштаб (лимиты внешних сервисов — на КЛЮЧ, общий на всех клиентов):
- EXA: параллельный пул findSites (concurrency=5) вместо 40 запросов по одному.
- Агрегатор: батч вместо ~90 запросов по одному + чанкинг по 40 и длинный
  таймаут 90с (большой список gpt-4o-mini не успевал в 30с).
- Очередь autopodbor + WithoutOverlapping на всех 3 джобах — глобальный
  потолок = число воркеров; assertNoInFlight держит один подбор на клиента.

Чистота выдачи (универсально, без зашитых ниш):
- Анализатор: узкие рубрики, запрет зонтичных слов («финансовые услуги» тащила
  юрфирмы/банки).
- Дедуп: ключ имени минус слова из СЛОВАРЯ рубрик прогона («Яричъ Ломбард»→«Яричъ»);
  слово-категория из данных, не из списка. Гард: склейка только если остаётся
  ровно один слово-токен (генерики вроде «займы под залог» не трогаем).

TDD, 233/233 (unit+feature) зелёные. НЕ прод.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:18:09 +03:00
Дмитрий f9f4743392 docs(автоподбор): файл состояния движка + промт восстановления контекста (01.07)
Единая точка: что сделано (движок шага 1 пересобран — АНАЛИЗ→канал А 2ГИС(xfetch)+Яндекс
(Playwright)→слияние→канал В федералы(EXA)→сборка; 136 unit+74 feature зелёные, HEAD dee2ebbc),
что осталось (полный живой прогон+сверка на 72, калибровка, параллельность), ключи/окружение,
карта кода, промт для восстановления после компакта.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 04:55:51 +03:00
Дмитрий 5a65165114 feat(автоподбор): движок шага 1 пересобран под финал v4 (каналы А+В, EXA)
Замена вырожденного «одна фраза → одна страница» на §12/§11.3 финал:
- Шаг АНАЛИЗ (ChannelA\AitunnelQueryAnalyzer): описание → запросы-рубрики (мелкая модель).
- Канал А (ChannelA\CategoryScraper): скрейп категории 2ГИС с пагинацией → резолв карточек.
- Канал В (ChannelB\*): ОДНА модель sonar-reasoning-pro × 2 прохода → ТОЛЬКО имена
  федералов; стоп-лист = имена из А + примеры; сайт федерала через EXA (ExaSiteFinder),
  т.к. у федерала нет карточки в 2ГИС/Яндексе на регион.
- Оркестратор LiveFindCompetitors переписан: АНАЛИЗ→А→В→слияние→отсев→дедуп→похожесть→DTO.
- Провайдер перепрошит; config services.php +research_model/exa.
Похожесть — эмбеддер-модель (математически), резолвер/дедуп — без изменений.
Всё за тонкими границами, офлайн-тесты на фикстурах: модуль 130 unit + 74 feature зелёные.
Провайдер за флагом autopodbor.real_find; на проде не меняется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 21:06:10 +03:00
Дмитрий d468471707 docs(автоподбор): зафиксирован последний движок §12 v4 + сверка PHP
Свод воедино: методика §12 (3 канала А/В/0 → резолвер → DaData → дедуп →
эмбеддинги → DTO) + все дословные промпты (5 моделей-исследователей,
нормализатор, фильтр агрегаторов). Честная сверка: PHP LiveFindCompetitors
собрал только нижнюю половину (резолвер/дедуп/похожесть/телефоны) и берёт
1 фразу/1 страницу → 3 находки; верхняя половина (АНАЛИЗ+мульти-запрос+
пагинация канала А, канал В 5-моделей) не перенесена. Эталон работы движка —
прогон 29.06 (72 конкурента). План переделки PHP под §12 один-в-один.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 20:03:48 +03:00
Дмитрий f940c060b8 docs(автоподбор): контекст-промпт продолжения — живой портал :8001 (белый экран) + полное состояние 2026-06-30 19:11:39 +03:00
Дмитрий 0636a3c1b4 docs(автоподбор): план шага1 — весь движок собран (A–F+C+D+H-ядро), осталось живое подключение+флип 2026-06-30 18:04:21 +03:00
Дмитрий f7ec94e107 docs(автоподбор): план шага1 — прогресс A/E/F/B готовы, осталось живое C/D/G/H 2026-06-30 17:47:30 +03:00
Дмитрий 4de9474a27 docs(автоподбор): план шага1 — под-блок A (резолвер) отмечен готовым 2026-06-30 17:29:53 +03:00
Дмитрий 858d8be1b2 docs(автоподбор): контекст-промпт продолжения шага 1 — за основу движок v4 (2 дня прогонов)
Резюме для подхвата после компакта: брать ПОСЛЕДНИЙ вариант (§12 v4), ранние
не воскрешать (ИНН выкинут, похожесть=эмбеддинги, агрегаторы=LLM, is_federal по факту).
Следующая задача A2 TwoGisResolver.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:51:40 +03:00
Дмитрий 2e37a7a380 docs(автоподбор): план шага 1 — реальный findCompetitors, фундамент-первым
Декомпозиция движка v4 (§12) на под-блоки A–H: резолвер (фундамент, офлайн-TDD) →
слияние/похожесть → каналы А/В/0 → DaData → оркестрация+флип за флагом. Живые
блоки с платными API — отдельными заходами после «го» владельца.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:35:48 +03:00
Дмитрий 92e2eef702 docs(автоподбор): статус — шаг 2 встроен в боевой путь и проверен живым прогоном
Обновлён GOTOVNOST: интеграция шага 2 (провод реального агента, где-нашли→БД→API,
код города по региону, ретрай 2ГИС) сделана и проверена вживую на kraslombard24.ru.
Следующий блок — шаг 1 (реальный findCompetitors).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 16:29:09 +03:00
Дмитрий c2d5592696 docs(автоподбор): статус готовности + контекст-промпт продолжения
- GOTOVNOST-status: к внедрению НЕ готов; шаг 2 собран по частям, не встроен;
  шаг 1 — реальной логики никогда не писали (всегда заглушка), из репо ничего
  не терялось (проверено git); собрать шаг 1 = написать одну findCompetitors.
- KONTEKST-prodolzhenie: промпт для корректного подхвата контекста + план.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:08:53 +03:00
Дмитрий 3f3a142b76 fix(безопасность): XSS в ссылках «где нашли» + path traversal в serve.js
Сек-ревью коммита нашло две дыры:
1. FieldCompetitorScreen: href из where_found рендерился без проверки схемы —
   javascript:-ссылка выполнила бы скрипт при клике. Добавлен safeUrl():
   кликабельны только http/https, остальное — простым текстом. +TDD-тест.
2. serve.js (локальный dev-сервер прототипов): обход каталога. realpath-сверка
   с ROOT + require isFile + bind 127.0.0.1.

Autopodbor FieldCompetitor 13/13.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:53:04 +03:00
Дмитрий 6e5f47962c docs(автоподбор шаг2): живой показ КрасЛомбарда + сводка + резервный Playwright-путь
- Скриншоты живого прогона: страница глазами клиента (мой рендер) и НАСТОЯЩИЙ
  экран портала FieldCompetitorScreen с живыми данными (findings/*.png).
- Сводка STEP2-SVODKA: §4 чистый прогон (29 номеров + 2ГИС), §6.7 привязка
  офис↔номер, §6.8 ретрай флаки 2ГИС.
- R&D, план-промпт, прототипы sbor1/sbor2 + живая собранная страница.
- Резервный Playwright-путь справочников (CurlPlaywrightFetcher.directory +
  render-firm.cjs) — заменён xfetch; хвост SSRF §6.4 не на боевом пути.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:48:26 +03:00
Дмитрий 1b76cfec15 feat(автоподбор шаг2): справочники 2ГИС через xfetch.ru
Антибот 2ГИС бьём сервисом xfetch.ru (render:true, timeout:20 —
без timeout страница не дорисовывается). Доказано на живом КрасЛомбаре:
поиск → 12 филиалов → телефон + адрес каждой карточки.

- PageFetcher — граница «достать HTML» (тестируется без сети)
- XfetchClient — POST к xfetch, декод base64; без ключа молча пусто
- XfetchDirectoryFetcher — список→филиалы→карточки через DirectoryParser
- DirectoryParser — чтение списка и карточки 2ГИС (был в хвостах)
- config services.xfetch + .env.example; ключ только в .env (gitignored)

Яндекс.Карты — отдельно (другой формат URL карточек). TDD: Autopodbor 46/46.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:38:18 +03:00
Дмитрий b78c3edb8c fix(автоподбор шаг2): не терять номера офисов, написанные без кода города
Сканер кода сайта выкидывал короткие локальные номера (271-33-33,
2-828-828 и т.п.) как мусор — терялись реальные телефоны филиалов.
Теперь короткий номер из tel:/schema/microdata достраивается кодом
города: сперва по преобладающему коду полных номеров той же страницы,
иначе по коду региона запроса; если код не определить — номер не
теряется, а помечается «требует проверки» (phoneKind=uncertain).
Из тела текста короткие формы не достраиваются (защита от ложных).

TDD: 6 новых тестов, весь Autopodbor 40/40.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 12:11:27 +03:00
Дмитрий aedefa3a94 docs(автоподбор): план Plan A (ядро) + resume-промпт
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:42:47 +03:00
Дмитрий fbebe40383 docs(автоподбор): план Plan B — добыча + RealCompetitorAgent (TDD)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 10:41:19 +03:00