feat(tradein/deactivate-stale): страховочные рельсы объёма снятия (PR-B) #3066

Merged
lekss361 merged 1 commit from feat/deactivate-stale-safety-rails into main 2026-08-24 08:44:33 +00:00

1 commit

Author SHA1 Message Date
bot-backend
22ccc1050c feat(tradein/deactivate-stale): страховочные рельсы объёма снятия
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 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 4m34s
Продолжение #2659 после PR-A. У джобы деактивации не было НИ ОДНОГО
ограничителя ОБЪЁМА: min_confirmations может пропустить прогон целиком,
пол переобхода поднимает TTL, cap_mult ограничивает сам пол — но ни один
не смотрит на РАЗМЕР ВЫБОРКИ, из которой посчитан квантиль, и ни один не
смотрит на то, сколько строк UPDATE снимет. UPDATE идёт одним statement'ом
без LIMIT: при обвале любого гейта выше снимается сколько снимется.

Добавлено два рельса, оба внутри revisit_floor_quantile > 0:

1. Гейт деградации пола. floor_n_pairs (счётчик из PR-A) ниже
   min_floor_pairs=30 ЛИБО упал более чем впятеро относительно предыдущего
   успешного прогона ТОГО ЖЕ РАСПИСАНИЯ -> skipped_floor_degraded, ни одна
   строка не тронута. Сравнение идёт «прогон сам с собой» по
   scrape_runs.source, а не по listing_source: у cian и cian_null_segment
   listing_source один, но это две серии с несопоставимым масштабом
   выборки — сравнивать их было бы категориальной ошибкой.

2. Аварийный потолок max_deactivated=15000. Preflight count(*) по ТОМУ ЖЕ
   предикату, что исполнит UPDATE — abort ДО записи. Не рабочий порог, а
   предохранитель: исторический максимум легитимного снятия 9 300 (avito
   06.06), запас 1.6x.

Порога по ДОЛЕ пула сознательно НЕТ: пул, с которым джоба реально работает,
никогда не записывался, а восстановленный постфактум ряд даёт 40 %, 97 %,
317 %, 1652 % — калибровать не на чем. Поэтому deactivation_candidates,
active_pool и deactivated_pct пока только ПИШУТСЯ.

Валидация параметров — тот же класс, что у ttl_days/cap_mult: bool
отбивается ДО числового сравнения (jsonb-опечатка true/false в
default_params расписания — единственный запланированный способ их задать).

uv.lock: две строки requires-dist подтянуты к pyproject (curl-cffi
>=0.7.0 -> >=0.15.0). Это устранение устаревшей записи, а не смена
зависимости — разрешённые версии не изменились; лок расходился с
pyproject на main и регенерировался при любом вызове uv.

Проверено: 249 passed, 1 skipped (-k "deactivate or stale").

Refs #2659
2026-08-24 00:24:56 +03:00