gendesign/tradein-mvp/backend/tests/test_3234_tier_h_settings.py
bot-backend 08216e4c43
All checks were successful
CI Trade-In / changes (pull_request) Successful in 23s
CI / changes (pull_request) Successful in 24s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (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 / backend-tests (pull_request) Successful in 6m30s
feat(mera/estimator): порог и окно этажности Tier H — в настройки, для бэктеста (#3234)
Tier H («тот же класс дома») отдаётся только при ≥5 комплах, иначе молча уходит в
Tier W без фильтра по классу. Прод, Loki 30 сут (комментарий 12.09): 61 из 86
попыток не доживает, в 31 из них в своей полосе 0 комплов. Какой вариант лучше —
порог 5→4, окно этажей ±30%→±50% или оба — решает только MAPE бэктеста на сделках,
а оба числа были литералами: сравнить варианты без правки кода было нельзя.

estimate_tier_h_min_comps (5) и estimate_tier_h_floors_tol (0.30) в Settings,
_fetch_analogs берёт их оттуда. Дефолты дают те же tf_min/tf_max, что литералы
(сверено для total_floors 1..199), поведение прода не меняется. Бэктест варианта:
docker exec -e ESTIMATE_TIER_H_MIN_COMPS=4 -e ESTIMATE_TIER_H_FLOORS_TOL=0.5 …

Дефолт не меняю: бэктест вариантов не прогнан (нужен деплой этих ручек).

Refs #3234

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

78 lines
3.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.

"""#3234: порог и окно этажности Tier H берутся из settings, дефолты = прежние литералы.
Tier H в _fetch_analogs отдаёт «тот же класс дома» только при ≥ N комплов, иначе
уходит в Tier W без фильтра по классу. N и окно total_floors были литералами (5 и
×0.7…×1.3); вынесены, чтобы бэктест сравнил варианты через env. Проверяем значения,
которые реально уходят в SQL, и тир, который реально возвращается.
"""
from __future__ import annotations
import os
from unittest.mock import MagicMock
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
import pytest
import app.services.estimator as est
def _run(n_tier_h: int, total_floors: int) -> tuple[str, dict]:
"""_fetch_analogs на mock-БД: Tier H отдаёт n_tier_h строк, Tier W — одну."""
tier_h_params: dict = {}
def side_effect(stmt, params): # type: ignore[no-untyped-def]
result = MagicMock()
if "tf_min" in params:
tier_h_params.update(params)
rows = [{"source": "avito", "relevance_score": float(i)} for i in range(n_tier_h)]
else:
rows = [{"source": "cian", "relevance_score": 0.0}]
result.mappings.return_value.all.return_value = rows
return result
db = MagicMock()
db.execute.side_effect = side_effect
_, _, tier = est._fetch_analogs(
db,
lat=56.83,
lon=60.6,
rooms=2,
area=50.0,
radius_m=1000,
year_built=1975,
total_floors=total_floors,
)
return tier, tier_h_params
def test_defaults_keep_old_literals() -> None:
tier, params = _run(n_tier_h=5, total_floors=9)
assert (params["tf_min"], params["tf_max"]) == (6, 12) # floor(6.3), ceil(11.7)
assert tier == "H"
tier, params = _run(n_tier_h=4, total_floors=16)
assert (params["tf_min"], params["tf_max"]) == (11, 21) # floor(11.2), ceil(20.8)
assert tier == "W"
def test_min_comps_from_settings(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(est.settings, "estimate_tier_h_min_comps", 4)
assert _run(n_tier_h=4, total_floors=9)[0] == "H"
assert _run(n_tier_h=3, total_floors=9)[0] == "W"
def test_floors_tol_from_settings(monkeypatch: pytest.MonkeyPatch) -> None:
monkeypatch.setattr(est.settings, "estimate_tier_h_floors_tol", 0.5)
_, params = _run(n_tier_h=5, total_floors=9)
assert (params["tf_min"], params["tf_max"]) == (4, 14) # floor(4.5), ceil(13.5)
def test_env_reaches_settings(monkeypatch: pytest.MonkeyPatch) -> None:
# Бэктест вариантов идёт через `docker exec -e ESTIMATE_TIER_H_…` — env обязан доехать.
from app.core.config import Settings
monkeypatch.setenv("ESTIMATE_TIER_H_MIN_COMPS", "4")
monkeypatch.setenv("ESTIMATE_TIER_H_FLOORS_TOL", "0.5")
s = Settings()
assert (s.estimate_tier_h_min_comps, s.estimate_tier_h_floors_tol) == (4, 0.5)