«ФАКТИЧЕСКИЕ СДЕЛКИ» на экране — сделки семимесячной давности, и возраст никуда не доезжает #2846

Closed
opened 2026-08-12 17:51:04 +00:00 by bot-backend · 1 comment
Collaborator

Что видит пользователь

Плитка ДКП · РОСРЕЕСТР (ФАКТИЧЕСКИЕ СДЕЛКИ) показывает медиану в млн ₽, диапазон и число сделок. Подпись внизу — «ПОСТРОЕНО ПО N АНАЛОГАМ И M СДЕЛКАМ».

Что за этими числами на самом деле (прод, 12.08)

свежайшая сделка 2026-01-01 — 223 дня назад
всего сделок 96 974
различных месяцев 9, все квартальные: 2026-01, 2025-10, 2025-07, 2025-04, 2025-01, 2024-10, 2024-07…
все даты первое число месяца — deal_date это метка квартальной пачки, а не дата сделки

Плюс поверх этого числа переиндексированы внешним ориентиром цен, который сам устарел на 72 дня. На экране про это нет ни слова.

Это не недосмотр, а осознанно упёртый тупик

frontend/.../v2/mappers.ts:1150:

«No "ЗА N МЕСЯЦЕВ" — we have no honest period to show»

Автор заметил, что честного периода нет, и решил не показывать ничего. В момент написания это было правильно: единственное подходящее поле схемы — period_months, а оно означает окно поиска, а не возраст данных. Подпись «за 24 месяца» при свежайшей сделке семимесячной давности была бы хуже молчания.

Но у молчания своя цена: слово «ФАКТИЧЕСКИЕ» читается как «недавние». Пользователь, сверяющий оценку с рынком сегодня, не имеет способа узнать, что опорные сделки — января.

Честное поле есть, его просто никто не передаёт

Возраст выборки знаем точно: max(deal_date) по отобранным сделкам. Это не оценка и не догадка. Схема DkpCorridor (app/schemas/trade_in.py:142) несёт count / low_ppm2 / median_ppm2 / high_ppm2 / period_months — даты «по состоянию на» среди них нет.

Предложение: добавить в DkpCorridor поле вида latest_deal_month, заполнять его максимумом по отобранным сделкам и выводить на плитку — «сделки по январь 2026» либо «I кв. 2026». Тогда подпись перестаёт обещать свежесть, которой нет, и не приобретает ложной точности period_months.

Отдельным решением — стоит ли понижать доверие к коридору при таком возрасте, а не только подписывать. Сегодня он ADVISORY (не клампит оценку), так что срочности нет, но вопрос стоит задать явно.

Чего это НЕ стоит (проверено, чтобы не чинили не то)

Инструментированный реплей фикстуры на 277 сделках:

  • поправка внешним ориентиром двигает цену на 1–3.5% у ~4% оценок, вклад в MAPE — 0.00 пп;
  • снятие поправки целиком: MAPE 13.18 → 13.18, покрытие даже +0.37 пп;
  • фактически применяются ровно два множителя — 1.0360 и 1.0065, потому что в наборе сделок за 12 месяцев есть только два месяца.

То есть устарелость якоря — вчетверо меньше самой поправки, и как денежный дефект она не стоит внимания. Дорогое — не якорь, а возраст самих сделок и то, что он не виден.

Смежное, из того же разбора

  • sber_price_index.fetched_at уничтожается апсертом: загрузчик тянет всю серию и ставит fetched_at = now() всем строкам. На проде все 639 строк имеют одну метку, включая период 2017-01. Как признак свежести колонка пуста; ретроспективно такт публикации источника по ней невосстановим. Дёшево чинится: убрать fetched_at из DO UPDATE SET либо завести first_seen_at.
  • Порог свежести якоря sber_index_max_age_days = 35 недостижим по построению: period_month — первое число, значит возраст ≥30 уже на закрытии месяца, плюс лаг источника 0-16 суток ⇒ рабочий диапазон 46…53. За всю жизнь таблицы (74 суток наблюдений) ни одного дня с возрастом ≤35. Сторож был истинным 100% времени и несёт ноль бит. При этом в коде два разных порога на один факт: монитор держит 60 = 35 + запас 25, оценщик — 35.
  • logger.warning про устаревший якорь событием не становится никогда: интеграция поднята с event_level=ERROR (app/main.py:96). Остаются логи контейнера, которые сносит каждый деплой.
  • Ловушка для будущих замеров: status='done' у загрузчика не означает успех — прогон id=37 имеет {errors: 9, upserted: 0} и статус done. Успех = errors=0 AND upserted>0.
