fix(tradein/estimator): ghost-anchor invariant guard — n_analogs=0 forces low (audit #1871 P1.2) #1874

Merged
bot-backend merged 1 commit from audit/1871-p1-2-ghost-anchor-guard into main 2026-06-23 06:06:47 +00:00
Collaborator

Summary

Audit-issue #1871 пункт P1.2 — Ghost-anchor: confidence='high' при n_analogs=0.

Факт по prod-DB (tradein): ghost 5fcc1e99 (n=0, high, 38.45M ₽, yandex_valuation) — единственная legacy-строка от 2026-05-30, давно expired (TTL оценок = 1 день; все 66 high/medium-с-n<5 строк expired). Текущий live-код уже честен (_compute_confidence → low при median=0; #755 cap n<5→не high). Живого инцидента нет — фикс делает инвариант явным и вечным против будущих external-valuation-blend / rehydrate / migration путей.

Изменения (4 файла, +173/-6)

  • Backend estimator.py: helper _enforce_zero_analog_low форсит low + caveat когда n_analogs==0 and confidence!='low'. Идемпотентен, не трогает high+N>0, реальный median сохраняется (no fake), logger.warning для observability. Вызов перед сборкой AggregatedEstimate в estimate_quality (единственный уязвимый return; _empty_estimate хардкодит low). +5 unit-тестов.
  • Frontend HeroSummary.tsx / HeroTransparency.tsx / history/page.tsx: effectiveConfidence = n_analogs===0 ? 'low' : confidence — defensive depth. Caveat из backend confidence_explanation (не хардкод). Type-safe.

Honesty contract

Реальный median внешнего сервиса НЕ подменяется фейком — только понижается confidence + добавляется caveat «оценка без сопоставимых аналогов рядом». Никаких выдуманных коэффициентов.

Test plan

  • backend: 5 новых unit (high+0→low+caveat, medium+0→low, low+0 идемпотент, high+N unchanged, None-explanation), 326 passed без регрессий
  • ruff clean, tsc clean (3 ошибки — stale .next кэш, pre-existing)
  • code-reviewer APPROVE
  • CI deploy-tradein.yml pytest-gate зелёный
  • post-deploy: оценка с n_analogs=0 показывает «низкая» + caveat

Deploy

Триггерит deploy-tradein.yml (отдельный pipeline на tradein-mvp/**), не основной gendesign.

Refs #1871

🤖 Generated with Claude Code

## Summary Audit-issue #1871 пункт **P1.2 — Ghost-anchor: `confidence='high'` при `n_analogs=0`**. **Факт по prod-DB (`tradein`):** ghost `5fcc1e99` (n=0, high, 38.45M ₽, `yandex_valuation`) — единственная legacy-строка от 2026-05-30, **давно expired** (TTL оценок = 1 день; все 66 `high/medium`-с-`n<5` строк expired). Текущий live-код уже честен (`_compute_confidence` → low при median=0; `#755 cap` n<5→не high). **Живого инцидента нет** — фикс делает инвариант явным и вечным против будущих external-valuation-blend / rehydrate / migration путей. ## Изменения (4 файла, +173/-6) - **Backend** `estimator.py`: helper `_enforce_zero_analog_low` форсит `low` + caveat когда `n_analogs==0 and confidence!='low'`. Идемпотентен, не трогает `high+N>0`, реальный median сохраняется (no fake), `logger.warning` для observability. Вызов перед сборкой `AggregatedEstimate` в `estimate_quality` (единственный уязвимый return; `_empty_estimate` хардкодит low). **+5 unit-тестов.** - **Frontend** `HeroSummary.tsx` / `HeroTransparency.tsx` / `history/page.tsx`: `effectiveConfidence = n_analogs===0 ? 'low' : confidence` — defensive depth. Caveat из backend `confidence_explanation` (не хардкод). Type-safe. ## Honesty contract Реальный median внешнего сервиса НЕ подменяется фейком — только понижается confidence + добавляется caveat «оценка без сопоставимых аналогов рядом». Никаких выдуманных коэффициентов. ## Test plan - [x] backend: 5 новых unit (high+0→low+caveat, medium+0→low, low+0 идемпотент, high+N unchanged, None-explanation), 326 passed без регрессий - [x] ruff clean, tsc clean (3 ошибки — stale `.next` кэш, pre-existing) - [x] code-reviewer ✅ APPROVE - [ ] CI `deploy-tradein.yml` pytest-gate зелёный - [ ] post-deploy: оценка с n_analogs=0 показывает «низкая» + caveat ## Deploy Триггерит **`deploy-tradein.yml`** (отдельный pipeline на `tradein-mvp/**`), не основной gendesign. Refs #1871 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 1 commit 2026-06-23 06:06:03 +00:00
fix(tradein/estimator): ghost-anchor invariant guard — n_analogs=0 forces low (audit #1871 P1.2)
All checks were successful
CI / changes (pull_request) Successful in 7s
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
41e9c820c9
Audit #1871 P1.2: estimate 5fcc1e99 показывал confidence='high' при
n_analogs=0 (median 38.45M ₽, sources=['yandex_valuation']) при реальной
истории листингов дома 4.2-6.5M. По prod-DB это ЕДИНСТВЕННАЯ legacy-строка
от 2026-05-30, давно expired (TTL оценок = 1 день, все 66 high/medium-c-n<5
строк expired). Текущий mainline уже честен. Фикс — defensive invariant,
чтобы ghost не мог родиться снова из будущих external-valuation-blend /
rehydrate / migration путей.

Backend (estimator.py):
- helper _enforce_zero_analog_low(confidence, n_analogs, explanation, ...)
  форсит 'low' + caveat когда n_analogs==0 and confidence!='low'.
  Идемпотентен (low+0 → low без дубля caveat), не трогает high+N>0, реальный
  median внешнего сервиса сохраняется (fake-коэффициенты не вводятся),
  logger.warning для observability.
- Вызов перед сборкой AggregatedEstimate в estimate_quality (единственный
  уязвимый return-путь; _empty_estimate хардкодит low — структурно безопасен).
- +5 unit-тестов (high+0→low+caveat, medium+0→low, low+0 идемпотент,
  high+N unchanged, None-explanation).

Frontend (HeroSummary, HeroTransparency, history):
- effectiveConfidence = n_analogs===0 ? 'low' : confidence — defensive depth
  на случай legacy-rehydrate / контракт-drift. Caveat-текст из backend
  confidence_explanation (не хардкод). Type-safe (ConfidenceLevel).

Refs #1871
bot-backend merged commit dfa1e27dab into main 2026-06-23 06:06:47 +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#1874
No description provided.