feat(tradein): DaData /suggest tier в /suggest endpoint (PR Q2) #605

Merged
Light1YT merged 1 commit from feat/tradein-dadata-suggest into main 2026-05-27 14:09:31 +00:00
Owner

Summary

Добавляет DaData /suggest/address как Tier 2 в geocoder.suggest() чейн. Yandex Suggest заблокирован (демо 1k/день исчерпан) → DaData (10k/день free, token-only auth) теперь основной источник автокомплита.

Новый tier chain:

Cadastral FDW (db local)
  → DaData /suggest (10k/день free, отлично для РФ)
  → Yandex Suggest (1k/день paid, сейчас blocked)
  → Nominatim (бесплатно, fallback)

Обратно совместимо: если DADATA_API_TOKEN пустой → tier skipped, Yandex/Nominatim как раньше. После PR Q1 (#604) creds уже на VPS в tradein-mvp/backend/.env.runtime.

Changes

File LOC Type
backend/app/services/dadata.py +197 mod (DadataSuggestion dataclass + suggest_addresses)
backend/app/services/geocoder.py +50 -10 mod (_dadata_suggest wrapper + tier insert)
backend/tests/services/test_dadata.py +309 mod (15 new tests)
backend/tests/services/test_cadastral_reverse.py +42 -7 mod (tier chain update)

4 файла, +581/-17.

Архитектура

  • DadataSuggestion(value, unrestricted_value, lat, lon, fias_id, house, street, city, kind) — frozen slots dataclass
  • suggest_addresses(query, limit=8, city="Екатеринбург") — async, token-only (нет X-Secret для /suggest endpoint!), graceful [] на все ошибки, 5s timeout
  • _classify_kind(fias_level): 8=house, 7=street, ≤6=city (для UI badges)
  • count clamped to [1, 20] (DaData API hard limit)
  • _dadata_suggest() в geocoder.py дропает entries с NULL lat/lon (для autocomplete нужны coords)
  • Все external tiers wrapped в try/except + logger.exception(...) — fall-through graceful

Tests

tests/services/test_dadata.py — 35 passed (20 PR Q1 + 15 new)
tests/services/test_cadastral_reverse.py — 20 passed (+2 tier chain tests)
tests/test_geocode_reverse_api.py — 7 passed (sanity, unaffected)

Combined: 62 passed in 0.41s

New tests:

  • happy_path (3 suggestions parsed)
  • no_token / short_query / network_error / 6× HTTP errors → []
  • malformed (no suggestions key, empty list, list-not-dict) → []
  • filters_null_coords (verified at geocoder layer)
  • classifies_kind (8/7/4/1/9/missing fias_level)
  • sends_proper_request (body shape, Authorization header, assert X-Secret is None locking in API contract diff vs /clean)
  • clamps_limit (count ≤ 20)

Ruff clean. Reuse DADATA_API_TOKEN from PR Q1 (#604) — secret не нужен для /suggest endpoint.

## Summary Добавляет **DaData /suggest/address** как Tier 2 в `geocoder.suggest()` чейн. Yandex Suggest заблокирован (демо 1k/день исчерпан) → DaData (10k/день free, token-only auth) теперь основной источник автокомплита. **Новый tier chain**: ``` Cadastral FDW (db local) → DaData /suggest (10k/день free, отлично для РФ) → Yandex Suggest (1k/день paid, сейчас blocked) → Nominatim (бесплатно, fallback) ``` Обратно совместимо: если `DADATA_API_TOKEN` пустой → tier skipped, Yandex/Nominatim как раньше. После PR Q1 (#604) creds уже на VPS в `tradein-mvp/backend/.env.runtime`. ## Changes | File | LOC | Type | |---|---|---| | `backend/app/services/dadata.py` | +197 | mod (DadataSuggestion dataclass + suggest_addresses) | | `backend/app/services/geocoder.py` | +50 -10 | mod (_dadata_suggest wrapper + tier insert) | | `backend/tests/services/test_dadata.py` | +309 | mod (15 new tests) | | `backend/tests/services/test_cadastral_reverse.py` | +42 -7 | mod (tier chain update) | **4 файла, +581/-17.** ## Архитектура - `DadataSuggestion(value, unrestricted_value, lat, lon, fias_id, house, street, city, kind)` — frozen slots dataclass - `suggest_addresses(query, limit=8, city="Екатеринбург")` — async, token-only (нет X-Secret для /suggest endpoint!), graceful [] на все ошибки, 5s timeout - `_classify_kind(fias_level)`: 8=house, 7=street, ≤6=city (для UI badges) - `count` clamped to [1, 20] (DaData API hard limit) - `_dadata_suggest()` в `geocoder.py` дропает entries с NULL lat/lon (для autocomplete нужны coords) - Все external tiers wrapped в `try/except + logger.exception(...)` — fall-through graceful ## Tests ``` tests/services/test_dadata.py — 35 passed (20 PR Q1 + 15 new) tests/services/test_cadastral_reverse.py — 20 passed (+2 tier chain tests) tests/test_geocode_reverse_api.py — 7 passed (sanity, unaffected) Combined: 62 passed in 0.41s ``` New tests: - happy_path (3 suggestions parsed) - no_token / short_query / network_error / 6× HTTP errors → [] - malformed (no `suggestions` key, empty list, list-not-dict) → [] - filters_null_coords (verified at geocoder layer) - classifies_kind (8/7/4/1/9/missing fias_level) - sends_proper_request (body shape, Authorization header, **assert X-Secret is None** locking in API contract diff vs /clean) - clamps_limit (count ≤ 20) Ruff clean. Reuse `DADATA_API_TOKEN` from PR Q1 (#604) — secret не нужен для /suggest endpoint.
Light1YT added 1 commit 2026-05-27 14:09:24 +00:00
Yandex Suggest заблокирован (демо 1k/день исчерпан) — DaData /suggest/address
(10k/день free, token-only auth, лучшее покрытие РФ) заменяет его как Tier 2
после Cadastral FDW. Yandex остаётся как Tier 3 fallback.

- app/services/dadata.py: DadataSuggestion dataclass + async suggest_addresses()
  (token-only, без X-Secret; graceful [] на все ошибки; 5s timeout;
  classifies fias_level→kind: 8=house, 7=street, ≤6=city; count clamp ≤20)
- app/services/geocoder.py: _dadata_suggest() обёртка, дропает entries без
  координат; suggest() теперь Cadastral → DaData → Yandex → Nominatim
- tests/services/test_dadata.py: 15 новых unit-тестов (happy + все error paths)
- tests/services/test_cadastral_reverse.py: +тест DaData до Yandex, update
  fallback теста под новый tier chain

Tests: 62 passed (35 dadata + 20 cadastral reverse + 7 geocode reverse api).
Ruff clean. Reuse DADATA_API_TOKEN from PR Q1 (#604) — secret не нужен для suggest.
Light1YT merged commit f438f536b8 into main 2026-05-27 14:09:31 +00:00
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#605
No description provided.