## Что видит пользователь Плитка `ДКП · РОСРЕЕСТР (ФАКТИЧЕСКИЕ СДЕЛКИ)` показывает медиану в млн ₽, диапазон и число сделок. Подпись внизу — «ПОСТРОЕНО ПО N АНАЛОГАМ И M СДЕЛКАМ». ## Что за этими числами на самом деле (прод, 12.08) | | | |---|---| | свежайшая сделка | **2026-01-01 — 223 дня назад** | | всего сделок | 96 974 | | различных месяцев | **9**, все квартальные: 2026-01, 2025-10, 2025-07, 2025-04, 2025-01, 2024-10, 2024-07… | | все даты | первое число месяца — `deal_date` это **метка квартальной пачки**, а не дата сделки | Плюс поверх этого числа переиндексированы внешним ориентиром цен, который сам устарел на 72 дня. На экране про это нет ни слова. ## Это не недосмотр, а осознанно упёртый тупик `frontend/.../v2/mappers.ts:1150`: > «No "ЗА N МЕСЯЦЕВ" — we have no honest period to show» Автор заметил, что честного периода нет, и решил не показывать ничего. **В момент написания это было правильно**: единственное подходящее поле схемы — `period_months`, а оно означает **окно поиска**, а не возраст данных. Подпись «за 24 месяца» при свежайшей сделке семимесячной давности была бы хуже молчания. Но у молчания своя цена: слово «ФАКТИЧЕСКИЕ» читается как «недавние». Пользователь, сверяющий оценку с рынком сегодня, не имеет способа узнать, что опорные сделки — января. ## Честное поле есть, его просто никто не передаёт Возраст выборки знаем точно: `max(deal_date)` по отобранным сделкам. Это не оценка и не догадка. Схема `DkpCorridor` (`app/schemas/trade_in.py:142`) несёт `count / low_ppm2 / median_ppm2 / high_ppm2 / period_months` — даты «по состоянию на» среди них нет. **Предложение:** добавить в `DkpCorridor` поле вида `latest_deal_month`, заполнять его максимумом по отобранным сделкам и выводить на плитку — «сделки по январь 2026» либо «I кв. 2026». Тогда подпись перестаёт обещать свежесть, которой нет, и не приобретает ложной точности `period_months`. Отдельным решением — стоит ли **понижать доверие** к коридору при таком возрасте, а не только подписывать. Сегодня он ADVISORY (не клампит оценку), так что срочности нет, но вопрос стоит задать явно. ## Чего это НЕ стоит (проверено, чтобы не чинили не то) Инструментированный реплей фикстуры на 277 сделках: - поправка внешним ориентиром двигает цену на **1–3.5% у ~4% оценок**, вклад в MAPE — **0.00 пп**; - снятие поправки целиком: MAPE 13.18 → 13.18, покрытие даже +0.37 пп; - фактически применяются ровно два множителя — 1.0360 и 1.0065, потому что в наборе сделок за 12 месяцев есть **только два месяца**. То есть устарелость якоря — вчетверо меньше самой поправки, и как денежный дефект она не стоит внимания. **Дорогое — не якорь, а возраст самих сделок и то, что он не виден.** ## Смежное, из того же разбора - `sber_price_index.fetched_at` **уничтожается апсертом**: загрузчик тянет всю серию и ставит `fetched_at = now()` всем строкам. На проде все 639 строк имеют одну метку, включая период 2017-01. Как признак свежести колонка пуста; ретроспективно такт публикации источника по ней невосстановим. Дёшево чинится: убрать `fetched_at` из `DO UPDATE SET` либо завести `first_seen_at`. - Порог свежести якоря `sber_index_max_age_days = 35` **недостижим по построению**: `period_month` — первое число, значит возраст ≥30 уже на закрытии месяца, плюс лаг источника 0-16 суток ⇒ рабочий диапазон 46…53. За всю жизнь таблицы (74 суток наблюдений) **ни одного дня** с возрастом ≤35. Сторож был истинным 100% времени и несёт ноль бит. При этом в коде **два разных порога на один факт**: монитор держит 60 = 35 + запас 25, оценщик — 35. - `logger.warning` про устаревший якорь событием не становится **никогда**: интеграция поднята с `event_level=ERROR` (`app/main.py:96`). Остаются логи контейнера, которые сносит каждый деплой. - Ловушка для будущих замеров: `status='done'` у загрузчика **не означает успех** — прогон id=37 имеет `{errors: 9, upserted: 0}` и статус `done`. Успех = `errors=0 AND upserted>0`.
lekss361 added the
bug
priority/p2
scope/backend
scope/frontend
tradein
ux
вторичка
labels 2026-08-16 10:25:23 +00:00
Author
Collaborator

