feat(cadastral): geo-nearest building matcher via local cad mirror #1750
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#1750
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/cadastral-geo-match"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What & why
listings.building_cadastral_number/cadastral_numberare 0% filled (columns exist, empty), while listing geo is ~93–100% present. The cadastral building registry (gendesign_cad_buildings, ~36.7k EKB rows) is reachable via postgres_fdw but has lat/lon only, no geom, and a per-listing nearest query over the FDW is ~1.16s/row (~13h for 43k — unusable). This adds a geo-nearest matcher that fillsbuilding_cadastral_numberfor the ~93% of listings that have geo. (estimator.pyTier-0 already consumesbuilding_cadastral_numberas a house-match hint, so this is pure net gain — no caller change.)Approach
124_cad_buildings_local.sql): emptycad_buildings_local(cad_num PK, …, geom geometry(Point,4326))+ GIST. Migration does NOT read the FDW (deploy never depends on the gendesign DB being up).TRUNCATE + INSERT … FROM gendesign_cad_buildingsbuildingST_SetSRID(ST_MakePoint(lon,lat),4326)— 36732 rows in 1.47s.ST_DWithin(cb.geom, l.geom, :deg_gate)(GIST-indexable degrees, no geography cast) +geom <-> l.geomKNN order +ST_DistanceSphere(...) <= :threshold_mmetric recheck on the single nearest row.:deg_gateis a degree radius (1.5× safety on the cos(lat)-shrunk longitude axis) that always encloses the metric threshold, so the GIST pre-gate never drops true positives. A geography-castST_DWithinin the WHERE defeats the index (ran 58s+ unfinished); this is ~175× faster.trigger_cadastral_geo_match_run+ dispatchsource='cadastral_geo_match'; seed (125_…sql) withnext_run_at=tomorrow 09:00 UTC (no deploy-time fire).Correctness fix included (caught in review)
The original matcher chunked with
OFFSETover the very predicate it mutates (building_cadastral_number IS NULL). Matched rows drop out, so OFFSET (reset or advanced) skips un-processed rows: once ≥batch_sizeunmatched listings (beyond threshold) pile up at the low-id front, the chunk matches 0 and the loop breaks early, leaving higher-id matchable listings unfilled. Counterexample (batch=3, matchable ids 1,4,7,10): matches 1 & 4, then breaks — missing 7 & 10. The unit tests used single-chunk fixtures so never caught it. Fix: the full UPDATE is ~5s for ~41k listings, so it's now one set-based LATERAL KNN UPDATE over all candidates — correct and fast (batch_sizekept in the signature for schedule-param compat, unused).Prod evidence (read-only; scratch table, rolled back,
listingsuntouched)Index Scan using cad_buildings_local_geom_idx,geom <-> l.geomKNN, GIST used per listing.Tests
test_cadastral_geo_match.py: 18 + 1 optional real-PostGIS (self-skips). Includestest_match_is_single_statement_no_offset(guards the OFFSET regression) + the geometry-gate/DistanceSphere shape + deg-gate-encloses-threshold + psycopg-v3 CAST discipline.pytest -q(2 deselects) → 1931 passed, 1 skipped, 2 deselected. ruff clean.Note
Geo-nearest is approximate (street-level-geocoded listings match the nearest building, not necessarily the exact one) — documented in module/match docstrings + migration comments + logged threshold. Exact cadastral + parcel-containment are deferred (cad_parcels FDW not exposed). Post-merge: migrations auto-apply; nudge
UPDATE scrape_schedules SET next_run_at=now() WHERE source='cadastral_geo_match'to backfill immediately.