diff --git a/backend/app/services/site_finder/competitors.py b/backend/app/services/site_finder/competitors.py index cfba9d81..c59889d4 100644 --- a/backend/app/services/site_finder/competitors.py +++ b/backend/app/services/site_finder/competitors.py @@ -132,7 +132,10 @@ _COMPETITORS_SQL = text(""" ORDER BY d.distance_m ASC """) -# Средняя цена м² по проданным квартирам для набора obj_id +# Средняя цена м² по квартирам с известной ценой для набора obj_id. +# Фильтр status='sold' убран: поле status в domrf_kn_flats заполнено в ~0.2% строк +# (99.8% NULL) — фильтр давал 0 строк и avg_price_per_m2 всегда None (Issue #112/227). +# AVG по всем квартирам с price_per_m2 IS NOT NULL даёт корректную среднюю цену ЖК. _AVG_PRICE_SQL = text(""" SELECT f.obj_id, @@ -140,7 +143,6 @@ _AVG_PRICE_SQL = text(""" FROM domrf_kn_flats f WHERE f.obj_id = ANY(:obj_ids) AND f.price_per_m2 IS NOT NULL - AND f.status = 'sold' GROUP BY f.obj_id """) diff --git a/backend/tests/api/v1/test_parcel_competitors.py b/backend/tests/api/v1/test_parcel_competitors.py index eacef554..6e16499b 100644 --- a/backend/tests/api/v1/test_parcel_competitors.py +++ b/backend/tests/api/v1/test_parcel_competitors.py @@ -334,6 +334,35 @@ def test_competitors_sold_pct_null() -> None: app.dependency_overrides.clear() +def test_competitors_avg_price_populated() -> None: + """avg_price_per_m2 не None если domrf_kn_flats возвращает строки с ценой. + + Регрессионный тест для Issue #227: фильтр status='sold' давал 0 строк + (поле status в domrf_kn_flats 99.8% NULL). После фикса — убран, AVG + считается по всем квартирам с known price_per_m2. + """ + rows = [_obj_row(obj_id=1)] + price_rows = [_price_row(obj_id=1, price=150_000.0)] + db = _make_db(coord=_coord_row(), obj_rows=rows, price_rows=price_rows) + + from app.core.db import get_db + + app.dependency_overrides[get_db] = _override_db(db) + try: + client = TestClient(app) + resp = client.post( + "/api/v1/parcels/66:41:0303161:5/competitors", + json={"radius_km": 1.0, "time_window": "last_quarter"}, + ) + assert resp.status_code == 200, resp.text + comp = resp.json()["competitors"][0] + assert comp["avg_price_per_m2"] == pytest.approx( + 150_000.0 + ), "avg_price_per_m2 должен быть не None — регрессия #227 status='sold' filter" + finally: + app.dependency_overrides.clear() + + def test_competitors_is_active_flag() -> None: """is_active=True для sales/construction, False для completed/null.""" rows = [