fix(tradein/estimator): analogs cards match n_analogs, no cross-source dups #2518
Merged
lekss361
merged 1 commit from 2026-07-13 20:19:21 +00:00
fix/tradein-analogs-count-dedup into main
1 commit
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
51c79f9324 |
fix(tradein/estimator): analogs cards match n_analogs, no cross-source dups
All checks were successful
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 55s
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
Two display-layer bugs surfaced by pre-launch smoke (golden-path address "Екатеринбург, улица Малышева, 51"): 1. n_analogs (same-building anchor path) was computed from the post-MAD-clip comp population (_compute_same_building_anchor: n = len(surviving ppm2)), but the displayed analogs cards were built from the ORIGINAL, un-clipped anchor_comps list. 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_anchor now returns the final surviving "comps" list (len == n), and the caller sets anchor_comps_used = anchor["comps"] instead of the raw pre-clip anchor_comps. 2. The statistical cross-source dedup (_dedup_cross_source) requires the same price_bucket (rounded price_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 WITHOUT price_bucket, applied only when building the displayed analogs_lots — never touches n_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_source guard) — same-source pairs are left untouched. The radius-path (non-anchor) branch had a related count-consistency gap: n_analogs counted only PRICED listings_clean entries, while the displayed cards were sliced from the full (unfiltered) listings_clean, occasionally letting price-less entries inflate the card count beyond n_analogs. Cards are now built from the same priced subset. Frozen backtest regression gate (tests/test_backtest_regression_gate.py, hermetic replay of committed fixture) is byte-identical after this change — zero delta on median/expected_sold/coverage/calibration, confirming this is display-only and does not touch pricing. Added tests/test_estimator_analogs_display_consistency.py: 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 documenting len(analogs) <= n_analogs when the display-only dedup catches a residual cross-source dup the stat dedup missed) + unit tests on _dedup_display_lots. Updated test_estimator_quarter_index.py's hand-rolled _compute_same_building_anchor test double to match the new "comps" contract. |