Связь ДКП Росреестра с listings (avito/cian/yandex/n1) через street
normalization + area window + listing-date window. Foundation для
sales-vs-asks comparison (discount_pct per pair).
DDL:
- data/sql/067_v_street_sales_vs_listings.sql:
* table-valued function street_sales_vs_listings(street, area, rooms,
window_days, area_tol) для filtered query с indexes
* view v_street_sales_vs_listings — full crossjoin для analytical queries
(used в reporting; для transactional path use TVF)
* Encapsulated JOIN logic: street ILIKE + area ±15% + listing_date в окне
[deal_date - window_days, deal_date]
* Computed columns: days_listing_to_deal, discount_pct
API:
- GET /api/v1/sales-vs-listings?address=...&area_m2=...&rooms=...
* Pydantic SalesVsListingsResponse с street/period/total_deals/
deals_with_listings/linkage_rate_pct/median_discount_pct/pairs list
* Использует extract_street_name() из existing estimator code
Schema:
- schemas/trade_in.py: SalesListingPair + SalesVsListingsResponse
Production verification:
- Migration applied на проде: CREATE FUNCTION + CREATE VIEW
- View total: 50,139 (deal, listing) pairs
- Sample case: «Бисертская» — ДКП 6.3 млн (86 м²) vs listing 8.5 млн (79 м²)
с listing_date за 4 дня до сделки → discount_pct = −25.88%
Tests: 8/8 pass в test_sales_vs_listings.py — happy path, no matches,
LEFT JOIN behavior, median discount, defaults, response shape.
Ruff clean.
Out of scope (separate PR L+M):
- Frontend extend StreetDealsCard «Связанные ASK»
- Estimator revert NOTE 2026-05-24 (rosreestr_deals в actual_deals)
Refs #564 (Phase 1 — backend foundation).