feat(tradein): cadastral reverse geocode via main backend cad_buildings #492

Merged
lekss361 merged 1 commit from feat/tradein-cadastral-reverse into main 2026-05-24 07:31:12 +00:00
Owner

Summary

Fix prod 500 на /trade-in/api/v1/geocode/reverse — Nominatim банит наш IP после batch geocode_missing.py, RetryError всплывает в FastAPI. Заменяем primary провайдер на локальный cad_buildings (37 624 здания ЕКБ с geom+address, <10ms), Nominatim остаётся fallback с обёрткой try/except.

Architecture

  • Main backend (новый): GET /api/v1/cadastral/reverse?lat=X&lon=Y&radius_m=200 — nearest cad_buildings, фильтрует гаражи/СНТ/садоводческие товарищества
  • Networks: main backend добавлен в gendesign_shared с alias gendesign-backend (чтобы tradein-стек достучался по DNS — у обоих стеков свой backend сервис)
  • Tradein backend: _cadastral_reverse() дёргает MAIN_BACKEND_URL=http://gendesign-backend:8000, при любой ошибке/404/timeout возвращает None → reverse_geocode fallback'ит на Nominatim → если и тот упал, возвращает None (endpoint = 404 вместо 500)

Verification

SQL прогон на проде (gendesign-postgres) для проблемной точки user'а lat=56.8518, lon=60.5671:

  • "ул. Готвальда, д. 15" в 71m (жилой дом, а не школа как у Nominatim)
  • Трамвайный 2/2 reverse → 0.0m exact match (cad_num 66:41:0205019:1105)
  • Forward тесты: Малышева 30-б, Куйбышева 65/Гоголя 50
  • pg_trgm fuzzy на опечатках ("Цвилинга" → "Цвиллинга" sim=0.39)

Files (8)

  • backend/app/api/v1/cadastral.py (new, 101)
  • backend/app/main.py (+2 router register)
  • docker-compose.prod.yml (+5 backend.networks shared alias)
  • tradein-mvp/backend/app/services/geocoder.py (+50/-6, _cadastral_reverse + _nominatim_reverse extract + try/except wrap)
  • tradein-mvp/backend/app/core/config.py (+5 main_backend_url setting)
  • tradein-mvp/docker-compose.prod.yml (+1 MAIN_BACKEND_URL env)
  • tradein-mvp/backend/tests/services/test_cadastral_reverse.py (new, 150) — 7 tests pass
  • tradein-mvp/backend/tests/services/__init__.py (new, empty — pytest collection)

Test plan

  • post-deploy curl: https://gendsgn.ru/trade-in/api/v1/geocode/reverse?lat=56.8518054702777&lon=60.56711733341218 → 200, address = ул. Готвальда, д. 15 (или 18)
  • post-deploy curl: https://gendsgn.ru/api/v1/cadastral/reverse?lat=56.8606&lon=60.6155 → 200, Трамвайный 2/2 dist=0.0
  • map-picker UI smoke: клик по дому в ЖК Малевич → адрес жилого дома в форме (не POI)
  • GlitchTip не пишет новые RetryError / HTTPStatusError от geocode endpoints

Risk

  • Cross-stack DNS: main backend получил alias gendesign-backend. Если main стек deploy'ится первым (alias ещё не на shared) — tradein _cadastral_reverse падает на ConnectionError → silent fallback на Nominatim. Не блокирующее.
  • No DB migration: использует существующую cad_buildings table в main DB, без новых ALTER/INDEX.
