fix(estimator): ghost-anchor — confidence='low' when radius analogs empty (#1871) #1920

Merged
bot-backend merged 1 commit from fix/1871-ghost-anchor-confidence-guard into main 2026-06-26 16:07:12 +00:00
Collaborator

#1871 P1 (Ghost-anchor) — fabricated confidence

When the same-building anchor replaces the headline estimate, the code sets confidence = anchor["confidence"] and overwrites n_analogs = anchor["n"] (>0). With zero radius analogs (listings_clean == []) this yielded high/medium confidence on an estimate built only on same-building comps (sometimes a single yandex_valuation, e.g. median 38.4M) — UI rendered «Высокая · 0 объектов» = выдумка-как-факт.

Root: the defensive _enforce_zero_analog_low only fires on n_analogs == 0, but n_analogs was already overwritten to anchor["n"] (>0), so it never caught this path.

Fix: inline guard inside the if anchor is not None: block, right after n_analogs = anchor["n"] (estimator.py:2495) — when listings_clean is empty and confidence isn't already low, downgrade to low, log a warning, and append an honest caveat. Sits before the result is finalized → propagates to the response. +18 lines, nothing else touched.

Test: tests/test_estimator_ghost_anchor_radius_empty_1871.py — tight-spread anchor that would be high + empty radius → asserts low + caveat; caveat appended not replacing; non-empty-radius control NOT downgraded. 56 passed (-k estimator/ghost/anchor/confidence/1871), ruff clean on touched files.

Audit #1871 verification note (this session)

Re-checked 4 core-estimator audit defects vs current code — only ghost-anchor was still real. Already-fixed since 06-22: velocity fallback 4500→750 (adf3ec7), radius-path dedup (rn_dup window + estimate_radius_dedup_enabled=True confirmed in prod + migration 133), avito non-EKB coords (non-issue — ST_DWithin(geom::geography) excludes NULL/out-of-radius).

Refs #1871

## #1871 P1 (Ghost-anchor) — fabricated confidence When the same-building anchor replaces the headline estimate, the code sets `confidence = anchor["confidence"]` and overwrites `n_analogs = anchor["n"]` (>0). With **zero radius analogs** (`listings_clean == []`) this yielded `high`/`medium` confidence on an estimate built only on same-building comps (sometimes a single `yandex_valuation`, e.g. median 38.4M) — UI rendered «Высокая · 0 объектов» = выдумка-как-факт. **Root:** the defensive `_enforce_zero_analog_low` only fires on `n_analogs == 0`, but `n_analogs` was already overwritten to `anchor["n"]` (>0), so it never caught this path. **Fix:** inline guard inside the `if anchor is not None:` block, right after `n_analogs = anchor["n"]` (estimator.py:2495) — when `listings_clean` is empty and confidence isn't already `low`, downgrade to `low`, log a warning, and append an honest caveat. Sits before the result is finalized → propagates to the response. +18 lines, nothing else touched. **Test:** `tests/test_estimator_ghost_anchor_radius_empty_1871.py` — tight-spread anchor that would be `high` + empty radius → asserts `low` + caveat; caveat appended not replacing; non-empty-radius control NOT downgraded. 56 passed (-k estimator/ghost/anchor/confidence/1871), ruff clean on touched files. ## Audit #1871 verification note (this session) Re-checked 4 core-estimator audit defects vs current code — only ghost-anchor was still real. Already-fixed since 06-22: velocity fallback 4500→750 (adf3ec7), radius-path dedup (rn_dup window + `estimate_radius_dedup_enabled`=True confirmed in prod + migration 133), avito non-EKB coords (non-issue — `ST_DWithin(geom::geography)` excludes NULL/out-of-radius). Refs #1871
bot-backend added 1 commit 2026-06-26 16:02:54 +00:00
fix(estimator): ghost-anchor — confidence='low' when radius analogs empty (#1871)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
99736b6e32
#1871 P1 (Ghost-anchor) trust defect: when the same-building anchor
replaces the headline estimate, the code sets confidence = anchor["confidence"]
and overwrites n_analogs = anchor["n"] (>0). With ZERO radius analogs
(listings_clean == []) this yielded confidence='high'/'medium' on an
estimate built only on same-building comps (sometimes a single
yandex_valuation, e.g. median 38.4M) with no comparable nearby listings.

The defensive guard _enforce_zero_analog_low only fires on n_analogs == 0,
but n_analogs was already overwritten to anchor["n"] (>0) — so it never
caught this path. UI rendered "Высокая · 0 объектов" = fabricated confidence.

Fix: inline guard inside the `if anchor is not None:` block, right after
n_analogs = anchor["n"]. When listings_clean is empty and confidence != 'low',
downgrade to 'low', log a warning, and append an honest caveat to explanation.
Sits before _enforce_zero_analog_low and before the result is finalized, so the
downgraded confidence + caveat propagate to the response.

Test: tests/test_estimator_ghost_anchor_radius_empty_1871.py exercises the
full estimate path (via the test_same_building_anchor harness) with a
tight-spread anchor (n=5, would be 'high') and empty radius → asserts
confidence='low' + caveat present; plus a control that a non-empty radius is
not downgraded.
bot-backend merged commit 2fe34ac785 into main 2026-06-26 16:07:12 +00:00
bot-backend deleted branch fix/1871-ghost-anchor-confidence-guard 2026-06-26 16:07:13 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: lekss361/gendesign#1920
No description provided.