Commit graph

5 commits

Author SHA1 Message Date
2390eec740 fix(mera/b2c): вернуть координаты сделки, снесённые моим же коммитом про студию
Коммит 555cce44 обещал в сообщении одно — подписать студию вместо «0-к», — а
сделал ещё и другое: удалил миграцию 280_landing_showcase_deals_coords.sql и
откатил правки mera.py, landing_showcase_deals.py и двух тестов из коммита
cd2a7671. Заметил не я: об этом написал агент, которому потом достался номер
281 и который увидел дыру на 280.

ПРИЧИНА. `git commit` фиксирует ИНДЕКС ЦЕЛИКОМ, а не то, что было добавлено
последним `git add`. В индексе главного рабочего дерева лежали staged-удаления,
оставшиеся от параллельных агентов (они работают в своих worktree, но индекс
основного дерева переживает переключения веток). Я добавил два файла, а
закоммитил вместе с ними чужие удаления.

Что восстановлено: миграция 280 целиком, поля lat/lon в ShowcaseDeal, перенос
координат в пересчёте витрины, оба теста.

Обе величины нужны и не заменяют друг друга: схема улицы (281) закрывает 92%
сделок, координата (280) — карту района для остальных 8%. Конфликты сведены
вручную: механическое «оставить обе стороны» задвоило список колонок в INSERT
и в SELECT, что тесты бы пропустили, а прод — нет.

Проверено: 5085 passed, 35 skipped; миграции 275-281 без дыры; список колонок
INSERT сверен со списком значений программно (15 и 15, порядок совпадает).
2026-08-29 23:25:44 +05:00
4e3279d448 fix(mera/b2c): студия подписывалась как «0-к» на витрине сделок
Увидел на скриншоте карточки игры: «0-к, 25,9 м²». Замер на проде — 3 строки
витрины из 20 имеют rooms = 0, то есть каждая шестая карточка так и выглядит.
«0-к» читается как ошибка выгрузки, а не как тип квартиры.

Студией её называет и наш собственный бэктест (per_rooms.label в
scripts/backtest_estimator.py), и рынок.

Тест двусторонний и фальсифицирован: возврат «0-к» для rooms=0 красит его по
значению, обратная правка — снова зелено.
2026-08-29 23:25:44 +05:00
f3401badd0 feat(mera/b2c): координата сделки доезжает до витрины — с записанной границей честности
Карта лэндинга не может показать точку, пока её нет в витрине: район, комнаты,
площадь и квартал в `landing_showcase_deals` есть, координаты не было.

Что сделано: миграция 280 добавляет lat/lon (double precision, NULLABLE),
задача пересчёта переносит их из `deals`, ручка /showcase отдаёт их как
Optional[float].

ГРАНИЦА ЧЕСТНОСТИ, записанная в трёх местах (COMMENT колонок, `note` каждой
строки, докстринг задачи), а не только в голове автора: это ЦЕНТРОИД УЛИЦЫ, а
не дом. Замер на проде 2026-08-29 по той самой выборке, из которой набирается
витрина (deals, city='Екатеринбург', deal_date >= '2025-01-01'): 34 021 сделка,
34 017 с координатой, но РАЗЛИЧНЫХ точек всего 991 — ≈34 сделки в одной точке,
при 2.7% известных номеров дома. Точка верна на масштабе района и улицы и
неверна на масштабе дома; `note` едет на фронт вместе с числами, поэтому
следующий, кто возьмётся зумить карту, об этом споткнётся.

NULLABLE и без отбраковки: строка без координаты остаётся на витрине с
lat=lon=None. Выбрасывать сделку за отсутствие точки — отбор по признаку, не
связанному с качеством оценки, то есть та же порча витрины, которую здесь уже
чинили (порог по величине ошибки).

Тесты двусторонние, каждый проверен фальсификацией — краснеет по ЗНАЧЕНИЮ:
  * перестановка lat/lon в build_row  → 60.6055 == 56.8386
  * `if lat is None: return None`     → None is not None
  * перестановка lat/lon в ручке      → 60.6055 == 56.8386
  * фильтр строк без координат в ручке → len([]) == 1
Перестановку широты и долготы не ловит ни схема, ни тип (обе float), поэтому
в фикстурах намеренно непохожие величины: 56.8386 против 60.6055.

Фронтенд не тронут — его делает следующий шаг.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 23:25:44 +05:00
abe559cf8f fix(mera): витрина больше не отсеивает промахи оценщика, счётчики едут на фронт
Ревью MAJOR по честности, два пункта.

1. Убран MAX_ABS_ERR_PCT = 40 из build_row. Докстринг модуля сам запрещает
отбор по величине ошибки, но запрет был реализован только в _sort_key, а
фильтр — тот же отбор ступенькой раньше, и злее: строка не попадала даже в
кандидаты. Обоснование «отклонение >40% — почти всегда занижение ДКП ради
налога» не держится: _load_sample уже режет выборку санитарным диапазоном
₽/м² (для ЕКБ это глобальные PPM2_MIN=30k / PPM2_MAX=600k — город намеренно
не заведён в deal_city_price_bands), то есть грубые занижения вырезаны выше
по потоку и ПО СВОЙСТВУ САМОЙ СДЕЛКИ. Всё, что после этого дало большую
ошибку, — работа оценщика, и посетитель обязан её видеть. Честность про
заниженные ДКП перенесена в note каждой строки.

Заодно убраны MIN_FACT_PPM2=30k (дублировал уже применённый фильтр) и
MAX_FACT_PPM2=1.2M (недостижим при потолке выборки 600k): из трёх отбраковок
в проде срабатывала ровно одна — та, что льстила витрине, а два мёртвых
порога читались как работающие. Осталась только структурная отбраковка «нет
прогноза / квартала / площади».

2. Счётчики прогона выведены в ответ ручки. Итог пересчёта пишется в
landing_showcase_runs (миграция 277) и уезжает в ShowcaseResponse.stats
вместе с правилом отбраковки: показано 20 из N годных, рассмотрено M сделок.
Отдельная таблица, а не колонки в строках, — иначе в самом важном случае
(показывать нечего) счётчики исчезли бы вместе со строками. Ручка теперь
берёт и строки, и числа ИЗ ОДНОГО прогона: иначе пустой прогон показал бы
вчерашние строки под сегодняшними счётчиками.

Тесты двусторонние и проверены на сломанном коде: возврат любого порога по
ошибке → красный с величиной отклонения в сообщении; возврат любой границы
₽/м² → красная своя половина; stats=None при живом прогоне → красный.
2026-08-29 19:20:04 +05:00
b72dbc5da3 feat(mera): витрина лэндинга на реальных ДКП-сделках вместо выдуманных
Лента «МЕРА сказала X — продали за Y» жила на константах в marketing-v3.ts.
Здесь появляется её настоящий источник: сделки Росреестра по ЕКБ, прогнанные
через тот же спайн оценщика, что и боевой расчёт (backtest_estimator).

Отбор строк идёт по полноте данных и свежести квартала и НЕ смотрит на
величину ошибки: отбор по малой ошибке дал бы формально работающий код и
врущую витрину — показанные строки перестали бы быть выборкой из работы
оценщика. Свойство закреплено двусторонним тестом.

Витрина не показывает адреса (номер дома есть у 2.7% сделок) и не показывает
дня сделки (deal_date — первое число квартала). Каждая строка несёт note о
том, что замер не point-in-time. Заниженные ради налога ДКП отбрасываются по
|отклонению| > 40% и ₽/м² вне [30k; 1.2M], счётчик отброшенного — в лог.
2026-08-29 19:20:04 +05:00