Геокодер был жёстко EKB-bound и ВЫБРАСЫВАЛ корректные non-EKB геокоды (bbox-accept-фильтры) → блокировал оценку любого адреса области. Обобщено до region-66.
- OBLAST66_BBOX + is_within_oblast66_bbox (superset EKB-tight, EKB не затронут); EKB Yandex Tier1/2 fast-path сохранён.
- Все accept-фильтры (Yandex/Nominatim forward+suggest, geocode() accept+typo) → oblast-66.
- City-prefix по word-boundary _has_oblast_marker (не substring): EKB-улицы 'Серова 27'/'Ирбитская 5' и др. больше не роняют 'Екатеринбург,'-префикс (был silent wrong-city баг).
- Two-pass tie-break в accept-циклах: tight-EKB кандидат приоритетнее → EKB-результат идентичен прежнему.
- Region cross-check (Nominatim address.state / Yandex AdministrativeAreaName) отсекает соседние области (Тюмень и др.); bbox-fallback когда region отсутствует.
- DaData suggest: region-параметр (hard-filter), убран no-op restrict_value.
- +26 тестов: OBLAST66 superset TIGHT, far-town accept, Тюмень reject, marker +/- (street-collision), DaData region body.
Foundation для A2 (геокодинг non-EKB сделок) и live-оценки адресов области.
Удаляет весь `app/services/scrapers/` (16 файлов, ~7100 строк) — Part D (D1-D5)
убрал всех внешних вызывающих, 0 runtime importers подтверждено grep'ом на main.
Заодно:
- удалены 7 осиротевших локальных probe/sweep-скриптов (tradein-mvp/scripts/),
импортировавших уже-удалённые или удаляемые сейчас legacy-модули
- тест-хирургия по 65+ файлам: DELETE прямых legacy-юнит-тестов, RETARGET
тестов, тестирующих ещё живую бизнес-логику (переключены на scraper_kit.*
эквиваленты, включая quality-gate #781/#753/#754/#755/#773/#740), partial-delete
golden-parity тестов, потерявших legacy-оракл
- kit save_listings/AvitoScraper/etc. требуют инжектируемые matcher/config —
ретаргетированные тесты обновлены под новую сигнатуру (RealScraperConfig(),
MagicMock HouseMatcher, region_code=66)
Полный pytest suite зелёный (2255 passed, 6 skipped) кроме известного флейка
#2208 (test_search_cache_hit, не связан со scrapers).
Финальный PR issue #2045 (BE-3): GET /api/v1/trade-in/location-coef для
LocationDrawer. FDW foreign table -> локальное зеркало osm_poi_ekb_local
(TRUNCATE+INSERT, тот же паттерн что cad_buildings_local/cadastral_geo_match,
избегает ~1.16s/row FDW round-trip) -> straight-line POI-скоринг, портированный
из Site Finder poi_score.py::compute_poi_weighted_top7 (CATEGORY_WEIGHTS as-is,
радиус 1200м для квартир вместо Ptica 2000м для участков). score->coef -
новая MVP-эвристика (0.95..1.05, не откалибрована на реальных дельтах).
Graceful fallback (не 500, не фабрикуем факторы): пустая/не отрефрешенная
osm_poi_ekb_local или отсутствие lat/lon у оценки -> coef=1.0, factors=[],
geo_source="unavailable".
Scheduler: source=osm_poi_ekb_refresh, daily, зарегистрирован и в боевом
dispatch (scheduler.py), и в kit-registry (product_handlers.py) - иначе
test_kit_registry_completeness падает на ship-dark инварианте (#2192).
Frontend wiring (mappers.ts/LocationDrawer.tsx) - вне scope, отдельная задача
после проверки endpoint'а curl'ом на деплое.
#1786 merged before the test fix landed → main red on
test_geocode_uses_cadastral_before_yandex. The new geoportal house-match
tier runs first in geocode(); this legacy cadastral test must mock it to
None so it exercises the cadastral forward path (else the MagicMock db row
leaks through the new tier as lat=1.0).
The legacy resolve_cian_zhk_url hit /zhk/<id>/ which now 404s, leaving the
318 geo-matched cian houses (house_sources.ext_id set, cian_zhk_url NULL)
unfetchable -> the newbuilding-enrichment backfill matched 0 houses.
Add resolve_cian_zhk_url_via_search(nb_id): fetch the cat.php newbuilding SERP
and extract the canonical zhk-<slug>.cian.ru url, ANCHORED on the
<h1 data-name="Title"> header anchor (NOT naive first-match — the SERP carries
promo/recommendation zhk-* links before the title that would otherwise resolve
the wrong ЖК and silently corrupt enrichment). Validated against the real
2.81MB prod SERP + an adversarial poisoned-recommendation test.
Wire into newbuilding_enrich_backfill: broaden selection to "has ext_id OR
cian_zhk_url", resolve+persist the url under a SAVEPOINT before enriching,
rate-limited + resumable + idempotent. Keep the old resolver (deprecated).
Bounded prod proof (5 houses, direct): 5/5 urls resolved+persisted, 3/5 fully
enriched (+18 price_dynamics, +3 reliability); the 2 misses were direct-mode
anti-bot on the 2nd fetch. Full 318-run gated on the cian mobile proxy
(mproxy.site) being restored. code-reviewer APPROVE (SQL/idempotency) +
resolver hardened against wrong-ЖК. 19 tests green.
Refs #972.
Replaces tradein.cad_buildings snapshot with live postgres_fdw foreign table reading gendesign.v_tradein_cad_buildings. Fixes /trade-in/api/v1/geocode/reverse 500 (Nominatim ban) and address_not_geocoded for cadastre addresses (e.g. Хохрякова 81).
Security (deep-review fixes):
- 100_tradein_fdw_role.sql: passwordless CREATE ROLE; password set by deploy.yml ALTER ROLE bootstrap reading GENDESIGN_FDW_PASSWORD from backend/.env.runtime (via psql :'pw' var → format %L — injection-safe).
- core/fdw.py: regex whitelist [A-Za-z0-9_-]{32,256} on password, ValueError without echoing value, try/rollback on commit.
- 060_postgres_fdw_extension.sql: connect_timeout='3' on FOREIGN SERVER + ALTER ADD/SET fallback.
- geocoder.py: _cadastral_forward_sync / _cadastral_reverse_sync wrapped in asyncio.to_thread.
- 100_*.sql: REVOKE ALL ON ALL TABLES/SEQUENCES/FUNCTIONS IN SCHEMA public; only GRANT SELECT on v_tradein_cad_buildings.
- pg_user_mappings query handles PUBLIC mapping (usename IS NULL).
Tests: 3 SQL-injection guards on ensure_fdw_user_mapping + rewritten cadastral suite.