From 41f2402b25b7de8821ffeee63c275077c586ceab Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 18 Jun 2026 08:03:05 +0300 Subject: [PATCH] fix(yandex): read predicted price from entity.predictions.predictedPrice (#1725) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Gate-API nests Yandex's per-offer valuation under predictions.predictedPrice, not predictedPrice.predictedPrice (that key does not exist). _predicted_prices read the wrong outer key, so predicted_price_rub/_min/_max were None for every offer (prod: pp=0 across all 6189 yandex rows). Verified live against the gate-API 2026-06-18. The rich-field test fixture used the same wrong wrapper, so the test passed vacuously. Updated _ENTITY_RICH_OWNER to the real predictions.predictedPrice shape — with the old code the assertions now fail, confirming non-vacuity. --- .../backend/app/services/scrapers/yandex_realty.py | 11 +++++++---- tradein-mvp/backend/tests/test_yandex_realty_serp.py | 4 +++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tradein-mvp/backend/app/services/scrapers/yandex_realty.py b/tradein-mvp/backend/app/services/scrapers/yandex_realty.py index 64792c94..35004ce2 100644 --- a/tradein-mvp/backend/app/services/scrapers/yandex_realty.py +++ b/tradein-mvp/backend/app/services/scrapers/yandex_realty.py @@ -189,12 +189,15 @@ def _to_bigint(raw: Any) -> int | None: def _predicted_prices(entity: dict[str, Any]) -> tuple[int | None, int | None, int | None]: - """Extract (value, min, max) from entity.predictedPrice.predictedPrice. + """Extract (value, min, max) from entity.predictions.predictedPrice. - Yandex's own per-offer valuation. Fields are strings (e.g. "7554000"). - Returns (None, None, None) when the nested path is absent. + Yandex's own per-offer valuation. The gate-API nests it under + ``predictions.predictedPrice`` (NOT ``predictedPrice.predictedPrice`` — + that key does not exist; verified live 2026-06-18 against the gate-API). + Fields are strings (e.g. "7554000"). Returns (None, None, None) when the + nested path is absent (e.g. novostroyki offers carry no valuation). """ - outer = entity.get("predictedPrice") or {} + outer = entity.get("predictions") or {} inner = outer.get("predictedPrice") or {} if not isinstance(inner, dict): return None, None, None diff --git a/tradein-mvp/backend/tests/test_yandex_realty_serp.py b/tradein-mvp/backend/tests/test_yandex_realty_serp.py index 17206a30..87ae446d 100644 --- a/tradein-mvp/backend/tests/test_yandex_realty_serp.py +++ b/tradein-mvp/backend/tests/test_yandex_realty_serp.py @@ -114,7 +114,9 @@ _ENTITY_RICH_OWNER: dict = { "trend": "DECREASED", "previous": 7500000, }, - "predictedPrice": {"predictedPrice": {"value": "7554000", "min": "7100000", "max": "8000000"}}, + # Real gate-API nests valuation under predictions.predictedPrice (verified + # live 2026-06-18) — NOT predictedPrice.predictedPrice. + "predictions": {"predictedPrice": {"value": "7554000", "min": "7100000", "max": "8000000"}}, "area": {"value": 48.0}, "roomsTotal": 2, "floorsOffered": [5],