## Summary Fix prod 500 на `/trade-in/api/v1/geocode/reverse` — Nominatim банит наш IP после batch `geocode_missing.py`, RetryError всплывает в FastAPI. Заменяем primary провайдер на локальный `cad_buildings` (37 624 здания ЕКБ с geom+address, <10ms), Nominatim остаётся fallback с обёрткой try/except. ## Architecture - **Main backend** (новый): `GET /api/v1/cadastral/reverse?lat=X&lon=Y&radius_m=200` — nearest cad_buildings, фильтрует гаражи/СНТ/садоводческие товарищества - **Networks**: main `backend` добавлен в `gendesign_shared` с alias `gendesign-backend` (чтобы tradein-стек достучался по DNS — у обоих стеков свой `backend` сервис) - **Tradein backend**: `_cadastral_reverse()` дёргает `MAIN_BACKEND_URL=http://gendesign-backend:8000`, при любой ошибке/404/timeout возвращает None → `reverse_geocode` fallback'ит на Nominatim → если и тот упал, возвращает None (endpoint = 404 вместо 500) ## Verification SQL прогон на проде (gendesign-postgres) для проблемной точки user'а `lat=56.8518, lon=60.5671`: - ✅ "ул. Готвальда, д. 15" в 71m (жилой дом, а не школа как у Nominatim) - ✅ Трамвайный 2/2 reverse → 0.0m exact match (cad_num 66:41:0205019:1105) - ✅ Forward тесты: Малышева 30-б, Куйбышева 65/Гоголя 50 - ✅ pg_trgm fuzzy на опечатках ("Цвилинга" → "Цвиллинга" sim=0.39) ## Files (8) - `backend/app/api/v1/cadastral.py` (new, 101) - `backend/app/main.py` (+2 router register) - `docker-compose.prod.yml` (+5 backend.networks shared alias) - `tradein-mvp/backend/app/services/geocoder.py` (+50/-6, `_cadastral_reverse` + `_nominatim_reverse` extract + try/except wrap) - `tradein-mvp/backend/app/core/config.py` (+5 `main_backend_url` setting) - `tradein-mvp/docker-compose.prod.yml` (+1 `MAIN_BACKEND_URL` env) - `tradein-mvp/backend/tests/services/test_cadastral_reverse.py` (new, 150) — 7 tests pass - `tradein-mvp/backend/tests/services/__init__.py` (new, empty — pytest collection) ## Test plan - [ ] post-deploy curl: `https://gendsgn.ru/trade-in/api/v1/geocode/reverse?lat=56.8518054702777&lon=60.56711733341218` → 200, address = ул. Готвальда, д. 15 (или 18) - [ ] post-deploy curl: `https://gendsgn.ru/api/v1/cadastral/reverse?lat=56.8606&lon=60.6155` → 200, Трамвайный 2/2 dist=0.0 - [ ] map-picker UI smoke: клик по дому в ЖК Малевич → адрес жилого дома в форме (не POI) - [ ] GlitchTip не пишет новые `RetryError` / `HTTPStatusError` от geocode endpoints ## Risk - **Cross-stack DNS**: main backend получил alias `gendesign-backend`. Если main стек deploy'ится первым (alias ещё не на shared) — tradein `_cadastral_reverse` падает на ConnectionError → silent fallback на Nominatim. Не блокирующее. - **No DB migration**: использует существующую `cad_buildings` table в main DB, без новых ALTER/INDEX.
lekss361 added 1 commit 2026-05-24 07:24:59 +00:00
Fixes 500 on /trade-in/api/v1/geocode/reverse when Nominatim bans prod IP
after geocode_missing batch. Cadastral lookup via main backend new
endpoint /api/v1/cadastral/reverse is primary (37k buildings in ЕКБ,
<10ms, no external API). Nominatim is fallback wrapped in try/except
so HTTPStatusError no longer bubbles to FastAPI 500.

Architecture:
- main backend: new GET /api/v1/cadastral/reverse — cad_buildings nearest
  within radius, filters out гаражи/СНТ. Added to gendesign_shared network
  with alias gendesign-backend for cross-stack DNS.
- tradein backend: _cadastral_reverse() calls main backend, returns None
  on any error. reverse_geocode prefers cadastral → falls back to
  Nominatim → returns None (endpoint becomes 404 instead of 500).

Env: MAIN_BACKEND_URL=http://gendesign-backend:8000 in tradein prod.
Tests: 7 new cases covering cadastral/nominatim/fallback/error paths.
lekss361 merged commit 0efd2dca1d into main 2026-05-24 07:31:12 +00:00
Author
Owner

Merged via deep-code-reviewer — verdict APPROVE.

Cross-stack DNS architecture verified end-to-end:

  • main backend получил networks.shared.aliases: [gendesign-backend] — tradein backend (уже в gendesign_shared) резолвит DNS
  • Оба deploy workflows создают gendesign_shared network если отсутствует (deploy.yml:214, deploy-tradein.yml:149) — independent of deploy order
  • Fallback chain полный: cadastral (httpx 3s, broad except) → Nominatim (10s, retry x3, wrapped) → None → 404. NO path к 500.

GIST index cad_buildings_geom_gist присутствует на cad_buildings.geom (data/sql/63_schema_nspd_runs.sql:40-41, recreated в 92_cad_bulk_layers.sql:521) — ST_DWithin будет быстрым.

