fix(estimator): топ-5 фиксов аудита «Мера» (anchor gate, analog_tier, freshness, MAD-order, data-age) #1836

Merged
lekss361 merged 6 commits from feat/estimator-audit-top5 into main 2026-06-20 12:27:14 +00:00
Owner

Топ-5 находок аудита trade-in оценки «Мера» (vault audits/Mera_Tradein_Estimate_Audit_0620.md). 6 коммитов покоммитно, все пороги в settings с дефолтом ≈ текущему поведению, расчётные изменения за флагами.

Коммиты

  1. 55c7bc2c anchor low-confidence gate — якорь с confidence=="low" ИЛИ (n<3 И fsd>0.20) больше не подменяет headline → fallback на radius. Settings: estimate_sb_low_conf_gate_enabled=True, estimate_sb_gate_min_n=3, estimate_sb_gate_max_fsd=0.20. Здоровые якоря (n≥4, fsd<0.15) проходят.
  2. 6b99f7d2 структурный analog_tierAggregatedEstimate.analog_tier: Literal["same_building","micro_radius","district","city"] | None. Tier A→same_building, C→micro_radius, W→city, иначе district. Убирает хрупкий string-parse на фронте. address_precision уже был в схеме (стр 208), не тронут. explanation сохранён (фронт fallback).
  3. 17edac53 Yandex history freshness_fetch_price_trend исключает items старше estimate_price_trend_max_age_months=6 (scraped_at > now() - CAST(...)). Старая история (2024) больше не весит наравне со свежей.
  4. 5b2a39cd MAD-clip после similarity-weighting + sigma>0 guard — clip на взвешенных значениях (видовой компл с меньшей площадью не выкидывается); guard против div-by-zero при σ=0 из config. Флаг estimate_sb_clip_after_weight=True.
  5. a6ef2c73 data-age guards — sber-серия старше sber_index_max_age_days=35 → warning; AvitoImvSummary.thin_market: bool при market_count < avito_imv_thin_market_threshold=10. + empty-series guard (чинит pre-existing mock-test краш).
  6. daa2edfa тестыtests/test_estimator_audit_fixes.py 18 тестов.

Проверка

ruff clean. pytest полный suite 445 passed, 0 failed.

