fix(tradein/estimator): yandex/cian valuation в /estimate через пул прокси; честный лог при пустом пуле (#3398) #3399

Merged
bot-backend merged 2 commits from fix/3398-estimator-valuations-proxy-pool into main 2026-09-06 07:06:48 +00:00
Collaborator

Часть #3398. Не закрывает issue (п. 1 acquire на event loop, п. 2 атрибуция банов, п. 4 IMV-403 — остаются).

Дефект (прод, 06.09, каждая проба /estimate): curl_cffi ProxyError: CONNECT tunnel failed, response 407yandex_valuation: empty result и Cian valuation fetch failed: … 407. Оба источника — без proxy_provideruse_pool=False → env-фолбэк SCRAPER_PROXY_URL (выключенный узел 9). Оба мертвы с 02.09; в ответе это читалось как «нет данных».

Фикс (только app/services/estimator.py; kit уже принимал провайдер): YandexValuationScraper(..., proxy_provider=RealProxyProvider()) (lease на сессию в __aenter__, release в __aexit__); estimate_via_cian_valuation"proxy_provider": RealProxyProvider() в _c_kwargs (покрывает и основной вызов, и отложенную фоновую догрузку; lease на вызов, release в finally curl_proxy_url). Оба под существующим _with_budget (8 с); отмена по бюджету → health=False + release (33cad4f8).

Деградация: caused_by_no_proxy → WARNING «пул прокси пуст — продолжаем без Yandex/Cian» (не ERROR — пустой пул не сбой площадки), прежний None-путь; строка yandex «empty result» переписана честно («данных нет ЛИБО фетч не дошёл — причина строкой выше от scraper_kit»), без новых полей API — fetch_house_history съедает ProxyError и отдаёт None, неотличимый от «дом не найден» (потолок).

Тесты tests/test_3398_estimator_valuations_proxy_pool.py (7): провайдер передан в обоих вызовах; пустой пул в production → оценка без источников, без исключения, честный лог; lease освобождён ровно один раз на успехе/ошибке — через настоящий curl_proxy_url (yandex — реальный YandexValuationScraper с подменённым _CurlCffiSession; cian — реальная kit-функция под estimate_quality).

Фальсификация (git apply -R estimator.py): 7 failed по значению (assert [] == ['cian'], провайдера нет / lease не взят). Восстановлено.

Прогоны: полный backend 5568 passed, 35 skipped (rc=0); ruff OK.

Приёмка на проде: POST /api/v1/trade-in/estimate изнутри контейнера → 200; в логах за окно нет 407 CONNECT tunnel failed, есть yandex_valuation: fresh fetch saved …/cian_valuation: price=… либо честное «пул прокси пуст»; узел leased_by занят в момент вызова и свободен после; USE_PROXY_POOL_CURL=true в backend (есть с #3387).

Часть #3398. Не закрывает issue (п. 1 acquire на event loop, п. 2 атрибуция банов, п. 4 IMV-403 — остаются). **Дефект (прод, 06.09, каждая проба `/estimate`):** `curl_cffi ProxyError: CONNECT tunnel failed, response 407` → `yandex_valuation: empty result` и `Cian valuation fetch failed: … 407`. Оба источника — без `proxy_provider` → `use_pool=False` → env-фолбэк `SCRAPER_PROXY_URL` (выключенный узел 9). Оба мертвы с 02.09; в ответе это читалось как «нет данных». **Фикс (только `app/services/estimator.py`; kit уже принимал провайдер):** `YandexValuationScraper(..., proxy_provider=RealProxyProvider())` (lease на сессию в `__aenter__`, release в `__aexit__`); `estimate_via_cian_valuation` — `"proxy_provider": RealProxyProvider()` в `_c_kwargs` (покрывает и основной вызов, и отложенную фоновую догрузку; lease на вызов, release в `finally` `curl_proxy_url`). Оба под существующим `_with_budget` (8 с); отмена по бюджету → `health=False` + release (33cad4f8). **Деградация:** `caused_by_no_proxy` → WARNING «пул прокси пуст — продолжаем без Yandex/Cian» (не ERROR — пустой пул не сбой площадки), прежний None-путь; строка yandex «empty result» переписана честно («данных нет ЛИБО фетч не дошёл — причина строкой выше от scraper_kit»), без новых полей API — `fetch_house_history` съедает `ProxyError` и отдаёт `None`, неотличимый от «дом не найден» (потолок). **Тесты** `tests/test_3398_estimator_valuations_proxy_pool.py` (7): провайдер передан в обоих вызовах; пустой пул в production → оценка без источников, без исключения, честный лог; lease освобождён ровно один раз на успехе/ошибке — через настоящий `curl_proxy_url` (yandex — реальный `YandexValuationScraper` с подменённым `_CurlCffiSession`; cian — реальная kit-функция под `estimate_quality`). **Фальсификация** (`git apply -R` estimator.py): 7 failed по значению (`assert [] == ['cian']`, провайдера нет / lease не взят). Восстановлено. **Прогоны:** полный backend `5568 passed, 35 skipped` (rc=0); ruff OK. **Приёмка на проде:** `POST /api/v1/trade-in/estimate` изнутри контейнера → 200; в логах за окно **нет** `407 CONNECT tunnel failed`, есть `yandex_valuation: fresh fetch saved …`/`cian_valuation: price=…` либо честное «пул прокси пуст»; узел `leased_by` занят в момент вызова и свободен после; `USE_PROXY_POOL_CURL=true` в backend (есть с #3387).
bot-backend added 1 commit 2026-09-06 06:32:03 +00:00
fix(tradein/estimator): yandex/cian valuation в /estimate через пул прокси, пустой пул не роняет ответ (#3398)
All checks were successful
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 Trade-In / changes (pull_request) Successful in 7s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / changes (pull_request) Successful in 10s
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 5m4s
8a5f75d5f4
Тот же корень, что у 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`, а не мок провайдера.
Author
Collaborator

Code-review (06.09): ⚠️ minor. Подтверждено: оба вызова под _with_budget 8 с; NoProxyAvailableError у yandex летит из __aenter__ и ловится в _get_or_fetch_yandex_valuation_cached; у cian curl_proxy_url стоит СНАРУЖИ внутреннего try (докстрока «не кидает исключений» врёт) — новая ветка живая; lease не течёт (ExitStack/finally, except BaseException); asyncio.gather в estimate_quality нет — три последовательных lease; тесты через настоящий curl_proxy_url; строку «empty result» никто не парсил.

Докатываю до мержа главное: на проде ESTIMATE_EXTERNAL_SOURCES_BACKGROUND=true → синхронный cian возвращает None до прокси-слоя, а настоящий фетч идёт в _defer_external_refresh, где пустой пул ловится общим except Exception → logger.exception → ERROR+traceback в GlitchTip; демоушен через caused_by_no_proxy в общей функции (3 строки) + тест в background-режиме. Известное поведение (без правки): при фоне yandex/cian живут параллельно между запросами (потолок 8 задач) и при пуле из 3 узлов /estimate под нагрузкой может сам себе исчерпать пул — деградация честная.

Code-review (06.09): ⚠️ minor. Подтверждено: оба вызова под `_with_budget` 8 с; `NoProxyAvailableError` у yandex летит из `__aenter__` и ловится в `_get_or_fetch_yandex_valuation_cached`; у cian `curl_proxy_url` стоит СНАРУЖИ внутреннего `try` (докстрока «не кидает исключений» врёт) — новая ветка живая; lease не течёт (`ExitStack`/`finally`, `except BaseException`); `asyncio.gather` в `estimate_quality` нет — три последовательных lease; тесты через настоящий `curl_proxy_url`; строку «empty result» никто не парсил. Докатываю до мержа главное: на проде `ESTIMATE_EXTERNAL_SOURCES_BACKGROUND=true` → синхронный cian возвращает None до прокси-слоя, а настоящий фетч идёт в `_defer_external_refresh`, где пустой пул ловится общим `except Exception → logger.exception` → ERROR+traceback в GlitchTip; демоушен через `caused_by_no_proxy` в общей функции (3 строки) + тест в background-режиме. Известное поведение (без правки): при фоне yandex/cian живут параллельно между запросами (потолок 8 задач) и при пуле из 3 узлов `/estimate` под нагрузкой может сам себе исчерпать пул — деградация честная.
Light1YT added 1 commit 2026-09-06 06:58:56 +00:00
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
ec7838b7a9
На проде `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-вызов
состояние — фоновой задаче нужен свой инстанс.
bot-backend merged commit bbf38f544f into main 2026-09-06 07:06:48 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#3399
No description provided.