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
Пять булевых флагов кластера сняты ещё в #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>
77 lines
4.1 KiB
Python
77 lines
4.1 KiB
Python
"""#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
|