fix(tradein/estimator): honest repair-note + ratio_basis label; doc EKB-calibrated constants (LOW audit R2 #1-4) #2505
3 changed files with 52 additions and 3 deletions
|
|
@ -222,7 +222,9 @@ class AggregatedEstimate(BaseModel):
|
|||
# конкретному дому за 12 мес) — другая база и другой смысл «торга», см. докстринг
|
||||
# HouseAnalyticsKpi.
|
||||
asking_to_sold_ratio: float | None = None # =sold/asking, ~0.72–0.93
|
||||
ratio_basis: str | None = None # 'per_rooms' | 'global_fallback'
|
||||
# 'per_rooms' | 'global_fallback' | 'expected_over_median' (LOW audit #2: set when
|
||||
# #2141's honest_ratio recompute overwrites the raw ratio_resolver value below).
|
||||
ratio_basis: str | None = None
|
||||
# ── DaData enrichment (PR Q1) — on-demand для target адреса ──
|
||||
# canonical_address — DaData-нормализованная форма (с улицей в short form).
|
||||
# house_cadnum — кадастровый номер ДОМА (для будущего matching Росреестра).
|
||||
|
|
|
|||
|
|
@ -1886,6 +1886,16 @@ def _band_haircut(anchor_ppm2: float) -> float:
|
|||
|
||||
Премиум (высокий ppm²) торгуется плотнее → меньше скидка; эконом — больше.
|
||||
Пороги ЕКБ-вторички: ≥350k → 4%; 180-350k → 5%; <180k → 7%. Дефолт из config.
|
||||
|
||||
LOW audit #3: 0.04/0.07 (и mid из settings.asking_to_sold_haircut) —
|
||||
EKB-secondary-market calibration constants, но применяются ENGINE-WIDE (нет
|
||||
city-параметра ни здесь, ни у единственного вызывающего
|
||||
`_compute_same_building_anchor`). Реального импакта на не-ЕКБ область пока нет
|
||||
(same-building anchor pool для oblast сейчас не формируется — anchor_ppm2 сюда
|
||||
просто не доходит), но это доверие к отсутствию данных, а не к дизайну. Как
|
||||
только oblast anchor pools появятся (см. #oblast-D fallback выше), эти пороги
|
||||
нужно пересмотреть/сделать per-city — не оставлять ЕКБ-калибровку по умолчанию
|
||||
для другого рынка. No behavior change here (doc-only).
|
||||
"""
|
||||
if anchor_ppm2 >= 350_000:
|
||||
return 0.04
|
||||
|
|
@ -2360,9 +2370,13 @@ def _price_from_inputs(
|
|||
range_high = int(range_high * repair_coef)
|
||||
median_ppm2 = median_ppm2 * repair_coef
|
||||
pct = round((repair_coef - 1.0) * 100)
|
||||
# LOW audit #1: _REPAIR_COEF (см. предупреждение выше, строки ~262-266) —
|
||||
# tunable market-эвристика с покрытием данных ~2%, НЕ data-derived величина.
|
||||
# Формулировка ниже честно маркирует поправку как ориентировочную/эвристическую,
|
||||
# а не как вычисленную из данных корректировку.
|
||||
repair_note = (
|
||||
f" Цена скорректирована на состояние ремонта "
|
||||
f"({_REPAIR_LABEL.get(repair_state, '')} {pct:+d}%)."
|
||||
f" Оценка ориентировочно скорректирована на состояние ремонта "
|
||||
f"(эвристика: {_REPAIR_LABEL.get(repair_state, '')} {pct:+d}%)."
|
||||
)
|
||||
|
||||
# Build sources_used_pre from listings + external sources
|
||||
|
|
@ -2903,6 +2917,12 @@ def _price_from_inputs(
|
|||
honest_ratio = expected_sold_price / median_price
|
||||
if abs(honest_ratio - asking_to_sold_ratio) > _RATIO_DESCRIPTOR_EPS:
|
||||
asking_to_sold_ratio = honest_ratio
|
||||
# LOW audit #2: once honest_ratio overwrites the raw per_rooms/
|
||||
# global_fallback ratio, `ratio_basis` (still the pre-shift
|
||||
# ratio_resolver provenance) mislabels the value's origin. Relabel
|
||||
# to an honest, self-describing basis so consumers (API/PDF) don't
|
||||
# attribute expected_over_median math to a stale per-rooms lookup.
|
||||
ratio_basis = "expected_over_median"
|
||||
|
||||
# ── #oblast-D: deals-headline-fallback ───────────────────────────────────
|
||||
# When the radius/anchor analog pipeline found NOTHING usable (median_ppm2
|
||||
|
|
@ -3913,6 +3933,20 @@ def _is_premium_building(db: Session, house_id: int | None) -> tuple[bool, int |
|
|||
Best-effort: любая из таблиц может ещё не существовать в части окружений — любая
|
||||
ошибка (нет таблицы / SQL) деградирует в (False, None, None) и НИКОГДА не валит
|
||||
оценку.
|
||||
|
||||
LOW audit #4 — EKB-bias (doc-only, no behavior change): и `premium_buildings_curated`
|
||||
(data/sql/142, см. header-комментарий там), и её MV-фолбэк `premium_houses`
|
||||
(data/sql/139) построены и выверены по ЕКБ-методологии. Кураторская таблица
|
||||
активно ДЕПРОМОУТИТ non-EKB описания как шум/mislabel (см. пример house_id=372944
|
||||
в 142_premium_buildings_curated.sql — «это НЕ ЕКБ» → is_premium=false). Значит для
|
||||
областных (oblast) элитных домов эта функция систематически вернёт (False, None,
|
||||
None) — premium_building флаг не выставится, даже если дом объективно элитный по
|
||||
рынку своего города. Это не тихая потеря сигнала: `_manual_review` (см. ниже,
|
||||
строки ~3944-3993) всё равно ловит такие объекты по АБСОЛЮТНОЙ стоимости —
|
||||
`estimate_manual_review_high_value_rub` (expected_sold_price ≥ порога) и
|
||||
`estimate_manual_review_elite_ppm2` (asking ₽/м² ≥ порога) — независимо от
|
||||
premium_building. То есть oblast-элита не помечается как «премиальный дом», но
|
||||
почти всегда подхватывается value-based триггером той же функции.
|
||||
"""
|
||||
if house_id is None:
|
||||
return (False, None, None)
|
||||
|
|
|
|||
|
|
@ -22,6 +22,19 @@
|
|||
--
|
||||
-- Dependencies: 002_core_tables.sql (house_id space). No backend schema coupling.
|
||||
-- Re-run safe: CREATE TABLE IF NOT EXISTS + idempotent upsert.
|
||||
--
|
||||
-- LOW audit #4 — EKB scope (doc-only, no behavior change): this seed and its
|
||||
-- `premium_houses` MV fallback (data/sql/139) are both curated/calibrated against
|
||||
-- Ekaterinburg secondary-market listings ONLY. Non-EKB descriptions encountered
|
||||
-- during curation were treated as noise and actively DEMOTED (is_premium=false) —
|
||||
-- e.g. house_id=372944 below ("г. Нижний Новгород, Александровская слобода... это
|
||||
-- НЕ ЕКБ" — mislabeled, not premium). Consequence: an oblast (non-EKB) elite
|
||||
-- building will NOT receive the `premium_building` flag from this table or its MV
|
||||
-- fallback — the estimator's `_is_premium_building` (app/services/estimator.py)
|
||||
-- has no per-city variant of this overlay. This is not a silent gap: the estimator's
|
||||
-- `_manual_review` still routes genuinely high-value objects to manual review via
|
||||
-- absolute-value thresholds (estimate_manual_review_high_value_rub,
|
||||
-- estimate_manual_review_elite_ppm2) independent of the premium_building flag.
|
||||
|
||||
BEGIN;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue