gendesign/tradein-mvp/backend/tests/test_geocoder_bbox.py
Light1YT 04550bc94f
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
fix(tradein): drop non-EKB avito detail coords on ingest (audit #1871 P2)
avito_detail.parse_detail_html писал data-map-lat/lon прямо в listings
UPDATE COALESCE без bbox-валидации — в отличие от geocoder.py, который
фильтрует везде. Итог: 406 active avito-листингов с координатами
Питер/Тюмень/Уфа просочились в ЕКБ-датасет (ложные аналоги через Tier A
address-match оценщика). Prod-verified 2026-06-23.

- geocoder: DRY-хелпер is_within_ekb_bbox(lat, lon, bbox) + именованные
  EKB_BBOX_TIGHT (geocoder-фильтр, числа не изменены) и EKB_BBOX_WIDE
  (ingest-guard); 2 inline tight-проверки → хелпер (поведение identical).
- avito_detail: после извлечения lat/lon → reset None + logger.warning
  если вне wide-bbox; листинг падает в geocode-cron путь (COALESCE NULL
  не затирает прежнее хорошее значение).
- tests: bbox-хелпер (ЕКБ True, Питер/Тюмень/Уфа False, inclusive,
  WIDE⊇TIGHT инвариант) + parse_detail_html guard end-to-end.

WIDE строго содержит TIGHT — guard физически не режет ничего, что
geocoder сам бы пропустил. NULL lat (14935, geocode backlog) — отдельная
проблема, не в scope. One-time cleanup существующих 406 — отдельно
(database-expert, prod data mutation).

Refs #1871
2026-06-23 14:33:10 +05:00

88 lines
3.5 KiB
Python
Raw 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.

"""Тесты для ЕКБ bbox-хелперов geocoder (#1871).
Покрывают:
- is_within_ekb_bbox_wide — ingest-guard (avito_detail координаты);
- is_within_ekb_bbox — geocoder tight-фильтр (поведение НЕ изменилось после DRY-рефактора);
- инвариант WIDE ⊇ TIGHT.
"""
import pytest
from app.services.geocoder import (
EKB_BBOX_TIGHT,
EKB_BBOX_WIDE,
is_within_ekb_bbox,
is_within_ekb_bbox_wide,
)
# Центр ЕКБ (Плотинка) — внутри обоих bbox.
EKB_CENTER = (56.838, 60.605)
@pytest.mark.parametrize(
"lat,lon,expected",
[
(*EKB_CENTER, True), # центр ЕКБ
(56.797713, 60.609135, True), # реальная EKB-карточка из avito-fixture
(59.9386, 30.3141, False), # Питер (#1871)
(57.1530, 65.5343, False), # Тюмень (#1871)
(54.7388, 55.9721, False), # Уфа (#1871)
(57.0, 60.5, True), # приграничье севернее tight, но в wide (В.Пышма зона)
],
)
def test_is_within_ekb_bbox_wide(lat: float, lon: float, expected: bool) -> None:
assert is_within_ekb_bbox_wide(lat, lon) is expected
@pytest.mark.parametrize(
"lat,lon",
[
(56.6, 60.3), # юго-западный угол wide
(57.1, 60.9), # северо-восточный угол wide
(56.6, 60.9), # юго-восточный угол
(57.1, 60.3), # северо-западный угол
],
)
def test_wide_bbox_boundary_inclusive(lat: float, lon: float) -> None:
"""Границы wide-bbox включаются (inclusive)."""
assert is_within_ekb_bbox_wide(lat, lon) is True
def test_wide_bbox_just_outside() -> None:
"""Чуть за границей wide-bbox → False."""
assert is_within_ekb_bbox_wide(56.59, 60.605) is False # южнее
assert is_within_ekb_bbox_wide(57.11, 60.605) is False # севернее
assert is_within_ekb_bbox_wide(56.838, 60.29) is False # западнее
assert is_within_ekb_bbox_wide(56.838, 60.91) is False # восточнее
def test_tight_bbox_behavior_unchanged() -> None:
"""geocoder tight-фильтр: те же числа, что и до DRY-рефактора (60.40-60.85 / 56.65-56.95)."""
# внутри
assert is_within_ekb_bbox(56.838, 60.605) is True
# границы tight inclusive
assert is_within_ekb_bbox(56.65, 60.40) is True
assert is_within_ekb_bbox(56.95, 60.85) is True
# чуть за tight → False (но в wide)
assert is_within_ekb_bbox(56.64, 60.605) is False
assert is_within_ekb_bbox(56.96, 60.605) is False
assert is_within_ekb_bbox(56.838, 60.39) is False
assert is_within_ekb_bbox(56.838, 60.86) is False
def test_wide_strictly_contains_tight() -> None:
"""Инвариант: всё, что прошло бы tight, проходит и wide.
Гарантирует, что ingest-guard не режет ничего, что geocoder сам бы пропустил.
"""
t_lat_min, t_lat_max, t_lon_min, t_lon_max = EKB_BBOX_TIGHT
w_lat_min, w_lat_max, w_lon_min, w_lon_max = EKB_BBOX_WIDE
assert w_lat_min <= t_lat_min
assert w_lat_max >= t_lat_max
assert w_lon_min <= t_lon_min
assert w_lon_max >= t_lon_max
# точечная проверка на углах tight
for lat in (t_lat_min, t_lat_max):
for lon in (t_lon_min, t_lon_max):
assert is_within_ekb_bbox(lat, lon) is True
assert is_within_ekb_bbox_wide(lat, lon) is True