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
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>
82 lines
3.6 KiB
Python
82 lines
3.6 KiB
Python
"""#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
|