Примечание (verify-don't-trust)

Аудит ОТКЛОНИЛ 2 заявленных CRITICAL после code-trace: repair-coef «double-apply» (anchor заменяет median, repair 1×) и temporal «double-count» QI+Sber (QI = кадастровый geo-квартал, не календарный). Эти НЕ менялись — они были false-positive.

Деплой

Backend Python → пересборка + recreate tradein-backend. Аддитивно (новые опц. поля/settings), без миграций. Парный фронт-PR (analog_tier/expected_sold UI) мержить ПОСЛЕ деплоя этого. NB: не вплотную к другим tradein-PR (rapid-merge → stale image).

Топ-5 находок аудита trade-in оценки «Мера» (vault `audits/Mera_Tradein_Estimate_Audit_0620.md`). 6 коммитов покоммитно, все пороги в `settings` с дефолтом ≈ текущему поведению, расчётные изменения за флагами. ## Коммиты 1. **`55c7bc2c` anchor low-confidence gate** — якорь с `confidence=="low"` ИЛИ (`n<3` И `fsd>0.20`) больше не подменяет headline → fallback на radius. Settings: `estimate_sb_low_conf_gate_enabled=True`, `estimate_sb_gate_min_n=3`, `estimate_sb_gate_max_fsd=0.20`. Здоровые якоря (n≥4, fsd<0.15) проходят. 2. **`6b99f7d2` структурный analog_tier** — `AggregatedEstimate.analog_tier: Literal["same_building","micro_radius","district","city"] | None`. Tier A→same_building, C→micro_radius, W→city, иначе district. Убирает хрупкий string-parse на фронте. `address_precision` уже был в схеме (стр 208), не тронут. explanation сохранён (фронт fallback). 3. **`17edac53` Yandex history freshness** — `_fetch_price_trend` исключает items старше `estimate_price_trend_max_age_months=6` (`scraped_at > now() - CAST(...)`). Старая история (2024) больше не весит наравне со свежей. 4. **`5b2a39cd` MAD-clip после similarity-weighting + sigma>0 guard** — clip на взвешенных значениях (видовой компл с меньшей площадью не выкидывается); guard против div-by-zero при σ=0 из config. Флаг `estimate_sb_clip_after_weight=True`. 5. **`a6ef2c73` data-age guards** — sber-серия старше `sber_index_max_age_days=35` → warning; `AvitoImvSummary.thin_market: bool` при `market_count < avito_imv_thin_market_threshold=10`. + empty-series guard (чинит pre-existing mock-test краш). 6. **`daa2edfa` тесты** — `tests/test_estimator_audit_fixes.py` 18 тестов. ## Проверка ruff clean. pytest полный suite **445 passed, 0 failed**. ## Примечание (verify-don't-trust) Аудит ОТКЛОНИЛ 2 заявленных CRITICAL после code-trace: repair-coef «double-apply» (anchor заменяет median, repair 1×) и temporal «double-count» QI+Sber (QI = кадастровый geo-квартал, не календарный). Эти НЕ менялись — они были false-positive. ## Деплой Backend Python → пересборка + recreate `tradein-backend`. Аддитивно (новые опц. поля/settings), без миграций. Парный фронт-PR (analog_tier/expected_sold UI) мержить ПОСЛЕ деплоя этого. NB: не вплотную к другим tradein-PR (rapid-merge → stale image).
lekss361 added 6 commits 2026-06-20 12:10:32 +00:00
Якорь с confidence=low ИЛИ (n<gate_min_n И FSD>gate_max_fsd) не заменяет
headline — fallback на radius-median. Дефолты (min_n=3, max_fsd=0.20) не
режут здоровые якоря (n≥4, FSD<0.15 проходят). За флагом
estimate_sb_low_conf_gate_enabled (дефолт True). Логируется с причиной.
Добавляет AggregatedEstimate.analog_tier — стабильный enum для фронта:
same_building (Tier A), micro_radius (Tier C), district (S/H/0), city (W),
null (нет данных). Фронт не парсит tier из explanation.
address_precision уже был в схеме (подтверждено: line 208). Explanation не
удаляется — фронт fallback'ает на него.
_fetch_price_trend(freshness_months) — новый параметр, дефолт из
settings.estimate_price_trend_max_age_months (6 мес).
WHERE scraped_at > now() - N months исключает устаревшие items
(yandex_valuation 2024 и т.д.) из house_placement_history fallback.
houses_price_dynamics (Source 1) не затронут. psycopg v3 CAST syntax.
sigma=0 guard: safe_area_sigma2/safe_floor_sigma2 предотвращает div/0 при
нулевом sigma из конфига. Post-weight MAD-clip (estimate_sb_clip_after_weight,
дефолт True) выполняется ПОСЛЕ Gaussian weighting — видовые/топ-юниты
с высоким ppm² больше не выкидываются до учёта similarity. За флагом.
5a: _load_sber_index_series логирует warning если latest месяц серии
старее sber_index_max_age_days (дефолт 35) — расчёт не блокируется.
5b: AvitoImvSummary.thin_market (bool, дефолт False) — True когда
market_count < avito_imv_thin_market_threshold (дефолт 10). Все три
точки конструирования AvitoImvSummary обновлены. Warning при thin.
test(estimator): unit tests для audit fixes #audit-1..5 + empty-series guard
All checks were successful
CI / changes (pull_request) Successful in 6s
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
daa2edfac3
18 тестов покрывают все 5 фиксов: low-conf gate, analog_tier enum,
price_trend freshness, sigma=0 guard, sber staleness, thin-market flag.
Попутный fix: _load_sber_index_series — guard 'if not series: continue'
предотвращает max() на пустом dict при MagicMock rows (truthy=True).
lekss361 merged commit 485a3eb253 into main 2026-06-20 12:27:14 +00:00
lekss361 deleted branch feat/estimator-audit-top5 2026-06-20 12:27:14 +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#1836
No description provided.