fix(tradein/estimator): analogs cards match n_analogs, no cross-source dups #2518
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#2518
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "fix/tradein-analogs-count-dedup"
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
Display-layer fix for two bugs surfaced by pre-launch smoke (golden-path address "Екатеринбург, улица Малышева, 51"):
Card count != claimed n_analogs.
n_analogs(same-building anchor path) was computed from the post-MAD-clip surviving comp population, but the displayedanalogscards were built from the ORIGINAL, un-clippedanchor_compslist. A MAD-clip-excluded price outlier (e.g. a cross-source duplicate priced ~4.5x above the rest) never affected the median/n_analogs, yet still showed up as a card. Fix:_compute_same_building_anchornow returns the final survivingcompslist (len == n), and the caller setsanchor_comps_used = anchor["comps"]instead of the raw pre-clipanchor_comps.Cross-source duplicate of the same physical unit shown twice. The statistical cross-source dedup (
_dedup_cross_source) requires the sameprice_bucket(roundedprice_rub) to consider two listings duplicates. Real cross-posts of the same physical unit almost always have a small asking-price drift between platforms, which can straddle a bucket boundary and let the duplicate survive into both the price population and the UI. Fix: a new display-only dedup pass (_dedup_display_lots/_union_find_phys_dedup) uses the same physical key (cadnum-or-street + floor + area_bucket) but WITHOUTprice_bucket, applied only when building the displayedanalogs_lots— never touchesn_analogs/median/cv. To avoid over-merging genuinely distinct same-building units that happen to share floor/area, it requires the two lots to come from DIFFERENT sources (require_diff_sourceguard) — same-source pairs are left untouched.The radius-path (non-anchor) branch had a related count-consistency gap:
n_analogscounted only PRICEDlistings_cleanentries, while the displayed cards were sliced from the full (unfiltered)listings_clean, occasionally letting price-less entries inflate the card count beyondn_analogs. Cards are now built from the same priced subset.Backtest / frozen gate result
tests/test_backtest_regression_gate.py(hermetic replay of committed frozen fixture) is byte-identical after this change — zero delta on median/expected_sold/coverage/calibration (rel_tol=1e-6). Confirms the fix is display-only and does not touch pricing. Livescripts/backtest_estimator.pyagainst prod DB was not run in this sandboxed session (no DB credentials available here) — the hermetic gate is the authoritative frozen-metric check per the codebase's own test docstring; happy to run the live variant on request.Test plan
pytest tests/ -k "estimator or backtest"— 548 passedpytest tests/(full suite) — 2526 passed, 6 skipped, 1 pre-existing unrelated failure (test_search_api.py::test_search_cache_hit, confirmed failing identically onmainbefore this change — auth/env issue, unrelated to estimator.py)tests/test_backtest_regression_gate.py— 2 passed, byte-identical to frozen baselinetests/test_estimator_analogs_display_consistency.py(6 tests): full-pipeline regression for both root causes (outlier duplicate pair fully excluded from both count and cards; non-outlier duplicate pair collapses in cards only, honestly documentinglen(analogs) <= n_analogswhen display-only dedup catches a residual cross-source dup the stat dedup missed) + unit tests on_dedup_display_lotstests/test_estimator_quarter_index.py's hand-rolled_compute_same_building_anchortest double to match the new"comps"contractruff checkclean on all changed files