fix(site-finder): velocity fallback 4500→750, coverage/proxy disclosure, market_trend staleness honesty (audit #1871 P2) #1876

Merged
bot-backend merged 1 commit from audit/1871-p2-velocity-market into main 2026-06-23 07:15:22 +00:00
Collaborator

Summary

Audit-issue #1871 — три 🟠 P2 honesty/correctness-фикса (market/velocity домен). Все факты подтверждены prod-DB. ETL не трогаем (почему rosreestr встал на янв-2026 — отдельная тяжёлая задача).

Изменения (9 файлов, +376/-78)

Fix 1 — velocity fallback 4500→750 (velocity.py:41)
Audit-verified медиана ЕКБ 593-766 м²/мес; 4500 завышало знаменатель нормализации 7.6× → занижало velocity_score при DB-error fallback (latent). Консервативно 750.

Fix 2 — velocity coverage/proxy disclosure (velocity.py VelocityResult + as_dict)
Новые поля objective_coverage_pct, proxy_used, proxy_sqm_per_deal — видно что объём rosreestr-пути фабрикуется count×45 м²/сделка при покрытии Objective <50%. Проброшено во все 4 конструктора, additive (codegen не нужен — dict[str,Any] в schemas).

Fix 3 — market_trend staleness (parcels.py:2385-2455)
rosreestr_deals MAX(period_start_date)=2026-01-01 (stale 5.5 мес, prod-verified) → recent-окно пусто → раньше голый null. Теперь status ∈ {ok, source_stale, no_deals} + as_of_date + label + recent_deals_count. Consumer scoring читает defensively (без регрессии).

Frontend (types + MarketTrendBlock + VelocityBlock + ptica market-drawer + Section3 verdict + legacy guard): staleness/no_deals → честный caveat вместо фейкового тренда; velocity → caption «объём оценочный (прокси N м²/сделка, покрытие P%)»; formatAsOfMonth TZ-safe.

Honesty contract

Числа не выдумываются — раскрываем proxy/coverage/staleness, не молчим null. Genuine measured data не понижается. velocity 750 — консервативный floor (занижает, не завышает горячесть рынка).

Test plan

  • backend: +3 velocity unit + extended as_dict contract, 17 velocity passed, 12 cross-module
  • frontend: 138 vitest passed, tsc/lint/prettier clean
  • ruff clean; mypy 2 pre-existing unused-ignore (shifted, не введены)
  • code-reviewer APPROVE
  • post-deploy: участок со stale rosreestr → «источник устарел (данные до 01.2026)», не пусто; velocity rosreestr-fallback → caption про proxy

Follow-ups (вне scope)

  • ETL: почему rosreestr_deals встал на 2026-01-01 (внешний лаг Росреестра / скрейпер) — отдельный P2
  • producer-branch тесты source_stale/no_deals когда появится route test harness

Refs #1871

🤖 Generated with Claude Code

## Summary Audit-issue #1871 — три 🟠 P2 honesty/correctness-фикса (market/velocity домен). Все факты подтверждены prod-DB. **ETL не трогаем** (почему rosreestr встал на янв-2026 — отдельная тяжёлая задача). ## Изменения (9 файлов, +376/-78) **Fix 1 — velocity fallback 4500→750** (`velocity.py:41`) Audit-verified медиана ЕКБ 593-766 м²/мес; 4500 завышало знаменатель нормализации 7.6× → занижало velocity_score при DB-error fallback (latent). Консервативно 750. **Fix 2 — velocity coverage/proxy disclosure** (`velocity.py` VelocityResult + as_dict) Новые поля `objective_coverage_pct`, `proxy_used`, `proxy_sqm_per_deal` — видно что объём rosreestr-пути фабрикуется `count×45 м²/сделка` при покрытии Objective <50%. Проброшено во все 4 конструктора, additive (codegen не нужен — `dict[str,Any]` в schemas). **Fix 3 — market_trend staleness** (`parcels.py:2385-2455`) `rosreestr_deals MAX(period_start_date)=2026-01-01` (stale 5.5 мес, prod-verified) → recent-окно пусто → раньше голый `null`. Теперь `status ∈ {ok, source_stale, no_deals}` + `as_of_date` + `label` + `recent_deals_count`. Consumer scoring читает defensively (без регрессии). **Frontend** (types + MarketTrendBlock + VelocityBlock + ptica market-drawer + Section3 verdict + legacy guard): staleness/no_deals → честный caveat вместо фейкового тренда; velocity → caption «объём оценочный (прокси N м²/сделка, покрытие P%)»; `formatAsOfMonth` TZ-safe. ## Honesty contract Числа не выдумываются — раскрываем proxy/coverage/staleness, не молчим null. Genuine measured data не понижается. velocity 750 — консервативный floor (занижает, не завышает горячесть рынка). ## Test plan - [x] backend: +3 velocity unit + extended as_dict contract, 17 velocity passed, 12 cross-module - [x] frontend: 138 vitest passed, tsc/lint/prettier clean - [x] ruff clean; mypy 2 pre-existing unused-ignore (shifted, не введены) - [x] code-reviewer ✅ APPROVE - [ ] post-deploy: участок со stale rosreestr → «источник устарел (данные до 01.2026)», не пусто; velocity rosreestr-fallback → caption про proxy ## Follow-ups (вне scope) - ETL: почему `rosreestr_deals` встал на 2026-01-01 (внешний лаг Росреестра / скрейпер) — отдельный P2 - producer-branch тесты `source_stale`/`no_deals` когда появится route test harness Refs #1871 🤖 Generated with [Claude Code](https://claude.com/claude-code)
bot-backend added 1 commit 2026-06-23 07:04:39 +00:00
fix(site-finder): velocity fallback 4500→750, coverage/proxy disclosure, market_trend staleness honesty (audit #1871 P2)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 1m5s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
CI / backend-tests (pull_request) Successful in 10m9s
adf3ec7c00
Три honesty/correctness-фикса из audit-issue #1871 (🟠 P2). Все факты
подтверждены prod-DB + разведкой. ETL не трогаем (отдельная задача).

velocity.py:
- _EKB_MEDIAN_FALLBACK 4500→750 (audit-verified медиана ЕКБ 593-766;
  4500 завышало знаменатель нормализации 7.6× → занижало velocity_score
  при DB-error fallback, latent). Консервативно 750 (верх диапазона).
- VelocityResult + as_dict() раскрывают: objective_coverage_pct,
  proxy_used, proxy_sqm_per_deal — теперь видно что объём rosreestr-пути
  фабрикуется count×45 м²/сделка при низком покрытии Objective (<50%).
  Проброшено во все 4 конструктора, additive.

parcels.py (market_trend producer):
- Различать «источник устарел» от «нет сделок». rosreestr_deals
  MAX(period_start_date)=2026-01-01 (stale 5.5 мес) → recent-окно пусто →
  раньше голый null. Теперь status ∈ {ok, source_stale, no_deals} +
  as_of_date + label + recent_deals_count. MAX(deal_date) добавлен в CTE.
  Consumer scoring читает recent_deals_count защищённо (без регрессии).

frontend (types + MarketTrendBlock + VelocityBlock + ptica market-drawer
+ Section3 verdict + legacy guard):
- staleness/no_deals → честный caveat вместо фейкового тренда.
- velocity → caption «объём оценочный (прокси N м²/сделка, покрытие P%)».
- formatAsOfMonth парсит YYYY-MM напрямую (TZ-safe).
- Типы MarketTrend/Velocity расширены (optional, backward-compat;
  отсутствие status = "ok").

Тесты: +3 velocity unit + extended as_dict contract. 17 velocity passed,
138 frontend vitest passed.

Refs #1871
bot-backend merged commit c698af68ce into main 2026-06-23 07:15:22 +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#1876
No description provided.