Commit Graph

2863 Commits

Author SHA1 Message Date
Дмитрий de10c633e6 docs(handoff): исправлен устаревший статус — deep-study/render-cache/EXA уже на проде
Хендоф называл три фичи «невыкаченными, за тумблерами» — по факту (проверено
командой на боевом) все три на проде и флаги ON: AUTOPODBOR_DEEP_STUDY,
AUTOPODBOR_RENDER_CACHE, EXA_PROXY (тоннель отдаёт зарубежный IP). Уехали 06.07.
Следствие: баг «шаг 2 AutoMoney» — в уже-живом deep-study. Добавлена сегодняшняя
сортировка регионов (REGIONS_ALPHA), gitea-ссылка обновлена на 94cfc7ce.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 17:15:20 +03:00
Дмитрий 94cfc7ced6 fix(projects): регионы в 4 выпадашках раздела «Проекты» — сортировка по сути названия
Те же выпадашки региона (новый проект, фильтр списка, массовое
add/remove регионов, карточка проекта) переведены на REGIONS_ALPHA —
единый порядок с автоподбором, республики по своему имени, а не кучей.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:30:42 +03:00
Дмитрий 4451231c1c fix(autopodbor): регионы в выпадашках — сортировка по сути названия
Регионы выводились в конституционном порядке (республики → края →
области), из-за чего список выглядел кашей (Дагестан → Донецкая
Народная → Ингушетия → Кабардино-Балкарская). Добавлен REGIONS_ALPHA:
сортировка по «настоящему» имени, слова-типы (Республика/область/край/
автономный округ) отбрасываются — Татарстан на «Т», Адыгея на «А».
Подставлен во все 7 выпадашек автоподбора. Тест regionsAlpha.spec.ts.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:27:44 +03:00
Дмитрий 52dcd6ccb2 docs(handoff): промт продолжения автоподбора 07.07 (состояние + открытое + рецепты деплоя)
Accessibility (Pa11y live) / a11y (push) Waiting to run
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:10:54 +03:00
Дмитрий d4aec22933 docs(pilot): доработки автоподбора 07.07 (не режем+денежный баг+повтор, Списания, плашки, «Отказаться»)
Accessibility (Pa11y live) / a11y (push) Waiting to run
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:05:51 +03:00
Дмитрий f888c4baec chore(autopodbor): кнопка «Отказаться» (было «Удалить») + автоподбор в CSV списаний + canon-sync schema.sql v8.63
Accessibility (Pa11y live) / a11y (push) Waiting to run
SAST — Semgrep / Semgrep SAST scan (push) Waiting to run
- Кнопка на актуализации переименована «Удалить»→«Отказаться» (чтобы клиент не путал с удалением конкурента).
- CSV-выгрузка «Списаний» теперь включает автоподбор-строки (отдельным блоком, «Автоподбор конкурентов»);
  тест на export. charges 11/11.
- schema.sql canon-sync v8.63: +колонка dismissed_actualize_keys у autopodbor_competitors + CHANGELOG
  (заодно в цепочку шапки вписана пропущенная v8.62 CHECK autopodbor_charge).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 16:03:43 +03:00
Дмитрий 43012c982e feat(autopodbor): «Удалить» на актуализации — мягкое отклонение находки
Accessibility (Pa11y live) / a11y (push) Waiting to run
SAST — Semgrep / Semgrep SAST scan (push) Waiting to run
Кнопка «Удалить» на карточках «На актуализацию» рядом с Добавить/Заменить.
Отклоняет ТОЛЬКО находку (новый сайт/адрес): фирма поля остаётся, находка → архив,
её новые ключи запоминаются у фирмы поля (новая колонка dismissed_actualize_keys jsonb).
Классификатор вычитает отклонённые ключи → та же находка больше не всплывает; реально
другой новый сайт даст новый ключ → покажется. Не глушим по имени, конкурента не убираем.

