fix(tradein/estimator): не отравлять asking→sold ratio-кэш транзиентной ошибкой #2175

Merged
lekss361 merged 1 commit from fix/tradein-asking-sold-ratio-cache-poison into main 2026-07-02 17:37:12 +00:00
Owner

Проблема

Блок «ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ» (expected_sold, «с учётом торга») молча показывал «—» у части оценок с полноценным headline. Прод-пример (сегодня): 2к/54м², 17 аналогов, high confidence, headline 10.99М ₽ — а expected_sold NULL.

Корень

_get_asking_sold_ratio (estimator.py:226) gracefully ловит ЛЮБУЮ ошибку БД → (None, None), но запись в _asking_sold_ratio_cache шла безусловно (после try/except). Один транзиентный сбой (poisoned tx от вышестоящего graceful-except, коннект-хиккап) отравлял кэш бакета (None, None) на весь TTL (300с) → expected_sold гас для всех оценок этого rooms-бакета на воркере до истечения TTL. Само-заживало через ≤5 мин → редкие разрозненные кейсы.

Фикс

  • Ранний return None, None из except без записи в кэш → следующая оценка ретраит.
  • Кэшируем только успешный lookup (ratio может быть None, если строки реально нет — стабильный факт БД, безопасно кэшировать).

Прод-диагностика (30 дней, 304 оценки)

39 пустых expected_sold: 37 легитимно n_analogs=0 (нет аналогов — не баг), 2 — этот баг (headline есть, ratio-кэш отравлён). Таблица asking_to_sold_ratios полна по всем бакетам + global fallback 0.843 → при исправном lookup ratio всегда резолвится.

Test plan

  • ruff зелёно, pre-commit passed
  • tests/test_asking_sold_ratio_cache_poison.py (3 юнита: транзиентная ошибка → None БЕЗ кэша; error→retry не залипает; успех кэшируется)
  • 41 связанный тест зелёно, вкл. test_backtest_regression_gate (детерминир. спайн не затронут — резолвер DB-injected в harness)
## Проблема Блок **«ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ»** (`expected_sold`, «с учётом торга») молча показывал «—» у части оценок с полноценным headline. Прод-пример (сегодня): 2к/54м², **17 аналогов, high confidence, headline 10.99М ₽** — а `expected_sold` NULL. ## Корень `_get_asking_sold_ratio` (`estimator.py:226`) gracefully ловит ЛЮБУЮ ошибку БД → `(None, None)`, но запись в `_asking_sold_ratio_cache` шла **безусловно** (после `try/except`). Один транзиентный сбой (poisoned tx от вышестоящего graceful-except, коннект-хиккап) отравлял кэш бакета `(None, None)` на весь TTL (**300с**) → `expected_sold` гас для **всех** оценок этого rooms-бакета на воркере до истечения TTL. Само-заживало через ≤5 мин → редкие разрозненные кейсы. ## Фикс - Ранний `return None, None` из `except` **без** записи в кэш → следующая оценка ретраит. - Кэшируем **только успешный** lookup (ratio может быть None, если строки реально нет — стабильный факт БД, безопасно кэшировать). ## Прод-диагностика (30 дней, 304 оценки) 39 пустых `expected_sold`: **37** легитимно `n_analogs=0` (нет аналогов — не баг), **2** — этот баг (headline есть, ratio-кэш отравлён). Таблица `asking_to_sold_ratios` полна по всем бакетам + global fallback 0.843 → при исправном lookup ratio всегда резолвится. ## Test plan - [x] `ruff` зелёно, pre-commit passed - [x] `tests/test_asking_sold_ratio_cache_poison.py` (3 юнита: транзиентная ошибка → None БЕЗ кэша; error→retry не залипает; успех кэшируется) - [x] 41 связанный тест зелёно, вкл. `test_backtest_regression_gate` (детерминир. спайн не затронут — резолвер DB-injected в harness)
lekss361 added 1 commit 2026-07-02 17:34:43 +00:00
fix(tradein/estimator): не отравлять asking→sold ratio-кэш транзиентной ошибкой
All checks were successful
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
3909db745b
Блок «ОЖИДАЕМАЯ ЦЕНА СДЕЛКИ» (expected_sold, «с учётом торга») молча гас в «—» у
части оценок с полноценным headline (прод: high-confidence, 17 аналогов, цена
есть — expected_sold NULL). Причина: `_get_asking_sold_ratio` gracefully ловит
ЛЮБУЮ ошибку БД → (None, None), но запись в `_asking_sold_ratio_cache` шла
БЕЗУСЛОВНО (после try/except) → один транзиентный сбой (poisoned tx от
вышестоящего graceful-except, коннект-хиккап) отравлял кэш бакета `(None, None)`
на весь TTL (300с) и гасил expected_sold для ВСЕХ оценок этого rooms-бакета на
воркере до истечения TTL.

Фикс: ранний `return None, None` из except БЕЗ записи в кэш → следующая оценка
ретраит. Кэшируем только успешный lookup (ratio может быть None, если строки
реально нет — стабильный факт БД, безопасно кэшировать).

Прод-диагностика (30 дней, 304 оценки): 39 пустых expected_sold, из них 37 —
легитимно n_analogs=0 (нет аналогов), а 2 — этот баг (headline есть, ratio-кэш
отравлён). Таблица asking_to_sold_ratios полна по всем бакетам + global fallback
0.843 → при исправном lookup ratio всегда резолвится.

Юнит-тесты: транзиентная ошибка → None БЕЗ кэша; error→retry не залипает;
успех кэшируется.
lekss361 merged commit 94114adca1 into main 2026-07-02 17:37:12 +00:00
lekss361 deleted branch fix/tradein-asking-sold-ratio-cache-poison 2026-07-02 17:37:13 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
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#2175
No description provided.