gendesign/tradein-mvp/backend/tests/test_3063_pro_seller_from_agency.py
bot-backend 1aaa8b88a1
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m29s
feat(tradein/yandex): is_pro_seller выводится из агентского блока + бэкфилл 4469 строк (#3063 п.2)
Detail-обогащение извлекало agency_name, но признак «продаёт профи» не
выводило — 4 469 активных листингов с известным агентством стояли с
is_pro_seller=NULL (признак эрозирован SERP-затиранием до PR #3067, а
заново не появлялся). Признак идёт в оценщик trade-in.

- save_detail_enrichment: is_pro_seller=TRUE при известном agency_name,
  fill-only COALESCE; отсутствие блока НЕ доказывает «частник» (п.3 задачи —
  отдельное решение), в ту сторону ничего не пишем;
- миграция 271: одноразовый бэкфилл уже существующих строк всех источников
  (правило источник-независимо), идемпотентна.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 12:48:18 +05:00

82 lines
3.6 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.

"""#3063 п.2: is_pro_seller выводится из наличия агентского блока на детальной.
Прод-факт (23-24.08): 4 469 активных yandex-листингов с заполненным agency_name
(из detail-бэкфилла) имели is_pro_seller = NULL — признак эрозирован
SERP-затиранием до PR #3067, а detail-обогащение его и не выводило, хотя данных
достаточно. Признак идёт в оценщик trade-in.
Два конца:
- save_detail_enrichment пишет is_pro_seller=TRUE при известном agency_name
(fill-only COALESCE; отсутствие блока НЕ доказывает «частник» — п.3 задачи);
- миграция 271 бэкфиллит уже существующие строки («починка разбора ≠ починка
строк»: UPDATE прошлого задним числом никто не перезапишет).
Красный на origin/main по ЗНАЧЕНИЮ: в параметрах UPDATE нет ключа
is_pro_seller → .get() даёт None, не True.
"""
from __future__ import annotations
import os
os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test")
from pathlib import Path
from typing import Any
from unittest.mock import MagicMock
from scraper_kit.providers.yandex.detail import DetailEnrichment, save_detail_enrichment
_MIGRATION = (
Path(__file__).resolve().parents[1] / "data" / "sql" / "271_listings_pro_seller_from_agency.sql"
)
class _CaptureDb:
def __init__(self) -> None:
self.params: list[dict[str, Any]] = []
def execute(self, _stmt: Any, params: dict[str, Any] | None = None) -> Any:
if params:
self.params.append(params)
return MagicMock(rowcount=1)
def commit(self) -> None:
pass
def _update_params(db: _CaptureDb) -> dict[str, Any]:
upd = [p for p in db.params if "listing_id" in p]
assert upd, "save_detail_enrichment не выполнил UPDATE"
return upd[-1]
def test_agency_block_derives_pro_seller_true() -> None:
db = _CaptureDb()
save_detail_enrichment(
db, 101, DetailEnrichment(offer_id="o1", source_url="https://x/1", agency_name="Этажи")
) # type: ignore[arg-type]
assert _update_params(db).get("is_pro_seller") is True, (
"агентский блок известен, а is_pro_seller не выводится — "
"4 469 строк на проде копились именно так"
)
def test_no_agency_block_leaves_pro_seller_untouched() -> None:
"""Отсутствие блока — NULL в параметре: COALESCE сохранит ранее известное
значение, и «частник» из этого НЕ выводится (п.3 задачи — отдельно)."""
db = _CaptureDb()
save_detail_enrichment(
db, 102, DetailEnrichment(offer_id="o2", source_url="https://x/2", agency_name=None)
) # type: ignore[arg-type]
assert _update_params(db).get("is_pro_seller") is None
def test_backfill_migration_shape() -> None:
"""Миграция существует, идемпотентна (IS DISTINCT FROM TRUE), не трогает
is_homeowner и пустые имена агентств."""
sql = _MIGRATION.read_text()
assert "SET is_pro_seller = TRUE" in sql
assert "agency_name IS NOT NULL" in sql
assert "IS DISTINCT FROM TRUE" in sql
assert "is_homeowner" not in sql.split("--")[0] or "SET is_homeowner" not in sql