Бэк: миграция + модель-каст + ProposalClassifier (вычитание) + эндпоинт dismissActualize
+ роут. Фронт: api + store + кнопка «Удалить». Тесты: классификатор 8/8, ветка 185/185,
фронт 30/30. Canon-sync schema.sql (v8.62) — follow-up (миграция idempotent-guarded).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 15:50:59 +03:00
Дмитрий 7755a7213e fix(billing+autopodbor): автоподбор-списания в табе «Списания» + фикс вёрстки плашек «Предложений»
Accessibility (Pa11y live) / a11y (push) Waiting to run
SAST — Semgrep / Semgrep SAST scan (push) Waiting to run
- «Списания» (TenantChargesController.index) теперь union lead_charges + autopodbor_charge
  (balance_transactions) — клиент видит списания за прогоны, не только в «Истории транзакций».
  Автоподбор-строки: kind=autopodbor, метка вместо сделки, «—» для tier. Фильтр charge_source
  автоподбор не подмешивает. 2 теста, всего charges 10/10.
- Плашки «Предложений» разъезжались за экран на длинных URL (utm/рекламные хвосты): grid-элементы
  min-width:auto не сжимались. Фикс: .ld-grid>* min-width:0 + перенос длинных ссылок.
- Фронт 16/16.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:21:30 +03:00
Дмитрий 2e0f5a4d29 fix(autopodbor): лимит 0 = «не режем» (защита обрезки) + авто-повтор при пустом сборе
Accessibility (Pa11y live) / a11y (push) Waiting to run
SAST — Semgrep / Semgrep SAST scan (push) Waiting to run
Баг: при autopodbor_max_competitors=0 джоба делала array_slice($unique,0,0)=[]
→ сохраняла 0 конкурентов, НО всё равно списывала 50₽ (chargeForRun после обрезки,
проверка empty стои́т ДО неё). Теперь $max>0?slice:все — 0 значит «сохранить всех».
Плюс авто-повтор findCompetitors при пустом сборе (search_retry_on_empty, деф. 2):
моргание 2ГИС/Яндекс/EXA не показывает клиенту «ничего не нашлось». Реально пустая
ниша → empty без списания. 2 теста; ветка автоподбора 176/176.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 14:01:29 +03:00
Дмитрий 9dac8c16ea docs(findings): шаг2 разворот — соседние изучения чистые, глюк только у AutoMoney (агрегатор)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 13:04:59 +03:00
Дмитрий ec07f2aeeb docs(findings): шаг 2 автоподбора — разворот по телефону мостит чужих (баг AutoMoney), корень + фикс-план
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 13:00:55 +03:00
Дмитрий 0c4d47f3c3 docs(pilot): хотфикс автоподбора 07.07 — tenant-контекст ДО чтения прогона (RLS)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 12:34:47 +03:00
Дмитрий a42ee07c42 fix(autopodbor): выставлять tenant-контекст ДО чтения прогона в джобах (прод-сбой под RLS)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Джобы Search/Study/Resolve делали AutopodborRun::findOrFail() ДО set_config
app.current_tenant_id. На проде роль crm_app_user под FORCE RLS (на боевом
кластере ни одна роль не BYPASSRLS) не видит строку без tenant-контекста →
ModelNotFound, все три кнопки автоподбора падали. На dev маскировалось
суперюзером postgres. Теперь tenantId передаётся в dispatch и контекст
выставляется первым. 3 регресс-теста на порядок (set до чтения runs).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 12:22:22 +03:00
Дмитрий 5ace9635dc docs: фиксация состояния автоподбора 07.07 + промт следующей сессии
Accessibility (Pa11y live) / a11y (push) Has been cancelled
ПИЛОТ: укрепление тоннеля (sshd ClientAlive drop-in + сторож чистит порт на проде).
Добавлен NEXT-SESSION снимок: автоподбор live, EXA через тоннель+роботы, бэкап,
ротация пароля; открыт один пункт — робастный серверный прокси для EXA.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 08:28:32 +03:00
Дмитрий fccf3e34bf docs(pilot): доделки автоподбора 07.07 — EXA через прокси+сторож, бэкап, ротация пароля
Accessibility (Pa11y live) / a11y (push) Has been cancelled
EXA/Cloudflare режет РФ IP → EXA_PROXY через обратный тоннель к Happ (сторож
ExaTunnel/Watchdog). Бэкап: логический дамп кластера невозможен (RLS), защита =
YC managed backup+PITR; мёртвый-дамп-скрипт обезврежен, добавлен монитор свежести.
Пароль crm_migrator ротирован (Lockbox авто-синк).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-07 04:19:04 +03:00
Дмитрий 9ade3115d1 feat(autopodbor): EXA через прокси (EXA_PROXY) — обход блокировки российских IP
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
EXA за Cloudflare режет российские IP, федеральный канал возвращал пусто с боевого.
Добавлен services.exa.proxy (env EXA_PROXY) → Guzzle proxy option в findSite/findSites.
Пусто → напрямую (dev). Тест: с proxy цепочка не ломается, домен извлекается (8/8).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 18:21:39 +03:00
Дмитрий a1820e22b0 docs(pilot): автоподбор выкачен на боевой liderra.ru 06.07.2026
Accessibility (Pa11y live) / a11y (push) Has been cancelled
Флаги ВСЕ ВКЛ, 4 таблицы autopodbor_* в кластере, воркеры find/study,
autopodbor_enabled=1, приёмка вживую пройдена. Ключи движка перенесены
в боевой .env. Известная дыра: liderra-backup.sh дампит мёртвую копию.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 17:42:11 +03:00
Дмитрий 9c8178bbf2 docs(release): промт-хендофф для боевого выката автоподбора (под компакт)
Accessibility (Pa11y live) / a11y (push) Has been cancelled
SAST — Semgrep / Semgrep SAST scan (push) Has been cancelled
Собирает контекст новой сессии: что прочитать, состояние (ветка efbad075
смёржена/зелёная/запушена), ловушка сокета Managed PG, гейты (пароль crm_migrator
+ бэкап + выкатывай), последовательность выката и откат.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 16:01:09 +03:00
Дмитрий efbad075e8 test(autopodbor): актуализировать 4 устаревших фронт-теста создания проектов
Групповое и одиночное создание теперь ТРЕБУЮТ регион (vsyaRf=false по умолчанию;
«Вся РФ» — явная галочка, дефолта больше нет — подтверждено владельцем). Тесты
были под старый дефолт «Вся РФ» → doCreate бэйлился на гейте региона. Добавлен
явный выбор «Вся РФ» перед doCreate. Плюс requisites_required теперь открывает
форму реквизитов (reqGate), а не тост — тест переведён на новое поведение,
reqGate добавлен в defineExpose для наблюдаемости. Спек 6/6 зелёный.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:51:45 +03:00
Дмитрий a2729cee95 Merge remote-tracking branch 'gitea/main' into worktree-avtopodbor
# Conflicts:
#	app/config/services.php
#	app/tests/Pest.php
2026-07-06 14:50:58 +03:00
Дмитрий 69aa7af7a4 docs(plan): актуализировать план релиза — ловушка сокета Managed PG, grants F1, canon-sync F2, billing-audit, диск
Правки по находкам prod-deploy-validator и rls-reviewer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:44:28 +03:00
Дмитрий a75705c80e style(autopodbor): pint — причесать миграции/фетчер/тесты перед релизом
Стиль-only (braces_position, ordered_imports, fully_qualified_strict_types и т.п.),
поведение не меняется. Autopodbor 476/476 зелёный после причёсывания.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:35:22 +03:00
Дмитрий 8a6f674ac6 chore(autopodbor): убрать диагностическую времянку _shot.cjs из ветки
Скриншот-скрипт-времянка, случайно закоммичен ранее; при overlay-деплое
попал бы в app/scripts на прод. Мусор — удалён до выката.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:31:18 +03:00
Дмитрий 68c8eaead8 test(schema): обновить сторож метрик под v8.62 (автоподбор +4 таблицы)
SchemaDeltaTest жёстко ждал метрики v8.55 (74 таблицы/128 индексов/44 RLS),
но автоподбор добавил в schema.sql 4 таблицы (runs/competitors/sources/
merge_events) + 9 индексов + 4 RLS-политики → 78/137/48. Это был давно
красный сторож (не баг фичи). Обновлены ожидаемые числа + комментарий.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 14:10:52 +03:00
Дмитрий 04a94c922c docs(autopodbor): план полного боевого релиза + ранбук кэша карточек
План релиза всей системы автоподбора (190 коммитов, greenfield на проде, миграции
на денежную базу) с гейтами, откатом, находками prod-deploy-validator (ловушка
локального сокета vs Managed PG, диск) и rls-reviewer (grants после migrate, canon-sync).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:22:42 +03:00
Дмитрий 31cff0ca81 chore(schema): canon-sync v8.62 — 'autopodbor_charge' в CHECK balance_transactions
rls-reviewer при подготовке боевого выката нашёл: миграция 2026_06_28_110100
добавляет тип 'autopodbor_charge' в balance_transactions_type_check, но в тело
schema.sql это не было свёрнуто (CHECK заканчивался на 'migration'). Денежная
таблица — §5 п.8. Свёрнуто + запись в CHANGELOG. Структурно только значение CHECK.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:22:40 +03:00
Дмитрий ff1c3ed210 test(autopodbor): закрепить phone_reverse_parallel=false в тесте композита
Тест «reverse = композит» жёстко ждал CompositePhoneReverse, но не пинил флаг
parallel — из-за dev-.env (AUTOPODBOR_PHONE_REVERSE_PARALLEL=true) собирался
ParallelPhoneReverse и тест падал. Явно пиним parallel=false: тест проверяет
именно композитную ветку, независимо от окружения. Autopodbor-набор 476/476.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:13:47 +03:00
Дмитрий 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
Дмитрий 06fc4680f4 feat(autopodbor): кэш отрисованных карточек 2ГИС/Яндекс за тумблером
CachingPageFetcher — декоратор над загрузчиком карточек (SelfRenderClient/
XfetchClient): кэширует в общем Cache (Redis/file) по ключу URL на N дней ТОЛЬКО
карточки (/firm/ 2ГИС, /maps/org/ Яндекс). Поиск (/search/) и ленты — всегда мимо
кэша (свежее добывание новых фирм). Пустое не кэшируем — разовый сбой не отравит кэш.