reverse_geocode() callers: единственный — tradein-mvp/backend/app/api/v1/geocode.py:82, уже корректно обрабатывает None → 404. None-семантика не ломает API contract. PR #490 batch (geocode_missing.py) использует только forward geocode(), не затронут.

Post-merge verify steps:

  1. deploy.yml (на backend/** + docker-compose.prod.yml) и deploy-tradein.yml (на tradein-mvp/**) триггерятся параллельно. Both pipelines независимы — concurrency группа разная.
  2. curl https://gendsgn.ru/api/v1/cadastral/reverse?lat=56.8606&lon=60.6155 → 200 Трамвайный 2/2 dist=0.0
  3. curl https://gendsgn.ru/trade-in/api/v1/geocode/reverse?lat=56.8518054702777&lon=60.56711733341218 → 200 ул. Готвальда, д. 15
  4. GlitchTip без новых RetryError / HTTPStatusError от geocode endpoints за 1h

Vault TODO (main session ownership):

  • fixes/Bug_Nominatim_IP_Ban_May24.md
  • code/modules/tradein/Reverse_Geocode.md
  • decisions/2026-05-24-CrossStack_DNS_Sharing.md
Merged via deep-code-reviewer — verdict APPROVE. Cross-stack DNS architecture verified end-to-end: - main backend получил `networks.shared.aliases: [gendesign-backend]` — tradein backend (уже в `gendesign_shared`) резолвит DNS - Оба deploy workflows создают `gendesign_shared` network если отсутствует (`deploy.yml:214`, `deploy-tradein.yml:149`) — independent of deploy order - Fallback chain полный: cadastral (httpx 3s, broad except) → Nominatim (10s, retry x3, wrapped) → None → 404. NO path к 500. GIST index `cad_buildings_geom_gist` присутствует на `cad_buildings.geom` (`data/sql/63_schema_nspd_runs.sql:40-41`, recreated в `92_cad_bulk_layers.sql:521`) — ST_DWithin будет быстрым. `reverse_geocode()` callers: единственный — `tradein-mvp/backend/app/api/v1/geocode.py:82`, уже корректно обрабатывает None → 404. None-семантика не ломает API contract. PR #490 batch (`geocode_missing.py`) использует только forward `geocode()`, не затронут. Post-merge verify steps: 1. `deploy.yml` (на backend/** + docker-compose.prod.yml) и `deploy-tradein.yml` (на tradein-mvp/**) триггерятся **параллельно**. Both pipelines независимы — concurrency группа разная. 2. `curl https://gendsgn.ru/api/v1/cadastral/reverse?lat=56.8606&lon=60.6155` → 200 Трамвайный 2/2 dist=0.0 3. `curl https://gendsgn.ru/trade-in/api/v1/geocode/reverse?lat=56.8518054702777&lon=60.56711733341218` → 200 ул. Готвальда, д. 15 4. GlitchTip без новых RetryError / HTTPStatusError от geocode endpoints за 1h Vault TODO (main session ownership): - `fixes/Bug_Nominatim_IP_Ban_May24.md` - `code/modules/tradein/Reverse_Geocode.md` - `decisions/2026-05-24-CrossStack_DNS_Sharing.md`
Author
Owner

Closing — discovered tradein-postgres already has local cad_buildings snapshot (manual import from gendesign via import-cadastre.sh, 36k rows, frozen 2026-05-22). Replacing this approach with postgres_fdw to read directly from gendesign.cad_buildings (live, no snapshot lag). New PR will follow.

What we found also: forward geocode bug — user estimate 9a08503e with address Хохрякова 81 got address_not_geocoded because Nominatim returns empty array for that exact string. The same address exists in cad_buildings (cad_num 66:41:0401034:66, lat=56.8274, lon=60.5953) → FDW approach fixes forward + reverse simultaneously.

Closing — discovered tradein-postgres already has local `cad_buildings` snapshot (manual import from gendesign via `import-cadastre.sh`, 36k rows, frozen 2026-05-22). Replacing this approach with **postgres_fdw** to read directly from `gendesign.cad_buildings` (live, no snapshot lag). New PR will follow. What we found also: forward geocode bug — user estimate `9a08503e` with address `Хохрякова 81` got `address_not_geocoded` because Nominatim returns empty array for that exact string. The same address exists in `cad_buildings` (cad_num 66:41:0401034:66, lat=56.8274, lon=60.5953) → FDW approach fixes forward + reverse simultaneously.
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#492
No description provided.