feat(yandex-valuation): link history rows to houses via match_or_create_house #531
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#531
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/tradein-yandex-valuation-house-link"
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?
Summary
_save_yandex_history_itemsresolveshouse_idONCE per page using existingmatch_or_create_house(3-tier: cadastral, fingerprint, geo-proximity, new)house_id,source_confidence,removed_date,total_floors,notescolumns ofhouse_placement_historyhouse_id=NULL+source_confidence=0when match failsWhy
Prior to this PR, 160 rows in
house_placement_historywithsource='yandex_valuation'had ALL ofhouse_id,source_confidence,removed_date,total_floorsas NULL — comment in code admitted "estimator doesn't compute target_house_id yet". This PR wires the existingmatch_or_create_house()infra (was unused for yandex_valuation) and adds the columns now provided by the PR #526 parser.Tests
test_yandex_valuation_save.py+ 11 existingtest_estimator_yandex_integration.py)Followup
Deep Code Review — PR #531 verdict
Summary
ruff formatpass)f04e391matchespulls/531.head.shaScope
Wires the existing 3-tier
match_or_create_house()(cadastr → source_exact → fingerprint → geo-proximity → new) into_save_yandex_history_itemsand persists the 5 columns that PR #526 made available in the parser but_save_yandex_history_itemswas silently dropping:house_id,source_confidence,total_floors,removed_date,notes. Closes the persistence gap flagged in #526 review.Also reformatted call-sites of
db.execute(text(...))in the unchanged_fetch_analogs/_fetch_deals/_get_or_fetch_imv_cachedblocks; no behaviour delta there.Verified correctness (the boring stuff that matters)
UNIQUE (source, ext_item_id)(017_house_placement_history.sql:50) +ON CONFLICT (source, ext_item_id) DO NOTHINGpreserved. Syntheticext_item_id = sha256(address|publish_date|area|floor|prices)[:32]is deterministic — re-runs no-op.ext_idfor house resolution =sha256(strip+lower(address))[:16]— also stable.house_id(017:22),total_floors(017:32),removed_date(017:41),source_confidence(045:11),notes(045:12). No migration needed.CAST(:raw AS jsonb)correct.house_id/total_floors/removed_datebind None → NULL for INSERT VALUES (no CAST needed, that trap is forIS NOT NULLpredicates only).test_save_resolves_house_once_per_page(m.call_count == 1, saved == 2). Correct — avoids hammeringpg_advisory_xact_lock(42, hashtext(fp))N times per page.match_or_create_houseraises →db.rollback()+house_id=None, confidence=0.0, method='fail', history INSERT then proceeds standalone.notesgets None (notmatch_method=fail) per line 524. Testtest_save_handles_match_failure_gracefullyasserts row dict shape correctly.houseswithtradein_normalize_short_addr. PR #531's calls intomatch_or_create_house(viaaddress_fingerprint→normalize_address) hit a populatedhouse_address_aliasestable → most yandex pages will resolve via Tier 2 (fingerprint, conf=0.9) immediately. Step #5 of migration 063 left 160 yandex rows withhouse_id=NULLbecause raw_payload had no address — bulk re-run (followup PR #3 per description) backfills them through this new path.matching/houses.pyat head_sha (per memory rule feedback_verify_dependency_contract_in_review) —match_or_create_housedoes raise on real DB errors (db.execute(...).one()will raiseNoResultFound/IntegrityError). It is NOT a silent-swallow-and-return-default; the PR's try/except is needed and correct.Findings
Cosmetic (non-blocking)
estimator.py:793-794— stale log:"(house_id=NULL — matching deferred)"is now wrong on every successful match. INFO-level prod log scrape will show misleading message. Suggested next-PR fix:Quality enhancement (next iteration)
_save_yandex_history_itemscallsmatch_or_create_housewith nolat/lon.geo.lat/geo.lonare available at the call site inestimate_quality(Stage 8 runs after geocoding succeeded). Without coords, Tier 3 geo-proximity (30 m PostGIS) is skipped, and the new-house INSERT lands withlat=NULL, lon=NULL(so thehouses_set_geom_trgtrigger leavesgeomNULL too). Recommend threadinggeointo the call: Requires passinggeothrough_save_yandex_history_itemssignature. Not a blocker for this PR — the bootstrap from #527 covers most EKB houses via fingerprint Tier 2, but Tier 3 is the safety net for off-by-one address normalizations.Worth tracking
db.rollback()inside the inner try/except only clobbers uncommitted state in the request-scoped Session. Upstream_get_or_fetch_yandex_valuation_cacheddoes its owndb.commit()on line 433 before returning, so the external_valuations cache write is durable. IMV cache writes viasave_imv_evaluationhappen even further upstream — also safe assuming they commit (would need a separate verification pass onsave_imv_evaluationif that flow is ever changed). For this PR's scope: tx boundaries are fine.houses.addressviacadastral_number(Tier 0) we'd get 1.0, but Yandex pages don't carry cadastr → Tier 0 always skipped. Acceptable.Tests
test_yandex_valuation_save.py) cover: once-per-page resolution, row dict house_id/confidence/total_floors/notes propagation, removed_date populated for sold listing, removed_date None for active listing, graceful match failure (saved=N, house_id=None, notes=None, confidence=0.0), kwargs forwarding (address/year_built/ext_source).test_estimator_yandex_integration.py) updated withpatch("app.services.estimator.match_or_create_house")to keep them deterministic. No integration coverage was lost.Security
git --no-verifytraces in recent log.Vault cross-check
Cross_Source_Matching_Strategy.md(referenced inmatching/houses.pydocstring) describes the 3-tier algorithm andsource_confidencesemantics from sec 14.1 — PR honors that contract (1.0 cadastr/new, 0.9 fp, 0.7 geo, 0.0 fail). 045_house_placement_history_extend.sql:18-22 also documents these confidence values.Schema_Avito_Houses_Listings_History.mdsec 6:house_idis FK, NULL allowed for orphan-rows, comment-documented as "Backfill через UPDATE … при первом scrape дома" — this PR is the realtime-scrape version of that backfill, complementary not contradictory.Verdict reasoning
This is a focused, well-tested change that closes a concrete persistence gap. Code reuses existing infra (
match_or_create_house) rather than reinventing matching logic, transaction boundaries are sound, all schema columns exist, idempotency is preserved, psycopg v3 conventions followed. The two findings (stale log, missing lat/lon) are low-priority and orthogonal — fine as separate followups.APPROVE — squash-merge + delete branch.