fix(tradein/estimator): kitchen_area_m2/ceiling_height_m/is_apartments comp-scoring (#2012) #2396

Merged
lekss361 merged 1 commit from fix/tradein-2012-comp-scoring-features into main 2026-07-04 08:33:17 +00:00
Owner

Summary

Три независимых feature-флага, все default OFF — dormant capability, zero изменений текущего прод-поведения (грепнул все .env*/compose — ничего не оверрайдит на true).

  • estimate_kitchen_area_signal_enabled / estimate_ceiling_height_signal_enabled — у house_type/year_built есть target-значение из инпута юзера для сравнения; у kitchen_area_m2/ceiling_height_m target-значения физически нет (форма юзера их не собирает, backtest ground-truth — rosreestr ДКП — тоже без этих колонок). Изобретать внешнюю "типичную" константу без данных — необоснованно. Вместо этого: self-referential штраф — отклонение кандидата от медианы САМОГО ПУЛА кандидатов текущего запроса, в Tier H/W relevance_score. NULL-safe (не штрафует, не участвует в медиане), sparse-safe (полный skip при <5 non-null в пуле).
  • estimate_is_apartments_filter_enabled — hard-filter в _COMMON_WHERE + Tier W, симметрично existing novostroyki-guard (#1186). NULL проходит без штрафа, только явный true исключается.

Review fixup

_stratify_candidates требует вход, уже отсортированный по relevance_score (per-source guaranteed-quota selection зависит от порядка). _apply_kitchen_ceiling_signal мутирует relevance_score in-place ПОСЛЕ SQL-фетча, но ДО stratify — добавлен явный re-sort в обоих call-sites (Tier H, Tier W), чтобы diversity-quota selection видел пост-adjustment порядок, когда флаг когда-нибудь включат. Сейчас no-op (флаги off), но был latent footgun для первого PR, который включит хоть один флаг.

⚠️ Live-backtest НЕ прогнан

Ни у воркера, ни у main-сессии не было доступа к prod DB в этой сессии (db-tunnel down). Обязательно перед флипом любого флага в default ON:

cd tradein-mvp/backend
DATABASE_URL=... uv run python -m scripts.backtest_estimator --engine full --sample 300 --since 2025-06-01 --json > before.json
DATABASE_URL=... ESTIMATE_KITCHEN_AREA_SIGNAL_ENABLED=true ESTIMATE_CEILING_HEIGHT_SIGNAL_ENABLED=true ESTIMATE_IS_APARTMENTS_FILTER_ENABLED=true \
  uv run python -m scripts.backtest_estimator --engine full --sample 300 --since 2025-06-01 --json > after.json

Флаги независимы — можно тестировать по отдельности. Сравнить MAPE/range-coverage/calibration.

Test plan

  • 15 новых тестов (test_estimator_kitchen_ceiling_2012.py) — NULL-safety/sparse-safety/математика/is_apartments на всех тирах — все green
  • Full -k estimator: 495 passed (no regressions)
  • ruff check — clean
  • code-review: ⚠️MINOR (stratify-order footgun — пофикшен; both-flags-together test gap — оставлен как follow-up, не блокер)
  • Live A/B backtest — needs DB access, см. команды выше

Refs #2012

## Summary Три независимых feature-флага, **все default OFF** — dormant capability, zero изменений текущего прод-поведения (грепнул все `.env*`/compose — ничего не оверрайдит на true). - `estimate_kitchen_area_signal_enabled` / `estimate_ceiling_height_signal_enabled` — у `house_type`/`year_built` есть target-значение из инпута юзера для сравнения; у `kitchen_area_m2`/`ceiling_height_m` target-значения физически нет (форма юзера их не собирает, backtest ground-truth — rosreestr ДКП — тоже без этих колонок). Изобретать внешнюю "типичную" константу без данных — необоснованно. Вместо этого: **self-referential** штраф — отклонение кандидата от медианы САМОГО ПУЛА кандидатов текущего запроса, в Tier H/W relevance_score. NULL-safe (не штрафует, не участвует в медиане), sparse-safe (полный skip при <5 non-null в пуле). - `estimate_is_apartments_filter_enabled` — hard-filter в `_COMMON_WHERE` + Tier W, симметрично existing novostroyki-guard (#1186). NULL проходит без штрафа, только явный `true` исключается. ## Review fixup `_stratify_candidates` требует вход, уже отсортированный по `relevance_score` (per-source guaranteed-quota selection зависит от порядка). `_apply_kitchen_ceiling_signal` мутирует `relevance_score` in-place ПОСЛЕ SQL-фетча, но ДО stratify — добавлен явный re-sort в обоих call-sites (Tier H, Tier W), чтобы diversity-quota selection видел пост-adjustment порядок, когда флаг когда-нибудь включат. Сейчас no-op (флаги off), но был latent footgun для первого PR, который включит хоть один флаг. ## ⚠️ Live-backtest НЕ прогнан Ни у воркера, ни у main-сессии не было доступа к prod DB в этой сессии (db-tunnel down). **Обязательно перед флипом любого флага в default ON:** ```bash cd tradein-mvp/backend DATABASE_URL=... uv run python -m scripts.backtest_estimator --engine full --sample 300 --since 2025-06-01 --json > before.json DATABASE_URL=... ESTIMATE_KITCHEN_AREA_SIGNAL_ENABLED=true ESTIMATE_CEILING_HEIGHT_SIGNAL_ENABLED=true ESTIMATE_IS_APARTMENTS_FILTER_ENABLED=true \ uv run python -m scripts.backtest_estimator --engine full --sample 300 --since 2025-06-01 --json > after.json ``` Флаги независимы — можно тестировать по отдельности. Сравнить MAPE/range-coverage/calibration. ## Test plan - [x] 15 новых тестов (`test_estimator_kitchen_ceiling_2012.py`) — NULL-safety/sparse-safety/математика/is_apartments на всех тирах — все green - [x] Full `-k estimator`: 495 passed (no regressions) - [x] `ruff check` — clean - [x] code-review: ⚠️MINOR (stratify-order footgun — пофикшен; both-flags-together test gap — оставлен как follow-up, не блокер) - [ ] Live A/B backtest — needs DB access, см. команды выше Refs #2012
lekss361 added 1 commit 2026-07-04 08:22:49 +00:00
fix(tradein/estimator): kitchen_area_m2/ceiling_height_m/is_apartments comp-scoring (#2012)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 7s
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 1m36s
d39e136d88
Three independent feature flags, all default OFF pending live A/B
backtest (no DB access in worker/main sessions this round — see PR
body for exact before/after commands to run once available):

- estimate_kitchen_area_signal_enabled / estimate_ceiling_height_signal_enabled:
  house_type/year_built have a user-input target to compare candidates
  against; kitchen_area_m2/ceiling_height_m have neither a target
  (TradeInEstimateInput doesn't collect them) nor ground-truth on
  rosreestr deals (backtest DealSample lacks these columns). Inventing
  an external "typical" constant would be unfounded, so the penalty is
  self-referential: deviation from the CANDIDATE POOL's own median in
  Tier H/W relevance_score. NULL-safe (no penalty, excluded from
  median) and sparse-safe (fully skipped when <5 non-null values in
  the pool).
- estimate_is_apartments_filter_enabled: hard-filter in _COMMON_WHERE +
  Tier W, symmetric to the existing novostroyki-guard (#1186). NULL
  passes through untouched, only explicit true excluded.

Review fixup: _stratify_candidates requires its input pre-sorted by
relevance_score (its per-source guaranteed-quota selection depends on
that order). _apply_kitchen_ceiling_signal mutates relevance_score
in-place after the SQL fetch but before stratify — added an explicit
re-sort in both call sites (Tier H, Tier W) so the diversity-quota
selection sees the post-adjustment order once either flag ships live.
Currently a no-op (flags off), but was a latent footgun for the first
PR to flip one on.
lekss361 merged commit 480d4c2128 into main 2026-07-04 08:33:17 +00:00
lekss361 deleted branch fix/tradein-2012-comp-scoring-features 2026-07-04 08:33:18 +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#2396
No description provided.