Закрываю: обе половины задачи сегодня закрыты — одна данными, другая уже в коде

Возраст данных. Плитка показывала сделки 223-дневной давности потому, что источник стоял на Q1 2026 — корень в #2998 (партиции rosreestr_deals кончались на Q1, новые не создавались). Сегодня Q2 2026 загружен и геокодирован (PR #3014, +11 649 сделок, 7 610 с geom). Живой ответ /api/v1/trade-in/estimate изнутри контейнера, 21.08:

dkp_corridor.latest_deal_date = 2026-04-01   (было 2026-01-01)
period_months = 12 · actual_deals = 10 · analog_tier = micro_radius

Возраст доезжает до экрана. latest_deal_date есть в схеме (schemas/trade_in.py:169), эстиматоре (latest_deal), фронт-типах и маппере: mappers.ts:660asOf: dealsAsOfLabel(c.latest_deal_date, "quarter")mappers.ts:1177 — кладётся в строку ppm плитки ДКП вместе с медианой и числом сделок, ResultPanel.tsx:367 её рисует. Для сегодняшних данных подпись — «по II кв. 2026». Оговорка из постановки («No "ЗА N МЕСЯЦЕВ" — we have no honest period to show») относится к окну поиска (period_months), а не к возрасту данных — они разведены, и комментарий в схеме это прямо говорит.

Что остаётся вне этой задачи: квартальная точность deal_date (метка пачки, не дата сделки) — свойство источника, не витрины; и геокод сделок не входит ни в одно расписание — каждая квартальная загрузка оставит долг (см. #2998). Оба зафиксированы там.

## Закрываю: обе половины задачи сегодня закрыты — одна данными, другая уже в коде **Возраст данных.** Плитка показывала сделки 223-дневной давности потому, что источник стоял на Q1 2026 — корень в #2998 (партиции `rosreestr_deals` кончались на Q1, новые не создавались). Сегодня Q2 2026 загружен и геокодирован (PR #3014, +11 649 сделок, 7 610 с geom). Живой ответ `/api/v1/trade-in/estimate` изнутри контейнера, 21.08: ``` dkp_corridor.latest_deal_date = 2026-04-01 (было 2026-01-01) period_months = 12 · actual_deals = 10 · analog_tier = micro_radius ``` **Возраст доезжает до экрана.** `latest_deal_date` есть в схеме (`schemas/trade_in.py:169`), эстиматоре (`latest_deal`), фронт-типах и маппере: `mappers.ts:660` → `asOf: dealsAsOfLabel(c.latest_deal_date, "quarter")` → `mappers.ts:1177` — кладётся в строку `ppm` плитки ДКП вместе с медианой и числом сделок, `ResultPanel.tsx:367` её рисует. Для сегодняшних данных подпись — **«по II кв. 2026»**. Оговорка из постановки («No "ЗА N МЕСЯЦЕВ" — we have no honest period to show») относится к окну поиска (`period_months`), а не к возрасту данных — они разведены, и комментарий в схеме это прямо говорит. **Что остаётся вне этой задачи:** квартальная точность `deal_date` (метка пачки, не дата сделки) — свойство источника, не витрины; и геокод сделок не входит ни в одно расписание — каждая квартальная загрузка оставит долг (см. #2998). Оба зафиксированы там.
Sign in to join this conversation.
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#2846
No description provided.