698 commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
| ec7838b7a9 |
fix(#3398): пустой пул в фоновой догрузке — WARNING без трейсбека (общая функция); комментарий про stateless
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
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) Has been skipped
CI / changes (pull_request) Successful in 9s
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
На проде `ESTIMATE_EXTERNAL_SOURCES_BACKGROUND=true` (docker-compose.prod.yml:296), поэтому синхронный cian-вызов идёт с `fetch_on_miss=False` и возвращает None ДО прокси-слоя (`providers/cian/valuation.py:171`) — добавленная в этой ветке ветка WARNING в `estimate_quality` на проде почти не звучит. Настоящий фетч уходит в `_defer_external_refresh`, где `NoProxyAvailableError` попадал в общий `except Exception: logger.exception(...)` → ERROR + traceback → событие в GlitchTip на каждый /estimate по новому адресу: ровно тот шум, который PR и убирает. Правка в ОБЩЕЙ функции отложенной догрузки, а не в cian-ветке: через неё идут все источники фонового режима (yandex тоже — у него swallow живёт внутри `_get_or_fetch_yandex_valuation_cached`, дыры нет, но следующий источник получит поведение бесплатно). Для прочих исключений всё как было: `logger.exception`. Тест по значению: background=True + пустой пул в production → фоновая догрузка cian логирует WARNING «пул прокси пуст», записей ERROR/traceback у логгера эстиматора нет. Задача дожидается внутри того же loop'а и не снимая патчей (`_DEFERRED_REFRESH_TASKS` + `asyncio.gather`) — иначе `anyio.run` закрывает loop раньше старта задачи и тест был бы зелёным по построению. На HEAD ветки тест красный: ERROR app.services.estimator:estimator.py:910 deferred cian_valuation: догрузка не удалась (кэш не прогрет) + Traceback … NoProxyAvailableError. Комментарий у `_c_kwargs`: весь dict переиспользуется замыканием фоновой задачи, то есть `config`/`proxy_provider` — один инстанс на два возможно-одновременных вызова. Корректно ровно пока оба stateless (`RealScraperConfig` — read-only снимок настроек, `RealProxyProvider` без полей, короткая сессия БД на операцию); появится per-вызов состояние — фоновой задаче нужен свой инстанс. |
|||
| 8a5f75d5f4 |
fix(tradein/estimator): yandex/cian valuation в /estimate через пул прокси, пустой пул не роняет ответ (#3398)
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 5m4s
Тот же корень, что у IMV (#3386 / PR #3397): оба вызова шли БЕЗ `proxy_provider` — `YandexValuationScraper(RealScraperConfig(), delay_provider=...)` и `estimate_via_cian_valuation(**_c_kwargs)`. `providers/_proxy.py::curl_proxy_url` считает `use_pool = флаг AND provider is not None`, поэтому пул был выключен по построению, а curl уходил на env-прокси SCRAPER_PROXY_URL (выключенный узел #2613): на проде 06.09 каждая проба `/estimate` давала `curl_cffi ProxyError: CONNECT tunnel failed, response 407`, а в логах это читалось как «yandex_valuation: empty result» — оба источника мертвы с 02.09. Kit-стороне правки не нужны: `YandexValuationScraper.__init__` и `estimate_via_cian_valuation` уже принимают kwarg-only `proxy_provider` и сами зовут `curl_proxy_url` (yandex — lease на сессию __aenter__/__aexit__, cian — lease на вызов, release в finally). Провайдер берётся из module-level импорта `RealProxyProvider` (он stateless: короткая сессия на операцию), поэтому один инстанс в `_c_kwargs` покрывает и основной вызов, и отложенную фоновую догрузку. Мягкая деградация сохранена и стала честной в логах: `NoProxyAvailableError` (проверка по цепочке причин `caused_by_no_proxy`) → WARNING «пул прокси пуст — продолжаем без Yandex/Cian» и прежний None-путь, а не ERROR «fetch failed» / «lookup failed» (запрос вообще не уходил — GlitchTip-событие тут было бы шумом). Строка «yandex_valuation: empty result» переписана: None неразличимо «дом не найден» и «фетч не дошёл», причина — в строке scraper_kit выше. Тесты (7, по значению; на main все красные): оба call site получают провайдера; пустой пул в production → оценка без этих источников, без исключения, HTTP не уходит, в логе «пул прокси пуст»; lease освобождён ровно один раз на успехе и на ошибке фетча — через настоящий `curl_proxy_url`, а не мок провайдера. |
|||
| 075cec4b57 |
Merge pull request 'fix(tradein/estimator): IMV-путь /estimate через пул прокси; пустой пул — честная деградация без IMV (#3386 п.в)' (#3397) from fix/3386-estimator-imv-proxy-pool into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
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 4m2s
Deploy Trade-In / build-backend (push) Successful in 1m39s
Deploy Trade-In / deploy (push) Successful in 1m14s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
| 6f97995140 |
fix(tradein/estimator): IMV-путь берёт прокси из пула, пустой пул не ломает /estimate (#3386)
All checks were successful
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 9s
CI / changes (pull_request) Successful in 10s
CI Trade-In / backend-tests (pull_request) Successful in 4m55s
Оба вызова `evaluate_via_imv` в `_get_or_fetch_imv_cached` шли без `proxy_provider`, а `providers/_proxy.py::curl_proxy_url` считает `use_pool = флаг AND provider is not None` — пул был выключен по построению, curl-сессия уходила на env-прокси SCRAPER_PROXY_URL (мёртвый узел, #2613). Провайдер берётся из уже существующего module-level импорта `app.services.scraper_adapters` (в estimator цикла нет, в отличие от house_imv_backfill — там lazy import вынужденный). Lease — один на вызов IMV, acquire/release внутри `curl_proxy_url`, release в finally на всех выходах. Пустой пул в проде (`NoProxyAvailableError`, в т.ч. завёрнутый — проверка по цепочке причин `caused_by_no_proxy`) остаётся graceful: `_get_or_fetch_imv_cached` возвращает None, ответ отдаётся без IMV-якоря. Причина в логе теперь честная — «пул прокси пуст», а не «fetch failed» (запрос не уходил вовсе). |
|||
| ba4fb03a05 |
fix(#3393): capped по сканированному окну — interrupted-строка не глушит лестницу навсегда; контроль zero-сторожа
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 4m55s
|
|||
| 5f94bf0eeb |
fix(tradein/scrape_runs): оборванный деплоем прогон вне лестниц стриков и honest-status (#3393)
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 4m57s
После #3392 SIGTERM-дрейн финализирует in-flight прогоны штатными mark_done/mark_failed с counters.interrupted=1 — раньше они оставались 'running' → 'zombie' и сторожей не касались. В популяции стриков эти строки судят частичные счётчики: - detail-бэкфилл, убитый на 20% отказов, получал 'failed' с диагнозом «сбор деградировал» (_failed_ratio_too_high) — диагноз про площадку, которого никто не измерял, — и входил в стрик неудач; - cian-бэкфилл (без attempted) получал 'done' и ОБНУЛЯЛ стрик банов — ровно вред, задокументированный в orchestration/scheduler.py:99 («5 банов подряд обнулил один 'cancelled' 09.08»). Прогон с меткой interrupted теперь считается так, будто его не было: он стрик ни продлевает, ни обнуляет (обе лестницы, обе копии модуля), а три honest-status-гейта в mark_done пропускаются — статус остаётся 'done' с меткой interrupted (конвенция #3319/#3333/#3355), причина в логе. 'cancelled' оставлен как был: там прогон прервал человек. SELECT сторожа неудач дополнен колонкой counters — по ней и идёт отбор. |
|||
| a7362bc5fa |
fix(#3391): пульс не пишет по финализированной строке; отмена в теле тика тоже помечает; rollback/try/честный лог
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 4m56s
Пять замечаний deep-ревью к PR #3392, ровно они.
1. Пульс стирал метку дрейна. `update_heartbeat` обеих копий бил `WHERE id = :run_id`
без гейта по статусу, а app-копия counters ЗАМЕНЯЕТ (#3390): задача, помеченная
`interrupted`, но ещё живая (ветка таймаута drain_inflight отдаёт её внешнему
hard-cancel'у — несколько итераций спустя, пульс на каждый батч —
app/services/scheduler.py:141), следующим же ударом стирала метку, и оборванный
прогон снова читался как полный проход. Гейт — `IN ('running', 'cancelled')`, а не
`= 'running'`: 'cancelled' финализирует строку, но задача встаёт лишь на ближайшей
границе якоря, и её последний пульс — ЕДИНСТВЕННЫЙ писатель чекпоинта в этот момент
(pipeline.py:1308/2368/2986/4488, mark_done там уже no-op по своему гейту), а
'cancelled' входит в _RESUME_STATUSES — сужение до 'running' молча съело бы точку
возобновления у каждой отмены. Возвращаемое значение update_heartbeat не читает
никто (обе копии -> None, ни одного присваивания на 130 сайтах вызова), так что
«0 строк обновлено» ломать нечего; no-op логируется WARNING'ом, как у mark_done.
2. Отмена вне drain_inflight. Hard-cancel приходит по расписанию grace'а
scheduler_main, а не по нашему, и может застать ТЕЛО тика (reap / stale-digest /
`_dispatch` с сетевым pre_claim). `except Exception` тика CancelledError не ловит,
до `await ctx.drain_inflight()` дело не доходит — строки оставались 'running'.
Тело вынесено в `_tick_loop`, `scheduler_loop` ловит CancelledError, помечает
in-flight и пробрасывает отмену.
3. rollback в except пометки: отказавший statement оставляет сессию в aborted-tx, и
первый же непроходимый run_id утаскивал все следующие (образец — defensive rollback
в mark_failed/mark_banned).
4. session_factory()/db.close() втянуты в try: исключение оттуда ЗАМЕНИЛО бы собой
CancelledError, а suppress(CancelledError) в scheduler_main его не глушит — процесс
уходил бы с трейсбеком вместо чистого drain-выхода.
5. WARNING перечисляет marked_ids, а не весь run_ids (там были и пропущенные по
статусу). В докстринге назван потолок: SELECT синхронный, у движка нет ни connect-,
ни statement-таймаута (app/core/db.py:8-19) — недоступная БД блокирует луп до
SIGKILL'а через 20 с docker-grace; данные при этом не хуже прежних (строки остаются
'running' → boot-reap).
Тесты — по значению, не по факту вызова; на исходниках
|
|||
| 3fe2d310b2 |
Merge pull request 'fix(tradein/yandex): newbuilding resolve/fetch_jk через пул прокси + стоп на пустом пуле (#3197, точки 2-3)' (#3389) from fix/3197-yandex-newbuilding-proxy-pool into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
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 4m5s
Deploy Trade-In / build-backend (push) Successful in 1m38s
Deploy Trade-In / deploy (push) Successful in 7m53s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
|
|||
| 47dabec678 |
fix(#3384): cian — в mark_failed уходит только измеренное, нули heartbeat не затираются; стаб пула как прод
All checks were successful
CI / changes (pull_request) Successful in 11s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 9s
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 5m0s
Ревью нашло у цианa (в отличие от avito/домклика с живым counters.to_dict()) старый
словарь в общем except: реальные значения присваиваются уже ПОСЛЕ возврата из
backfill_cian_history, а отказ бывает и посреди неё — пул опустел между стадиями, упал
SELECT домов. Тогда поверх измеренного в запись прогона уезжали нули, и SQL-разбор
простоя (#3288/#3367) читал «к площадке не ходили» про прогон, который ходил.
Механизм оказался хуже описанного в ревью: mark_failed мержит counters (`counters ||
:counters`) только в kit-копии, а cian/avito/домклик зовут app.services.scrape_runs, где
UPDATE counters ЗАМЕНЯЕТ (scrape_runs.py:738). Поэтому «отдать только {no_proxy_stop: 1}»
стёрло бы измеренное начисто; вместо этого _heartbeat кладёт свой снимок в те же
counters (nonlocal), и в mark_failed уезжает последнее измеренное + флаг.
Тест по значению: heartbeat записал listings_processed=5, дальше пул пуст → в jsonb-
payload mark_failed должно остаться 5, а не 0 (проверяется сам payload UPDATE'а,
runs_mod настоящий). На HEAD ветки красный: `counters={'listings_processed': 0, ...,
'no_proxy_stop': 1}: нули поверх измеренных 5`.
Стаб пула приведён к проду: RealProxyProvider.acquire при пустом пуле ВОЗВРАЩАЕТ None
(scraper_adapters.py:230), а не поднимает, — исключение из провайдера глотал
`except Exception` в _acquire_lease и приходило к тому же отказу другим путём. Теперь
NoProxyAvailableError рождается там же, где в проде (browser_fetcher.py:712, ветка
`lease is None and use_pool and production`) — проверено прогоном против до-#3384
исходников: все три теста красные, трейс из _acquire_lease.
_prod_pool патчит app.core.config.settings явно + assert, что все три задачи держат тот
же синглтон: раньше патч через chb.settings выглядел настройкой одного циана.
|
|||
| 939a8a1646 |
fix(#3197): no_proxy_stop=1 как у соседей, INFO на финале хендлера, докстринг фабрики
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 4m57s
|
|||
| cb5714fff0 |
fix(#3197): yandex newbuilding — обе точки в пул прокси + стоп на пустом пуле
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 4m56s
`YandexNewbuildingScraper.fetch_jk` и `resolve_yandex_jk_slug` строили `BrowserFetcher(source="yandex", endpoint=...)` без proxy_provider/use_pool/ environment — сайдкар брал env-узел SCRAPER_PROXY_URL, на проде выключенный (407 → camoufox InvalidIP → /fetch 503, факт #3386), то есть путь шёл мимо пула целиком, а прод-отказ «пул пуст» (#2616) был мёртв: он смотрит на environment, который до конструктора не доезжал. Обе точки собраны через build_browser_fetcher(config, "yandex", proxy_provider=...), как yandex/serp.py. Второе: общий `except Exception` в обеих функциях глотал NoProxyAvailableError и возвращал None — прогон, не ходивший к площадке, перебирал все ЖК и уходил в 'done'. Теперь «пул пуст» пробрасывается наружу (caused_by_no_proxy), sweep обрывается на первом доме с no_proxy_stop, а прогон финализируется как failed (образец дефекта — #3382, cian/detail.py). |
|||
| caa3b8b056 |
fix(tradein): пустой пул ДО первого объявления теряет диагноз в записи прогона (#3384)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
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
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
Lease берётся один раз в BrowserFetcher.__aenter__, поэтому на проде с пустым пулом NoProxyAvailableError вылетает из самого `async with` — ДО первой карточки и мимо стоп-механики внутри цикла (no_proxy_stop = True; break), которая и пишет counters.no_proxy_stop. Общий `except Exception` ловил его и делал mark_failed с нулевыми counters без ключа: прогон, который к площадке не ходил вообще, в SQL-разборе простоя по counters.no_proxy_stop (#3288/#3367) не находится. Дыра одинаковая у всех трёх бэкфиллов (avito её тоже не обрабатывал: __aenter__ вызывается напрямую строкой 427, отказ уходит в тот же общий except). Правка — в трёх уже существующих обработчиках, которые и так зовут mark_failed: ключ no_proxy_stop=1 при caused_by_no_proxy(exc). Оборачивать `async with` в try/except пришлось бы с переносом ~200 строк тела под новый отступ в каждом файле, и покрывало бы только падение на входе; здесь ловится любой путь мимо цикла. Тест — через настоящий BrowserFetcher: подделан только провайдер прокси (его acquire поднимает NoProxyAvailableError), отказ рождается там же, где в проде. Проверяется failed + no_proxy_stop=1 + attempted=0 (у циана listings_processed=0) и ноль POST'ов в сайдкар. Closes #3384 |
|||
| 33444b7eab |
Merge pull request 'fix(tradein): гейт против сдвига разряда (×10) в offer_price_history у всех писателей + миграция 286 (#3376)' (#3383) from fix/3376-price-history-decimal-slips into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 11s
Deploy Trade-In / test (push) Successful in 4m4s
Deploy Trade-In / build-backend (push) Successful in 1m35s
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
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
|
|||
| dc63b1830b |
docs(#3376): ссылка на follow-up #3385 вместо TODO в yandex_price_history
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
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 / backend-tests (pull_request) Successful in 4m52s
|
|||
| 721ceb9876 |
fix(tradein): выборка миграции 286 повторяет гейт 1:1, правило первой точки (#3376)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
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 / 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 4m56s
Ревью нашло, что миграция и код ловили РАЗНОЕ. Миграция брала базой предыдущую СЫРУЮ строку (lag), гейт — предыдущую ОСТАВЛЕННУЮ. На 1M→10M→1M→10M (цена 1M) lag-версия удаляла честную точку, на 1M→10M→1.05M→9.9M — не была идемпотентной (второй прогон доедал 9.9M). Теперь кандидаты выбирает PL/pgSQL-цикл, пошагово повторяющий drop_decimal_slips, а правило первой точки — отдельным INSERT..SELECT уже по ОСТАВШИМСЯ строкам. Правило первой точки — из прод-разбора: 12 из 20 остатков domklik это серии вида 330 000 → 3 300 000 (текущая цена 3 300 000) и 420 000 → 4 200 000 → 4 500 000, где дефектная точка ПЕРВАЯ и базы слева у неё нет. Свидетелей по-прежнему два: ×10 ко второй точке И подтверждение второй третьей-или-текущей-ценой. Решение по первой точке принимается по kept-серии, а не по сырой, — иначе гейт теряет идемпотентность (перебор ловит 1122 таких прогона). Идемпотентность доказана НА ГЕЙТЕ: property-тест gate(gate(s)) == gate(s) по всем сериям длины 2-6 (19 525 серий × 3 текущие цены). Фальсифицирован обеими поломками — сырая база даёт 136 красных прогонов, сырые соседи первой точки 1122. Раз SQL зеркалит гейт, свойство переносится на миграцию. Ещё в 286: третий свидетель ПРОТИВ удаления (цена подтверждена триггерной строкой того же объявления — значит она реально наблюдалась в listings.price_rub) и финальный шаг |diff_percent| > 100 → NULL по всем источникам, то же правило, что validate_diff_percent на записи. Ожидаемое число удалений в шапке — 35 + ~12 из двухсвидетельского предзамера, а не 76 (то была односвидетельская цифра). Прогон обеих фаз дважды с ROLLBACK — tradein-mvp/scripts/sql/286_dryrun.sql. yandex: проводка гейта снята как мёртвая. На том пути серия из двух точек, а свидетель последней — текущая цена лота, то есть она же сама: ветка по построению не могла выбросить ничего. Оставлен честный комментарий-потолок и ссылка на follow-up (отлов требует DELETE на следующем наблюдении). cian: после выброса точки соседу пересчитывается diff_percent (было только у domclick). domclick: цена листинга берётся RETURNING'ом у UPDATE вместо отдельного SELECT по PK, пересчёт вынесен в общий recompute_diff_percent с гейтом на пустую цену (ручной ingest кладёт price_changes из JSONL без валидации). |
|||
| 0c21e522c0 |
fix(#3197): пустой пул пробрасывается из cian fetch_detail; тест через настоящий fetch_detail
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 4m58s
|
|||
| 142967d064 |
fix(tradein): гейт против сдвига разряда в offer_price_history (#3376)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
CI / 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 / 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
В priceHistory источников встречаются точки ровно ×10/÷10 к соседям с возвратом к базе следующей же точкой — это потерянный разряд у источника, а не рынок. Прод-замер 06.09.2026: 76 таких строк у 55 объявлений (domklik 48, cian 21, yandex 6; единственная avito-строка — триггерная). Читатели колонки — медианный торг лендинга (#3223), админка, /scrapers. drop_decimal_slips живёт рядом с validate_diff_percent — на той же единой границе записи, что и гейт #3225, и подключён ко ВСЕМ писателям истории (domclick/detail.py, cian/detail.py, yandex_price_history.py). Критерий требует двух свидетелей: скачок ×10 к предыдущей точке И возврат к базе у следующей; у последней точки серии свидетель — текущая цена объявления, нет и её → точку не трогаем (без второго свидетеля ×10 может быть честной сменой цены). У domklik после выброса пересчитывается diff_percent соседа: парсер считал его от базы, которой больше нет. Миграция 286 чистит уже собранные строки тем же критерием и только у загрузчика (change_time <> recorded_at): триггерные строки — это живые смены listings.price_rub, у них другая база отсчёта (см. 285). Падает, если кандидатов больше 200. |
|||
| 8129e21b95 |
fix(tradein/cian): суточный бэкфилл истории — в прокси-пул, а не в env-прокси (#3197)
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 4m51s
BrowserFetcher(source="cian") в cian_history_backfill конструировался без proxy_provider/use_pool/environment — трёх аргументов, которые кладут "proxy" в тело POST /fetch. Сайдкар брал свой env-прокси (SCRAPER_PROXY_URL): пул из 4 узлов, его баны и ротация проходили мимо, а прод-отказ «пул пуст → не ходить на env/direct» (#2616) на этом пути был мёртв, потому что смотрит на environment. Проводка теперь как у соседей — domclick_detail_backfill и house_imv_backfill. Ожившему отказу нужен обработчик: NoProxyAvailableError ловился общим except на объявление, и батч крутил впустую весь список (пул пуст с первого — значит пуст и на 1000-м). Распознаём по цепочке причин, обрываем прогон, counters.no_proxy_stop=1 и mark_failed вместо mark_banned — отказ нашей стороны не должен записываться как бан Циана. Дома и оценки после стопа пропускаем: они идут через тот же пул. caused_by_no_proxy вынесен в scraper_kit.proxy_errors (у avito #3288 и domclick #3283 живут приватные копии — их схлопывание отдельной правкой). |
|||
| 7afaa12d75 |
Merge pull request 'fix(tradein/avito): брейкер добора считает долю только по отказам площадки — сайдкар/пустой пул не рвут прогон и не красят его banned' (#3367) from fix/3288-breaker-block-kind into main
Some checks failed
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 4m3s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Failing after 32s
Deploy Trade-In / perimeter-smoke (push) Has been skipped
Deploy Trade-In / deploy-status (push) Failing after 1s
|
|||
| c82ec7c3af |
fix(tradein): понижать статус только у прогона, который работу сделал (#3288)
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 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 4m59s
Прошлая правка понижала banned→failed/done по одному диагнозу infra и ломала обратный контракт: нулевой прогон с infra (yandex 5xx #3196, финализатор #2764) получал 'failed' — настоящий бан площадки, опознанный как infra, прятался под «нашу поломку». Хуже исходного дефекта: 2 красных теста в полном прогоне. Понижение сужено до случая прогона 5425 — dominant='infra' И produced > 0: брейкер оборвал по доле, а карточки при этом обогащались → 'done'. Нулевой прогон остаётся 'banned' (честность несёт ban_kind), пустая перепись — тем более: dominant='unknown', статус не трогаем. Тесты: контроль на обратную ошибку (ноль результата → banned+infra) и на пустой census (→ banned+unknown); основной кейс {'infra': 20} при 10 обогащённых — не banned. |
|||
| ec8213c269 |
fix(payments): обрыв соединения и Init без PaymentURL перестают запирать покупателя
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 5m11s
Три дыры в одном замке (строка без payment_url невидима для _find_live_payment, но видима предикату UNIQUE 279 → ложный 409 на 30 минут): - tbank_client ловил пару (TimeoutException, NetworkError): RemoteProtocolError, ProxyError и UnsupportedProtocol летели наружу голым httpx-типом мимо `except TBankApiError` в checkout. Ловим родителя — httpx.TransportError. - Ветка «Success:true без PaymentURL» отвечала 502, не трогая статус, — здесь банк заказ ПРИНЯЛ. Тот же терминальный статус, error_code=no_payment_url; UPDATE вынесен в общий _mark_init_failed. - _FakeDb в тестах применял статус по наличию ключа в params, а не по тексту SQL: мутант без `SET status = :status` оставался зелёным. Гейт по SQL — мутант краснит все три теста про замок. Комментарий про «возможный холд» на ветке отказа Init поправлен: Init холд не создаёт, авторизация идёт с оплаты формы, а форму покупателю не выдавали. |
|||
| 1a693192e1 |
fix(tradein): вид блока решает обрыв по доле и статус прогона (#3288 часть B)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-tests (pull_request) Has been skipped
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 / 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 5m19s
Прогон 5425 оборвался по доле блоков и получил статус banned на 48 «блоках», из которых 41 был отказом нашего сайдкара: record_block() вида не принимал, поэтому infra падал в числитель скользящего окна #3184 наравне с настоящим баном, а mark_backfill_finished считал диагноз только ради телеметрии. - record_block(kind): в числитель идёт только platform; всё остальное — в знаменатель (как record_failure), мимо серии и safety-net; - NoProxyAvailableError у avito — не блок и не отказ площадки: прогон завершается no_proxy_stop=1 + mark_failed «пул прокси пуст» (как домклик после #3283); опознаётся по цепочке __cause__, не по подстроке (#3272); - статус banned — только при доминировании platform; при infra прогон получает failed (нулевой результат) или done, с честной причиной. |
|||
| aaf8119408 |
Merge remote-tracking branch 'origin/main' into HEAD
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 5m1s
# Conflicts: # tradein-mvp/backend/app/services/estimator.py |
|||
| 99f112db0b |
Merge pull request 'fix(tradein/auth): единый источник ролей — БД first, YAML только legacy-fallback (закрывает эскалацию до admin и 403 своим)' (#3331) from fix/3316-role-single-source into main
All checks were successful
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / changes (push) Successful in 13s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / test (push) Successful in 4m5s
Deploy Trade-In / build-backend (push) Successful in 1m47s
Deploy Trade-In / deploy (push) Successful in 2m8s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 13s
|
|||
|
|
f2f9ac941b |
fix(tradein/pdf): клиентский PDF не называет площадки-источники — «Источник N», как в веб-отчёте (#3341)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
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
Веб-отчёт и лендинг с #3342 не показывают названия площадок (канон publicLabel в frontend/src/lib/source-registry.ts), а клиентский PDF по тому же /estimate/{id} печатал Avito / Циан / «Домклик · Сбер» / Я.Недвижимость / Этажи в пилюлях источников, «подтверждают Росреестр, ДомКлик…» в советах и «на Циан, Авито, Я.Недвижимости» в тарифах. Один клиент — два документа с разной нормой, и юр-риск, ради которого всё делалось, в PDF оставался открытым. - `_SOURCE_DISPLAY_NAMES` → публичные лейблы 1:1 с реестром фронта: avito → «Источник 1», cian → 2, yandex → 3, domklik → 4, etazhi → 5, rosreestr → «Росреестр»; fallback для незнакомого id — «Другой источник», а не `source.title()` (сырой id — та же утечка). - Алиасы (avito_imv, cian_valuation, yandex_valuation, domclick, etagi) канонизируются ДО выбора цвета и лейбла (`_canonical_source`), а списки источников на страницах объявлений и сделок дедуплицируются до среза [:5] (`_public_sources`): иначе `sources_used` = listing ∪ valuation давал «Источник 1, Источник 1, Источник 2, Источник 2, Источник 4» с серой точкой у алиасов и вытеснял yandex. - Цвета пилюль не тронуты: цвет — опознаватель источника, как на вебе. - Тексты: «Росреестр, сделки площадок и продажи агентств», «на основных площадках объявлений» (двойник offer-rates.ts). - Гейт `tests/test_pdf_public_source_labels.py`: видимый текст страниц (без тегов и атрибутов, href на домены площадок законны) не содержит названий площадок и сырых id, по одному кейсу на имя; дедуп алиасов; ветка совета с процентом. Не тронуто: ссылки на объявления (avito.ru/domclick.ru) — отдельное решение; `_QUALITY_SOURCE_SLOTS` — только счётчик, имена не рендерит. |
||
|
|
5541aa9d37 |
fix(mera-public): публичная копия — сервис не называет себя оценщиком, площадки-источники не названы
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 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 / frontend-checks (pull_request) Successful in 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 5m2s
Три юр-правки по документу владельца продукта «Сайт_МЕРА_v2» (31.08.2026), сделаны локально 31.08, но не были закоммичены — на meraocenka.ru всё оставалось по-старому. 1. «Оценщик» про собственный алгоритм. Витрина сделок (`landing_showcase_deals.py`, REJECTION_RULE/NOTE) и сноска статьи «Как оценить квартиру» теперь говорят «расчёт МЕРЫ»: самоназвание обесценивало дисклеймер «не официальный отчёт оценщика». Комментарии и докстринги не тронуты — посетитель их не видит. 2. «Путь 2»: «стоимость услуг фиксированная и известна заранее» — читалось как фиксированная цена квартиры. 3. Названия площадок убраны из видимой копии лендинга и веб-отчёта. Канон — `publicLabel` + `sourcePublicLabel()` в `source-registry.ts`: одна площадка = один номер «Источник N» и один цвет точки (цвет остаётся опознавателем между блоками), Росреестр под своим именем, неизвестный id → «Другой источник» (раньше fallback отдавал сырой id). Три параллельные реализации `sourceLabel` сведены к одной; `sourceLabel()` с реальными именами живёт для админки. Backend: якорь confidence_explanation «по оценке Avito IMV» → «по оценочной модели площадки». Подсказки геокодера «Yandex / Nominatim» и «по Яндексу» сняты из клиентских форм. Гейт `public-copy-no-platform-names.test.ts` сканирует mera-public/** и components/trade-in/** без комментариев, по Unicode-границе слова; список запретов регистрозависимый намеренно (строчные id `"avito"` законны), поэтому капс-варианты и словоформы перечислены явно — «6202 ОБЪЯВЛЕНИЯ ДОМКЛИК» в статье именно так проходил первую версию гейта. Не закрыто здесь: клиентский PDF (#3341) и ссылки на объявления на доменах площадок. Текст витрины хранится в БД (`landing_showcase_runs.rejection_rule`, `landing_showcase_deals.note`), планировщика у пересчёта нет — после деплоя нужен ручной пересчёт или UPDATE трёх подстрок на проде. |
||
| 4f4345e27c |
fix(tradein): завершить thin-market гейт IMV — Guard-1b и GET-путь (#3323)
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 13s
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 5m23s
MEDIUM-1: imv_anchor_present ставился по anchor_total МИМО гейта — на тонком рынке якорь отброшен, а Guard-1b (#764) продолжал глушить квартальную поправку «потому что якорь есть»: headline не получал ни одной поправки, отброшенный якорь двигал деньги вычитанием. Теперь present = not thin_market. MEDIUM-2: trade_in.py (GET ?id= — расшаренная ссылка/PDF) — третья точка сборки карточки: market_count=0 читался как «неизвестно», thin_market не передавался вовсе → одна оценка показывала thin_market=True в POST и False при переоткрытии. |
|||
| 148da3e5a7 |
fix(tradein): thin-market IMV не двигает деньги (#3323)
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 13s
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 5m6s
avito_imv_thin_market_threshold рождал только warning: IMV с market_count=1 всё равно уходил в blend (w=0.5 при A > median*1.15) и растягивал range_high. Гейт поставлен в _apply_imv_blend — единственной точке, через которую IMV влияет на деньги (обе ветки якоря, imv_anchor и imv_eval, сходятся там): market_count < threshold → no-op, якорь остаётся display-only в карточке. market_count >= threshold и market_count=None (порог не передан) — поведение прежнее. market_count=0 больше не читается как «неизвестно». Warning теперь говорит, что IMV ОТБРОШЕН, а не просто «тонкий рынок». |
|||
| 4feb61c006 |
fix(tradein): резолвить роль из реестра, roles.yaml — только fallback (#3316)
Some checks failed
CI Trade-In / changes (pull_request) Successful in 12s
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
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Failing after 5m27s
Роль жила в двух местах сразу: люди заводятся в БД (`tradein_users.role`),
а `get_role` читал ТОЛЬКО `auth/roles.yaml` — и никто эти два источника не
сверял. Дефект двусторонний:
* вверх: менеджер заводил сотрудника с именем, которое уже числится в
roles.yaml админом (проверялись лишь regex и уникальность в БД) — на
входе тот получал admin из YAML, то есть чтение ЛЮБОЙ чужой оценки
(admin проходит мимо ownership-check в trade_in.py) и безлимитную квоту;
* вниз: сотрудник, которого в roles.yaml нет, ловил KeyError → 403 на
СОБСТВЕННУЮ оценку.
Источник теперь один и лечится один раз — в `app.core.auth.get_role`:
реестр (`tradein_users.role` / `auth.users.role`) спрашивается первым,
roles.yaml остаётся fallback для legacy-юзеров, у которых строки в реестре
нет. Реестр недоступен → тоже fallback: падение БД не выключает legacy-вход.
Вызывающие (rbac, trade_in, team, account_quota) не менялись.
Сопутствующее, чтобы поведение существующих аккаунтов не поехало:
* rbac_guard выбирает матчер путей по РОДУ роли (роль реестра → DB_ROLE_PATHS),
иначе employee/manager на legacy-пути получил бы 403 на всё;
* get_user_scope отдаёт scope роли реестра из того же DB_ROLE_PATHS;
* право на персональный `unlimited` осталось за roles.yaml (account_quota +
_batch_quota_status) — фикс убирает эскалацию, а не раздаёт новую;
* `_batch_quota_status` берёт роли из уже прочитанных строк — иначе список
«Команды» снова стал бы N+1.
Defense-in-depth: create_employee отдаёт 409 на username, за которым в
roles.yaml числится не-employee роль.
|
|||
|
|
d708f15019 |
fix(tradein/support): один повтор терял каждое одиннадцатое сообщение в поддержку
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 11s
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 4m50s
Замер прода 01.09.2026 из контейнера бота: канал до api.telegram.org рвётся всплесками, доля отказов на попытку 15-38% (пять проб: 3/8, 15/40, 5/20, 3/20, 1/25), в логе long-polling'а 353 ConnectTimeout за сутки. Транспорт ни при чём — httpx и сырой сокет отваливаются одинаково (25% против 35% в чередующемся замере), и прокси не помогает, а мешает: через SCRAPER_PROXY_URL 0 из 20. Ручка веб-поддержки ходила с max_retries=1, то есть двумя попытками. При 30% отказов на попытку до пользователя доходило ~9% отказов — каждое одиннадцатое сообщение возвращало 502 «сервис недоступен». Два других числа из того же замера задают конструкцию. Успешный запрос отвечает за 0.13с (максимум из 25 проб — 0.18с), а неудачный НИКОГДА не отваливается быстро: все отказы упираются в таймаут целиком (10.02с при timeout=10.0). Значит десятисекундный таймаут не покупал ничего, кроме цены за неудачу, — снижен до 5с, это ~28-кратный запас к измеренному максимуму. И экспоненциальная пауза 2→4→8с здесь бессмысленна: отказ — неустановленное соединение, а не троттлинг, пережидать нечего; она лишь добавляла 14с к ожиданию. Правка: бюджет ручки — 3 повтора, таймаут 5с, потолок паузы 1с. Худший случай 4 попытки × 5с + 3 паузы × 1с = 23с и требует четырёх отказов подряд; типичный случай не меняется (0.13с). Расчётная потеря падает с ~9% до ~0.8%. В TelegramClient добавлен необязательный max_backoff. Воркерная политика НЕ меняется: без явного потолка откат прежний экспоненциальный до 30с, а retry_after из 429 уважается целиком — эту границу держит отдельный тест, потому что первая версия правки её сломала (капала 60с до 30с и для воркера тоже). Потолок на retry_after применяется только когда его передали явно: интерактивному пути нельзя ждать Telegram-овские 30-60с, за ним стоит открытый запрос от браузера. Тесты: 8 новых (потолок на network/429/5xx, неизменность воркерного пути, арифметика «max_retries=N → N+1 попыток», границы бюджета ручки). |
||
| 4c29b59041 |
fix(tradein/proxy): успешный fetch затирал exit_ip и latency_ms в NULL (#3283) (#3305)
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 12s
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
Co-authored-by: lekss361 <lekss361@gendsgn.local> Co-committed-by: lekss361 <lekss361@gendsgn.local> |
|||
| b7c1c4d9d4 |
fix(tradein/proxy_pool): истёкший прокси больше не выдаётся в аренду (#3303)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 1m16s
Deploy Trade-In / deploy (push) Successful in 7m32s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
|
|||
| ea8943e00e |
Merge pull request 'chore(tradein/scheduler): добор карточек Яндекса и Циана шёл раз в сутки и простаивал' (#3301) from chore/cadence-yandex-cian-detail into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 1m48s
Deploy Trade-In / deploy (push) Successful in 1m40s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 12s
|
|||
|
|
4ddb3c7196 |
chore(tradein/scheduler): добор карточек Яндекса и Циана шёл раз в сутки и простаивал
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 Trade-In / browser-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 4m58s
compute_next_run_at держит суточную гранулярность (interval_days, минимум 1),
подчасовой такт делается хуком reschedule_after_minutes как post_claim. У
avito_detail_backfill он есть (180 мин), у yandex_detail_backfill и
cian_detail_backfill не было — отсюда один прогон в сутки.
Цена простоя по замеру прода 31.08:
Яндекс: 375-450 карточек за прогон, блоков НОЛЬ за неделю, очередь 11110
→ 25 суток при нынешнем такте
Циан: блоков ноль, очередь 20501
Такты разные, и это не произвол:
yandex — 180 мин (8 прогонов/сутки). Ходит через resolve_proxy_url: берёт
URL узла, но НЕ лизует его, поэтому чужие прогоны не блокирует.
cian — 360 мин (4 прогона/сутки). Ходит через BrowserFetcher и ДЕРЖИТ
lease весь прогон, то есть отнимает узел у Авито и Домклика. Пул
дефицитен (#2638), поэтому осторожнее.
Асимметрия зафиксирована комментарием у обоих хендлеров и в докстринге
миграции — иначе следующий читатель выровняет интервалы и сожжёт пул. Тест
test_cian_default_interval_is_360_minutes_not_180 ассертит именно неравенство,
чтобы выравнивание без замера покраснело.
283_scrape_schedules_cadence_yandex_cian_detail.sql — идемпотентный
UPDATE ... SET default_params = default_params || jsonb, остальные ключи
параметров не трогает.
Значения 180/360 — консервативная отправная точка по аналогии с Авито, а не
найденный оптимум: двигать вниз только по замеру нескольких суток, глядя и на
свипы тоже (тот же довод, что в комментарии у avito_detail_backfill).
Тесты (5): наличие post_claim у обоих, дефолтные интервалы, переопределение
через params. Прогон: 397 passed, 1 skipped, ruff чист.
|
||
| a256077211 |
feat(tradein/proxy_rotation): ротация IP через mobileproxy.space (aproxy.site) (#3298)
All checks were successful
Deploy Trade-In / changes (push) Successful in 13s
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 4m11s
Deploy Trade-In / build-backend (push) Successful in 1m5s
Deploy Trade-In / deploy (push) Successful in 7m37s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 13s
|
|||
|
|
5f9dc5d512 |
feat(tradein/cian): у Циана не было добора карточек — только побочный эффект задачи про историю (#3284)
All checks were successful
CI Trade-In / 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 / 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 Trade-In / backend-tests (pull_request) Successful in 5m2s
Карточки Циана доставались побочным эффектом cian_history_backfill, а её выборка ключуется по offer_price_history. Следствия на 30.08: карточка есть у 4494 из 25222 объявлений (17.8% — последнее место при втором месте по объёму), 19046 без истории при квоте 100/сутки (190 дней на остаток, тогда как очередь растёт вдвадцатеро быстрее), и 1697 объявлений с историей и без карточки, которые исторической выборке недостижимы в принципе. Фетчер при этом исправен: прогоны 5154/5240/5328 дали 100/100, 99/100, 100/100. Чинить нечего — не выдана мощность. Добавлен второй режим выборки (listings_pending="detail", по detail_enriched_at, свежие первыми) и второе расписание поверх ТОГО ЖЕ тела: машинерия работает, дублировать её новым модулем незачем. Историческая выборка оставлена побайтово — по ней живёт суточный прогон. batch_size=400 не на глаз: замеренный темп ~28с на объявление, порог reap_zombies 6ч по heartbeat, бюджетного сторожа у задачи нет — 400×28с≈3.1ч проходит, 800 как у Яндекса (≈6.2ч) убивало бы жнецом. Расписание засеяно enabled=false, как domclick_detail_backfill в миграции 175: это третий круглосуточный добор на общий пул из четырёх узлов, влияние на соседей надо посмотреть, а не предположить. Тесты: 13 проверок, ключ выборки / порядок / неизменность прежнего режима / проводка параметров через посредника / регистрация обоих source. Проверено мутациями: снятие ORDER BY, молчаливый дефолт вместо ValueError и потеря listings_pending в посреднике роняют по 2-3 теста каждая. Набор целиком — 5196 passed, 37 skipped. |
||
| 663d63aea4 |
fix(tradein): подсказки адреса — пустые части адреса выпадают, а не печатаются
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 16s
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 5m50s
Прод отдавал «г Екатеринбург, ул Фролова , д. 29, корп.»: реестровый readable_address (ЕГРН) приходит УЖЕ склеенным вместе с пустыми частями — маркер без значения печатается, лишний пробел перед запятой остаётся, и все тиры /suggest (cadastral / geoportal / houses / DaData / Nominatim) пропускали строку насквозь. Чиним склейку, а не конкретный случай: `tidy_address()` — один проход по частям (схлопнуть пробелы, выбросить пустые и маркер-без-номера: корп./стр./лит./кв./оф./пом. и пр.), склейка обратно. Применяется в `GeocodeSuggestion.__post_init__` — единственной точке, через которую проходят все тиры, включая локальные, которые питают и `geocode()`. Тест на функцию склейки, фальсифицирован снятием фильтра пустых: краснеет по значению строки (10 из 14), а не исключением. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
|||
|
|
44633b0df4 |
fix(tradein/domclick): свип ходил на QRATOR без кук и вис на PoW каждым запросом (#3264)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
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 4m53s
serp.py не передавал куки сессии вообще — слова cookie в файле не было. Свип приходил к QRATOR с чистым браузером и был обязан решать proof-of-work с нуля на каждый запрос. Прод, прогон 5330 (30.08 03:49-03:55): 9 запросов к bff-search-web.domclick.ru, все 9 зависли на челлендже (перезагрузка 1/2, 2/2, отказ), status=failed, 0 лотов. Прокси при этом ротировался (узел 9 → 10) — узел тут ни при чём. Добор с тем же сайдкаром и тем же пулом в ту же ночь взял 37 карточек из 37 без единого блока. Разница ровно в куках. Чинить это стало возможно только сейчас: свип ходит не на ekaterinburg.domclick.ru, а на отдельный хост bff-search-web.domclick.ru, и до правки _cookie_domain (PR #3262) куки легли бы на .bff-search-web.domclick.ru, не совпав с сессией площадки. Теперь оба хоста схлопываются в общий .domclick.ru. Снимок приходит параметром снаружи, а не читается внутри kit: kit не импортирует app.* (strangler-инвариант #2133). Поэтому джоба domclick_city_sweep переопределена продуктовым Handler'ом — build_registry это прямо допускает («последнее слово за продуктом»), а БД читает только app-сторона. Отсутствие сессии не авария: load_session вернул None → свип идёт как раньше, без инъекции, факт логируется один раз. Цена решения — override повторяет вызов kit-джобы целиком и может тихо с ней разойтись. Добавлен тест, который зовёт оба джоба одинаково и сравнивает наборы kwargs, допуская расхождение ровно в cookies. Проверен мутацией: с искусственно добавленным в kit-версию аргументом краснеет, без него зелёный. Тесты: 245 passed, 1 skipped (domclick + parity). |
||
| ca3f073b0e |
feat(mera/b2c): улица сделки доезжает до витрины готовой схемой, а не геометрией
Карта в карточке игры показывала полигон района — единственную геометрию, до которой у tradein был доступ. Улицы живут в базе gendesign, foreign table и гранта на них не было. Мост по образцу соседей (v_tradein_cad_buildings / v_tradein_osm_poi_ekb): gendesign 195 — вьюха v_tradein_osm_roads_ekb (highway + water) с GRANT В ТОМ ЖЕ ФАЙЛЕ (после #3227 грант отдельной миграцией теряется при пересоздании); tradein 281 — foreign table gendesign_osm_roads_ekb плюс колонки street_name / street_scheme в landing_showcase_deals. Пересчёт витрины кладёт в строку УЖЕ СПРОЕЦИРОВАННЫЕ SVG-пути окна 840x840 м вокруг центра улицы. Проекция — та же равнопромежуточная с cos(широты), что в export_ekb_districts_svg.py; второй в проекте нет. Замер 2026-08-29: GeoJSON того же окна 7-8 КБ на строку, схема — 2.8-2.9 КБ. ЧТО ДАННЫЕ ВЫДЕРЖИВАЮТ, И НИ СЛОВОМ БОЛЬШЕ · Это УЛИЦА, а не дом: deals.address уровня улицы, номер дома у 2.7% сделок. В схеме намеренно НЕТ координат окна и констант проекции — точку дома по ней нельзя поставить даже случайно. Это замок, а не забывчивость. · Зданий нет: cad_buildings — 18 307 контуров на город, в плотном центре 51 здание на радиус 450 м, где их в разы больше. Нарисованная застройка заявляла бы полноту, которой в данных нет. · Улицы — фильтрованная выгрузка «источников шума»: именованные покрыты хорошо, дворовые и служебные проезды отсутствуют. · Улица сматчилась у 550 названий из 654 — 31 410 сделок из 34 021 (92.3%). Остальным street_scheme = NULL, и это штатно: фронт показывает район, который для этого и оставлен. · Перекрёсток («Челюскинцев/Шейнкмана») берём первой улицей: таких адресов три на 34 021 сделку, и обе улицы одинаково верны на уровне улицы. · Наличие схемы НА ОТБОР СТРОК НЕ ВЛИЯЕТ — то же правило, что запрещает отбор по величине ошибки: иначе витрина показывала бы не работу оценщика, а те 92% адресов, что удобно легли на OSM. Тесты (каждый сломан вручную и покраснел): нормализация на реальных адресах включая ё/е и «8 Марта»; недоступная вьюха и упавший запрос дают None, а не исключение; схема не раздувается — потолок в байтах на реальной плотности плюс прямая проверка округления до 0.1. |
|||
| 6dd8d131ae |
feat(mera/estimate): характеристики дома из справочника, а не только из формы (#3242)
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 4m3s
Deploy Trade-In / build-backend (push) Successful in 1m7s
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 11s
|
|||
| 195f9f3697 |
Merge pull request 'chore(tradein): две ручки ротации без читателей и врущий комментарий над ними' (#3232) from chore/3212-dead-rotation-knobs 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 / changes (push) Successful in 11s
Deploy Trade-In / test (push) Successful in 4m2s
Deploy Trade-In / build-backend (push) Successful in 1m40s
Deploy Trade-In / deploy (push) Successful in 1m35s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 11s
|
|||
| 7563f849db |
Merge pull request 'chore(tradein): четыре символа, на которые нет ни одной ссылки' (#3224) from chore/3212-dead-code-tradein into main
Some checks failed
Deploy Trade-In / changes (push) Successful in 12s
Deploy Trade-In / test (push) Has been cancelled
Deploy Trade-In / perimeter-smoke (push) Has been cancelled
Deploy Trade-In / build-backend (push) Has been cancelled
Deploy Trade-In / build-frontend (push) Has been cancelled
Deploy Trade-In / build-browser (push) Has been cancelled
Deploy Trade-In / deploy (push) Has been cancelled
Deploy Trade-In / deploy-status (push) Has been cancelled
|
|||
|
|
1141035899 |
chore(tradein): две ручки ротации, которых не осталось читателей, и врущий комментарий над ними
All checks were successful
CI Trade-In / 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 Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
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
proxy_rotate_attempts / proxy_rotate_attempt_timeout_s тюнили ретраи changeip-GET. Сам changeip снят в #2616 шаг 2 (аккаунт mobileproxy закрыт, ссылки нет), и с тех пор ручки живут пламбингом: Settings -> property адаптера -> поле протокола ScraperConfig -> и всё. Ни одного потребителя, только четыре теста, которые заполняют их при сборке конфига. Комментарий над ними в contracts.py оправдывал их сохранение так: «оставлены как budget-верхняя-граница для app.tasks.avito_detail_backfill wait_for» — но wait_for там берёт СОСЕДНЕЕ поле, avito_proxy_rotate_settle_s (avito_detail_backfill.py:249). То есть комментарий приписывал этим двум полям работу третьего и тем самым прикрывал их мёртвость. Соседние ручки проверены и ОСТАВЛЕНЫ, они действительно читаются: * avito/cian/yandex_proxy_max_rotations — pipeline._max_rotations; * avito_proxy_rotate_settle_s — asyncio.wait_for в avito_detail_backfill. Заодно сжат комментарий в config.py: перечисление истории changeip заменено на то, что нужно знать сейчас — кто читает оставшиеся две ручки и где живая ротация (ASOCKS_API_TOKEN / proxy_rotation, #2611). ruff clean, 4974 passed / 37 skipped. |
||
| b5645ec1bc |
feat(mera/b2c): витринные метрики лэндинга считаются по проду
All checks were successful
CI Trade-In / changes (pull_request) Successful in 11s
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 13s
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
Числа на публичном лэндинге лежали литералами во фронте
(mera-public/marketing-v3.ts) — то есть были выдуманы и не имели срока
годности. Теперь их считает ночная задача и отдаёт публичная ручка,
вместе с размером выборки и описанием того, что именно измерено.
Что считается: число расчётов и период работы, медиана аналогов на
расчёт, медианная ЭКСПОЗИЦИЯ активного объявления по ЕКБ (не срок
продажи — так и написано в note), доля снижавших цену и медианное
снижение за 30 дней, сделки Росреестра по ЕКБ за 12 месяцев.
Ценовые метрики берут ТОЛЬКО domklik: у avito/yandex триггер не пишет
стартовую цену, а yandex вдобавок сеет синтетическую пару со сдвигом в
сутки — на такой смеси «снизил» и «не снижал» неразличимы. Знаменатель
доли — все объявления, наблюдавшиеся от 14 дней, включая не менявшие
цену; считая только по менявшим, получили бы 85% вместо честных 48%.
Метрика без входных данных строку НЕ пишет: подставленный ноль читался
бы как измеренный ноль. Пустая таблица — валидные {} и 200, а не 500.
«Точность прогноза» и «срок продажи» здесь не считаются намеренно —
таких величин в данных нет.
|
|||
|
|
4902d1e29e |
chore(tradein): четыре символа, на которые нет ни одной ссылки
All checks were successful
CI Trade-In / changes (pull_request) Successful in 10s
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
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
Найдено сверкой двух независимых источников: подсчёт упоминаний по всем
python-файлам (включая тесты) дал имена, встречающиеся ровно один раз — в
собственном объявлении; каждое затем подтверждено serena find_referencing_symbols
(LSP, видит и косвенные ссылки) и проверено grep'ом по yml/sql/ts/md на случай
ссылки строкой.
Удалено:
* _qr_code_data_url (trade_in_pdf) — QR как SVG data URL, не вызывался
ниоткуда; вместе с ним ушёл осиротевший import segno. `io` ОСТАВЛЕН —
io.BytesIO используется дальше по файлу (строка 774);
* _local_name (gar_flats_loader) — снятие namespace с имени тега;
* AvitoParseError — класс никто не поднимает и не ловит;
* IMVCityMismatchError — то же.
Что НЕ тронуто, хотя фильтр их показал:
* ~200 обработчиков FastAPI и celery-задач — их поднимает декоратор, по имени
их действительно никто не зовёт;
* refresh_ddu_price_indicator — точка ручного обслуживания, задокументирована
в комментарии к матвью (data/sql/152_mv_ddu_price_indicator.sql: «Refresh:
... (не в beat)»);
* schemas/parcel.py::MarketPrice — половина контракта, фронт использует
(frontend/src/types/site-finder.ts: market_price?: MarketPrice);
* JobSetting — SQLAlchemy-модель, живёт через metadata Base.
segno остался в backend/pyproject.toml и больше нигде не используется — снятие
зависимости требует пересборки лока, поэтому отдельным PR.
ruff clean, 4974 passed / 37 skipped.
|
||
| cdcb152d76 |
Merge pull request 'fix(tradein/scrapers): ABORT-лог называл серию блоков, хотя рвал прогон по доле' (#3201) from fix/3184-abort-log-names-wrong-criterion into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 14s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Successful in 43s
Deploy Trade-In / test (push) Successful in 4m10s
Deploy Trade-In / build-backend (push) Successful in 1m35s
Deploy Trade-In / deploy (push) Successful in 2m32s
Deploy Trade-In / deploy-status (push) Successful in 1s
Deploy Trade-In / perimeter-smoke (push) Successful in 10s
Reviewed-on: #3201 |
|||
| 79e469de1c |
Merge pull request 'fix(tradein/scrapers): диагноз блока из HTTP-статуса, а не из маркеров чужой площадки (#3196)' (#3202) from fix/3196-ban-kind-http-status into main
Some checks failed
Deploy Trade-In / build-backend (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 12s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / deploy (push) Blocked by required conditions
Deploy Trade-In / test (push) Has been cancelled
Deploy Trade-In / build-browser (push) Has been cancelled
Reviewed-on: #3202 |
|||
|
|
bf3214b9e4 |
fix(tradein/scrapers): диагноз блока брался из текстовых маркеров чужой площадки, а не из HTTP-статуса (#3196)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
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 / browser-tests (pull_request) Successful in 1m7s
CI Trade-In / backend-tests (pull_request) Successful in 4m57s
Сайдкар вообще не читал код ответа page.goto: страница классифицировалась только по маркерам, снятым с Авито. Домклик отдаёт статическую `403 | Домклик` на 26 624 байта, где нет ни одного такого маркера (замер прода 28.08.2026) — она уезжала наверх как валидный HTML, парсер не находил состояние, и прогон получал блок неизвестной природы. За 14 дней все 14 прогонов домклика легли с ban_kind='unknown'; у Яндекса счётчика blocked не было вовсе, поэтому ветка перевода прогона в 'banned' была недостижима по построению — ноль банов. - browser/server.py: статус целевой навигации сохраняется per-provider и доезжает в тело /fetch аддитивным ключом "status" (ключ "html" не тронут); 403/429 с маркерами челленджа больше не ждут PoW — ждать нечего, статическая страница сама себя не перезагрузит. Наверх идёт BanPageDetectedError, а не заглушка: вернув её контентом, воскресили бы #3045. - scraper_kit/browser_fetcher.py: BrowserFetcher.last_response_status + ban_kind_from_status (403/429 → platform, 5xx → infra, прочее → None). Поток управления не менялся: fetch() по-прежнему отдаёт str. - domclick: DomClickBlockedError несёт .status — один тип исключения на маркер-детект и на сбой фетча разводится без размножения типов; прогон передаёт перепись диагнозов в mark_backfill_finished. - yandex: появился счётчик blocked, оживляющий ветку бана. Серии блоков и промахов парсера считаются РАЗДЕЛЬНО: иначе четыре промаха плюс один 403 пятым давали 'banned' с переписью {platform: 1}. - cian: ban_kinds наполняется только диагностируемым статусом. HTTP 200 с пустым разбором — дрейф разметки на нашей стороне, а не отказ площадки; записав его блоком, мы бы штамповали фиктивные баны у здорового источника (13 done против 1 banned за 14 дней). Инвариант: непустой ban_kinds ⟺ виден ответ 403/429/5xx. Значения остаются в пределах CHECK scrape_runs.ban_kind. Известный пробел: шов providers/domclick/detail.py `blocked.status = status` тестами не покрыт — существующие домкликовые тесты подают исключение готовым моком и боевой fetch_detail не исполняют. |
||
|
|
8176e8d167 |
fix(tradein/scrapers): ABORT-лог называл серию блоков, хотя рвал прогон по доле
All checks were successful
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 Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / browser-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 4m49s
Прод-прогон 5210 оборвался по ratio-критерию — 14 блоков из 20, ровно порог 0.7 — и отчитался строкой «ABORT -- 1 consecutive blocks». Число верное: последняя серия в тот момент действительно равнялась единице (19-я попытка успех, 20-я блок). Величина не та. Читатель лога видит цифру, по которой обрыва быть не могло, и идёт искать несуществующий баг в брейкере. Причина: #3184 заменил критерий обрыва на долю в скользящем окне, а текст лога остался от прежнего критерия «N подряд» — то есть ровно та же болезнь, которую #3178 лечил у соседней строки (литерал «IP rate-limited» вместо измеренной причины). - BlockRatioBreaker.abort_reason() возвращает "ratio" / "safety_net" / None; should_abort() выражен через него, поведение не меняется. - abort_explanation() даёт текст с той величиной, по которой обрыв и произошёл: доля печатает «доля блоков 14/20 в окне (порог 70%)», safety-net — «5 блоков подряд без единого успеха (снапшот 5 короче окна 20)». - counters["abort_reason"] — чтобы причина обрыва читалась SQL-запросом по scrape_runs, а не грепом контейнера. Ключа нет, если прогон не обрывался. Тесты (проверено мутацией источника — на прежнем сообщении оба падают): - ratio-обрыв на раскладке прогона 5210 (серия на обрыве = 1) требует «14/20» в логе и отсутствия слова consecutive; - safety-net требует «5 блоков подряд» и отсутствия «доля блоков» — без этого зеркала первый тест проходил бы и у сообщения, всегда печатающего долю; - прогон без обрыва (13/20) не пишет abort_reason в counters. Refs #3184, #3178 |
||
|
|
c995af73c2 |
docs(tradein/scrapers): в репозитории лежало опровергнутое обоснование хранилища сессий Яндекса
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 4m44s
#3195 замержен с утверждением, что авторизованная сессия раскрывает контакты продавца. Утверждение неверно, а лежит оно в двух местах, которые читают в первую очередь: докстринг app/services/yandex_session.py и шапка миграции 274. Повторный замер (#3192, 2026-08-28) сделан на ПРОД-транспорте — curl_cffi + прокси из пула, тот же путь, что у yandex_detail_backfill, — а не на сайдкаре, как первый: - offerCard.card.author у целевой карточки не несёт phones/phoneNumbers ни в одном из 12 случайных объявлений (6 AGENCY, 6 DEVELOPER), одинаково с куками и без; только encryptedPhones (1 токен) и redirectPhones; - phoneNumbers во всём INITIAL_STATE встречается только под offerCard.visitedOffers[*].author — истории просмотров НАШЕЙ учётки; анонимно список пуст, с куками в нём 9-10 записей; - первый замер («0 → 3,4,5,6») считал рост именно этой истории: +1 на каждый фетч; - authorStats.phones (коммутатор застройщика) отдаётся анонимно — тот же номер в обеих ветках. Правка только текстовая: ни схема, ни поведение не меняются. Шапку применённой миграции правлю сознательно — файл повторно не выполняется (учёт по имени в _schema_migrations), а неверное описание пережило бы любой следующий разбор. Таблицу не трогаю: она пуста, но DROP без явного решения владельца делать нельзя. Судьба #3195 — на владельце, #3192 помечен needs-human. Refs #3192, #3195 |