Кэш ОБЩИЙ для шага 1 (поиск) и шага 2 (изучение+разворот) и повторных прогонов:
ключ по URL → любые экземпляры делят один кэш. Вшит в AutopodborServiceProvider
через wrapCache(); 2ГИС-база RoutingBatchFetcher и self-render card reader обёрнуты,
яндекс-Playwright/ленты — нет.

Флаг AUTOPODBOR_RENDER_CACHE (ВЫКЛ по умолчанию) + render_cache_ttl_days (5).
TDD: 8 тестов CachingPageFetcher + 4 теста проводки — зелёные. Живой замер ДСК:
тёплый прогон — 26 карточек из кэша, 0 обращений к 2ГИС/Яндексу, источников 11=11.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 10:52:14 +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
Дмитрий b9620238c3 docs(render-service): настройка прокси Proxy.Market (mobile-static, HTTP)
Формат PROXIES под Proxy.Market: только HTTP-эндпоинт (Playwright не умеет
авторизацию SOCKS5). Один мобильный порт = один IP; для нашего объёма хватает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:20:47 +03:00
Дмитрий 7512ec2bad feat(autopodbor): полное прокси-покрытие Яндекса через виртуалку (фаза 7b)
Закрыт хвост §5.3: node-скриптовые точки Яндекса уведены на рендер-сервис, чтобы
под self_render ВЕСЬ Яндекс шёл через виртуалку+прокси (не крутил браузер на портале).

- YandexListSource (интерфейс) + LocalYandexListSource (xf4, локальный Playwright) +
  RenderServiceYandexListSource (self-render, POST type=yandex-list на виртуалку).
- PlaywrightYandexDirectory (листинг шаг 1) и PlaywrightYandexPhoneSearch (разворот)
  расшиты на YandexListSource — заодно стали тестируемы офлайн (6 новых тестов).
- PlaywrightYandexCardReader под self_render получает SelfRenderClient (был PageFetcher).
- Рендер-сервис: режим type=yandex-list (скролл ленты + сбор организаций → {orgs}),
  проброс scrolls.
- Провайдер: yandexListSource() по флагу; разворот-Яндекс на виртуалку+прокси.

Итог: при self_render ВКЛ на xf4 и на локальном браузере портала не остаётся НИ ОДНОЙ
точки — 2ГИС и весь Яндекс через виртуалку+прокси. 459/459 тестов, Pint+Larastan чисто.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:18:00 +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
Дмитрий 316924f00e feat(autopodbor): проводка флага self_render в провайдере (фаза 7)
Флаг autopodbor.self_render переключает слой добычи 2ГИС/Яндекс пары фетчер+ридер:
 ВКЛ  → SelfRenderClient (виртуалка+прокси; 2ГИС→initialState) + TwoGisJsonReader;
 ВЫКЛ → XfetchClient (xf4) + HtmlTwoGisReader (как сейчас).
