fix(poi-sync): split Overpass query per category (504 Gateway Timeout)

This commit is contained in:
lekss361 2026-05-11 18:54:28 +03:00
parent 93f13795bb
commit 3aeda297ee

View file

@ -42,17 +42,20 @@ OSM_CATEGORIES: dict[tuple[str, str], str] = {
}
def _build_overpass_query() -> str:
"""Строит Overpass QL запрос для всех POI типов в bbox ЕКБ."""
def _build_overpass_query_single(key: str, value: str) -> str:
"""Запрос для одной пары tag → нормированной категории.
Раньше делали один большой запрос на все 14 категорий Overpass возвращал
504 Gateway Timeout (запрос слишком тяжёлый). Сплит на per-category даёт
14 быстрых запросов вместо одного 60+ секундного.
"""
south, west, north, east = EKB_BBOX
bbox = f"({south},{west},{north},{east})"
# out body даёт теги + meta для timestamp последней правки
queries: list[str] = []
for (k, v), _cat in OSM_CATEGORIES.items():
queries.append(f'node["{k}"="{v}"]{bbox};')
queries.append(f'way["{k}"="{v}"]{bbox};')
inner = "\n".join(queries)
return f"[out:json][timeout:60];(\n{inner}\n);out center meta;"
return (
f"[out:json][timeout:30];"
f'(node["{key}"="{value}"]{bbox};way["{key}"="{value}"]{bbox};);'
f"out center meta;"
)
def _classify(tags: dict[str, str]) -> str | None:
@ -64,24 +67,37 @@ def _classify(tags: dict[str, str]) -> str | None:
async def fetch_overpass() -> list[dict]:
"""Запросить Overpass API, вернуть список raw OSM elements.
"""Запросить Overpass API per category, вернуть combined список elements.
NB: Overpass блокирует default `python-httpx/*` User-Agent (отдаёт 406).
Передаём явный UA с контактом проекта это требование Overpass usage
policy (https://operations.osmfoundation.org/policies/api/).
Делаем 14 отдельных запросов вместо одного гигантского большой запрос
отдаёт 504 Gateway Timeout. Между запросами sleep 1с (Overpass usage
policy: max 2 concurrent, лучше 1 req/s).
Overpass блокирует default `python-httpx/*` User-Agent (406) поэтому
явный UA с контактом проекта.
"""
query = _build_overpass_query()
logger.info("Overpass: отправляем запрос (%d символов)", len(query))
headers = {
"User-Agent": "GenDesign-SiteFinder/1.0 (+https://gendsgn.ru)",
"Accept": "application/json",
}
async with httpx.AsyncClient(timeout=120, headers=headers) as client:
r = await client.post(OVERPASS_URL, data={"data": query})
r.raise_for_status()
elements: list[dict] = r.json().get("elements", [])
logger.info("Overpass: получено %d элементов", len(elements))
return elements
all_elements: list[dict] = []
async with httpx.AsyncClient(timeout=60, headers=headers) as client:
for (key, value), category in OSM_CATEGORIES.items():
query = _build_overpass_query_single(key, value)
try:
r = await client.post(OVERPASS_URL, data={"data": query})
r.raise_for_status()
elements: list[dict] = r.json().get("elements", [])
logger.info("Overpass: %s=%s (%s) → %d", key, value, category, len(elements))
all_elements.extend(elements)
except Exception as e:
# Не падаем на одной категории — логируем и продолжаем
logger.warning("Overpass failed for %s=%s: %s", key, value, e)
await asyncio.sleep(1.0)
logger.info(
"Overpass: total %d elements across %d categories", len(all_elements), len(OSM_CATEGORIES)
)
return all_elements
def sync_poi_to_db() -> dict[str, int]: