gendesign/tradein-mvp/backend/tests/test_1970_estimator_constants.py
bot-backend 31c31f8f82
All checks were successful
CI Trade-In / changes (pull_request) Successful in 22s
CI / changes (pull_request) Successful in 25s
CI Trade-In / browser-tests (pull_request) Has been skipped
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 / frontend-checks (pull_request) Successful in 2m35s
CI Trade-In / backend-tests (pull_request) Successful in 6m20s
МЕРА: якорь «тот же дом» и IMV-blend — константы движка, не настройки (#2381)
Пять булевых флагов кластера сняты ещё в #2475. Оставшиеся 14 числовых полей
Settings перенесены в estimator.py константами с теми же значениями:
IMV_BLEND_WEIGHT 0.5, IMV_BLEND_THRESHOLD 1.15, SB_MIN_COMPS 4, SB_AREA_SIGMA
0.18, SB_ROOMS_MATCH_BOOST 1.6, SB_FLOOR_SIGMA 0.25, SB_GUARDRAIL_TOL 0.05,
SB_MAD_K 3.5, SB_MAD_K_SMALL_N 2.5, SB_SMALL_N_THRESHOLD 10,
ANCHOR_TIER_C_CORRIDOR_MULT 1.5, FSD_K 1.65, SB_GATE_MIN_N 3, SB_GATE_MAX_FSD
0.20. На проде 17.09 все 14 равны дефолтам, ENV-оверрайдов нет. Мёртвая
проверка `tier_c_mult > 0` (константа против нуля) убрана.

Тесты подменяют SB_MIN_COMPS на модуле вместо поля settings. Реплей бэктеста
по сделкам побитово тот же, срабатываний якоря 985, IMV-blend 3, low-conf
гейта 11 — как на main. Шесть порогов (rooms_boost, floor_sigma,
guardrail_tol, tier_c_mult, fsd_k, gate_max_fsd) не ловит ни один
поведенческий тест и ни гейт, их держит только тест боевых значений.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-17 12:38:44 +05:00

77 lines
4.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""#1970: калибровочные константы движка, вынесенные из Settings, — значения.
Поля estimate_* жили в Settings и формально переопределялись через ENV. При
переносе в константы estimator.py значения обязаны остаться боевыми. Числа ниже
сняты с прода 17.09.2026 (`settings.<поле>` в tradein-backend, tradein-scraper и
tradein-tgbot — совпали, ENV-оверрайдов нет), а не скопированы из кода.
Зачем отдельный тест: замороженная фикстура бэктеста часть механизмов не
проходит вовсе (квартальный индекс в реплее применяется 0 раз из 1600 сделок),
а поведенческие тесты ловят не все пороги. Поменять число — осознанно: здесь,
в estimator.py и с прогоном регрессионного гейта.
"""
from __future__ import annotations
import os
import pytest
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from app.core.config import Settings
from app.services import estimator
# имя константы в estimator.py → (бывшее поле Settings, боевое значение 17.09.2026)
_PROD_VALUES: dict[str, tuple[str, float]] = {
# #2379, #764 квартальный индекс
"QUARTER_INDEX_MIN_N_DEALS": ("estimate_quarter_index_min_n_deals", 10),
"QUARTER_MATCH_SKIP_RATIO": ("estimate_quarter_match_skip_ratio", 0.6),
"QUARTER_INDEX_MAX_FOR_SMALL_N": ("estimate_quarter_index_max_for_small_n", 2.0),
"QUARTER_INDEX_SMALL_N_THRESHOLD": ("estimate_quarter_index_small_n_threshold", 50),
"QUARTER_INDEX_FACTOR_MIN": ("estimate_quarter_index_factor_min", 0.6),
"QUARTER_INDEX_FACTOR_MAX": ("estimate_quarter_index_factor_max", 1.8),
# #2380, #1795 anti-inflation (CORRIDOR_CLAMP_MIN_N определён в app.core.config)
"CORRIDOR_CLAMP_MIN_N": ("estimate_corridor_clamp_min_n", 10),
"CORRIDOR_CLAMP_SLACK": ("estimate_corridor_clamp_slack", 0.40),
"RADIUS_FLOOR_FACTOR": ("estimate_radius_floor_factor", 0.8),
"OUTLIER_SMALL_N_THRESHOLD": ("estimate_outlier_small_n_threshold", 15),
"OUTLIER_TUKEY_K_SMALL": ("estimate_outlier_tukey_k_small", 1.0),
# #2381, #651/#652 same-building anchor + IMV-blend
"IMV_BLEND_WEIGHT": ("estimate_imv_blend_weight", 0.5),
"IMV_BLEND_THRESHOLD": ("estimate_imv_blend_threshold", 1.15),
"SB_MIN_COMPS": ("estimate_sb_min_comps", 4),
"SB_AREA_SIGMA": ("estimate_sb_area_sigma", 0.18),
"SB_ROOMS_MATCH_BOOST": ("estimate_sb_rooms_match_boost", 1.6),
"SB_FLOOR_SIGMA": ("estimate_sb_floor_sigma", 0.25),
"SB_GUARDRAIL_TOL": ("estimate_sb_guardrail_tol", 0.05),
"SB_MAD_K": ("estimate_sb_mad_k", 3.5),
"SB_MAD_K_SMALL_N": ("estimate_sb_mad_k_small_n", 2.5),
"SB_SMALL_N_THRESHOLD": ("estimate_sb_small_n_threshold", 10),
"ANCHOR_TIER_C_CORRIDOR_MULT": ("estimate_anchor_tier_c_corridor_mult", 1.5),
"FSD_K": ("estimate_fsd_k", 1.65),
"SB_GATE_MIN_N": ("estimate_sb_gate_min_n", 3),
"SB_GATE_MAX_FSD": ("estimate_sb_gate_max_fsd", 0.20),
}
# Булевы выключатели, схлопнутые к боевому True: их OFF-ветки из кода удалены.
_REMOVED_SWITCHES = (
"estimate_dedup_analogs_enabled", # #2378
"estimate_expected_sold_le_asking", # #2380
)
@pytest.mark.parametrize("const", sorted(_PROD_VALUES))
def test_constant_keeps_prod_value(const: str) -> None:
assert getattr(estimator, const) == _PROD_VALUES[const][1]
@pytest.mark.parametrize("const", sorted(_PROD_VALUES))
def test_settings_field_is_gone(const: str) -> None:
# Поле вернулось в Settings → снова два источника одного числа (ENV и код).
assert _PROD_VALUES[const][0] not in Settings.model_fields
@pytest.mark.parametrize("field", _REMOVED_SWITCHES)
def test_switch_is_gone(field: str) -> None:
assert field not in Settings.model_fields