SelfRenderClient один покрывает и 2ГИС (initialState), и Яндекс (html) — заменяет
и xfetch, и локальный Playwright-роутинг. DeepStudyCollector под self_render получает
yandexDir через виртуалку. 453/453 теста зелёные, Pint+Larastan чисто.

Известный хвост (follow-up): PlaywrightYandexDirectory (листинг Яндекс шаг 1) и
YandexPhoneReverse Playwright-классы остаются на ЛОКАЛЬНОМ Playwright (не xf4, но и
без прокси) — для полного прокси-покрытия Яндекса их надо развязать на PageFetcher.
Гарантия §5.7 «ни одной точки на xf4 при ВКЛ» соблюдена (локальный Playwright ≠ xf4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:06:26 +03:00
Дмитрий de544c25b0 feat(autopodbor): рендер-сервис на виртуалке — Node+Playwright (фаза 6)
Рисовалка топологии Б (§5.6): портал зовёт по HTTP как xf4, она рисует и
отдаёт данные. Ни базы, ни Redis, ни денег. Смоук на живом 2ГИС: листинг 12
фирм, карточка 1 — чистый initialState.

- browser.mjs — постоянный браузер + маскировка + контекст-на-страницу +
  museum double-tap для поиска 2ГИС + отсечка картинок/шрифтов/медиа.
- proxy-pool.mjs — round-robin пул прокси из env PROXIES (резидентные/мобильные RU).
- server.mjs — POST /render {url,type}→{data}, auth X-Render-Key, семафор
  MAX_CONCURRENCY, /health.
- package.json, README.md, smoke.mjs, .gitignore (node_modules не в гит).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 19:00:48 +03:00
Дмитрий 55bf6348b3 feat(autopodbor): SelfRenderClient — HTTP-клиент к рендер-сервису (фаза 5)
HTTP-клиент к своей виртуалке (замена xf4). Реализует BatchPageFetcher (и
PageFetcher): для 2ГИС просит initialState, для Яндекса/сайтов — HTML. Секрет
в заголовке X-Render-Key (из .env). Без ключа/адреса — молча пусто (деградация).
Http::pool для батча, ретраи. 7/7 тестов, Pint чисто.

- config services.self_render (endpoint/key/concurrency, всё из .env).
- config autopodbor: флаги self_render + гранулярные self_render_2gis/_yandex
  (наследуют master), дефолт ВЫКЛ.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:56:10 +03:00
Дмитрий ef2ea54d38 refactor(autopodbor): расшивка точек 2ГИС на шов TwoGisReader (фаза 4)
Расшиты 4 потребителя 2ГИС — вместо прямых вызовов парсеров зовут интерфейс
TwoGisReader (дефолт HtmlTwoGisReader = поведение 1:1). Готовит подмену на
self-render без слома xf4-пути. 443/443 теста автоподбора зелёные, Pint чисто.

- CategoryScraper: CategoryListingParser → TwoGisReader (шаг 1 листинг).
- CompetitorCardEnricher: 2ГИС-карточки через TwoGisReader, Яндекс на parseYandexCard.
- PhoneReverseExpander: полностью на TwoGisReader (2ГИС-only разворот).
- DeepStudyCollector (2 вскрытых стыка): конструктор XfetchClient→BatchPageFetcher
  (под SelfRenderClient); collectYandex — инлайновый фетчер вынесен в инъекцию
  ($yandexDir), под self_render пойдёт на рендер-сервис+прокси, а не локальный
  Playwright. 2ГИС-разбор через TwoGisReader.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:53:10 +03:00
Дмитрий 46426d586e feat(autopodbor): шов TwoGisReader + HtmlTwoGisReader + TwoGisJsonReader (TDD)
Фазы 2-3 плана self-render. Только новые файлы — существующий код не тронут,
xf4-путь цел. 281/281 юнит-тестов зелёные, Pint чисто.

- TwoGisReader — интерфейс разбора 2ГИС (3 метода: listing/card/branchList),
  единый контракт поверх HTML (xf4) ИЛИ initialState-JSON (self-render).
- HtmlTwoGisReader — xf4-путь: тонкий делегат к CategoryListingParser/
  DirectoryParser, поведение 1:1 (4 теста контракт-идентичности на реальных
  фикстурах 2gis-*-kraslombard).
- TwoGisJsonReader — self-render путь: разбор window.initialState по карте
  путей спайка (pagination-список → entity.profile), фильтр type=branch
  (фирма-в-фирму, без зданий/улиц). 5 тестов на реальных фикстурах: 12 фирм
  листинга, телефон+сайт карточки, 2 пути поиска-по-телефону, деградация.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 18:40:39 +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
Дмитрий 5c8b344bcf fix(autopodbor): разворот глубина 1 по умолчанию + массовое удаление предложений
Разворот по телефонам 2ГИС и Яндекс больше не уходит цепочкой «до талого»:
по умолчанию берём только фирмы с ЛИЧНЫМ номером фирмы — глубина 1. Именно
неограниченная цепочка уводила в чужие фирмы, давая мусорные сайты в
предложениях холдинга. Многошаговый разворот сохранён за параметром maxDepth,
по умолчанию 1. Изолировано: работает только под флагом phone_reverse, основной
пул сбора не затрагивается. Бэкенд автоподбора 430 из 430.

Фронт: на вкладке «Предложения» добавлена кнопка массового удаления выбранных
источников — bulkDeleteProps через removeSource, покрыта тестами.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:33:31 +03:00
Дмитрий 43e29823a8 perf(autopodbor): разнести шаги по очередям — find (ИИ) и study (тупой слой)
Раньше поиск, резолв и изучение делили одну очередь autopodbor — при одном
воркере быстрый шаг 2 (изучение) стоял за тяжёлым шагом 1 (поиск с ИИ) другого
клиента. Теперь два процесса:
  • шаг 1 «собрать поле» (поиск + резолв по имени, ИИ+внешние) → autopodbor-find
  • шаг 2 «дайте источники / изучение» (без ИИ) → autopodbor-study
Идут параллельно, друг друга не держат. Замок WithoutOverlapping по runId цел.

Запуск теперь двумя воркерами:
  queue:work redis --queue=autopodbor-find  --timeout=3600
  queue:work redis --queue=autopodbor-study --timeout=3600

TDD: AutopodborQueueThrottleTest 4/4, автоподбор 434/434.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:28:51 +03:00