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
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>
78 lines
3.1 KiB
Python
78 lines
3.1 KiB
Python
"""#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)
|