Commit graph

1206 commits

Author SHA1 Message Date
c14119a20b Merge remote-tracking branch 'origin/main' into fix/snapshot-change-only
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 8m31s
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 / changes (pull_request) Successful in 28s
CI / changes (pull_request) Successful in 31s
2026-09-17 15:49:56 +05:00
4ccf701df6 merge origin/main: конфликт skip_allowlist.txt (оба блока сохранены)
All checks were successful
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 7m45s
CI Trade-In / changes (pull_request) Successful in 12s
CI / changes (pull_request) Successful in 13s
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
Полный сьют после слияния: 6378 passed, 61 skipped, rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 15:48:25 +05:00
bdc169af8b merge origin/main: починка красного main (#3572)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 19s
CI / changes (pull_request) Successful in 26s
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 7m47s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:56:10 +05:00
7350287be7 merge origin/main: починка красного main (#3572)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 21s
CI / changes (pull_request) Successful in 26s
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 7m54s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:56:03 +05:00
e4d44025e5 fix(tradein/snapshot): снимок источника пишется только при изменении (#2993, PR-C)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 26s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 31s
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) Failing after 7m37s
listing_source_snapshot каждую ночь копировал listing_sources целиком: прод
14-17.09 — 290-293 тыс. строк в сутки, из них отличались от предыдущего
снимка 7.7-11.4 тыс. (3-4 %). Решение владельца 2026-08-23 — строка на
изменение; читатель (пол переобхода, PR-A #3056) и рельсы объёма (PR-B
#3066) уже смержены.

Строка пишется, если снимка ещё нет или (price_rub, is_active, last_seen_at,
payload_hash) отличается от последнего снимка источника, включая сегодняшний
(повторный прогон в те же сутки перезаписывает строку, только если значение
снова сдвинулось). Состояние на дату D = последняя строка с snapshot_date <= D
— для всех четырёх колонок то же, что дала бы суточная копия, поэтому пол
переобхода и события не меняются. Старые суточные снимки не трогаются.

Замер чтением на проде 17.09: отбор с условием — 1.0 с на 293 602 источника
(бюджет 900 с). Миграция 325 — только COMMENT на таблицу и view: прежние
«ежедневный снимок»/«на дату снимка» стали бы неправдой.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:46:36 +05:00
635381f7b8 fix(tests): красный main — тесты #3466 читали настройки, которые #3556 перевёл в константы
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 8m23s
CI Trade-In / changes (pull_request) Successful in 14s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 20s
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
Семантический конфликт двух PR, смерженных 17.09 подряд. Текстового конфликта не
было, CI каждого PR был зелёным на своей голове, а объединённое дерево падало:

  test_3466_corridor_tier_a.py (2), test_estimator_radius_floor.py (2)
  AttributeError: 'Settings' object has no attribute 'estimate_corridor_clamp_slack'
  TypeError: _run_estimate() got an unexpected keyword argument 'radius_floor_factor'

#3554 (Tier A и advisory_only) писал тесты против settings.estimate_corridor_clamp_*,
а #3556 (#2380) убрал эти поля из Settings в константы CORRIDOR_CLAMP_SLACK
(estimator) и CORRIDOR_CLAMP_MIN_N (app.core.config) и снял параметр
radius_floor_factor у хелпера. Продуктовый код не затронут: в app/ старые имена
остались только в комментарии, на проде AttributeError не было. Но красный test
блокировал деплой МЕРЫ: на проде до сих пор образ до второй пачки мержей.

Тесты переведены на константы, значения прежние (0.40, 10, 0.8).
pytest tests/ — 6374 passed, 44 skipped, rc=0; ruff check/format — rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:45:54 +05:00
90b93b90b5 Merge origin/main в fix/proxy-pool-guards (#3299)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 16s
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 19s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 5m54s
Конфликт только в tests/skip_allowlist.txt: обе стороны дописали блок в конец —
live-тесты пула прокси (#3299/#3310/#3404) и миграции 310 (#3385). Оставлены оба.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:39:38 +05:00
fc5735fd6d Merge origin/main в fix/domclick-card-fields
Some checks failed
CI Trade-In / changes (pull_request) Successful in 12s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 14s
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) Failing after 5m46s
Конфликт только в tradein-mvp/backend/tests/skip_allowlist.txt: обе стороны
дописали блок в одно место (#3252 — тесты апсерта карточки ДомКлика, #3385 —
миграция 310). Оставлены оба блока. Номер миграции ветки 320 не пересекается
с main (максимум там 310).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:38:58 +05:00
a130303cc9 Merge pull request 'Сайдкар МЕРЫ: зона браузера по IP узла, якорь Домклика без Яндекса, автологин Циана через пул, время навигации в логах' (#3563) from fix/browser-sidecar into main
Some checks failed
Deploy Trade-In / build-browser (push) Successful in 2m54s
Deploy Trade-In / build-frontend (push) Successful in 3m49s
Deploy Trade-In / test (push) Failing after 5m1s
Deploy Trade-In / build-backend (push) Has been skipped
Deploy Trade-In / deploy (push) Has been skipped
Deploy Trade-In / perimeter-smoke (push) Has been skipped
Deploy Trade-In / deploy-status (push) Failing after 2s
Deploy Trade-In / changes (push) Successful in 15s
2026-09-17 09:24:11 +00:00
10c80e9e1d Merge pull request 'Скрапер: newbuilding-провайдеры без config больше не уходят мимо пула прокси, IMV Авито строит сессию общей фабрикой' (#3562) from fix/kit-providers-config into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:23:57 +00:00
1e99d91afe Merge pull request 'Сбор МЕРА: упавший прогон не висит zombie 6 часов, провалившаяся фаза не попадает в чекпоинт, проверка отмены не держит транзакцию часами' (#3561) from fix/kit-orchestration into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
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 / build-frontend (push) Blocked by required conditions
Deploy Trade-In / build-browser (push) Blocked by required conditions
Deploy Trade-In / deploy-status (push) Blocked by required conditions
Deploy Trade-In / changes (push) Has been cancelled
2026-09-17 09:23:42 +00:00
e52057e66d Merge pull request 'Авито: добор карточек больше не сжигает 1600 попыток на упавшем сайдкаре и везде ходит браузером' (#3560) from fix/avito-detail-backfill into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:23:20 +00:00
a672178bcb Merge pull request 'Оценка: дедуп склеивает улицы вида «40-летия Комсомола», пороги Tier H — в настройки для бэктеста' (#3552) from fix/estimator-analog-quality into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:23:05 +00:00
a9e5322b7a Merge pull request 'Оценка: при якоре того же дома видно, что коридор сделок цену не ограничивал' (#3554) from fix/corridor-advisory-tier-a into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:22:46 +00:00
0be4d91d30 Merge pull request 'МЕРА: настройки оценщика, которые никогда не менялись, стали кодом — 4 кластера флагов #1970' (#3556) from fix/estimator-flag-collapse into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:22:26 +00:00
6a6fc7031d Merge pull request 'Алерты GlitchTip: медленный отказ Telegram отвечает 502 и уходит в фон, а не обрывается на 10-й секунде' (#3555) from fix/glitchtip-delivery into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:22:08 +00:00
99f8525b1c Merge pull request 'Мера: о новой заявке узнаёт ответственный, а на дашборде видно, сколько оценок доходит до заявки' (#3551) from fix/lead-funnel into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
Deploy Metrics / server (push) Successful in 1m12s
Deploy Metrics / agent-apps (push) Successful in 36s
Deploy Metrics / agent-infra (push) Successful in 33s
2026-09-17 09:21:44 +00:00
57b03092ea Merge pull request 'Витрина лендинга: пустой пересчёт больше не считается успешным, а итог игры «Проверьте себя» говорит, что квартиры отобраны' (#3550) from fix/landing-showcase-and-game into main
Some checks failed
Deploy Trade-In / test (push) Blocked by required conditions
Deploy Trade-In / build-backend (push) Blocked by required conditions
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 / changes (push) Has been cancelled
2026-09-17 09:21:02 +00:00
d551cfbab9 test(tradein): объявить пропуски новых live-тестов защиты пула (#3299)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 14s
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 17s
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 5m32s
Четыре live-теста из правок по ревью без БД пропускаются, а гейт conftest
роняет прогон с DSN-заглушкой на необъявленных пропусках (rc=1 при
«6226 passed» в сводке). Внесены в skip_allowlist.txt к остальным тестам #3299.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:10:03 +05:00
29ac3043b4 fix(tradein/proxy_pool): защита бана не считает узел с истёкшей арендой, условия резерва проверены по значению (#3299)
Ревью PR #3565: у нового предиката резерва две части не проверялись ни одним
тестом. Снятие `other.expires_at` из подзапроса acquire и снятие
`other.consecutive_fails` из подзапроса mark_banned оставляли прогон
test_3299 + services/test_proxy_pool.py зелёным (72 passed). Проверено тем же
способом, и нашлась третья такая часть: `other.expires_at` в mark_banned.

Добавлено по live-тесту на каждое условие: просроченный резерв в acquire,
резерв в карантине и просроченный резерв в mark_banned.

Попутно найден дефект рядом. Внешний отбор mark_banned («есть ли у источника
другой узел») срок аренды не проверял, хотя acquire такой узел не выдаёт, а
докстринг обещает «тем же правилом, что acquire()». Если у источника остались
только просроченные узлы, бан уходил последнему живому, и источник оставался
без прокси, пока healthcheck не наберёт просроченным узлам отказов. Добавлена
проверка `sp.expires_at` и тест на этот случай.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 14:02:32 +05:00
69e7047398 Тесты гейта чекпоинта: частичный отказ фазы, порог в одну попытку, упавший запрос домов (#3415)
Ревью PR #3561: три мутации гейта оставались зелёными — порог `attempted >= 2`,
жадный гейт `failed >= 1` и снятый `houses_attempted += len(nb_id_list)` в ветке
«houses DB query failed». Добавлены проверки по чекпоинту:
- 1 из 2 домов отказал в каждом якоре — оба якоря в чекпоинте (контроль жадности);
- единственный дом якоря отказал — якорь не в чекпоинте (порог);
- упал запрос домов — якоря не в чекпоинте, houses_attempted == houses_failed == 2.

Код не менялся.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:58:43 +05:00
2e4a82dd46 Упавший прогон финализирует общий runs.mark_crashed: и ручные запуски из админки, и без дубля алерта (#1940)
Ревью PR #3561, две находки.

1. Ручные запуски из админки (avito/cian city sweep, cian_full_load,
   yandex_full_load, yandex_city_sweep) идут мимо scheduler._dispatch: в except
   был только logger.exception, и отказ «пул пуст» до try-финализатора пайплайна
   оставлял строку running до zombie. Логика финализации переехала из
   планировщика в runs.mark_crashed; её зовут планировщик и все пять ручек.

2. Обычный путь пайплайна — mark_failed и raise; планировщик звал mark_failed
   второй раз. UPDATE — no-op, но _alert_on_run_id срабатывал снова с тем же
   стриком, и на вехе лестницы в Sentry уходил дубль (+ WARNING «no-op»).
   mark_crashed сначала читает статус и финализирует только running.

Тесты по значению над двойником строки scrape_runs и боевыми mark_*:
статус banned/infra или failed после планировщика и после каждой из пяти ручек,
одна отправка в Sentry на неудаче пайплайна.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:57:34 +05:00
91a075bc81 МЕРА: снимок фикстуры бэктеста снова знает пороги движка (#1970)
Ревью #3556: settings_at_capture в _write_fixture писал только поля estimate_*
из Settings. После переноса 31 порога в константы estimator.py будущая фикстура
не знала бы, с какими порогами её захватили. Теперь в снимок попадают и
числовые константы модуля (63 штуки, CORRIDOR_CLAMP_MIN_N в том числе).
replay_fixture снимок по-прежнему не читает.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:57:07 +05:00
228c5f58f9 МЕРА: тест якоря «тот же дом» проверяет веса на месте вызова (#2381)
Ревью #3556: подмена rooms_boost=1.0 на месте вызова _compute_same_building_anchor
проходила весь сьют — тест значений держит только число в константе. Новый тест
оборачивает расчёт якоря и требует боевые sigma/rooms_boost/floor_sigma/min_comps/
mad_k (прод 17.09.2026). SB_GUARDRAIL_TOL, FSD_K, SB_GATE_MAX_FSD и сдвиг
ANCHOR_TIER_C_CORRIDOR_MULT вниз по-прежнему без поведенческих тестов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:57:05 +05:00
bc7fe9b0b9 МЕРА: тесты квартального индекса ловят пороги на месте использования (#2379)
Ревью #3556: тест значений держит число в константе, но не то, что код её читает.
Подмена min_n_deals=5 на обоих местах вызова проходила весь сьют. Хуже: Guard-2 и
bimodal-гард снимались целиком (`if False:`) при зелёном test_estimator_quarter_index —
оба теста были пустыми. Guard-2: заглушка отдавала аналогам индекс цели, factor=1.0.
Bimodal: аналоги без кадастра, поправку гасил Guard-5 (#2583) независимо от гарда.

- оба теста получили аналоги с известным уровнем 1.0 — без гарда цена сдвигается;
- границы: доля ровно 0.6 поправку даёт, индекс ровно 2.0 даёт, 2.01 — нет;
- заглушки lookup'ов пишут min_n_deals, тест требует 10 на обоих вызовах.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:57:04 +05:00
c7a03495e2 Свип ДомКлика: корзина без сохранённых строк не попадает в чекпоинт (#2406)
Ревью PR #3561: тест таймаута фазы закреплял как контракт done_buckets == ['st'].
Лоты ДомКлика копятся в памяти и пишутся одним save_listings после всех корзин,
поэтому снятая watchdog'ом (или упавшая на save) фаза не сохраняет ничего, а
чекпоинт всё равно получал completed_buckets живого скрейпера — следующий прогон
пропускал корзину навсегда (механизм миграции 308).

Чекпоинт пополняется только если фаза дошла до конца (флаг _saved после save).
Тест развёрнут: при таймауте fetch_city и при падении save_listings
done_buckets == [], статус failed. Контроль «полный проход несёт
унаследованное ∪ пройденное» — test_3369.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:54:55 +05:00
45017d2535 fix(mera/estimator): дедуп различает порядковые улицы «2-я Синичкина» во всех трёх форматах (#2291, класс 4)
Что было. После «40-летия Комсомола» остались однобуквенные порядковые:
регексы требовали после «N-» минимум две буквы. Одна улица в трёх форматах
разбиралась по-разному: cian «улица 2-я Синичкина» давал ('', ''),
avito/yandex «2-я ул. Синичкина» и domklik «2-я Синичкина улица» теряли номер
('синичкина'). Потеря номера — это ложная склейка: «9-я Парковая, 5» и
«12-я Парковая, 5» получали один ключ ('парковая', '5').

Что сделано. В обоих регексах названия улицы добавлен необязательный
порядковый «N-x » перед (нумерованным) именем; формат «номер тип имя»
переставляется в «тип номер имя» до разбора. Номер дома с дефисом не тронут.

Замер на проде (tradein-postgres, SELECT, 17.09): 12 848 разных адресов с
шаблоном «цифра-дефис/пробел-буква», 29 959 объявлений. Разбор изменился у
8 713 объявлений; ни один адрес не потерял токен. Межисточниковых групп
(улица, дом) 1305 -> 1473. Из 81 прежней группы, которая распалась, 60 —
разные порядковые улицы, 3 — ложный mkr-ключ, 18 — голая и порядковая
улица в разных городах (одна — источник сам потерял номер). Гейт бэктеста
без изменений baseline.

Refs #2291

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:50:31 +05:00
1a9a612a82 test(mera/estimator): тесты дедупа #2291 не трогают флаг, который #3556 удаляет
Два новых теста класса 4 ставили monkeypatch.setattr(settings,
"estimate_dedup_analogs_enabled", True). PR #3556 (#2378) удаляет это поле из
Settings, и setattr по несуществующему атрибуту падает AttributeError. CI
гоняет голову ветки, а не merge-ref, поэтому второй смерженный PR пришёл бы
зелёным и уронил main. На main флаг и так ON по умолчанию
(test_dedup_default_is_on), так что строки лишние при любом порядке мержа.

Проверено: дифф PR, наложенный на голову #3556 (31c31f8f), до правки
2 failed / 144 passed (AttributeError), после 146 passed. Тесты не стали
пустыми: откат префиксного регекса без флага даёт assert 4 == 1 и 1 == 2.

Refs #2291

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:44:45 +05:00
49bcfdacd6 test(tradein): граница radius-floor при n == порогу стережётся (#3466)
Ревью #3554: замена `>=` на `>` в гейте radius-floor оставляла
test_estimator_radius_floor.py зелёным — в тестах были только count=15 и
count=min_n−1. При n == min_n витрина не пишет «справочно»
(advisory_only=False), значит floor обязан поднять цену.

Новый тест: n = estimate_corridor_clamp_min_n, медиана 80k → ровно 120k,
плюс проверка, что DkpCorridor на той же границе не справочный. Мутант
`>` краснеет: «коридор из 10 сделок (ровно порог) должен поднять цену
ровно до floor=120000».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:42:57 +05:00
6e8c261ff8 fix(tradein/domclick): собственники, нулевые площади и жилая площадь карточки перестали теряться (#3252)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 19s
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 4m55s
Сверка парсера с живым __SSR_STATE__ трёх карточек (17.09) нашла три тихие дыры:

1. owners_count. egrnData отдаёт поля ЕГРН обёрткой {status, value}; int() от
   словаря молча давал None. Прод: 0 из ~2970 карточек, обогащённых с 29.08,
   против 5075 из 6296 у ручного прогона 18.07, который обёртку разворачивал.
   Ключ egrnData.area подтверждён — гадания rosreestrArea/object_area удалены,
   в raw_payload обёртка остаётся как есть (status = сверка с объявлением).
2. livingArea/kitchenArea = 0 у Домклика значит «не указано», а в колонку ложился
   0.00 и затирал известное значение. Прод: 303 кухни и 84 жилые площади = 0,
   только domklik. Разбор: _pos_float; строки: миграция 320 ставит NULL.
3. Жилую площадь и балконы стирал переобход выдачи: апсерт scraper_kit.base писал
   их сырым EXCLUDED, а SERP Домклика и Авито этих полей не отдаёт. Прод: domklik,
   переобойдённые после обогащения, living_area_m2 0 из 5217 (без переобхода
   2990 из 4058); avito 1 из 8639 (4122 из 6505). COALESCE в SET, в гейте #2992
   и на reconcile-пути.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:25:38 +05:00
47a91acf61 test(tradein): объявить пропуски live-тестов пула прокси (#3299, #3310, #3404)
All checks were successful
CI Trade-In / 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 / changes (pull_request) Successful in 15s
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 5m47s
Без БД 11 новых live-тестов пропускаются, и гейт conftest роняет прогон
(«НЕУЧТЁННЫЙ ПРОПУСК», rc=1 при «6226 passed» в сводке). В ci-tradein.yml они
идут по-настоящему: локальный прогон на Postgres со схемой из миграций —
6278 passed, 1 skipped, rc=0.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:14:01 +05:00
4961128f87 fix(scraper-kit/cian): детальная Циана на своей сессии — пул прокси не на event loop (#3408 п.3)
fetch_detail без session/browser_fetcher входил в синхронный curl_proxy_url:
acquire/mark_health/release RealProxyProvider'а ходят в БД прямо на loop'е.
Вызывающий — админ-ручка истории цен Циана в публичном tradein-backend
(один воркер uvicorn): пара блокирующих вызовов на каждый листинг батча.
#3398 перевёл так три сайта /estimate, этот остался.

Теперь async with acurl_proxy_url: вход и выход в потоке, contextvars
(current_run_id) копируются, ProxyBanError/CianBlockedError изнутри блока
доходят до пула как раньше (test_2700/test_3402 зелёные). Попутно потолок в
докстринге acurl_proxy_url: pool_timeout теперь 5 с (#3444), не 30.

Тест по образцу test_3398: acquire спит 0.3 с, соседняя корутина тикает.
На синхронном входе — «тиков всего 0».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:04:54 +05:00
e83742ac41 МЕРА: yandex-история больше не сеет цену на порядок ниже/выше текущей (#3385)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI / changes (pull_request) Successful in 21s
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 5m40s
Все 7 скачков ×10 в yandex-истории цен на проде (17.09) — не «сдвиг на следующем
наблюдении», как предполагал issue, а синтетический посев из gate-поля
price.previous: нижняя точка стоит ровно на 24 ч раньше второй и записана тем же
батчем. Ошибочна нижняя цена (13–37 тыс/м² против 133–375 у верхней). Ещё 4 посева
того же рода — ×175…×1000 (цена в тысячах). Ветка писателя «история есть» на проде
не записала ни одной строки: смену цены раньше неё пишет триггер
record_listing_price_change, поэтому отлов «на следующем наблюдении» там поймал бы 0.

- yandex_price_history: price.previous, отличающаяся от текущей цены в 9.5 раза и
  больше в любую сторону (или не > 0), не сеется; счётчик seed_rejected в логе.
- миграция 310: удаляет yandex-посевы по подписи батча (recorded_at совпадает,
  change_time второй точки ровно +24 ч) с тем же порогом; lock_timeout 5s,
  остановка при > 30 строк; на проде ровно 11 кандидатов, повторный прогон — 0.
- тесты по значению: посев отвергнут/сохранён на прод-ценах; настоящий файл
  миграции на живом Postgres через временную таблицу.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:04:02 +05:00
ab003877dc fix(tradein/proxy_egress): прогон на egress-пути тоже знает свой узел (#3404)
PR #3405 писал scrape_runs.proxy_id только из proxy_pool.acquire(). Прогоны,
которые берут прокси через proxy_egress.resolve_proxy_url без аренды
(yandex_detail_backfill, yandex_address_backfill, curl-ветка
avito_detail_backfill), атрибуцию не получали: прод 17.09 —
yandex_detail_backfill 0 из 56, yandex_address_backfill 0 из 1.

resolve_proxy_url при выбранном узле и выставленном current_run_id зовёт
attribute_run_proxy. Своей короткой сессией: db вызывающего — долгоживущая
сессия прогона посреди работы, а атрибуция коммитит и на сбое откатывает.

Тесты на живом Postgres: атрибуция внутри прогона (proxy_id и
counters.proxy_ids), вне прогона scrape_runs не тронут, незакоммиченная
работа вызывающего не коммитится. Красные прогоны: на старом коде
«assert None == 70»; с атрибуцией через db вызывающего — «атрибуция
закоммитила чужую транзакцию».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:03:22 +05:00
611878df4c fix(scraper-kit/proxy): бан на curl-пути не копит счётчик отказов узла (#3310)
Защита последнего узла в mark_banned бережёт узел от бан-строки и пишет
«узел продолжит выдаваться», но curl_proxy_url на том же ProxyBanError
вслед за баном звал mark_health(ok=False). Три ответа 403/капчи подряд —
consecutive_fails = 3, и acquire отсекал узел для ВСЕХ источников: пул
пуст при живом узле. Браузерный путь это исправил в #3288
(report_platform_ban), curl-путь — нет; через него ходят cian detail,
ЖК-резолв, история цен Циана и оценщик.

Теперь на ProxyBanError зовётся mark_banned вместо mark_health(False).
Транспортные сбои по-прежнему засчитываются узлу.

Тест на живом Postgres настоящим трактом (curl_proxy_url →
RealProxyProvider → proxy_pool): единственный узел, три CianBlockedError →
consecutive_fails == 0, acquire('cian') выдаёт узел; на старом коде
«assert 3 == 0». Пять старых проверок ждали mark_health(False) на бане —
они фиксировали дефект, поправлены.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 13:01:24 +05:00
92295971fe fix(tradein/proxy_pool): резерв выделенного узла считает узлы 'any' (#3299)
Запасной заход acquire() и защита последнего узла в mark_banned() спрашивали,
есть ли у выделенной привязки ВТОРОЙ узел той же привязки. Выделенный узел
штучный, поэтому ответ почти всегда «нет», и такой узел не выдавался никому,
хотя свой источник обслуживали 'any'-узлы. Прод 17.09: узел 15 (avito)
свободен и здоров, а cian/domclick его не получали; 30.08 так лёг добор
Домклика (прогоны 5449-5459).

Теперь резерв — любой узел той же привязки или 'any', здоровый
(consecutive_fails), с живой арендой порта и не забаненный источником
выделенной привязки. Предикат одинаковый в обоих местах. leased_by
намеренно не проверяется: аренда временная.

Тесты на живом Postgres (tests/test_3299_*): 6 случаев из приёмки, два
красные на старом SQL. Мок test_proxy_pool.py: маршрутизация primary/fallback
по полному фрагменту IN (:provider, 'any') и гейт нового предиката.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:59:44 +05:00
4549311720 Автологин в Циан идёт через узел пула, а не через мёртвый env-узел сайдкара (#3410)
/login сайдкара не принимал proxy из тела (_no_live_proxy(provider, None),
_ensure_browser без override), BrowserFetcher.login его не клал, а ручка
cian auto-login сознательно не брала провайдер. Итог: логин всегда шёл с
SCRAPER_PROXY_URL сайдкара — на проде это выключенный узел 9 (InvalidIP),
то есть ручка восстановления cian-сессии не работала вовсе.

- сайдкар: /login берёт proxy из тела, как /fetch (guard и _ensure_browser
  с override; crash-relaunch в _do_login сохраняет _launched_proxy);
- scraper-kit: login() кладёт в тело узел текущей аренды, как _post_fetch;
- backend: ручка передаёт _kit_proxy_provider(), как debug-карточка DomClick;
  пустой пул на проде — 503 «нет свободного узла для cian», а не 502.

Тесты #3197 про «логин без пула» переписаны на обратные утверждения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:58:03 +05:00
808a4fb4cb refactor(tradein): IMV Авито строит curl-сессию общей фабрикой, а не своей копией (#2386)
All checks were successful
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m55s
CI Trade-In / changes (pull_request) Successful in 13s
CI / changes (pull_request) Successful in 15s
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
avito/imv.py сам собирал curl_cffi AsyncSession с третьей копией
document-заголовков (_DOC_HEADERS) — из-за того, что parity-тесты патчили
curl_cffi.requests.AsyncSession, а _base импортирует класс на уровне модуля и
такой патч до него не долетает. Взят вариант B из issue: в кодовой базе уже
принят патч scraper_kit.providers._base.AsyncSession (test_scraper_proxy.py,
test_pipeline_browser_routing.py, test_kit_serp_proxy_pool.py), а вариант A
(живой lookup в _base) сломал бы эти тесты — атрибута _base.AsyncSession не стало бы.

Сессия теперь build_document_session(proxy_url, timeout=25); _DOC_HEADERS
удалён (идентичен DOCUMENT_HEADERS, проверено до правки), мёртвый
try/except ImportError убран — curl_cffi и так импортируется через _base.
Тесты патчат _base.AsyncSession; ассерт с config дополнительно фиксирует
impersonate, timeout=25 и заголовки — параметры сессии не изменились.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:57:29 +05:00
d2a31dff08 Тесты kit-свипов: таймаут якоря, дрейн и отмена посреди обхода у всех свипов, а не только у Авито (#2406)
All checks were successful
CI Trade-In / backend-tests (pull_request) Successful in 5m21s
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 15s
CI / frontend-tests (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
После удаления legacy-пайплайна эти сценарии были заассерчены только у avito
(test_3319) и дрейн до первого якоря у yandex/cian (test_3333). Добавлено:
- таймаут якоря у cian и yandex: якорь не в чекпоинте, следующий обработан,
  errors_count растёт; таймаут фазы у domclick: завершённые корзины в чекпоинте,
  статус failed, а не done;
- SIGTERM-дрейн после первого якоря у cian и yandex: interrupted=1, в
  чекпоинте ровно первый якорь, второй не посещён;
- пользовательская отмена (is_cancelled=True) после первого якоря у avito,
  cian и yandex: второй якорь не посещён, чекпоинт на месте, без mark_done.

Проверка по строке прогона: фейковая БД мержит counters всех записей, как
jsonb-мерж в runs.py. Мутации (снятый continue/return/interrupted/errors_count
в pipeline.py) роняют ровно свой тест, 6 из 6.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:57:13 +05:00
eade88bc66 fix(tradein): config обязателен в newbuilding-провайдерах Циана и Яндекса (#2385)
fetch_newbuilding, resolve_cian_zhk_url_via_search, YandexNewbuildingScraper и
resolve_yandex_jk_slug принимали config=None и в этом случае строили
BrowserFetcher(endpoint=None) мимо фабрики, а резолвер Циана — сессию без прокси.
Все боевые вызовы config уже передают, но новый вызывающий без config молча ушёл бы
на env-узел сайдкара мимо пула — именно так потеряли 8 суток в #2767 и сломались
в #3197. Теперь config обязателен по сигнатуре, запасные ветки удалены, фетчер
строит только build_browser_fetcher.

Тесты: кейсы *_without_config (ждали endpoint=None) заменены одним — без config
TypeError до сети, фетчер и сессия не строятся; остальные тестовые вызовы
получили config.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:55:32 +05:00
6be2bf9aaa fix(tradein/avito): серия отказов нашего тракта обрывает detail-добор статусом failed (#3439)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 17s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 19s
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
Прод: 7193, 7218, 7264, 7302 (15-16.09) и 6608/6631/6643/6662 (10.09) — по
1600 попыток, 1-10 карточек, ~1599 блоков, status='done', по 70+ минут
каждый. В те же окна в логах tradein-browser шторм «browser launch failed …
InvalidIP» и «/fetch 503». 6885 (13.09) — те же 1600 попыток при
ban_kinds {"infra": 1591}, но 'banned'.

Корень — регресс #3288/#3367: record_block(kind != platform) уходит в
record_failure(), то есть только в знаменатель окна. Числитель доли растёт
лишь на platform, safety-net смотрит на platform-серию, счётчика серии
infra нет вовсе. Серия отказов сайдкара любой длины не рвала прогон ничем,
а финализатор #3367 при одной карточке ставил 'done'.

- BlockRatioBreaker: infra_streak_limit и счётчик серии infra. Серию гасит
  любой ответ площадки — успех, её блок, 404. abort_reason() -> "infra_streak"
  (проверяется первым), abort_explanation() называет эту величину.
- avito_detail_backfill: порог = max_consecutive_failures (дефолт 25) — тот же
  разряд «отказ нашей стороны», что и отказы-не-блоки. На обрыве — mark_failed
  «тракт недоступен: N отказов … подряд без единого ответа площадки», в
  counters abort_reason=infra_streak и ban_kinds. Не 'banned' — площадка
  молчала; не через mark_backfill_finished — там одна карточка даёт 'done'.

Тесты по значению: 1 успех + 1599 отказов сайдкара -> attempted=26,
mark_failed, abort_reason=infra_streak, ban_kinds {"infra": 25}; обрыв ровно
на 25-м (24 — нет); 24 infra + ответ площадки (успех/блок/404) + 24 — не
обрыв; 24 infra + ответ, трижды — снапшот пройден; 25% настоящих блоков —
не обрыв; прежний контроль #3367 (20 infra + 10 успехов -> done) зелёный.

Фальсификация: без проводки порога — «assert (1600, 1600) == (26, 26)»; без
сброса серии на блоке площадки — «abort_reason='infra_streak': 24 + ответ
площадки (platform) + 24»; без ветки финала — «прогон ушёл в
backfill-honest-status: там 1 карточка превращает шторм в 'done'».

Пункт 3 задачи (browser_unfit узлу, на котором сайдкар не поднял браузер)
не сделан: это вердикт пула прокси и протокол сайдкара, отдельная задача.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:54:18 +05:00
fb02e41d77 Проверка отмены закрывает свою транзакцию: свип ДомКлика больше не держит горизонт vacuum на весь обход (#3480)
Виновник по живому снимку pg_stat_activity 17.09 07:48 UTC: pid 1933154 из
tradein-scraper, 'idle in transaction' 1 ч 35 мин, последний запрос
`SELECT status FROM scrape_runs WHERE id = $1` (runs.is_cancelled),
xact_start через 30 мс после старта domclick_city_sweep_moskva 7344.
По Prometheus за 03-17.09 10 из 12 окон «транзакция > 1 ч» совпадают по
началу и концу со свипами ДомКлика.

is_cancelled делал SELECT без commit, а вызывающие сразу уходят в сетевую
фазу (у ДомКлика — весь fetch_city, до 3 ч). Теперь commit после чтения —
в общей точке для всех 12 вызовов; незакоммиченных записей, рассчитанных на
откат, перед вызовами нет (перед каждым — update_heartbeat/save_listings с
commit или чтения).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:54:04 +05:00
a6c86e88ea fix(tradein/avito): диагноз блоков ложится в counters любого финала добора, не только 'banned' (#3288)
Пункт 5 из комментария 05.09: ban_kind пишется только у banned-прогонов, у
done/failed диагноза в БД нет. Прод: 7193, 7218, 7264, 7302 — по 1600
попыток, ~1599 блоков, status='done', counters без ban_kinds; что это были
отказы сайдкара, а не площадки, видно лишь по косвенному признаку (мелкая
block_streak_histogram считает только platform-серии). mark_backfill_finished
кладёт перепись в counters только на ветке бана.

Задача теперь кладёт counters.ban_kinds сама перед любым финализатором
(mark_backfill_finished / mark_failed пустого пула). Статусы и колонка
ban_kind не тронуты — контракты #3196/#2764 как были.

Тест по значению: 20 отказов сайдкара + 10 успехов — counters, уходящие в
финализатор, несут {"infra": 20}. Фальсификация: без строки —
«assert None == {'infra': 20}».

Стоп-кран для infra-серии (вторая половина п.5) — отдельным коммитом #3439.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:51:44 +05:00
19b6081242 fix(tradein): режим detail-добора Авито одинаков во всех контейнерах образа backend; убрана мёртвая YANDEX_COOKIES_FILE (#3182)
Прод 17.09, printenv: AVITO_DETAIL_BACKFILL_USE_CURL=false у tradein-scraper
и true у tradein-backend/tgbot/browser. Источник true — хостовый
backend/.env.runtime:37; false задан только environment'ом сервиса scraper.
tradein-backend исполняет добор сам (ручка админки
scrape_avito_detail_backfill), то есть ручной прогон шёл curl'ом, который
QRATOR PoW не проходит.

- docker-compose.prod.yml: AVITO_DETAIL_BACKFILL_USE_CURL "false" у backend
  и tgbot — у всех сервисов образа backend одно значение (environment
  перекрывает env_file). tgbot добор не исполняет; строка там, чтобы правило
  «одно значение на образ» проверялось без списка исключений.
- Остальные флаги режима проверены на проде: SCRAPER_FETCH_MODE=browser и
  AVITO_IMV_USE_BROWSER_FETCHER=true одинаковы во всех четырёх контейнерах.
- yandex_cookies_file удалён из Settings: его не читал ни один модуль
  (только мок в test_scraper_proxy.py), файла /app/yandex_cookies.json нет
  ни в одном контейнере, куки Яндекса давно живут в БД (#3192).
  extra="ignore" держит старт при оставшейся переменной в .env.runtime.

Тест по значению: разобранный compose — у каждого сервиса образа backend
значение "false"; Settings с YANDEX_COOKIES_FILE в env стартует без поля.
Фальсификация: без строки у backend — «{'backend': None} != {'backend':
'false'}»; с возвращённым полем — «assert not True».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:50:20 +05:00
facdbb2566 Свипы Циана и Авито: якорь с полностью отказавшей фазой не попадает в чекпоинт (#3415)
cian_city_sweep 6179 (06.09) — failed по «фаза houses отказала полностью, 30 из
30», но в done_buckets все пять якорей. 6276 резюмировался от него, пропустил
все якоря и отчитался done с houses_attempted=0, lots_fetched=0.

Отметка «якорь пройден» опиралась только на поток управления, а фаза отказывает
и без исключения (fetch_newbuilding -> None, растёт счётчик). Гейт
_bucket_phase_totally_failed сверяет прирост пар X_attempted/X_failed за якорь;
порог одна попытка. Тот же гейт в run_avito_city_sweep (detail). Ветка «houses
DB query failed» теперь растит houses_attempted вместе с houses_failed.
Форма чекпоинта прежняя (плоский список имён).

Перенос ветки fix/3415-bucket-done-only-if-phases-ok (19a335f4) на свежий main,
применилась без конфликтов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:48:20 +05:00
e42e370ac9 fix(tradein/avito): detail-добор по умолчанию идёт браузером, а не curl (#3045)
curl_cffi не проходит QRATOR proof-of-work Авито в принципе (замер 21.08:
~6% обогащений против ~74% на браузере). Шаг 3 задачи был сделан только
environment'ом сервиса scraper, дефолт в config.py остался True. Добор при
этом исполняет и tradein-backend — ручка админки scrape_avito_detail_backfill
вызывает run_avito_detail_backfill в своём процессе, и там printenv даёт true.
Ручной прогон через админку шёл curl'ом.

Дефолт avito_detail_backfill_use_curl -> False. Комментарий в задаче «true
только у tradein-backend, который добор не запускает» был неверен — исправлен,
как и два упоминания use_curl=True «прод-дефолтом».

Тест по значению: Settings без env и без .env даёт False; контроль — env
по-прежнему переключает на curl. Фальсификация: default=True ->
«AssertionError: assert True is False».

На проде хостовый backend/.env.runtime:37 задаёт true явно, поэтому для
tradein-backend нужен ещё environment-оверрайд — он в коммите #3182.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:48:12 +05:00
8e5f097c5e Планировщик: упавший до финализатора прогон получает статус сразу, а не zombie через 6 часов (#1940)
Исключение, вылетевшее из хендлера до его try с mark_failed (прод: пустой пул
прокси в BrowserFetcher.__aenter__ через 10 мс после claim), планировщик только
логировал. Строка оставалась 'running' с heartbeat_at == started_at, reaper через
6 ч ставил 'zombie' — за 21 сутки так 18 avito-свипов (7349 и 7350 висят сейчас).

_dispatch._run теперь финализирует прогон сам: пустой пул прокси в цепочке
причин -> banned/ban_kind=infra (как в пайплайнах), остальное -> failed.
Уже финализированную хендлером строку не трогает (WHERE status='running'),
пустые counters мержатся и чекпоинт не стирают.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:47:05 +05:00
bc0ef9536e fix(glitchtip): медленный отказ Telegram отвечает 502 до таймаута GlitchTip (#3157)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 28s
CI / changes (pull_request) Successful in 33s
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 / openapi-codegen-check (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 6m25s
Синхронная пересылка алерта была ограничена таймаутом одного HTTP-запроса
(8 с), а попыток после ретранслятора (#3471) больше одной: relay, прямой путь,
пауза 2 с, повтор. 16.09.2026 16:42 UTC отказ шёл медленно, GlitchTip
(aiohttp ClientTimeout total=10) оборвал запрос на 10-й секунде, Caddy записал
status=0, а хендлер досчитал 200 уже разорванному клиенту: uvicorn такой ответ
выбрасывает вместе со строкой access-log. Исход у отправителя и приёмника
расходился, 502 и фоновая доставка не срабатывали.

Вся синхронная попытка теперь под asyncio.timeout(7 с). TimeoutError идёт тем
же путём, что TelegramError: logger.exception, 502, фоновая доставка.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:40:56 +05:00
31c31f8f82 МЕРА: якорь «тот же дом» и IMV-blend — константы движка, не настройки (#2381)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 22s
CI / changes (pull_request) Successful in 25s
CI Trade-In / browser-tests (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 / frontend-checks (pull_request) Successful in 2m35s
CI Trade-In / backend-tests (pull_request) Successful in 6m20s
Пять булевых флагов кластера сняты ещё в #2475. Оставшиеся 14 числовых полей
Settings перенесены в estimator.py константами с теми же значениями:
IMV_BLEND_WEIGHT 0.5, IMV_BLEND_THRESHOLD 1.15, SB_MIN_COMPS 4, SB_AREA_SIGMA
0.18, SB_ROOMS_MATCH_BOOST 1.6, SB_FLOOR_SIGMA 0.25, SB_GUARDRAIL_TOL 0.05,
SB_MAD_K 3.5, SB_MAD_K_SMALL_N 2.5, SB_SMALL_N_THRESHOLD 10,
ANCHOR_TIER_C_CORRIDOR_MULT 1.5, FSD_K 1.65, SB_GATE_MIN_N 3, SB_GATE_MAX_FSD
0.20. На проде 17.09 все 14 равны дефолтам, ENV-оверрайдов нет. Мёртвая
проверка `tier_c_mult > 0` (константа против нуля) убрана.

Тесты подменяют SB_MIN_COMPS на модуле вместо поля settings. Реплей бэктеста
по сделкам побитово тот же, срабатываний якоря 985, IMV-blend 3, low-conf
гейта 11 — как на main. Шесть порогов (rooms_boost, floor_sigma,
guardrail_tol, tier_c_mult, fsd_k, gate_max_fsd) не ловит ни один
поведенческий тест и ни гейт, их держит только тест боевых значений.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:38:44 +05:00
1fe383f935 fix(tradein): коридор ДКП при якоре того же дома помечен как не вошедший в цену (#3466)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 15s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 20s
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 / frontend-checks (pull_request) Successful in 2m25s
CI Trade-In / backend-tests (pull_request) Successful in 7m2s
Кламп headline к коридору ДКП выключается не только малым числом сделок
(advisory_only, #3452), но и якорем Tier A: _apply_corridor_clamp его exempt,
radius-floor требует anchor_tier is None. Ревьюер #3462 воспроизвёл n=20,
advisory_only=False, headline 202 100 против потолка 140 000 — подписи нет.

- Признак — analog_tier == "same_building" (уже структурный в POST).
  corridorAdvisoryNote принимает тир и при same_building говорит
  «справочно: цена посчитана по аналогам в этом же доме — коридор её не
  ограничивает»; оба вызова (v1 HeroSummary, v2 mappers) передают тир.
- GET-rehydrate терял analog_tier (колонки нет) — якорный тир теперь
  восстанавливается из подписи якорного блока в confidence_explanation
  через общую константу (analog_tier_from_explanation), как радиус в #2632.
  Разбор по всей фразе: радиусный тир S пишет «(аналоги из того же дома)»,
  на проде таких строк 4.
- Тесты floor не доходили до floor: три лота уводили в #oblast-E, headline
  брался из медианы коридора. На main с полностью выключенным floor файл
  зелёный. Лотов шесть, ожидания точные, добавлен кейс n = min_n − 1.
- Полоса маркера corridor_advisory_zone — n = 1..9, не 3..9: уличный коридор
  отдаётся с одной сделки. Формулировки поправлены, тест на n=2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:38:35 +05:00