trade-in/v2: data-contract — рассинхрон счётчика источников / ratio-vs-медианы / базы торга + кросс-source дедуп (из аудита #2081) #2087

Open
opened 2026-06-28 22:05:22 +00:00 by lekss361 · 2 comments
Owner

Backend/data-contract хвост UI/UX-аудита #2081 (round 2 /trade-in/v2, МЕРА). Дочерняя к эпику #2036. Это НЕ FE-баги — математика мапперов (tradein-mvp/frontend/.../v2/mappers.ts) проверена adversarial-verify и верна; «противоречия» в числах идут из данных бэка. FE-часть аудита уже смержена (PR #2086), цифры в мапперах НЕ трогались под эти пункты намеренно.

Тест-кейс аудита: Белинского 86, 54 м², 2-к, ?id=2314f87b….


M1 · Счётчик «ИСТОЧНИКОВ X/7» не сходится с залитыми плитками и плавает между загрузками

Живьём: 5/7 сразу после submit → 6/7 после reload того же ?id, при 5 видимых залитых плитках (Авито + Restate = «нет данных»). FE-логика activeSourceCountbuildSources (mappers.ts:486-520) детерминирована (тайл активен если sources_used содержит ключ ИЛИ count>0). Значит причина в данных:

  • источник присутствует в e.analogs/sources_used, но не даёт видимой плитки (нерас­познанный label-ключ?), и/или
  • данные досчитываются между загрузками (один и тот же id отдаёт разный sources_used).
    → Свести бэковый source_counts/sources_used с тем, что реально считают плитки; один канонический счётчик, стабильный между загрузками одного estimate.

M2 · Донат «−16% к рынку» не совпадает с показанными ценами (−18,2%)

11,09 / 13,55 = 0,818 и 205 328 / 250 879 = 0,818 (т.е. фактический спред −18,2%), а донат рисует −16% из asking_to_sold_ratio≈0,84. Формула FE (asking_to_sold_ratio−1)·100 верна (mappers.ts:835-836) — рассинхронен сам бэковый asking_to_sold_ratio против медиан, из которых считаются показанные цены. (Сделки «−43%» сходятся: 7,67/13,55.)
→ Согласовать asking_to_sold_ratio с медианами на бэке (один источник истины для «ожидаемой» цены).

M3 · «СРЕДНИЙ ТОРГ −2,3%» (Аналитика) противоречит «ожидаемой −16/−18%» — несовместимые базы

median_bargain_pct (mappers.ts:1291, торг по placement start→last в доме) и asking_to_sold_ratio (:835, estimate-уровень) — разные источники и разные смыслы «торга», для юзера не примирены.
→ Либо одна согласованная история торга, либо явно подписать базы (что именно меряет каждый процент).

H4 · Нет кросс-source дедупа — кросс-постинг раздувает n аналогов и медиану

Живьём один и тот же лот посчитан несколько раз: 80 м²/265 000/21,2 млн = N1+Домклик+Циан (×3); 67 м²/328 283/21,995 млн = Циан+Домклик (×2). «14 аналогов» → ~6-7 уникальных. FE предполагает, что API уже дедуплицирован (mappers.ts:486-513 считает сырые source'ы, медиана/бины :928-939 из сырого e.analogs).
→ Дедуп на бэке по ключу (адрес+площадь+этаж+цена, с допуском) ДО подсчёта n_analogs и медианы. Связано с #1772 (дробление дома) и honesty-PR #2057.


Acceptance: счётчик источников канонический и стабильный (M1); asking_to_sold_ratio согласован с медианами (M2); базы торга примирены/подписаны (M3); медиана/n_analogs считаются по дедуплицированному набору (H4). Проверка — повторить тест-кейс на проде, сверить донат/счётчик/таблицу аналогов.

Backend/data-contract хвост UI/UX-аудита **#2081** (round 2 `/trade-in/v2`, МЕРА). Дочерняя к эпику #2036. Это **НЕ FE-баги** — математика мапперов (`tradein-mvp/frontend/.../v2/mappers.ts`) проверена adversarial-verify и верна; «противоречия» в числах идут из данных бэка. FE-часть аудита уже смержена (PR #2086), цифры в мапперах НЕ трогались под эти пункты намеренно. Тест-кейс аудита: Белинского 86, 54 м², 2-к, `?id=2314f87b…`. --- ### M1 · Счётчик «ИСТОЧНИКОВ X/7» не сходится с залитыми плитками и плавает между загрузками Живьём: `5/7` сразу после submit → `6/7` после reload того же `?id`, при 5 видимых залитых плитках (Авито + Restate = «нет данных»). FE-логика `activeSourceCount`→`buildSources` (`mappers.ts:486-520`) детерминирована (тайл активен если `sources_used` содержит ключ ИЛИ `count>0`). Значит причина в данных: - источник присутствует в `e.analogs`/`sources_used`, но не даёт видимой плитки (нерас­познанный label-ключ?), и/или - данные досчитываются между загрузками (один и тот же `id` отдаёт разный `sources_used`). → Свести бэковый `source_counts`/`sources_used` с тем, что реально считают плитки; один канонический счётчик, стабильный между загрузками одного estimate. ### M2 · Донат «−16% к рынку» не совпадает с показанными ценами (−18,2%) `11,09 / 13,55 = 0,818` и `205 328 / 250 879 = 0,818` (т.е. фактический спред −18,2%), а донат рисует −16% из `asking_to_sold_ratio≈0,84`. Формула FE `(asking_to_sold_ratio−1)·100` верна (`mappers.ts:835-836`) — рассинхронен сам бэковый `asking_to_sold_ratio` против медиан, из которых считаются показанные цены. (Сделки «−43%» сходятся: `7,67/13,55`.) → Согласовать `asking_to_sold_ratio` с медианами на бэке (один источник истины для «ожидаемой» цены). ### M3 · «СРЕДНИЙ ТОРГ −2,3%» (Аналитика) противоречит «ожидаемой −16/−18%» — несовместимые базы `median_bargain_pct` (`mappers.ts:1291`, торг по placement start→last в доме) и `asking_to_sold_ratio` (`:835`, estimate-уровень) — разные источники и разные смыслы «торга», для юзера не примирены. → Либо одна согласованная история торга, либо явно подписать базы (что именно меряет каждый процент). ### H4 · Нет кросс-source дедупа — кросс-постинг раздувает n аналогов и медиану Живьём один и тот же лот посчитан несколько раз: `80 м²/265 000/21,2 млн` = N1+Домклик+Циан (×3); `67 м²/328 283/21,995 млн` = Циан+Домклик (×2). «14 аналогов» → ~6-7 уникальных. FE предполагает, что API уже дедуплицирован (`mappers.ts:486-513` считает сырые source'ы, медиана/бины `:928-939` из сырого `e.analogs`). → Дедуп на бэке по ключу (адрес+площадь+этаж+цена, с допуском) ДО подсчёта `n_analogs` и медианы. Связано с #1772 (дробление дома) и honesty-PR #2057. --- **Acceptance:** счётчик источников канонический и стабильный (M1); `asking_to_sold_ratio` согласован с медианами (M2); базы торга примирены/подписаны (M3); медиана/`n_analogs` считаются по дедуплицированному набору (H4). Проверка — повторить тест-кейс на проде, сверить донат/счётчик/таблицу аналогов.
lekss361 added the
scope/backend
tradein
labels 2026-06-28 22:05:27 +00:00
Collaborator

M1 (стабильный канонический счётчик «ИСТОЧНИКОВ X/7») в PR #2144. Единый helper sources_used на POST и GET-rehydrate из одних persisted analogs; инвариант source_counts.keys() ⊆ sources_used; чинит и старые строки. M2/M3 не тронуты — issue остаётся открытой.

M1 (стабильный канонический счётчик «ИСТОЧНИКОВ X/7») в PR #2144. Единый helper sources_used на POST и GET-rehydrate из одних persisted analogs; инвариант source_counts.keys() ⊆ sources_used; чинит и старые строки. M2/M3 не тронуты — issue остаётся открытой.
Author
Owner

H4 (кросс-source дедуп до подсчёта n_analogs/медианы) закрыт: _dedup_cross_source() теперь применяется к обоим пулам в estimator.py — радиусному (listings, было в #2265/#2273) и anchor-tier (same_building/micro_radius, добавлено сегодня в #2311, закрывало gap где anchor-путь оставался недедуплицированным). Живая проверка на проде (5 последних estimate, все после фикса): distinct-count по ключу (адрес, площадь, этаж, цена) внутри analogs jsonb == длине массива на всех 5 записях — дублей нет.

M1 закрыт ранее (PR #2144, см. выше). Остаются M2/M3 (asking_to_sold_ratio рассинхрон с медианами; несовместимые базы «торга») — не трогал в этом раунде, оба требуют backend investigation в estimator.py на предмет откуда берётся asking_to_sold_ratio относительно показанных медиан.

H4 (кросс-source дедуп до подсчёта n_analogs/медианы) закрыт: `_dedup_cross_source()` теперь применяется к обоим пулам в `estimator.py` — радиусному (listings, было в #2265/#2273) и anchor-tier (`same_building`/`micro_radius`, добавлено сегодня в #2311, закрывало gap где anchor-путь оставался недедуплицированным). Живая проверка на проде (5 последних estimate, все после фикса): distinct-count по ключу (адрес, площадь, этаж, цена) внутри analogs jsonb == длине массива на всех 5 записях — дублей нет. M1 закрыт ранее (PR #2144, см. выше). Остаются M2/M3 (asking_to_sold_ratio рассинхрон с медианами; несовместимые базы «торга») — не трогал в этом раунде, оба требуют backend investigation в estimator.py на предмет откуда берётся asking_to_sold_ratio относительно показанных медиан.
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
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#2087
No description provided.