Commit graph

1639 commits

Author SHA1 Message Date
adf3ec7c00 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
Три 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
2026-06-23 12:04:08 +05:00
85d91375dc Merge pull request 'fix(ptica): honest isReal flags — placeholder stubs no longer marked real (audit #1871 P1.3)' (#1875) from audit/1871-p1-3-isreal-stubs into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m12s
Deploy / deploy (push) Successful in 1m29s
2026-06-23 06:27:59 +00:00
389bdd3306 test(ptica): use tracked mock fixtures instead of git-excluded __preview (CI fix)
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / frontend-tests (pull_request) Successful in 54s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m54s
CI frontend-tests упал: honesty-тест импортировал фикстуры из
src/app/%5F%5Fpreview/ptica/__fixtures__/ — эта dev-only директория в
.git/info/exclude, поэтому отсутствует в Linux CI checkout (vite:import-analysis
'Failed to resolve import'). Локально (macOS) резолвилось т.к. файлы на диске.

Переключил импорт на tracked src/lib/mocks/parcel-{forecast,analyze}.json
(NEXT_PUBLIC_USE_MOCKS source, в git) — report.exec_summary.key_numbers.
deficit_index присутствует, shape совместим. 138/138 vitest passed.

Refs #1871
2026-06-23 11:25:33 +05:00
2303207629 fix(ptica): honest isReal flags — placeholder stubs no longer marked real (audit #1871 P1.3)
Some checks failed
CI / frontend-tests (pull_request) Failing after 53s
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m52s
Adapter — единственный источник истины real-vs-placeholder; стаб с isReal:true
рисуется как живое измерение (обычная типографика «—», неотличимо от данных).
Helpers real()/placeholder()/notReal() корректны — баг в их misuse на 5 местах.

ptica-adapt.ts:
- :163 encumbrances (passport-card): {value:PLACEHOLDER, isReal:true} → notReal("источник ЕГРН")
- :272 buySignalFromForecast при deficit_index==null: {value:"наблюдать", isReal:true}
  → notReal("прогноз недоступен (Сценарии §22)") — отличает «прогноза нет» от
  «прогноз говорит наблюдать». di-present advisory-ветка (real) не тронута.
- :1942 adaptBuySignalGauge при di==null: убран synthetic gaugeValue=50 как real;
  ранний if(di==null) зеркалит no-report ветку (value:null, isReal:false, available:false).
- :681 adaptPassportDrawer registry, :1048 adaptLegalDrawer registry: те же
  «Обременения» стабы {value:DASH, isReal:true} → notReal (найдены code-review,
  recon их пропустил — drawer-зеркала card-фикса).

ptica.module.css: .kvPlaceholder += opacity:0.7 — усиленный визуальный mute стаба.

Корректные места НЕ тронуты (recon-verified): market demand-label (условный
real/placeholder), engineering utilities, InvestScore potential.

+5 unit-тестов (ptica-adapt.honesty.test.ts): encumbrances stub, buy-signal di=null,
buy-signal real di (защита от over-downgrade), gauge di=null, оба drawer-обременения.

Refs #1871
2026-06-23 11:20:48 +05:00
dfa1e27dab Merge pull request 'fix(tradein/estimator): ghost-anchor invariant guard — n_analogs=0 forces low (audit #1871 P1.2)' (#1874) from audit/1871-p1-2-ghost-anchor-guard into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / test (push) Successful in 1m27s
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 57s
Deploy Trade-In / build-frontend (push) Successful in 1m55s
Deploy Trade-In / deploy (push) Successful in 52s
2026-06-23 06:06:46 +00:00
41e9c820c9 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
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
2026-06-23 11:05:34 +05:00
fd107aeadb Merge pull request 'fix(forecasting): honest scenarios collapse when β rate-sensitivity gate fails (audit #1871 P1)' (#1873) from audit/1871-p1-scenarios-collapse into main
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Successful in 2m14s
Deploy / deploy (push) Successful in 1m36s
Deploy / build-frontend (push) Successful in 3m57s
Deploy / build-worker (push) Successful in 4m10s
2026-06-22 14:17:32 +00:00
dcccebf2c2 fix(forecasting): honest scenarios collapse when β rate-sensitivity gate fails (audit #1871 P1)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 1m17s
CI / openapi-codegen-check (pull_request) Successful in 2m11s
CI / backend-tests (pull_request) Successful in 10m19s
Когда demand_normalization honesty-gate отбивает β rate-sensitivity
(`sensitivity.confidence=='low'`, ЕКБ-регрессия не проходит n≥30/R²≥0.1/slope<0),
все 393 прод-отчёта получают coefficient=1.0 для всех трёх сценариев
conservative/base/aggressive. По дизайну backend честно деградирует, но фронт
рисует 3 разноцветные карточки/линии одинаковых чисел без caveat.

Backend (scenarios.py, report.py, report_assembler.py, orchestrator.py):
- compute_scenarios теперь возвращает (list, collapsed: bool, reason: str|None)
- _detect_collapsed(): math.isclose(rel_tol=1e-9, abs_tol=1e-6) сравнивает
  projected_demand_units И deficit_index на всех горизонтах между
  conservative и aggressive — расхождение в любой метрике на любом h → False
- _COLLAPSE_REASON_LOW_BETA — единственный источник истины каноничного
  русского предложения
- ReportScenarios получает поля scenarios_collapsed + scenarios_collapse_reason
- demand_normalization.py НЕ ТРОНУТ — поведение корректно по контракту

Backend экспортёры (report_pdf.py, excel.py):
- PDF: при collapse — тёмная плашка-параграф вместо таблицы трёх столбцов
- Excel: ОДНА строка «base» + caveat-ячейка вместо трёх идентичных столбцов

Frontend (forecast.ts, ScenariosBlock, ScenarioCards, ScenarioCompareTable,
ForecastChart, Section6Forecast, ptica.module.css):
- Тип ReportScenarios расширен двумя optional-полями
- ScenariosBlock (light): headline-bar + одна grid-карточка base + caveat
- ScenarioCards (ptica dark): sub-component ScenarioBaseCard, одна карточка
  + collapse-note, ScenarioCompareTable return null
- ForecastChart: effectiveScenarios filter — одна линия viz-1 «Базовый»
  вместо трёх перекрывающихся
- Section6Forecast: caveat выше графика, читается ТОЛЬКО из
  scenarios_collapse_reason (источник истины — backend constant)

Тесты: +18 новых (TestMetricsEqual×6, TestDetectCollapsed×7,
TestComputeScenariosCollapseDetection×4 + 1 проверка graceful + corner).
102 forecasting passed, 88 cross-module passed.

Refs #1871
2026-06-22 19:06:22 +05:00
06929e94b3 Merge pull request 'revert: remove Site Finder v2 cockpit (PR #1866/#1868/#1870)' (#1872) from revert/site-finder-v2 into main
Some checks failed
Deploy / build-backend (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m3s
Deploy / deploy (push) Failing after 6s
Deploy / changes (push) Successful in 7s
Deploy / build-worker (push) Has been skipped
2026-06-22 10:35:39 +00:00
470c6b1f32 revert: remove Site Finder v2 cockpit (PR #1866/#1868/#1870)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m49s
CI / frontend-tests (pull_request) Successful in 56s
Полный откат «Site Finder v2» с прода — пилот-беты редизайна
кокпита ПТИЦА. Удалён роут /site-finder/analysis/{cad}/v2, все
компоненты ptica-v2/* (18 файлов), беta-ссылка из AnalysisPageContent.

Сохраняется как было: старый Section1-6 на /site-finder/analysis/{cad},
PticaMapInner и старый /ptica-роут (через 301 на parent), ptica-adapt,
mocks — всё нетронуто. Прод-URL и UX не меняются для пилотов.

Старый ptica-кокпит, на основе которого делался v2, был забракован
визуально, новая попытка тоже не дошла до 1:1 с эталоном. Откат
к стабильному состоянию.
2026-06-22 15:33:25 +05:00
e09acec233 Merge pull request 'fix(site-finder/v2): remove resource legend overlay (dup of ИНЖЕНЕРИЯ card + POI overlap)' (#1870) from fix/v2-remove-resource-legend into main
All checks were successful
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / deploy (push) Successful in 1m21s
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Successful in 3m28s
2026-06-22 06:51:27 +00:00
49a84ba9f6 fix(site-finder/v2): remove resource legend overlay (overlapped POI legend)
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m0s
CI / changes (pull_request) Successful in 7s
CI / openapi-codegen-check (pull_request) Successful in 2m2s
PR #1868 добавил ресурсную легенду (электр/вода/канал/тепло/газ) поверх
карты, но PticaMapInner уже отрисовывает свою POI/категорийную легенду
top-left высотой ~215px. PR #1869 попытался разнести их (top → bottom-left)
— безуспешно: моя легенда всё равно попадала в нижний хвост POI-overlay.

Прагматичный исход: ресурсная легенда ДУБЛИРУЕТ данные карточки ИНЖЕНЕРИЯ
(те же расстояния и статусы). Убираю её совсем и оставляю per-theme заголовок
карты (тот фикс был полезный и не конфликтует) — POI-overlay PticaMapInner
несёт уникальные счётчики POI, ради которых карта и нужна.

Удалены: V2MapLegend.tsx и блок CSS-правил .mapLegend* в v2.module.css.
2026-06-22 11:48:55 +05:00
878200934a Merge pull request 'fix(site-finder/v2): move resource legend to bottom-left (no overlap with POI legend)' (#1869) from fix/v2-map-legend-position into main
All checks were successful
Deploy / changes (push) Successful in 8s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m15s
Deploy / deploy (push) Successful in 1m12s
2026-06-22 06:39:35 +00:00
2bdbb681b9 fix(site-finder/v2): move resource legend to bottom-left
All checks were successful
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m49s
CI / frontend-tests (pull_request) Successful in 53s
PR #1868 поместил ресурсную легенду в top-left карты, но там уже живёт
встроенная POI-категорийная легенда от PticaMapInner (ЖК/Школы/Метро…) —
они накладывались и были нечитаемы. Перенос ресурсной легенды в bottom-left
(над Leaflet-attribution) разводит две легенды по разным углам.
2026-06-22 11:37:10 +05:00
161d5cb112 Merge pull request 'feat(site-finder/v2): resource legend + light theme polish' (#1868) from feat/site-finder-v2-map-light-polish into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / deploy (push) Successful in 1m14s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m13s
2026-06-22 06:28:58 +00:00
c3e091c811 feat(site-finder/v2): resource legend overlay + light theme polish
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 52s
CI / openapi-codegen-check (pull_request) Successful in 1m48s
— V2MapLegend overlay над cockpit-картой (электр/вода/канал/тепло/газ)
  с nearest_m + статус-точка, источник utilities.summary (subtype-buckets
  зеркалят backend _nearest() aliases; electric расширен power_line, gas
  включает legacy generic pipeline).
— Per-theme заголовок карты: dark «СПУТНИК · СВЕЖИЙ СНИМОК», light
  «СПУТНИК · СХЕМА ПОДКЛЮЧЕНИЙ» (toggle через CSS [data-theme], без JS).
— Точечный light-theme contrast в v2.module.css: kv/eng значения,
  drawer eyebrow, .more / .ver / активная вкладка с cyan→accent-blue,
  status-dot halo off на белом. Dark не тронут.

Scope: только ptica-v2/* и v2.module.css. PticaMapInner (шарится со
старым ptica) не тронут.
2026-06-22 11:26:13 +05:00
ec7a710ca9 Merge pull request 'revert(site-finder): откат тёмной /site-finder обратно на светлую' (#1867) from rollback/site-finder-home-light into main
All checks were successful
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / changes (push) Successful in 6s
Deploy / build-frontend (push) Successful in 3m15s
Deploy / deploy (push) Successful in 1m22s
2026-06-22 06:11:33 +00:00
7cb008c551 revert(site-finder): rollback dark entry-landing skin to light
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / changes (pull_request) Successful in 6s
CI / openapi-codegen-check (pull_request) Successful in 1m55s
The ПТИЦА dark "operator terminal" skin was applied to the main
/site-finder map page in b3fd053 ("dark Site Finder"), but the
2026-06-21 rollback (#1865) only reverted the analysis route — the
entry-landing stayed dark.

Точечно откатывает 8 файлов entry-landing (page.tsx + 7 entry-компонентов)
до состояния b3fd053~1 и удаляет осиротевший site-finder.module.css.

НЕ трогает: PticaMapInner / ptica.module.css (используются и старым ptica,
и Site Finder v2 cockpit) — карта аналитики остаётся glyph-pin как была.
2026-06-22 11:08:44 +05:00
e16bad3ed6 Merge pull request 'feat(site-finder): Site Finder v2 cockpit route (ПТИЦА v2)' (#1866) from feat/site-finder-v2-cockpit into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-frontend (push) Successful in 3m15s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / deploy (push) Successful in 1m12s
2026-06-21 17:32:38 +00:00
eb7290dcf0 feat(site-finder): add Site Finder v2 cockpit route (ПТИЦА v2)
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 53s
CI / changes (pull_request) Successful in 5s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
Port the approved ptica-v2 prototype to a new non-destructive route
/site-finder/analysis/{cad}/v2 — dark/light cockpit shell, real Leaflet
map (reuses PticaMapInner), 6 scan cards, lower/bottom grids, 11 drawers
and grounded chat. All values flow through the existing ptica-adapt layer
(real /analyze data or honest «—»). One beta link added on the analysis page.
2026-06-21 22:29:18 +05:00
520fa55f2b Merge pull request 'revert(ptica): старый Section1-6 дизайн как дефолтный роут' (#1865) from rollback/sf-old-analysis-design into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m4s
Deploy / deploy (push) Successful in 1m14s
2026-06-21 13:42:58 +00:00
aba4e61be2 revert(ptica): restore old Section1-6 analysis view as default route
All checks were successful
CI / changes (pull_request) Successful in 5s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m50s
The ПТИЦА dark cockpit (made default in ba05a4c) is being redesigned from
scratch. Roll the live /site-finder/analysis/[cad] route back to the original
AnalysisPageContent (Section1-6) so pilots/investor see the stable design while
the new cockpit is reworked.

Non-destructive: only the route's rendered component + metadata/fallback are
reverted. All ptica code stays in the repo and the cockpit remains viewable at
/__preview/ptica-full for redesign reference.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 18:39:49 +05:00
2318f0906a Merge pull request 'feat(ptica): Сценарии 6.6 + Отчёты «Параметры» (tab prototype-parity)' (#1864) from feat/ptica-tabs-6-6-reports-params into main
All checks were successful
Deploy / build-frontend (push) Successful in 3m18s
Deploy / deploy (push) Successful in 1m13s
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
2026-06-21 06:34:17 +00:00
e8d0863996 feat(ptica): close tab prototype-parity gaps (Сценарии 6.6 + Отчёты Параметры)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m54s
- Сценарии: add section 6.6 «будущее предложение и конкуренты» (FutureSupply)
  rendering report.future_market.future_competitors after ScoringTransparency
  (order 6.1→6.6). Columns ЖК/Класс/Квартир/Дистанция/Поглощение, real data,
  honest empty-state. Reuses .dtable.
- Отчёты: add «Параметры» panel (карта/финмодель/прогноз §22); §22 row tied to
  the real forecast-ready flag. Reuses StatusRow.
Verified on the all-tabs harness; no console errors. tokens-only, no fabrication.
2026-06-21 11:34:11 +05:00
4a2ed52cb7 Merge pull request 'fix(estimator): low-findings аудита «Мера» — cian sanity, deal floor-edge, price_trend dedup, DEAL_MAX verify' (#1863) from fix/estimator-low-findings into main
All checks were successful
Deploy Trade-In / deploy (push) Successful in 46s
Deploy Trade-In / changes (push) Successful in 9s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / test (push) Successful in 1m19s
Deploy Trade-In / build-backend (push) Successful in 1m0s
Reviewed-on: #1863
2026-06-21 06:16:34 +00:00
92725c6122 fix(tradein): Mera-audit fix-4 — верифицировать DEAL_MAX_PPM2 vs prod данных
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
Данные на 2026-06-21 (prod deals table, 49 791 записей):
  p90=172 419 ₽/м², p95=199 583 ₽/м², p99=278 681 ₽/м², p99.9=500 000 ₽/м²
  deals >800k = 13 штук (0.026%) — нерыночные outlier'ы, не легитимный премиум ЕКБ.
  listings p99 (is_active, <800k фильтр) = 392 795 ₽/м².

Вывод: DEAL_MAX_PPM2=800_000 НЕ срезает легитимный ЕКБ-премиум (p99.9 сделок=500k).
Значение NOT изменено — задокументировано в комментарии рядом с константой
для обоснования порога и последующей ревалидации при росте рынка.
2026-06-21 09:06:36 +03:00
af1d882745 fix(tradein): Mera-audit fix-3 — cross-source dedup в _fetch_price_trend
Один объект на avito_imv + yandex_valuation с разными ext_item_id создаёт
дубликаты в house_placement_history и double-count'ится в помесячной медиане.
Решение: CTE с DISTINCT ON (round(area_m2), floor, price, price_date) с
приоритетом avito_imv через CASE sort. Легитимные разные квартиры (разные
площадь/этаж/цена) не затрагиваются.

За флагом estimate_price_trend_dedup_enabled (дефолт True в settings).
False → точно старое поведение (backward-compat). Graceful: без изменений
в логике min_points и возврата None при недостатке данных.

Тесты: 6 новых тест-кейсов в test_estimator_price_trend_dedup.py (SQL содержит
DISTINCT ON, flag OFF не содержит, Source1 preferred, данные возвращаются корректно).
2026-06-21 09:05:57 +03:00
81ee864d80 fix(tradein): Mera-audit fix-2 — _is_plausible_deal floor edge + area/price guards
Ужесточает валидацию ДКП-сделок в _is_plausible_deal:
- floor < 1 или floor=0 → drop (floor=-5/0/999 из битого парсера)
- area_m2 задана и <= 0 → drop (нулевая/отрицательная площадь из битого парсера)
- price_rub задана и <= 0 → drop (нерыночная/техническая сделка)
- floor=None → допустимо (graceful, нечем судить)

Все новые параметры area_m2/price_rub keyword-only с дефолтом None →
backward-compatible для существующих вызовов. Caller _fetch_deals обновлён
для передачи area_m2/price_rub из данных сделки.

Тесты: 16 новых тест-кейсов в test_deals_sanitize.py (floor=-5, floor=999,
floor=None ok, area_m2=0 drop, price_rub=-1 drop и пр.).
2026-06-21 09:04:54 +03:00
50a72f030a fix(tradein): Mera-audit fix-1 — Cian valuation price sanity bounds
Добавляет _is_price_sane() в cian_valuation.py: если sale_price_rub вне
[cian_valuation_min_rub, cian_valuation_max_rub] (дефолты 500k/500M),
или low_price > high_price, или любой bound отрицательный — результат
не кэшируется и возвращается None (graceful). Защита от garbage-ответов
API (999_999 < min, 9_999_999_999 > max).

Новые settings: cian_valuation_min_rub=500_000, cian_valuation_max_rub=500_000_000.
Тесты: 11 новых тест-кейсов в test_cian_valuation.py. Пофиксен pre-existing
KeyError в test_cache_hit_returns_cached (missing low_price/high_price в mock).
2026-06-21 09:02:32 +03:00
810bd056db Merge pull request 'fix(estimator): robustness — ratio-tier по финалу, radius-floor, yandex area, expected_sold ≤ asking' (#1859) from fix/estimator-robustness-medium3 into main
All checks were successful
Deploy Trade-In / changes (push) Successful in 10s
Deploy Trade-In / build-frontend (push) Has been skipped
Deploy Trade-In / build-browser (push) Has been skipped
Deploy Trade-In / build-backend (push) Successful in 53s
Deploy Trade-In / deploy (push) Successful in 58s
Deploy Trade-In / test (push) Successful in 1m21s
Reviewed-on: #1859
2026-06-21 05:45:57 +00:00
4f90334ffd Merge pull request 'feat(ptica): ВРИ row in Градостроительство (real ЕГРН)' (#1862) from fix/ptica-vri-row into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m20s
Deploy / deploy (push) Successful in 1m8s
2026-06-20 23:09:41 +00:00
28b853bcdf feat(ptica): add ВРИ row to Градостроительство (real ЕГРН permitted_use)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m4s
CI / openapi-codegen-check (pull_request) Successful in 2m4s
Prototype's first ГРАДОСТРОИТЕЛЬСТВО row is ВРИ — prod lacked it. Add it from
the real egrn.permitted_use_text (e.g. «многоквартирные дома»); honest «нет
данных ЕГРН» fallback. Value wraps cleanly in the narrow card (verified, no
overflow).
2026-06-21 04:09:36 +05:00
701e8e9fe7 Merge pull request 'feat(ptica): map glyph pins + compass, equal-height grids, full-height rail; dark Site Finder' (#1861) from feat/ptica-map-glyphs-sitefinder-dark into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m17s
Deploy / deploy (push) Successful in 1m8s
2026-06-20 22:41:18 +00:00
b3fd053e3d feat(ptica): map glyph pins + compass, equal-height grids, full-height rail; dark Site Finder
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 55s
CI / openapi-codegen-check (pull_request) Successful in 1m58s
ПТИЦА cockpit:
- map: POI/competitors/connections now render as glyph divIcon pins (per-category
  glyph + color) with dashed connection lines + a compass — matches the prototype
  (was plain CircleMarker dots). Legend/zoom/scale/base-toggle/tools kept.
- lower+bottom grids: align-items stretch → equal-height rows; cards are flex
  columns with footer «Подробнее» pinned to bottom and sparse empty-states
  centred (fixes «выровняй тут все»).
- rail: dropped sticky+height:100vh so it stretches to full page height like the
  prototype (fixes «обрезан»).

Main Site Finder (entry landing) — restyled to the ПТИЦА dark cockpit theme,
scoped to .sfRoot[data-theme=dark] (no leak to light pages): dark CARTO tiles,
Leaflet attribution flag removed (prefix=false, © credit kept), dark markers /
filter chips / legend / recent list / drawer / cad input.
2026-06-21 03:41:13 +05:00
ff77987260 Merge pull request 'feat(ptica): live 3D massing in Инсоляция card + honest Recommended Product verdict' (#1860) from fix/ptica-3d-preview-product-verdict into main
All checks were successful
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m23s
Deploy / deploy (push) Successful in 1m12s
Deploy / changes (push) Successful in 5s
Deploy / build-backend (push) Has been skipped
2026-06-20 22:11:20 +00:00
8c43410ad2 feat(ptica): live 3D massing in Инсоляция card + honest Recommended Product verdict
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m59s
- MassingScene: preview prop — viewport-only, hands-off autorotate (OrbitControls
  input disabled, autoRotate on), canvas pointer-events:none; teardown preserved.
- InsolationCard: replaces the abstract SVG cube with a live autorotating massing
  built from real parcel area + НСПД max_far (farIsReal). «Открыть 3D» still opens
  the full interactive drawer.
- RecommendedProductCard: degenerate mix no longer shows «сигнал недоступен» —
  it shows the real advisory verdict «Избегать» + reason (затоварка · индекс
  дефицита из mix.deficit_index) + product_tz.gate_caveat. Meaningful mixes still
  render quartirography bars. «Подробнее» → product drawer (full reasons).
- drop dead iso-cube CSS (pticaIsoSpin/.isoBoxPreview/.isoPreview*); add
  .massingPreviewViewport + verdict styles (tokens, dark-scoped).
- forecast type: product_tz.gate_caveat.

Clickability sweep: no dead controls found; cockpit already cleanly wired.
2026-06-21 03:11:15 +05:00
2a40b4fcc6 Merge pull request 'fix(ptica): button font-size — :where() so component classes win over reset' (#1858) from fix/ptica-button-fontsize into main
All checks were successful
Deploy / build-backend (push) Has been skipped
Deploy / deploy (push) Successful in 1m9s
Deploy / changes (push) Successful in 7s
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m20s
2026-06-20 21:35:51 +00:00
611c56a549 fix(ptica): button font-size — :where() so component classes win over reset
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / frontend-tests (pull_request) Successful in 58s
CI / backend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Successful in 1m59s
The scoped reset `.pticaRoot button { font: inherit }` had specificity (0,1,1),
beating single-class button rules like .navItem (9px), .tab (11px), .detailBtn
(9px), .ptab — so every cockpit button was forced back to the root 13px. The
oversized labels overflowed the 96px left rail (icons/text shoved right of
centre, «неровное») and bloated tabs/buttons across the cockpit.

Wrap the selector in :where(button) → specificity (0,1,0); the per-component
classes (later in source) now win their font-size, matching the prototype's
bare `button {}`. Verified on a live local render: rail labels 13→9px and every
item re-centres at 47.5px; tabs 11px; detail buttons 9px.
2026-06-21 02:35:33 +05:00
b2cd2925fd fix(estimator): clamp expected_sold <= asking (ratio cap 1.0) -- smoke 3к exp_sold>asking
All checks were successful
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI / changes (pull_request) Successful in 8s
CI / backend-tests (pull_request) Has been skipped
2026-06-21 00:35:23 +03:00
f2cfe66b02 fix(scrapers): yandex history skip area<=0 items
_save_yandex_history_items теперь пропускает items с area_m2 is None или
area_m2 <= 0 (битый парс «0,5 м²» и пр.) перед сохранением в
house_placement_history. Грязь копилась в БД и искажала price_trend.

Estimator защищён NULLIF на уровне SQL, но фильтрация на входе надёжнее.
Лог кол-ва отброшенных (info). Батч не падает из-за одного битого item.
2026-06-21 00:25:43 +03:00
b6ea986134 fix(estimator): radius-path нижний floor от DKP-коридора (анти-undershoot)
Anchor-путь имеет hard floor от comp_min_ppm2×(1-tol). Radius-путь аналогичной
защиты снизу не имел — quarter-index-вниз или corridor-clamp могли занизить
median неоправданно (asymmetry).

Добавлен floor: если итоговый median_ppm2 < dkp_low_ppm2 × factor → поднять
до floor + лог. Только radius-путь (anchor_tier is None); без dkp_raw → no-op.
За флагами estimate_radius_floor_enabled (default True) и
estimate_radius_floor_factor (default 0.8).
2026-06-21 00:25:05 +03:00
352dfc8053 fix(estimator): asking_to_sold ratio tier по финальному headline
_get_asking_sold_ratio теперь вызывается ПОСЛЕ anchor/IMV-blend/quarter-index/
corridor-clamp, получая ФИНАЛЬНЫЙ median_ppm2 для tier-placement (#928 audit).

До фикса: tier резолвился по pre-anchor radius-медиане (~105k), но ratio
применялся к post-anchor headline (~300k) — несовпадение tier'а в premium-
сегменте. После: ratio берётся из того же tier, что и финальный headline.

Graceful: нет ratio → expected_sold_* = None, headline не меняется.
2026-06-21 00:24:02 +03:00
ea20d8688a Merge pull request 'fix(ptica): populate scan cards with real /analyze data + product tabs + map zoom' (#1857) from fix/ptica-parity-data-mapping into main
All checks were successful
Deploy / changes (push) Successful in 6s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m41s
Deploy / deploy (push) Successful in 1m10s
2026-06-20 21:18:45 +00:00
5587154327 fix(ptica): populate scan cards with real /analyze data + product tabs + map zoom
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 54s
CI / openapi-codegen-check (pull_request) Successful in 1m53s
Verified against a real captured /analyze+/forecast fixture rendered locally:
much of the cockpit's «—» was unmapped data, not missing data.

- РЫНОК: map Поглощение (market_pulse.avg_velocity_m2), add Конкурентов
  (market_pulse.competitors_total), Уровень спроса (market_trend.label);
  keep Медиана + Новых проектов. Prototype row order.
- СРЕДА (adaptEnvironmentCard now takes analysis): Шум (noise.estimated_db),
  Воздух PM2.5 (air_quality.pm2_5), Ветер (wind dir + max_speed), Водоёмы
  (hydrology nearest). Honest «—» where genuinely absent.
- ГРАДОСТРОИТЕЛЬСТВО: Пятно застройки = area × max_building_pct/100 (real).
- RECOMMENDED PRODUCT: prototype class tabs (обж_class present in mix),
  default = recommended class; per-class honest empty-state preserved.
- MAP: dark-styled ZoomControl (+/-) + ScaleControl, matching prototype.
- types: optional MarketPulse/MarketPrice on ParcelAnalysis (no any).

Economy/Investment-clearance stay «после финмодели §23» (not built); the
0%/«нельзя»/all-«избегать» verdict is the parcel's real result, not a bug.
2026-06-21 02:18:20 +05:00
01c91a9ea3 Merge pull request 'feat(ptica): slow turntable rotation on insolation preview cube' (#1856) from chore/ptica-iso-preview-spin into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-backend (push) Has been skipped
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m10s
Deploy / deploy (push) Successful in 1m9s
2026-06-20 20:47:36 +00:00
829439a4fa feat(ptica): slow turntable rotation on insolation preview cube
All checks were successful
CI / backend-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 7s
CI / openapi-codegen-check (pull_request) Successful in 1m51s
CI / frontend-tests (pull_request) Successful in 1m0s
The «Инсоляционная матрица» preview cube now gently rotates (rotateY
turntable with a constant rotateX tilt for depth) so the 3D module reads
as alive without opening the drawer. Pure CSS; honours prefers-reduced-motion.
2026-06-21 01:46:00 +05:00
9370c7af8e Merge pull request 'fix(ptica): load Inter, surface map legend, drop flag, honest product chart, home link' (#1855) from fix/ptica-inter-map-product into main
All checks were successful
Deploy / build-backend (push) Has been skipped
Deploy / changes (push) Successful in 8s
Deploy / build-worker (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m13s
Deploy / deploy (push) Successful in 1m20s
2026-06-20 20:42:29 +00:00
f4b54671d9 fix(ptica): load Inter, surface map legend, drop flag, honest product chart, home link
All checks were successful
CI / changes (pull_request) Successful in 6s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 57s
CI / openapi-codegen-check (pull_request) Successful in 1m56s
Root causes behind the cockpit looking «криво/косо»:

- Inter was never loaded — app referenced literal "Inter" but only IBM Plex
  Mono went through next/font, so the whole cockpit rendered in -apple-system
  (wider metrics → rail labels overflow, tabular numbers misalign). Load Inter
  via next/font/google (latin+cyrillic) and wire --font-ui/body to it.
- Map legend overlay (z-index 4) was buried under Leaflet panes (200-700):
  give .leaflet-container its own stacking context (position:absolute; z-index:1)
  like the prototype, so the overlay + tools surface.
- Drop the Leaflet attribution flag/"Leaflet" prefix (prefix={false}) while
  keeping the © Esri/Maxar/CARTO data credits.
- RECOMMENDED PRODUCT rendered 15 degenerate full-red −1.00 bars: filter mix to
  the recommended class, show an honest empty state on degenerate signal, cap
  rows, and stop labels wrapping (nowrap+ellipsis).
- ПТИЦА wordmark is now a Link → /site-finder (home), with a focus ring.
2026-06-21 01:41:13 +05:00
721623bd0e Merge pull request 'fix(ptica): visual parity 1:1 with prototype (ОКС stretch + restored elements)' (#1854) from fix/ptica-visual-parity into main
All checks were successful
Deploy / changes (push) Successful in 7s
Deploy / build-worker (push) Has been skipped
Deploy / build-backend (push) Has been skipped
Deploy / build-frontend (push) Successful in 3m16s
Deploy / deploy (push) Successful in 1m10s
2026-06-20 20:10:43 +00:00
fcaa4d197b fix(ptica): visual parity 1:1 with prototype (incl. ОКС empty-card stretch)
All checks were successful
CI / changes (pull_request) Successful in 7s
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 59s
CI / openapi-codegen-check (pull_request) Successful in 1m57s
Multi-region audit vs the prototype styles.css/dashboard.html → 22 CSS fixes +
markup. Headline: .lowerGrid/.bottomGrid align-items:start so the sparse ОКС
«нет данных» card no longer stretches into a huge void. Plus restore missing
prototype bits: topbar icon-buttons (Экспорт→Отчёты, Полный экран→fullscreen,
Настройки aria-disabled «скоро»), rail help-dot, app-footer (honest disclaimer,
no fabricated model/timestamp), invest-score sparkline (decorative, aria-hidden),
green/amber value tones, engineering status-row dots (from real nearest_m),
verdict-card gradient, scan-card status-rows, .canvas/.hbar/.mixHead spacing.

All new CSS scoped under .pticaRoot[data-theme=dark] (status-row classes
descendant-scoped under .scanCard — no collision with the drawer's). Environment
card stays honest kvrow placeholders (no fake dots). a11y: aria-labels on icon
buttons. TS strict, no any. tsc/lint/prettier/build green. code-reviewer .
2026-06-21 01:10:36 +05:00