Merge pull request 'fix(tradein): чанковый геокодинг — cron не таймаутится (#1)' (#382) from fix/tradein-geocode-chunked into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 4s
Deploy Trade-In / build-frontend (push) Successful in 26s
Deploy Trade-In / build-backend (push) Successful in 44s
Deploy Trade-In / deploy (push) Successful in 16s

This commit is contained in:
lekss361 2026-05-21 16:43:46 +00:00
commit 0b9bf55ba0
3 changed files with 71 additions and 13 deletions

View file

@ -124,12 +124,15 @@ def _clean_address_for_geocode(addr: str) -> str:
@router.post("/geocode-missing")
async def geocode_missing(
db: Annotated[Session, Depends(get_db)],
limit: int = 500,
limit: int = 100,
) -> dict:
"""Геокодинг listings у которых нет lat/lon (используя address).
Запускать после scrape Cian/N1 не отдают координаты, только адрес.
Nominatim rate-limited 1 req/sec большой batch идёт ~limit секунд.
Чанк-обработка: limit=100 ~150-200с (Nominatim 1 req/sec). cron вызывает
в цикле пока `remaining` > 0.
geocode_tried_at: после КАЖДОЙ попытки (успех/провал) ставим NOW(). Failed-
адреса не выбираются повторно 7 дней cron-loop завершается, не зацикливается.
geom обновляется автоматически триггером listings_set_geom_trg.
"""
rows = db.execute(
@ -137,11 +140,13 @@ async def geocode_missing(
"""
SELECT id, address
FROM listings
WHERE (lat IS NULL OR lon IS NULL)
WHERE lat IS NULL
AND COALESCE(address, '') != ''
AND address NOT LIKE '%(Avito)%'
AND address NOT LIKE '%(N1)%'
ORDER BY scraped_at DESC
AND (geocode_tried_at IS NULL
OR geocode_tried_at < NOW() - interval '7 days')
ORDER BY geocode_tried_at NULLS FIRST, scraped_at DESC
LIMIT :limit
"""
),
@ -154,14 +159,43 @@ async def geocode_missing(
clean = _clean_address_for_geocode(row["address"])
result = await geocode(clean, db)
if result is None:
# Помечаем что пробовали — иначе ретрай на каждом cron.
db.execute(
text("UPDATE listings SET geocode_tried_at = NOW() WHERE id = :id"),
{"id": row["id"]},
)
db.commit()
skipped += 1
continue
# geom пересчитается триггером из lat/lon.
db.execute(
text("UPDATE listings SET lat = :lat, lon = :lon WHERE id = :id"),
text(
"UPDATE listings SET lat = :lat, lon = :lon, "
"geocode_tried_at = NOW() WHERE id = :id"
),
{"lat": result.lat, "lon": result.lon, "id": row["id"]},
)
db.commit()
geocoded += 1
return {"checked": len(rows), "geocoded": geocoded, "skipped": skipped}
# Сколько ещё не-геокоженных адресов ждут (для cron-loop'а).
remaining = db.execute(
text(
"""
SELECT count(*) FROM listings
WHERE lat IS NULL
AND COALESCE(address, '') != ''
AND address NOT LIKE '%(Avito)%'
AND address NOT LIKE '%(N1)%'
AND (geocode_tried_at IS NULL
OR geocode_tried_at < NOW() - interval '7 days')
"""
)
).scalar()
return {
"checked": len(rows),
"geocoded": geocoded,
"skipped": skipped,
"remaining": int(remaining or 0),
}

View file

@ -0,0 +1,18 @@
-- Trade-In MVP — отслеживание попыток геокодинга.
--
-- Проблема: geocode-missing раньше выбирал ВСЕ строки с lat IS NULL. Адреса,
-- которые Nominatim не может распознать (~12%), выбирались на каждом прогоне
-- → бесконечный ретрай + cron-таймаут.
--
-- Решение: колонка geocode_tried_at. После КАЖДОЙ попытки (успех или провал)
-- ставим NOW(). geocode-missing выбирает только не-пробованные ИЛИ
-- пробованные >7 дней назад. Failed-адреса ретраятся раз в неделю, не каждый cron.
--
-- Идемпотентно (ADD COLUMN IF NOT EXISTS) — безопасно при каждом деплое.
ALTER TABLE listings ADD COLUMN IF NOT EXISTS geocode_tried_at timestamptz;
-- Частичный индекс: ускоряет выборку «что ещё геокодить».
CREATE INDEX IF NOT EXISTS listings_geocode_pending_idx
ON listings (geocode_tried_at NULLS FIRST)
WHERE lat IS NULL;

View file

@ -64,11 +64,17 @@ for anchor in "${ANCHORS[@]}"; do
done
# Геокодинг свежих лотов без координат (Cian/N1 отдают только адрес).
# Nominatim 1 req/sec → batch ~500 идёт до ~8 мин.
log "→ geocode-missing"
geo=$(docker exec "$CONTAINER" curl -s -X POST \
"http://localhost:8000/api/v1/admin/geocode-missing?limit=500" \
-m 700 2>/dev/null || echo '{"error":"geocode_timeout"}')
log "← geocode-missing $geo"
# Чанками по 100 (~200с/чанк, под таймаутом), пока remaining > 0.
# Макс 15 чанков = 1500 адресов. geocode_tried_at не даёт зациклиться.
log "→ geocode-missing (чанками)"
for gi in $(seq 1 15); do
geo=$(docker exec "$CONTAINER" curl -s -X POST \
"http://localhost:8000/api/v1/admin/geocode-missing?limit=100" \
-m 320 2>/dev/null || echo '{"error":"geocode_timeout","remaining":0}')
log "← geocode чанк $gi: $geo"
remaining=$(echo "$geo" | grep -oE '"remaining":[0-9]+' | grep -oE '[0-9]+' || echo 0)
[ "${remaining:-0}" -eq 0 ] && break
sleep 2
done
log "=== cron-scrape done ==="