gendesign/tradein-mvp/packages/scraper-kit/src/scraper_kit/geo.py
bot-backend cf91ac6b24
All checks were successful
CI Trade-In / changes (pull_request) Successful in 9s
CI / changes (pull_request) Successful in 9s
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Successful in 52s
feat(tradein/scraper): per-city sweep capability для 5 городов области (schedules disabled)
B1: avito/cian/yandex city-sweep по городам Свердловской обл. (Н.Тагил/Каменск/Первоуральск/В.Пышма/Серов). Schedules засеяны DISABLED — rollout вручную по одному городу (proxy = 1 живой прокси).

- pipeline.py: CITY_ANCHORS (5) + get_city_anchors(slug) (None→EKB fallback).
- scheduler.py: sweep-jobs читают params.city → анкоры; wildcard-хендлеры *_city_sweep_* (source UNIQUE → per-city source, как deactivate_stale_*).
- geo.py: OBLAST66_BBOX + is_within_oblast66_bbox; avito/detail.py ingest-guard EKB→oblast66.
- price_brackets.py: 5 city-seed (reuse EKB). migration 179: 15 rows enabled=false, staggered, idempotent.
- +6 тестов. domclick (B2) не тронут.
2026-07-12 20:06:46 +03:00

51 lines
3 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.

"""Геометрические хелперы scraper_kit — чистая математика, без `app.*` / БД.
Strangler-копия (#2133) bbox-констант/предикатов из `app.services.geocoder`,
используемых скрапперами как ingest-guard (валидация координат detail-страниц
перед записью). Здесь — только чистые функции; продуктовый geocoder (Nominatim /
Yandex запросы, БД) НЕ копируется.
"""
from __future__ import annotations
# ЕКБ bbox = (lat_min, lat_max, lon_min, lon_max).
# TIGHT — центр агломерации; WIDE — с приграничьем, но всё ещё отсекает
# Питер/Тюмень/Уфу (#1871). WIDE строго содержит TIGHT.
EKB_BBOX_TIGHT = (56.65, 56.95, 60.40, 60.85)
EKB_BBOX_WIDE = (56.6, 57.1, 60.3, 60.9)
# Свердловская область целиком (region 66) — щедрый bbox под oblast-wide city-sweep
# (Н.Тагил/Каменск-Уральский/Первоуральск/В.Пышма/Серов, B1 rollout). Зеркалит
# `app.services.geocoder.OBLAST66_BBOX` дословно (та же (lat_min, lat_max, lon_min,
# lon_max)) — держим EKB_BBOX_TIGHT/WIDE отдельно для EKB-only fast-path (не заменяем).
OBLAST66_BBOX = (55.8, 62.2, 56.7, 66.6)
def is_within_ekb_bbox(
lat: float, lon: float, bbox: tuple[float, float, float, float] = EKB_BBOX_TIGHT
) -> bool:
"""True если (lat, lon) внутри bbox (inclusive). bbox = (lat_min, lat_max, lon_min, lon_max)."""
lat_min, lat_max, lon_min, lon_max = bbox
return lat_min <= lat <= lat_max and lon_min <= lon <= lon_max
def is_within_ekb_bbox_wide(lat: float, lon: float) -> bool:
"""Ingest-guard: True если координаты в широком ЕКБ-bbox (#1871).
Используется для валидации координат из avito detail-страниц перед записью в БД.
Шире geocoder-tight, поэтому не режет легитимное приграничье, но отсекает не-ЕКБ
(Питер/Тюмень/Уфа). EKB-only fast-path — для oblast-wide sweep см.
`is_within_oblast66_bbox`.
"""
return is_within_ekb_bbox(lat, lon, EKB_BBOX_WIDE)
def is_within_oblast66_bbox(lat: float, lon: float) -> bool:
"""Ingest-guard: True если координаты внутри bbox всей Свердловской области (region 66).
Oblast-wide аналог `is_within_ekb_bbox_wide` (B1 rollout — city-sweep за пределами ЕКБ:
Н.Тагил/Каменск-Уральский/Первоуральск/В.Пышма/Серов). Мирроит
`app.services.geocoder.is_within_oblast66_bbox` — щедрый bbox, всё ещё отсекает
кросс-региональные дубли (Тюмень/Курган и т.п. вне OBLAST66_BBOX).
"""
return is_within_ekb_bbox(lat, lon, OBLAST66_BBOX)