Commit graph

3642 commits

Author SHA1 Message Date
62b9d1d4a7 Merge pull request 'fix(msk-collector): страница выдачи — 50 карточек, не 60' (#3434) from fix/msk-collector-page-size into main 2026-09-09 07:00:14 +00:00
bot-backend
88f422c07b fix(msk-collector): страница выдачи — 50 карточек, не 60
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
PAGE_SIZE=60 был взят из потолка 30x60=1800, а не измерен. Реальная выдача
Москва+МО отдаёт 50 на страницу, поэтому planned_pages = ceil(count/60)
не запрашивал последние ~17% каждого коридора: по закрытым коридорам счётчик
Авито обещал 34 753, собрано 28 352.

Это ровно тот молчаливый недобор, который читается как «покрыто всё»: коридор
помечался done, дойдя до расчётной страницы, хотя выдача не кончилась. С 50
потолок пагинации даёт 1500 на запрос — совпадает с дефолтным --target-count,
так что коридоры бисекции по-прежнему добираются целиком.
2026-09-09 09:48:43 +03:00
3107712893 Merge pull request 'fix(msk-collector): пересоздавать свою вкладку каждые 75 загрузок' (#3433) from fix/msk-collector-tab-recycle into main
Reviewed-on: #3433
2026-09-09 06:45:24 +00:00
bot-backend
68b7c16d9d fix(msk-collector): пересоздавать свою вкладку каждые 75 загрузок
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Рендерер Chrome копит память по всем навигациям вкладки, а московский проход —
под тысячу страниц в одной. Живьём поймано: вкладка падает с «Опаньки… Код
ошибки: Out of Memory» при 34 ГБ свободных в системе — упирается рендерер, а не
машина. Свежая вкладка стоит одну навигацию и обнуляет счёт.

Закрывается только своя вкладка; контекст и вкладки владельца не трогаются.
2026-09-09 09:44:02 +03:00
6de69c4d05 Merge pull request 'feat(tradein/geocoder): регион-параметризация геокодера — region_code в geocode()/known_city_hint, --region-code у скрипта сделок, region_code у geocode-missing (#3051)' (#3432) from feat/3051-geocoder-region-param into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m18s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Successful in 2m11s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 23:58:57 +00:00
bot-backend
9898b6bc02 feat(tradein/geocoder): регион-параметризация геокодера — region_code в geocode()/known_city_hint, --region-code у скрипта сделок, region_code у admin geocode-missing (#3051)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m6s
Геокодер был жёстко привязан к Свердловской области: viewbox 66 +
bounded=1, accept только при state ~ 'свердловск' и точке в bbox 66,
known_city_hint знал лишь города области → для 212 937 московских сделок
(address 'Москва, <улица>') геокод давал None либо ложный хит по
одноимённой улице области, а cache-ключ без города смешивал регионы.
Теперь регион приходит от вызывающего (deals.region_code): viewbox и
bbox из REGIONS[code], state-маркер per region ('свердловск'/'москва'),
ЕКБ-тиры (geoportal/cadastral/local houses) только при 66, city-хинт
через словарь региона → cache-ключ '|city=москва'. Дефолт 66 везде —
для существующих вызовов поведение байт-идентично (ревью двумя
линзами). Побочно: geocode-missing по умолчанию больше не берёт
listings с region_code NULL (16 930 неактивных чужих городов, которые
и раньше геокодились впустую).
2026-09-09 02:51:28 +03:00
12476df7a2 Merge pull request 'feat(tradein/estimator): deal_city_price_bands по ключу (region_code, city) — миграция 298, refresh per-region (#3051 sub-PR B)' (#3431) from feat/3051-bands-region-key into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m14s
Deploy Trade-In / build-backend (push) Successful in 1m4s
Deploy Trade-In / deploy (push) Successful in 1m54s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 23:32:46 +00:00
db7eb00759 Merge pull request 'test(tradein): тесты трека «Москва» — импорт ДКП по 77, чекпоинт per-source, wildcard-хендлер, регион-скоуп коридора, миграция 289, deploy-скрипт (#3051)' (#3430) from test/3051-moscow-track-tests into main
Some checks failed
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Has been cancelled
2026-09-08 23:30:21 +00:00
bot-backend
1d9adb4a24 feat(tradein/estimator): deal_city_price_bands по ключу (region_code, city) — миграция 298, refresh per-region, потребители (#3051 sub-PR B)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m10s
После импорта 212 937 московских ДКП (region_code=77) region_stats
refresh'а (p1 для tier region_fallback) считался бы по пулу 66+77 и
поднял бы floor ~250 малым городам области. Ключ bands становится
(region_code, city): миграция 298 (колонка DEFAULT 66, смена PK через
DO-guard, re-seed per-region, ЕКБ-исключение только для 66, doc_type=ДКП),
тот же SQL в refresh-джобе, estimator/backtest читают bands по паре.
Для 66 набор строк байт-идентичен прежнему (ревью двумя линзами).
2026-09-09 02:25:47 +03:00
bot-backend
beb487c12e test(#3051): тесты трека «Москва» — импорт ДКП по 77, per-source чекпоинт, wildcard-хендлер, регион-скоуп ДКП-коридора, миграция 289, валидация deploy-скрипта
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m2s
Тесты пишутся ПОСЛЕ живой проверки функционала (правило от 08.09):
импорт по 77 — run 6460 (212 937 строк), регион-фильтр коридора — smoke
до/после импорта. 14 тестов: SQL-текст через inspect (regex \s+ для
многострочных клозов), чистые функции, мок db по образцу 2846, subprocess
bash с урезанным PATH (падение от валидации, не от отсутствия docker).
2026-09-09 02:23:47 +03:00
e0bef636e6 feat(tradein): bulk-дампы открытых данных ФНС по юрлицам + lookup по ИНН (#3429)
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m17s
Deploy Trade-In / build-backend (push) Successful in 1m3s
Deploy Trade-In / deploy (push) Successful in 7m11s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 22:29:10 +00:00
50f0674977 feat(tradein): слой ДТП из dtp-stat.ru в PostGIS + радиусные агрегаты (#3428)
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m9s
Deploy Trade-In / build-backend (push) Successful in 1m54s
Deploy Trade-In / deploy (push) Successful in 1m51s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 22:10:56 +00:00
3bd30aab87 Merge pull request 'feat(mera): макро-ряды ЦБ РФ — ипотека по субъектам и ключевая ставка' (#3427) from feat/mera-cbr-macro into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m13s
Deploy Trade-In / build-backend (push) Successful in 1m33s
Deploy Trade-In / deploy (push) Successful in 1m32s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 21:48:59 +00:00
bot-backend
1f3566c466 merge main в feat/mera-cbr-macro — оба handler'а (frt_mkd_load + cbr_macro_pull) в реестре
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 12s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
2026-09-09 00:43:13 +03:00
e7ef6bc4a2 Merge pull request 'feat(mera): загрузчик реестра МКД АИС ФРТ → обогащение houses' (#3426) from feat/mera-frt-mkd-loader into main
Some checks failed
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m24s
Deploy Trade-In / build-backend (push) Successful in 1m7s
Deploy Trade-In / deploy (push) Has been cancelled
2026-09-08 21:41:24 +00:00
bot-backend
2abab8218e feat(mera): макро-ряды ЦБ РФ — ипотека по субъектам и ключевая ставка
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m16s
Три XLSX ЦБ (выдачи, ставка, задолженность) в разрезе субъектов, помесячно
с 01.2019, + ключевая ставка через SOAP DailyInfo.asmx (метод KeyRate; GET
на нём не работает, только POST). Всё анонимно, без ключа.

Таблицы: cbr_mortgage_series (region, period_month, series) и cbr_key_rate.
fetched_at НЕ обновляется в DO UPDATE — по уроку #2846 колонка значит
«когда мы ВПЕРВЫЕ увидели период» и по ней меряется такт публикации источника.

Раскладка листов у ЦБ РАЗНАЯ, и это ловушка: в 02_11/02_13 шапка периодов в
строке 3 текстом («Январь 2019»), а в 02_14 (задолженность) — уже в строке 2 и
настоящими datetime. С захардкоженным индексом строки серия debt_rub молча
давала НОЛЬ строк при зелёных тестах. Теперь строка шапки ищется динамически:
берётся строка с наибольшим числом распознанных периодов среди первых шести.

Проверено на живых файлах: каждая из трёх серий даёт 8 736 точек
(96 территорий × 91 месяц, 01.2019–07.2026); по Свердловской области 91 месяц,
последняя ставка 11.49.

estimator.py не тронут: как ипотечная ставка войдёт в оценку — отдельное
продуктовое решение, этот PR только про данные.

Миграции 292 (таблицы) и 293 (сид расписания, enabled=false, interval_days 7).
Новая зависимость: openpyxl.
2026-09-09 00:33:12 +03:00
bot-backend
8aca820a46 feat(mera): загрузчик реестра МКД АИС ФРТ → обогащение houses
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m14s
Открытые данные АИС ППК «ФРТ» (бывш. Реформа ЖКХ), node 110 = реестр МКД
региона 66: 41 790 строк, houseguid (ФИАС GUID) заполнен на 100% → join к
houses.gar_house_guid без канонизации адреса. Анонимный GET, без ЕСИА.

Заполняет ТОЛЬКО NULL-поля houses: year_built, material_walls, material_floors,
total_floors, entrances, is_emergency, flat_count, heat_supply_type,
gas_supply_type, hot_water + новые area_land, foundation_type, elevators_total.

Замер по ЕКБ: wall_material 92.5% (было 75% от ДОМ.РФ), built_year 92.0%
(было 86%), area_land 86.8% и is_emergency — признаков, которых не давал
ни один из текущих источников.

Осознанно НЕ мапится:
- project_type → series_name: свободный текст и фактически дубль материала стен
  (пусто у 10 635 строк, «кирпичный» 1754, «нет данных» 1496);
- energy_efficiency: решение миграции 284 + реальный класс лишь у ~10% домов
  (у 25 595 из 41 790 значение «Не присвоен»);
- elevators_count → passenger_elevators: в источнике это ОБЩЕЕ число лифтов,
  в houses раздельно пассажирские и грузовые → отдельная колонка;
- playground/sportsground: в источнике id справочника (498/499/500), не флаг.

Дубликаты houseguid реальны и взаимодополняющи: 912 guid'ов, 968 лишних строк,
у одной строки пары заполнен area_total, у парной нет. Строки СЛИВАЮТСЯ по
полям (первое непустое побеждает), иначе бэкфилл терял бы данные ~2% домов.

estimator.py не тронут: аналоги подбираются FROM listings без JOIN к houses,
встраивание признаков в подбор когорты — отдельная задача.

Миграции 290 (staging frt_mkd + колонки houses) и 291 (сид расписания,
enabled=false, interval_days 30). robots.txt источника требует Crawl-delay 10.
2026-09-09 00:33:09 +03:00
1441e189c8 Merge pull request 'fix(msk-collector): ограниченный ретрай goto на таймауте навигации' (#3425) from fix/msk-collector-nav-timeout into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / deploy (push) Successful in 55s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
2026-09-08 20:53:34 +00:00
bot-backend
f820c3eac5 fix(msk-collector): ограниченный ретрай goto на таймауте навигации
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Авито изредка держит соединение до упора, goto падает по 90-секундному
таймауту и прогон умирает целиком. В наблюдавшемся случае это был не отказ:
вкладка показывала нормальную выдачу, маркеров фаервола и PoW не было.

Но тихий отказ выглядит так же, поэтому ретрай не слепой: перед каждым
повтором читаем то, что есть в документе, и прогоняем через _guard — реальный
блок останавливает прогон со своей причиной. Исчерпали 3 попытки — жёсткий
стоп с причиной nav_timeout, а не бесконечный повтор.
2026-09-08 23:50:36 +03:00
1e96a6f12d Merge pull request 'fix(msk-collector): узкий ретрай page.content() на гонке перенавигации' (#3424) from fix/msk-collector-nav-race into main
All checks were successful
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Successful in 54s
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Has been skipped
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-08 20:48:18 +00:00
bot-backend
3daf09c7e4 fix(msk-collector): узкий ретрай page.content() на гонке перенавигации
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Авито дорисовывает выдачу после domcontentloaded, и content() иногда попадает
ровно в смену документа — Playwright бросает "page is navigating and changing
the content". Это не отказ площадки, но прогон падал целиком: на 1110-й
карточке потеряно 100 несброшенных строк.

Ретрай узкий — только на этот текст ошибки и только 4 попытки; любая другая
ошибка поднимается как есть, гварды блокировки не ослаблены. runs/ (планы и
CSV прогонов) в .gitignore.
2026-09-08 23:44:09 +03:00
e2045582ab Merge pull request 'feat(tradein/rosreestr): импорт ДКП по Москве (77) — canonical_city, raw_payload, wildcard-расписание rosreestr_dkp_import_*, per-source чекпоинт (#3051)' (#3422) from feat/3051-rosreestr-import-region-param into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-browser (push) Successful in 39s
Deploy Trade-In / build-frontend (push) Successful in 2m13s
Deploy Trade-In / test (push) Successful in 4m22s
Deploy Trade-In / build-backend (push) Successful in 1m1s
Deploy Trade-In / deploy (push) Successful in 1m23s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
2026-09-08 20:28:20 +00:00
c6321c767d Merge pull request 'feat(tradein/estimator): регион-скоуп ДКП-коридора — фильтр по deals.region_code (#3051 PR-A)' (#3423) from feat/3051-estimator-dkp-region-filter into main
Some checks failed
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m14s
Deploy Trade-In / build-backend (push) Has been cancelled
2026-09-08 20:23:21 +00:00
bot-backend
fcf5887225 merge(#3051): main (#3421) в ветку импорта по региону — московская дельта поверх region_code/doc_type
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
#3421 въехал в main параллельно с той же миграцией 288 (deals.doc_type,
параметры region_code/doc_types). Разрешение: 288 — целиком версия main;
наша дельта (FDW-колонки okato/quarter_cad_number/district, выключенный seed
rosreestr_dkp_import_77) переехала в 289. scheduler.py — doc_types из main +
canonical_city-маппинг/raw_payload/per-source чекпоинт. deploy-скрипт —
валидация REGION_CODE и DOC_TYPE (интерполируются в SQL текстом).
2026-09-08 23:19:47 +03:00
bot-backend
fbe85fcc75 feat(tradein/estimator): регион-скоуп ДКП-коридора — фильтр по d.region_code (#3051 PR-A)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m9s
target_city резолвится ТОЛЬКО для городов Свердловской области
(_resolve_target_city матчит SVERDLOVSK_OBLAST_CITIES) — для Москвы/любого
нового региона city=None, и street ILIKE оставался единственным скоупом
сделки: одноимённая улица чужого региона утекала в коридор. Добавлен
d.region_code = CAST(:region_code AS int) в оба ДКП-запроса
(_fetch_dkp_corridor) + region_code передаётся из обоих вызывающих (POST
/estimate через geo.lat/lon, GET-rehydrate через row.lat/lon) с гарантией
«не резолвится → DEFAULT_REGION_CODE (66)», не NULL (NULL в SQL-параметре
обнулил бы фильтр целиком). Дефолт региона и джойн deal_city_price_bands не
трогаются — следующие PR (B, F).

Regression: 5622 passed, 37 skipped (полный прогон tests/).
2026-09-08 23:12:12 +03:00
bot-backend
1f24f12af9 chore(#3051): снять новые тесты до живой проверки; валидация REGION_CODE в deploy-скрипте
All checks were successful
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m13s
Тесты трека «Москва» пишутся отдельным заходом после живого прогона импорта по 77 —
правило проекта с 2026-09-08. Правки существующих тестов (bind-параметр вместо
литерала 66, канонический source rosreestr_dkp_import_77) остаются.
REGION_CODE в import-rosreestr.sh подставляется в SQL текстом — допускаем только целое.
2026-09-08 23:05:35 +03:00
541c4ff28d Merge pull request 'feat(#3051): deals.doc_type + регион и тип документа как параметры импорта Росреестра' (#3421) from feat/3051-deals-doc-type-region-param into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-browser (push) Successful in 41s
Deploy Trade-In / build-frontend (push) Successful in 2m22s
Deploy Trade-In / test (push) Successful in 4m24s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Successful in 6m35s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
2026-09-08 20:04:29 +00:00
76d018b5a0 Merge pull request 'feat(#2989): локальный ручной сборщик SERP Авито по Москве и МО' (#3420) from feat/2989-msk-avito-local-collector into main
Some checks failed
Deploy Trade-In / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / perimeter-smoke (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled
2026-09-08 20:04:25 +00:00
bot-backend
e9370818ea fix(#3051): lock_timeout в миграции 288 — гейт CI #2752
All checks were successful
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
ALTER TABLE deals ADD COLUMN без SET LOCAL lock_timeout встаёт в очередь за
чужой сессией и уводит за собой запросы приложения. Лучше упасть по таймауту
и повторить деплой.
2026-09-08 22:58:38 +03:00
bot-backend
84ee8e5990 feat(tradein/rosreestr): параметризовать импорт ДКП по региону, deals.doc_type (#3051)
Трек 2 подготовки Mera к Москве. import_rosreestr_dkp принимает region_code из
params (default 66 — байт-в-байт прежнее поведение), валидирует его через
app.services.regions.REGIONS. Регион с canonical_city (77 — Москва, Росреестр
отдаёт округ/поселение вместо города) подставляет city/address через одну
SQL-ветку на bind-параметре :canonical_city, а не Python if/else на код региона;
city IS NOT NULL не фильтруется для такого региона (иначе теряется ~10% строк),
исходные city/okato/quarter_cad_number/district уходят в raw_payload.

Чекпоинт курсора (_resume_dkp_cursor) стал per-region: source для поиска
предыдущего прогона строится через _dkp_source_for_region (66 сохраняет
легаси-имя 'rosreestr_dkp_import', остальные — суффикс кода) — иначе прогон по
77 либо никогда не резюмился бы (source-литерал не матчил), либо, при более
наивном фиксе, унёс бы курсор чужого региона.

product_handlers регистрирует wildcard rosreestr_dkp_import_* (по образцу
deactivate_stale_*/avito_city_sweep_*), deploy/import-rosreestr.sh получил
REGION_CODE env (bash-путь не region-generic — city-override только в Python).

Migration 288: deals.doc_type + backfill 'ДКП' для source=rosreestr, foreign
table gendesign_rosreestr_deals расширена okato/quarter_cad_number/district
(проверено live на прод-БД), выключенный seed rosreestr_dkp_import_77.
2026-09-08 22:58:26 +03:00
bot-backend
bde38cdce7 fix(msk-collector): --resume берёт base_url из сохранённого плана
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
Коридоры считаются под конкретный URL выдачи. При --resume код брал свежий
args.base_url, поэтому запуск без повтора --base-url молча качал другую
выдачу под тем же batch_id. Теперь URL из плана, расхождение с аргументом —
явная ошибка вместо тихого выбора одного из двух.
2026-09-08 22:54:44 +03:00
bot-backend
476fb9b77a chore(#3051): снять новые тесты — до живой проверки функционала тесты не пишем
Some checks failed
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Failing after 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
В существующем test_rosreestr_dedup_key оставлена только правка, без которой
он падает после параметризации (литералы region_code=66 / doc_type='ДКП'
ушли из SQL живого импорта). Новый тест-файл и добавленная функция сняты:
поведение ещё не проверено на живом импорте, тест зафиксировал бы догадку.
2026-09-08 22:53:20 +03:00
bot-backend
7b33ed9a45 feat(#2989): локальный ручной сборщик SERP Авито по Москве и МО
Трек 1 эпика: нужен разовый корпус вторички Москвы+МО в msk_raw, а прод-скрейпер
для этого не подходит — его расписания, прокси-пул и сайдкар держат ЕКБ и трогать
их ради ручного замера нельзя.

Поэтому скрипт: браузер — уже открытый Chrome владельца с залогиненным техаккаунтом
(connect_over_cdp, своя вкладка, чужие вкладки/контекст/браузер не трогаем и не
закрываем; своего профиля не поднимаем); парсер — импорт _parse_html /
_extract_total_count / _is_firewall_page из scraper-kit, а не копия (копия разъедется
с прод-парсером на первом же DOM-drift); заливка — поток в psql через ssh, потому что
прямого доступа к прод-Postgres с локалки нет, а одиночный psql -c ломается на
квотинге.

avito_serp_ekb_only=False обязателен: с True парсер выбрасывает всё, где в URL нет
/ekaterinburg/ — из московской выдачи не осталось бы ни одной карточки.

Потолок Авито 30x60=1800 на запрос, поэтому план ценовых коридоров с бисекцией по
ГЕОМЕТРИЧЕСКОЙ середине: цены логнормальны, арифметическая середина 1млн..100млн
даёт вырожденно-пустую верхнюю половину. Коридор, который не влезает в 1800 даже
на минимальной ширине, помечается truncated и недобор пишется в batches.notes —
молчаливое усечение читалось бы как полный охват.

Стоп на первом признаке блока (403/439, 429, firewall, PoW, 0 карточек при ненулевом
счётчике) без ретраев: ретрай по забаненному техаккаунту только углубляет бан.
Дефолт — --measure 100, полный проход только по явному --full.
2026-09-08 22:48:37 +03:00
bot-backend
cfe529fd14 feat(#3051): deals.doc_type + регион и тип документа как параметры импорта Росреестра
ПОЧЕМУ: расширение на Москву упирается в два литерала. В источнике за 2024 по региону 77
лежат 30 627 ДДУ с медианой 112 743 против 107 005 ДКП с медианой 256 250 — это цены
котлована, и без различимого признака в deals они развалят любую оценку. При этом тип
сделки терялся при загрузке вовсе (в deals колонки не было), а фильтры region_code = 66
и doc_type = 'ДКП' стояли литералами в scheduler.import_rosreestr_dkp и в двойнике
deploy/import-rosreestr.sh — сменить регион было нельзя, не правя код.

ЧТО:
- миграция 288: deals.doc_type text (idempotent) + бэкфилл 'ДКП' для source='rosreestr'
  (корректен, а не эвристика: всё загруженное прошло фильтр ДКП — и в импорте, и в 077)
  + явный region_code=66 в default_params расписания rosreestr_dkp_import вместо неявного
  дефолта в коде. Индекс НЕ добавлен: 2-3 значения, живые выборки идут по
  region_code/deal_date/geom — заведём частичный, когда появится режущий запрос;
- import_rosreestr_dkp: region_code (default 66) и doc_types (default ['ДКП']) из params,
  фильтры через bind-параметры CAST(:region_code AS int) / ANY(CAST(:doc_types AS text[])),
  doc_type едет из SELECT в INSERT и в ON CONFLICT DO UPDATE. Дефолты сохраняют текущее
  прод-поведение байт-в-байт;
- dedup_hash оставлен как 'ros:dkp:' || id: id уникален в источнике независимо от типа
  документа, а смена формы ключа осиротила бы уже загруженные строки (ровно то, что
  разгребала миграция 077);
- deploy/import-rosreestr.sh: REGION_CODE / DOC_TYPE как env со старыми дефолтами,
  doc_type протащен через staging в deals; шапка про «ЕКБ квартиры» переписана честно —
  city-фильтр снят давно, скоуп = весь регион;
- тесты: test_rosreestr_dedup_key переведён с ассертов на литералы на проверку
  «параметр + дефолт = скоуп 077»; новый test_3051_* проверяет bind-параметры реальным
  вызовом с моком Session, дефолты 66/['ДКП'], doc_type в колонках INSERT и текст 288.
2026-09-08 22:45:55 +03:00
278f8055a4 Merge pull request 'chore(tradein): последние обходы пула прокси — admin cian-login/domclick-debug через фабрику; снят костыль _PoolCurlConfig (#3197, #3386)' (#3409) from chore/backend-pool-wiring-tails into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m16s
Deploy Trade-In / build-backend (push) Successful in 1m42s
Deploy Trade-In / deploy (push) Successful in 1m53s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
2026-09-07 11:04:07 +00:00
f4d174283a chore(#3197): cian-login — без холостой аренды и ложного отказа (/login сайдкара override не берёт); честные докстринги
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m7s
Code-review хвоста #3197: на `cian-login` аренда из пула не доходила до сайдкара.
`BrowserFetcher._post_login` не кладёт `payload["proxy"]` (это делают только
`fetch`/`fetch_json`), а на приёме `login_handler` (browser/server.py:2814-2817)
зовёт `_no_live_proxy(provider, None)` и `_ensure_browser(provider)` без override —
`/login` proxy-override не принимает вовсе. Lease брался в `__aenter__` и
освобождался в `__aexit__` без пользы и без health-вердикта по узлу.

Хуже холостого хода: при пустом пуле в production `_acquire_lease` поднимает
`NoProxyAvailableError` ДО POST, `cian_auto_login` ловит любое `Exception` →
`502 Browser login failed`. То есть единственная ручка ВОССТАНОВЛЕНИЯ cian-сессии
отказывала ровно во время инцидента с пулом. Комментарий в admin.py при этом
утверждал, что фикс закрывает InvalidIP на логине — неправда.

Убран `proxy_provider=` (фабрика остаётся ради endpoint/environment из одного
места). `use_pool` без провайдера фетчер игнорирует сам — `_acquire_lease`:
`use_pool AND provider is not None` — поэтому ни аренды, ни прод-отказа.
`domclick-detail-debug` не тронут: он ходит через `fetch`, где override реально
кладётся в тело и читается сайдкаром — там #3197 остаётся настоящим фиксом.

Тесты для cian обратные по значению и падают на HEAD ветки:
`test_cian_auto_login_does_not_lease_from_pool` (провайдер не передан, `acquire`
не вызван) и `test_cian_auto_login_survives_empty_pool_in_production` (пустой пул
на проде не отдаёт 502). Стаб cian — подкласс настоящего `BrowserFetcher`, чтобы
второе утверждение шло через реальный `_acquire_lease`, а не через заглушку.

Follow-up (отдельной задачей): сайдкар `/login` не принимает proxy override →
логин cian всегда с env-узла (сейчас выключенный узел 9).
2026-09-06 17:48:29 +05:00
8761602e9b chore(tradein/proxy): последние две ручки admin.py — через фабрику фетчера; снят форс pool-режима у cian-history (#3197, #3386)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m5s
Два хвоста одной темы — проводка пула прокси в контейнере backend.

#3197: `cian-login` и `domclick-detail-debug` были последними прямыми
конструкциями `BrowserFetcher(source=, endpoint=)` мимо `build_browser_fetcher`.
Без `proxy_provider`/`use_pool`/`environment` сайдкар брал свой env-узел
`SCRAPER_PROXY_URL` (на проде выключенный узел 9: 407 → camoufox `InvalidIP`), а
прод-отказ «пул пуст» (#2616) на этих путях был мёртв — он смотрит на
`environment`, который до конструктора не доезжал. Соседи по эпику уже переведены
(#3382 cian, #3389 yandex). Прямых конструкций без провайдера вне тестов больше
не осталось: остальные (backfill-задачи, pipeline) пул получают своими kwargs,
а `endpoint=None`-ветки providers — это документированный `config=None` для
офлайн-тестов.

#3386: `_PoolCurlConfig` в `cian_price_history` форсил `use_proxy_pool_curl=True`,
потому что у контейнера `backend` не было переменной. #3387 задал
`USE_PROXY_POOL_CURL: "true"` сервису `backend` в compose — зашитая константа
стала лишней и делала рубильник неотключаемым ровно на этом пути (докстринг при
этом описывал уже неверную причину). Теперь `RealScraperConfig()` напрямую.

Тесты меряют значения, а не наличие kwarg'а: на откате исходников красные
4 параметризации нового `test_3197_admin_debug_browser_pool_wiring`
(`assert None is not None` — провайдер не передан) и
`test_price_history_honours_flag_off` (`assert ['cian'] == []` — пул дёргался при
выключенном флаге).
2026-09-06 17:22:46 +05:00
f52a39e3ad Merge pull request 'fix(tradein/proxy): операции пула прокси уходят с event loop публичного /estimate — acurl_proxy_url через to_thread (#3398 п.1)' (#3407) from fix/3398-pool-ops-off-event-loop into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m18s
Deploy Trade-In / build-backend (push) Successful in 1m48s
Deploy Trade-In / deploy (push) Successful in 1m16s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
2026-09-06 11:55:10 +00:00
7d3c0eab51 fix(#3398): acurl_proxy_url — вторая отмена не бросает lease; тест меряет наш release, а не teardown; потолок бюджета в докстринге
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
2026-09-06 16:49:12 +05:00
e4feadefe6 fix(tradein/proxy): операции пула прокси уходят с event loop публичного /estimate (#3398)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m8s
Вход в `providers/_proxy.py::curl_proxy_url` синхронный и стоял ДО первого await во
всех трёх async-сайтах `/estimate` (avito/imv, yandex/valuation, cian/valuation).
`RealProxyProvider.acquire/mark_health/release` ходят в БД синхронно (своя SessionLocal
на операцию), а публичный backend крутится на ОДНОМ воркере uvicorn (#3083): на
cache-miss это 3 источника x (acquire + mark_health + release) блокирующих вызовов
прямо на loop'е. `_with_budget(asyncio.wait_for)` синхронный вход прервать не может, а
при исчерпанном пуле коннектов (5+10) checkout ждёт до 30 с — весь инстанс молчит.

`acurl_proxy_url` — async-обёртка над тем же синхронным контекстом: вход и выход через
`asyncio.to_thread`, выход тоже (иначе mark_health/release держали бы loop на выходе).
Семантика прежняя: `NoProxyAvailableError` до запроса, BaseException-ветка (#3397:
отмена → health=False), release в finally. `to_thread` копирует contextvars, поэтому
`current_run_id` (#3404) виден в потоке как раньше.

Новое по сравнению с sync-путём: отмена может прийти ВО ВРЕМЯ acquire (раньше это было
невозможно по построению). Вход держится через `asyncio.shield` и добирается в except —
иначе выданная в потоке аренда висела бы до reap_stale_leases.

Переключены только три сайта `/estimate`. Sync-вызывающие и async-сайты под scheduler
(cian/detail.py::fetch_detail, cian/newbuilding.py::resolve_cian_zhk_url_via_search)
остаются на `curl_proxy_url` — там loop не обслуживает публичные запросы.
2026-09-06 16:05:50 +05:00
75b0931fad Merge pull request 'fix(cian): капча видна в отчёте прогона (ban_kinds.platform по типу) и банит узел на curl-пути (#3402 follow-up)' (#3406) from fix/3402-followups-ban-kinds-curl into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m10s
Deploy Trade-In / build-backend (push) Successful in 1m41s
Deploy Trade-In / deploy (push) Successful in 1m50s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-06 10:40:11 +00:00
224f91e8fa fix(cian): капча видна в отчёте прогона и банит узел на curl-пути (#3402)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
Два follow-up из ревью #3403.

1. Капча-волна была невидима в счётчиках. `_note_refusal` ключевался только по
   HTTP-статусу, а капча приходит с 200 (свой детект по <title>) или без статуса
   (сайдкар) → рос один `listings_failed_fetch`, `ban_kinds` оставался пустым, и
   волна отказа площадки читалась как дрейф нашей разметки. Теперь диагноз берётся
   сперва по ТИПУ исключения (`ban_kind_of_exception`), статус — фолбэк. Инвариант
   #3196 сохранён: 'unknown' по типу И None по статусу по-прежнему ничего не пишут.

   `ban_kind_of_exception` расширен с `AvitoBlockedError` до `ProxyBanError` — это
   ровно тот mixin, по которому generic-прокси-слой уже снимает узел с выдачи
   источнику. Для Авито поведение не меняется (AvitoBlockedError его наследует),
   Cian/DomClick перестают приезжать как 'unknown'.

2. curl-путь детектил капчу, но не банил: общий parse-путь лежит ЗА границей
   `with curl_proxy_url(...)`, и `CianBlockedError` поднимался уже после
   `mark_health(ok=True)` — узел, которому Циан показывает капчу, оставался в
   выдаче Циану (дефект #2700, только на HTTP 200). Проверка перенесена ВНУТРЬ
   блока, `finally` хелпера сам делает `mark_banned(source='cian')`.

Тесты по значению (оба красные на main): батч с капчей → ban_kinds == {platform: 1};
curl-путь + HTML капчи → mark_banned == [(1, 'cian')], mark_health(ok=True) нет.
2026-09-06 15:30:12 +05:00
72e3bc9e24 Merge pull request 'feat(tradein/proxy): прогон знает свой узел, а снятый бан перестаёт стирать историю (#3404)' (#3405) from feat/3404-proxy-run-attribution into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m14s
Deploy Trade-In / build-backend (push) Successful in 1m39s
Deploy Trade-In / deploy (push) Successful in 1m53s
Deploy Trade-In / deploy-status (push) Successful in 2s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-06 10:18:14 +00:00
bot-backend
f4099cb89a Merge remote-tracking branch 'forgejo/main' into feat/3404-proxy-run-attribution
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
2026-09-06 13:05:38 +03:00
bot-backend
b89788ee99 feat(tradein/proxy): прогон знает свой узел, а снятый бан перестаёт стирать историю (#3404)
Выбор оператора мобильного прокси опирался на две ненадёжные опоры.

Первая: `scrape_runs` не знала, через какой узел шёл прогон — колонка `proxy_id`
была только у банов и ротаций. «Какой узел собрал 5 карточек из 21» не выяснялось
ни одним запросом.

Вторая: `clear_source_bans` делала DELETE, а зовётся она после КАЖДОЙ успешной
ротации exit-IP. У #540723 (МегаФон) 23 успешные ротации и ноль строк банов,
у #540722 (Tele2) ротаций почти не было и 7 банов. «7 против 0» читалось как
«Tele2 хуже», хотя в той же мере это «у МегаФона историю стёрли 23 раза».

Теперь:
- `scrape_runs.proxy_id` — последний выданный прогону узел; полная цепочка
  (если узел менялся mid-run) копится в `counters.proxy_ids`. Пишет
  `proxy_pool.attribute_run_proxy` из единственной точки — сразу после выдачи
  лиза в `acquire()`, поэтому curl-путь, браузерный sticky lease и ре-acquire
  при ротации покрыты одинаково. `run_id` доходит до адаптера через ContextVar
  (`scraper_kit.orchestration.run_context`): протокол `ProxyProvider.acquire`
  его не несёт, а `RealProxyProvider` живёт одним объектом на весь планировщик.
  Best-effort: `lock_timeout` 2с и проглоченное исключение — диагностика не
  вправе ронять выдачу прокси или ждать на блокировке строки прогона.
- `clear_source_bans` гасит строку (`banned_until = now()`, `ban_count = 0`,
  `cleared_at`/`cleared_reason`) вместо удаления. Эскалация сохраняется 1:1:
  формула в `mark_banned` берёт ПРЕДЫДУЩИЙ `ban_count` показателем степени, при
  нуле это ровно `SOURCE_BAN_BASE_HOURS` — как после DELETE. Строка доживает до
  штатного purge по `SOURCE_BAN_PURGE_DAYS`.

Для всех читателей `scrape_proxy_source_bans` погашенная строка неотличима от
отсутствующей: acquire, оба guard-подзапроса `mark_banned`, `proxy_egress`
(ранжирование по `ban_count` даёт 0, как у узла без истории), admin `_active_ban` —
все гейтятся по `banned_until > now()`.

Ничего не бэкфиллится: связать прошедшие прогоны с узлами нечем (`leased_by`
исторически = NON_RUN_LEASE_MARKER), врать восстановленным значением нельзя.

Миграция 287. Тесты: 9 новых на обе части (главный — эскалация после гашения даёт
базовые 6ч, а не удвоенные) + 14 существующих переведены с DELETE-семантики на
гашение, включая проверку, что секрет ротации не утекает в новое `cleared_reason`.
Полный прогон бэкенда: 5600 passed, 37 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011WHFxVPWoBnSZihkdH1Uou
2026-09-06 13:05:20 +03:00
3d154f4ec0 Merge pull request 'fix(cian): капча/страница ошибки Циана (HTTP 200) — отказ площадки в сайдкаре и kit, ротация аренды внутри батча (#3402)' (#3403) from fix/cian-captcha-is-platform-refusal into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 2m14s
Deploy Trade-In / test (push) Successful in 4m18s
Deploy Trade-In / build-backend (push) Successful in 1m39s
Deploy Trade-In / deploy (push) Successful in 2m1s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-06 10:01:30 +00:00
957e9e8c01 fix(#3402): капча = «Captcha…» и «Вы не робот?» (бан+ротация); «Ошибка - Циан» — только лог на цикл наблюдения
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m8s
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m27s
Ревью (⚠️ minor) на #3403: «Ошибка - Циан» может быть транзиентной 5xx-страницей,
отданной с кодом 200, а не отказом конкретному узлу. Цена ошибки несимметрична —
mark_banned эскалирует TTL до часов, поэтому 20-минутный сбой площадки выбил бы из
выдачи весь пул. Один список маркеров этого различить не мог: и капча, и страница
ошибки шли одним путём в BanPageDetectedError.

Проба прода 06.09.2026 09:25 UTC (одна карточка по узлам через сайдкар):
* узел 14, час назад отдававший «Captcha - база объявлений ЦИАН», вернул НАСТОЯЩУЮ
  карточку — капча снимается за 1-2 часа, то есть TTL бана по назначению;
* узел 1 отдал ТРЕТИЙ вариант отказа — `<title>Вы не робот?`, 16 КБ (час назад —
  «Ошибка - Циан», 374 КБ). Прежние маркеры его не знали вовсе: отказ уезжал наверх
  как валидный HTML ровно так же, как до #3402.

Маркеры разделены на два класса, одинаково в обоих слоях (образы backend и browser
деплоятся раздельно и расходятся на часы):
* КАПЧА — «captcha - база объявлений циан» + «вы не робот?»: безусловный отказ
  площадки, прежний путь (сайдкар → BanPageDetectedError → 403 + ban_page; kit →
  report_platform_ban + CianBlockedError). За ней нет контента, и узел, которому её
  показали, будет получать её дальше;
* «ошибка - циан» — ТОЛЬКО ЛОГ: сайдкар отдаёт HTML клиенту как есть и пишет WARNING
  «страница ошибки Циана (title=…, upstream=…) — не бан, только лог (#3402)», kit при
  провале extract_state пишет WARNING и возвращает прежний None. Ни бана, ни рапорта,
  ни исключения — решение принимаем по частоте в логах за цикл наблюдения, а не по
  догадке о природе страницы.

Нормализация заголовка прежняя (регистр/пробелы/тире). `_is_cian_refusal` →
`_is_cian_captcha` + `_log_cian_error_page`; `_refusal_title` → `_page_title` и два
кортежа маркеров рядом.

Фальсификация: «вы не робот?» убран из маркеров обоих слоёв → kit 1 failed
(«DID NOT RAISE CianBlockedError»), сайдкар 3 failed («DID NOT RAISE
BanPageDetectedError», `_is_cian_captcha` → assert False is True). Маркер возвращён,
обе сьюты зелёные: backend 5599 passed / 35 skipped, browser 246 passed.
2026-09-06 14:55:45 +05:00
ff979d4afd fix(tradein/cian): капча Циана с HTTP 200 — отказ площадки в обоих слоях (#3402)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Successful in 1m28s
CI Trade-In / backend-tests (pull_request) Successful in 5m11s
Циан отдаёт капчу (`<title>Captcha - база объявлений ЦИАН`, 44 КБ) и страницу
ошибки (`<title>Ошибка - Циан`, 374 КБ) с кодом 200. Детектор сайдкара их не знал
(_REFUSAL_STATUSES {403,429} + маркеры Авито/Домклика), HTML уезжал клиенту как
успех, extract_state возвращал None и провайдер печатал «defaultState extraction
failed» — отказ ПЛОЩАДКИ читался как дрейф НАШЕЙ разметки. Аренда при этом не
менялась: fetch() уже отрапортовал mark_health(ok=True), fail-streak обнулялся, и
один капча-узел сжигал батч целиком (6200: 0/210; 6123/6091/6052/6032/6010/5981:
0/400 — против 161/162 через здоровый узел на прогоне 13).

Два слоя, потому что образы backend и browser деплоятся раздельно и расходятся
на часы:
* сайдкар (browser/server.py) — детект по <title> на обоих путях (navigate и
  подзапрос) → BanPageDetectedError → прежний путь #3288/#3379: 403 + ban_page +
  ЧЕСТНЫЙ upstream-статус 200;
* kit (providers/cian/detail.py) — при провале extract_state те же маркеры →
  CianBlockedError вместо тихого None, плюс report_platform_ban по живому lease.
  Там же ветка SidecarBanPageError: отказ, опознанный сайдкаром, больше не
  гасится общим `except` в «не смогли разобрать».

Слово `captcha` признаком быть не может: в нормальной карточке оно встречается 11
раз, на капче 17. Детект по <title> с нормализацией тире.

`_report_platform_ban` → `report_platform_ban` (публичный): тем же путём обязан
идти отказ, распознанный не сайдкаром, а провайдером. report_ban один только
пишет бан пары «узел×источник» — сменить сожжённую аренду ВНУТРИ батча позволяет
только fail-streak (_LEASE_ROTATE_AFTER_FAILS).
2026-09-06 14:10:57 +05:00
06ad504505 Merge pull request 'refactor(tradein): одна реализация scrape_runs — kit orchestration/runs.py, app-модуль = алиас; counters везде мержатся (#3390)' (#3400) from refactor/3390-single-runs-module into main
All checks were successful
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m9s
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-backend (push) Successful in 1m42s
Deploy Trade-In / deploy (push) Successful in 7m48s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
2026-09-06 08:11:52 +00:00
ae6ad9e440 docs(#3390): убрать ложные «counters ЗАМЕНЯЕТ» из комментариев/докстрингов; гейт test_3168 — честная формулировка
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m51s
2026-09-06 12:34:53 +05:00