From eee5e4bac5af79ef93029613793de82412dd00ed Mon Sep 17 00:00:00 2001 From: Light1YT Date: Sat, 13 Jun 2026 10:40:49 +0500 Subject: [PATCH] =?UTF-8?q?fix(competitors):=20=5FACTIVE=5FSTATUSES=20=3D?= =?UTF-8?q?=20=D1=80=D1=83=D1=81=D1=81=D0=BA=D0=B8=D0=B5=20=D0=B7=D0=BD?= =?UTF-8?q?=D0=B0=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=B0=D0=BA=20?= =?UTF-8?q?=D1=83=20=D0=B4=D0=BE=D0=BC=D1=80=D1=84=20(#1213)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _ACTIVE_STATUSES = frozenset({"sales", "construction"}) — английский словарь никогда не совпадал с domrf_kn_objects.site_status, который scraper берёт СЫРЫМ из siteStatus дом.рф (domrf_kn.py:316). Реальные prod-значения русские: «Строящиеся»/«Сданные». Прод-аудит: - data/sql/105_add_sales_started_flag.sql фильтрует по 'Строящиеся' (~1322 строки). - partial index 66_indexes_recommend.sql использует те же. - analytics_queries.py, MarketTab.tsx, CompetitorTable.tsx — все на русских. Эффект: у ВСЕХ Competitor в POST /parcels/{cad}/competitors is_active=False и CompetitorsSummary.active_count=0 при любых данных — типизированный контракт систематически врал. Patch: _ACTIVE_STATUSES = frozenset({"Строящиеся"}). Заодно обновил два unit-теста которые кодировали баг (использовали "sales"/"construction" в моках, тестировали логику против сломанного словаря). Теперь моки матчат реальную prod-форму. 51/51 competitors-тестов зелёные. ruff clean. Closes #1213 --- .../app/services/site_finder/competitors.py | 10 ++++++-- .../tests/api/v1/test_parcel_competitors.py | 23 +++++++++++-------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/backend/app/services/site_finder/competitors.py b/backend/app/services/site_finder/competitors.py index 1de7bd55..e9413a1b 100644 --- a/backend/app/services/site_finder/competitors.py +++ b/backend/app/services/site_finder/competitors.py @@ -57,8 +57,14 @@ _TIME_WINDOW_MONTHS: dict[str, float] = { "last_year": 12.0, } -# site_status значения, считающиеся «активными» -_ACTIVE_STATUSES = frozenset({"sales", "construction"}) +# site_status значения, считающиеся «активными» (#1213). domrf_kn_objects.site_status +# хранит русские значения ровно как scraper берёт из siteStatus дом.рф (см. domrf_kn.py:316 +# — без перевода). Прод-аудит: 105_add_sales_started_flag.sql фильтрует по 'Строящиеся' +# (~1322 строки), partial index в 66_indexes_recommend.sql использует те же, фронтовые +# проверки MarketTab/CompetitorTable — тоже русские. Английские 'sales'/'construction' +# не совпадали никогда → у всех Competitor is_active=false и active_count=0 при любых +# данных. is_sold_out_filter сохранён как раньше — на стороне фронта. +_ACTIVE_STATUSES = frozenset({"Строящиеся"}) # #968 (949-A): радиус спатиального матча domrf↔complexes для velocity gap-fill. # 200м + tolerant-name + nearest-per-domrf → подтверждаем «тот же ЖК» И гео, И diff --git a/backend/tests/api/v1/test_parcel_competitors.py b/backend/tests/api/v1/test_parcel_competitors.py index 63ca61e4..908bdfc5 100644 --- a/backend/tests/api/v1/test_parcel_competitors.py +++ b/backend/tests/api/v1/test_parcel_competitors.py @@ -26,7 +26,7 @@ def _coord_row(lat: float = 56.838, lon: float = 60.605) -> MagicMock: def _obj_row( obj_id: int = 1, distance_m: float = 400.0, - site_status: str = "sales", + site_status: str = "Строящиеся", obj_class: str | None = "comfort", velocity: float = 5.0, flat_count: int | None = 200, @@ -148,11 +148,15 @@ def test_competitors_basic() -> None: def test_competitors_summary_calc() -> None: - """summary: total_competitors, active_count, weighted_avg_velocity корректны.""" + """summary: total_competitors, active_count, weighted_avg_velocity корректны. + + site_status — реальные русские значения как у домрф (#1213 fix). + «Строящиеся» считаются активными, «Сданные» — нет. + """ rows = [ - _obj_row(obj_id=1, site_status="sales", velocity=10.0), - _obj_row(obj_id=2, site_status="construction", velocity=6.0), - _obj_row(obj_id=3, site_status="completed", velocity=2.0), + _obj_row(obj_id=1, site_status="Строящиеся", velocity=10.0), + _obj_row(obj_id=2, site_status="Строящиеся", velocity=6.0), + _obj_row(obj_id=3, site_status="Сданные", velocity=2.0), ] db = _make_db(coord=_coord_row(), obj_rows=rows) @@ -391,11 +395,12 @@ def test_competitors_avg_price_populated() -> None: def test_competitors_is_active_flag() -> None: - """is_active=True для sales/construction, False для completed/null.""" + """is_active=True для русского 'Строящиеся' (реальная prod-форма #1213), + False для 'Сданные'/null/прочих.""" rows = [ - _obj_row(obj_id=1, site_status="sales"), - _obj_row(obj_id=2, site_status="construction"), - _obj_row(obj_id=3, site_status="completed"), + _obj_row(obj_id=1, site_status="Строящиеся"), + _obj_row(obj_id=2, site_status="Строящиеся"), + _obj_row(obj_id=3, site_status="Сданные"), ] db = _make_db(coord=_coord_row(), obj_rows=rows) -- 2.45.3