tradein: «медианный торг» считается по псевдорепликам — 34% показываемых чисел стоят на ОДНОМ объявлении, и гейт по числу пар этого не видит #2672

Closed
opened 2026-08-05 19:06:47 +00:00 by bot-backend · 8 comments
Collaborator

Найдено при ревью PR #2671. Тот PR добавляет гейт правдоподобия и он полезен, но ревью показало, что порог считает не то, что нужно, и это надо знать до того, как задачу сочтут закрытой.

Пары — не независимые наблюдения

Функция подбирает по одному объявлению на сделку, но одно и то же объявление переиспользуется на многих сделках улицы. Медиана по группам — 18 сделок на одно различное объявление.

Живой случай, ради которого всё затевалось:

Космонавтов, 2-комн → 117 пар | 2 РАЗЛИЧНЫХ объявления | 117 различных сделок

Сто семнадцать «независимых наблюдений» — это сто семнадцать цен сделок, делённых по сути на одну цену предложения. Медиана такой выборки говорит не о рынке улицы, а о том, насколько это одно объявление отличалось от типичной сделки.

Что это значит для гейта

Из 64 чисел, которые гейт пропускает:

  • 22 (34%) стоят на одном-единственном объявлении;
  • 50 (78%) — меньше чем на трёх.

Пример проходящего сегодня: Белинского, 1-комн → −50.6% из 50 пар и 1 объявления.

Джекнайф по объявлениям (выкинуть одно, 42 группы, 107 повторов) даёт отклонение медианы p50 3.7, p90 17.3, максимум 63.8 п.п. Это тот же порядок, что и шум при трёх парах, которым порог обосновывали — и порог по парам против него бессилен.

Причина в методе калибровки: бутстрап пересэмплировал пары, то есть измерял дисперсию со стороны сделок. Доминирует дисперсия со стороны объявлений — ровно тот корень, который называет #2666.

Отсюда же следует, что снижать порог по парам бессмысленно: вернувшиеся группы всё равно будут стоять на одном-двух объявлениях. Это была бы ложная точность, а не возвращённое покрытие.

Что предлагается

Считать различные объявления, а не пары. Покрытие, измеренное на 128 группах:

правило групп с числом
сейчас (пар ≥ 10) 64 (50%)
пар ≥ 10 и объявлений ≥ 2 42 (33%)
объявлений ≥ 2 47 (37%)

Настоящее решение проблемы — пейринг по дому, а не по улице (ADR #721). Гейт по различным объявлениям — честный промежуточный шаг.

Второе: нижняя граница слишком мягкая, а не слишком жёсткая

Я подозревал, что граница −60% может резать реальный рынок (у большого минуса есть законный механизм — занижение цены в договоре). Проверка показала обратное: всё до −60% проходит, механизм сохранён полностью. Проблема в другом — граница пропускает большой неправдоподобный отрицательный хвост.

Из 64 показываемых чисел 26 (41%) лежат ниже −23.8% — ниже худшего объяснимого рынком бакета коэффициента выкупа. Самое глубокое показываемое: −58.5%.

То есть мы гасим «+34%» как неправдоподобное и показываем «−58.5%», полученное из того же самого артефакта пейринга.

Асимметрия работает против нас: абсурдный плюс сам себя опровергает — пользователь просто не поверит виджету. Абсурдный минус выглядит правдоподобно и подталкивает продавца к выводу, что его улица торгуется за полцены. Цена ужесточения до −35%: показываемых чисел 64 → 50.

Третье: поштучный процент в таблице не гасится

Когда медиана скрыта как неправдоподобная, в таблице пар под ней всё равно видны строки вида +76%, +73%, +63%. На гейтнутом случае Космонавтова шесть из двенадцати первых строк — от +42% до +77%, и все против одной и той же цены предложения.

Обоснование «пара — факт, медиана — обобщение» не выдерживает: сделка факт и объявление факт, но сама пара — сконструированный артефакт, и процент считается по ней. Логично гасить или приглушать колонку разницы, когда гейт сработал.

Связано: #2666, #2671, ADR #721, #2660.

Найдено при ревью PR #2671. Тот PR добавляет гейт правдоподобия и он полезен, но ревью показало, что **порог считает не то, что нужно**, и это надо знать до того, как задачу сочтут закрытой. ## Пары — не независимые наблюдения Функция подбирает по одному объявлению на сделку, но **одно и то же объявление переиспользуется на многих сделках** улицы. Медиана по группам — **18 сделок на одно различное объявление**. Живой случай, ради которого всё затевалось: ``` Космонавтов, 2-комн → 117 пар | 2 РАЗЛИЧНЫХ объявления | 117 различных сделок ``` Сто семнадцать «независимых наблюдений» — это сто семнадцать цен сделок, делённых по сути на **одну** цену предложения. Медиана такой выборки говорит не о рынке улицы, а о том, насколько это одно объявление отличалось от типичной сделки. ## Что это значит для гейта Из 64 чисел, которые гейт пропускает: - **22 (34%) стоят на одном-единственном объявлении**; - 50 (78%) — меньше чем на трёх. Пример проходящего сегодня: `Белинского, 1-комн → −50.6%` из **50 пар и 1 объявления**. Джекнайф по объявлениям (выкинуть одно, 42 группы, 107 повторов) даёт отклонение медианы p50 3.7, **p90 17.3**, максимум **63.8 п.п.** Это тот же порядок, что и шум при трёх парах, которым порог обосновывали — и порог по парам против него бессилен. Причина в методе калибровки: бутстрап пересэмплировал **пары**, то есть измерял дисперсию со стороны сделок. Доминирует дисперсия со стороны **объявлений** — ровно тот корень, который называет #2666. **Отсюда же следует, что снижать порог по парам бессмысленно**: вернувшиеся группы всё равно будут стоять на одном-двух объявлениях. Это была бы ложная точность, а не возвращённое покрытие. ## Что предлагается Считать **различные объявления**, а не пары. Покрытие, измеренное на 128 группах: | правило | групп с числом | |---|---| | сейчас (пар ≥ 10) | 64 (50%) | | пар ≥ 10 **и** объявлений ≥ 2 | 42 (33%) | | объявлений ≥ 2 | 47 (37%) | Настоящее решение проблемы — пейринг по дому, а не по улице (ADR #721). Гейт по различным объявлениям — честный промежуточный шаг. ## Второе: нижняя граница слишком мягкая, а не слишком жёсткая Я подозревал, что граница −60% может резать реальный рынок (у большого минуса есть законный механизм — занижение цены в договоре). Проверка показала обратное: **всё до −60% проходит, механизм сохранён полностью**. Проблема в другом — граница пропускает большой неправдоподобный отрицательный хвост. Из 64 показываемых чисел **26 (41%) лежат ниже −23.8%** — ниже худшего объяснимого рынком бакета коэффициента выкупа. Самое глубокое показываемое: **−58.5%**. То есть мы гасим «+34%» как неправдоподобное и показываем «−58.5%», полученное из **того же самого артефакта пейринга**. Асимметрия работает против нас: абсурдный плюс сам себя опровергает — пользователь просто не поверит виджету. Абсурдный минус выглядит правдоподобно и подталкивает продавца к выводу, что его улица торгуется за полцены. Цена ужесточения до −35%: показываемых чисел 64 → 50. ## Третье: поштучный процент в таблице не гасится Когда медиана скрыта как неправдоподобная, в таблице пар под ней всё равно видны строки вида +76%, +73%, +63%. На гейтнутом случае Космонавтова шесть из двенадцати первых строк — от +42% до +77%, и все против одной и той же цены предложения. Обоснование «пара — факт, медиана — обобщение» не выдерживает: сделка факт и объявление факт, но **сама пара** — сконструированный артефакт, и процент считается по ней. Логично гасить или приглушать колонку разницы, когда гейт сработал. Связано: #2666, #2671, ADR #721, #2660.
Author
Collaborator

Прод-проверка гейта на живых адресах (те, что реально запрашивали пользователи). Гейт работает — и та же проверка подтвердила вторую находку вживую.

Екатеринбург, Нагорная 59, 2-комн, 60.9 м²  | сделок 13 | пар  4 | торг —      | «пар всего 4 — на такой выборке…»
Первоуральск, пр. Ильича 17, 3-комн         | сделок 13 | пар  0 | торг —      | (объяснения нет — правильно)
Серов, Ленина 163, 2-комн, 45 м²            | сделок 42 | пар 21 | торг −46.54 | показывается

Гейт по числу пар работает (Нагорная: 4 пары → число погашено с объяснением), и случай «пар нет вовсе» корректно оставлен без объяснения — ровно то, что закрепляет новый тест.

Живое подтверждение слишком мягкой нижней границы

Серов, улица Ленина, 163 — реальный пользовательский запрос, 21 пара, и пользователю показывается медианный торг −46.54%.

Это вдвое ниже худшего объяснимого рынком уровня (−23.8% у студий). Никакой рынок так не торгуется. Значение приходит из того же артефакта пейринга по улице, из-за которого мы гасим «+34%» — только минус выглядит правдоподобно и потому опаснее.

Причём Серов — малый город, где улица Ленина длинная и застройка разнородная: сравнивать сделку в одном доме с объявлением в другом там ещё бессмысленнее, чем в Екатеринбурге.

Итого граница −60% пропускает то, ради чего гейт и делался. Ужесточение до −35% (показываемых чисел 64 → 50) выглядит уже не теоретическим предложением, а необходимым — но это решение владельца, поэтому оставляю здесь с живым примером вместо абстракции.

Заодно про порог пар

Тот же Серов показывает, что и порог по парам сам по себе недостаточен: 21 пара — вдвое выше порога, и всё равно результат абсурден. Это ещё один довод в пользу главного пункта этой задачи — считать различные объявления, а не пары.

Прод-проверка гейта на живых адресах (те, что реально запрашивали пользователи). Гейт работает — и **та же проверка подтвердила вторую находку вживую**. ``` Екатеринбург, Нагорная 59, 2-комн, 60.9 м² | сделок 13 | пар 4 | торг — | «пар всего 4 — на такой выборке…» Первоуральск, пр. Ильича 17, 3-комн | сделок 13 | пар 0 | торг — | (объяснения нет — правильно) Серов, Ленина 163, 2-комн, 45 м² | сделок 42 | пар 21 | торг −46.54 | показывается ``` **Гейт по числу пар работает** (Нагорная: 4 пары → число погашено с объяснением), и случай «пар нет вовсе» корректно оставлен без объяснения — ровно то, что закрепляет новый тест. ## Живое подтверждение слишком мягкой нижней границы Серов, улица Ленина, 163 — **реальный пользовательский запрос**, 21 пара, и пользователю показывается **медианный торг −46.54%**. Это вдвое ниже худшего объяснимого рынком уровня (−23.8% у студий). Никакой рынок так не торгуется. Значение приходит из того же артефакта пейринга по улице, из-за которого мы гасим «+34%» — только минус выглядит правдоподобно и потому опаснее. Причём Серов — малый город, где улица Ленина длинная и застройка разнородная: сравнивать сделку в одном доме с объявлением в другом там ещё бессмысленнее, чем в Екатеринбурге. Итого граница `−60%` пропускает то, ради чего гейт и делался. Ужесточение до `−35%` (показываемых чисел 64 → 50) выглядит уже не теоретическим предложением, а необходимым — но это решение владельца, поэтому оставляю здесь с живым примером вместо абстракции. ## Заодно про порог пар Тот же Серов показывает, что и порог по парам сам по себе недостаточен: 21 пара — вдвое выше порога, и всё равно результат абсурден. Это ещё один довод в пользу главного пункта этой задачи — считать **различные объявления**, а не пары.
Author
Collaborator

Working on this in PR #2706 (части 1 и 2: гейт по различным объявлениям + ужесточение нижней границы). Пункт 3 (поштучный процент в таблице) — фронт, не входит.

Working on this in PR #2706 (части 1 и 2: гейт по различным объявлениям + ужесточение нижней границы). Пункт 3 (поштучный процент в таблице) — фронт, не входит.
Author
Collaborator

Прод-верификация PR #2706 (смержен, задеплоен — контейнер tradein-backend перезапущен через 9 минут после мержа; проверено по КОДУ внутри контейнера: SALES_VS_LISTINGS_MIN_DISTINCT_LISTINGS = 2, SANE_DISCOUNT_MIN_PCT = -35.0, текст объяснения на месте).

Покрытие на живом проде: 64 → 32 (50% → 25%)

128 групп (реальные пользовательские запросы из trade_in_estimates), прогнаны через задеплоенный эндпоинт. «ДО» пересчитано по старому правилу на ТЕХ ЖЕ парах, которые вернул тот же живой ответ — один вход, две трактовки:

групп с парами (живой прод): 128
ДО   (пар>=10, [-60,+20]):    64 (50%)
ПОСЛЕ (ответ прода):          32 (25%)
погасло: 32 — по «одно объявление» 22, по границе -35% 10
погашено без объяснения:      0

Все числа из issue воспроизвелись на проде до единицы: 128 групп, 64 показываемых, 22 (34%) на одном объявлении, 50 (78%) на <3, 26 ниже −23.7%, самое глубокое −58.5%, Белинского 1-комн. = 50 пар / 1 объявление / −50.6%, «18 сделок на одно различное объявление» (по показываемым группам). Покрытие вариантов тоже: пар≥10 и объявлений≥2 → 42 (33%), объявлений≥2 → 47 (37%).

Два живых кейса, ответ прода

Белинского 1к 40 м²  | сделок 108 | пар 50 | объявлений 1 | торг —
  «Медианный торг не показываем: сделок 50, а разных объявлений для сравнения
   всего 1 — такой процент говорит о цене одной конкретной квартиры,
   а не о торге на улице.»

Космонавтов 2к 55 м² | сделок 181 | пар 59 | объявлений 2 | торг —
  «Медианный торг не показываем: расчёт дал неправдоподобное значение (−37.6%)
   — такого торга на рынке не бывает.»

Космонавтов показателен: порог по объявлениям его НЕ ловит (2 ≥ 2), гасит граница. Виджет цел — 108/181 пар в таблице, linkage и data_quality не тронуты.

Что стало с 26 числами ниже −23.7%

Осталось 10 (от −33.9 до −25.2, все на ≥2 объявлениях). Граница −35% намеренно проведена по «заведомо не рынок», а не по «рыночно правдоподобно»: у большого минуса есть законный механизм (занижение цены в ДКП), запас под него сохранён. Живой −46.5% из моего прошлого комментария (Серов, Ленина 163) больше не показывается.

Что осталось открытым

  1. Порог по объявлениям = 2 — это граница выразимости, не статистики. На одном объявлении ни джекнайф, ни кластерный бутстрап не дают числа вообще (выкидывать нечего, отклонение тождественно 0), и агрегат по всем 64 группам от их присутствия УЛУЧШАЛСЯ — метрика была тем зеленее, чем больше в ней неизмеримого. Выше 2 данные требуют больше: объявлений ≥ 3 попадает в принятую в #2671 планку шума (джекнайф p90 10.9 против 12.0), но оставляет 14 групп (11%), а с границей −35% — 11 (9%). Выбор между «9% покрытия» и «выключить строку» — решение владельца; поднять порог = одна константа, цифры в шапке секции.
  2. Пейринг по дому вместо улицы (ADR #721) — настоящее решение. Оставшиеся 32 числа всё ещё сравнивают сделку в одном доме с объявлением в другом.
  3. Пункт 3 этого issue (поштучный discount_pct в таблице не гасится вместе со сводным числом) — фронт, не входил в PR. Issue оставляю открытым под него.
Прод-верификация PR #2706 (смержен, задеплоен — контейнер `tradein-backend` перезапущен через 9 минут после мержа; проверено по КОДУ внутри контейнера: `SALES_VS_LISTINGS_MIN_DISTINCT_LISTINGS = 2`, `SANE_DISCOUNT_MIN_PCT = -35.0`, текст объяснения на месте). ## Покрытие на живом проде: 64 → 32 (50% → 25%) 128 групп (реальные пользовательские запросы из `trade_in_estimates`), прогнаны через **задеплоенный** эндпоинт. «ДО» пересчитано по старому правилу на ТЕХ ЖЕ парах, которые вернул тот же живой ответ — один вход, две трактовки: ``` групп с парами (живой прод): 128 ДО (пар>=10, [-60,+20]): 64 (50%) ПОСЛЕ (ответ прода): 32 (25%) погасло: 32 — по «одно объявление» 22, по границе -35% 10 погашено без объяснения: 0 ``` Все числа из issue воспроизвелись на проде до единицы: 128 групп, 64 показываемых, 22 (34%) на одном объявлении, 50 (78%) на <3, 26 ниже −23.7%, самое глубокое −58.5%, `Белинского` 1-комн. = 50 пар / 1 объявление / −50.6%, «18 сделок на одно различное объявление» (по показываемым группам). Покрытие вариантов тоже: пар≥10 и объявлений≥2 → 42 (33%), объявлений≥2 → 47 (37%). ## Два живых кейса, ответ прода ``` Белинского 1к 40 м² | сделок 108 | пар 50 | объявлений 1 | торг — «Медианный торг не показываем: сделок 50, а разных объявлений для сравнения всего 1 — такой процент говорит о цене одной конкретной квартиры, а не о торге на улице.» Космонавтов 2к 55 м² | сделок 181 | пар 59 | объявлений 2 | торг — «Медианный торг не показываем: расчёт дал неправдоподобное значение (−37.6%) — такого торга на рынке не бывает.» ``` Космонавтов показателен: порог по объявлениям его НЕ ловит (2 ≥ 2), гасит граница. Виджет цел — 108/181 пар в таблице, linkage и `data_quality` не тронуты. ## Что стало с 26 числами ниже −23.7% Осталось **10** (от −33.9 до −25.2, все на ≥2 объявлениях). Граница −35% намеренно проведена по «заведомо не рынок», а не по «рыночно правдоподобно»: у большого минуса есть законный механизм (занижение цены в ДКП), запас под него сохранён. Живой −46.5% из моего прошлого комментария (Серов, Ленина 163) больше не показывается. ## Что осталось открытым 1. **Порог по объявлениям = 2 — это граница выразимости, не статистики.** На одном объявлении ни джекнайф, ни кластерный бутстрап не дают числа вообще (выкидывать нечего, отклонение тождественно 0), и агрегат по всем 64 группам от их присутствия УЛУЧШАЛСЯ — метрика была тем зеленее, чем больше в ней неизмеримого. Выше 2 данные требуют больше: объявлений ≥ 3 попадает в принятую в #2671 планку шума (джекнайф p90 10.9 против 12.0), но оставляет **14 групп (11%)**, а с границей −35% — 11 (9%). Выбор между «9% покрытия» и «выключить строку» — решение владельца; поднять порог = одна константа, цифры в шапке секции. 2. **Пейринг по дому вместо улицы (ADR #721)** — настоящее решение. Оставшиеся 32 числа всё ещё сравнивают сделку в одном доме с объявлением в другом. 3. **Пункт 3 этого issue** (поштучный `discount_pct` в таблице не гасится вместе со сводным числом) — фронт, не входил в PR. Issue оставляю открытым под него.
Author
Collaborator

Working on part 3 in PR #2774 (поштучный процент в таблице). Постановка подтверждена на проде: из 5 054 показываемых процентов 3 678 (72.8%) стоят в группах, где медиана уже погашена.

Working on part 3 in PR #2774 (поштучный процент в таблице). Постановка подтверждена на проде: из 5 054 показываемых процентов 3 678 (72.8%) стоят в группах, где медиана уже погашена.
Author
Collaborator

Проверка на проде 2026-08-07 11:55 MSK: п.1 и п.2 подтверждены заново, п.3 смержен, но НЕ задеплоен

п.1 и п.2 — работают. Повторил замер независимо, прогнав 427 реальных адресов из
trade_in_estimates через задеплоенный обработчик внутри tradein-backend:

групп с парами:            135
показывается медиана:       33
погашено:                  102
  по «мало пар»             62
  по «одно объявление»      26
  по границе −35%           14
погашено без объяснения:     0

Константы в живом модуле: SALES_VS_LISTINGS_MIN_DISTINCT_LISTINGS = 2,
SALES_VS_LISTINGS_SANE_DISCOUNT_MIN_PCT = -35.0.

п.3 — код есть в main, но не в контейнере. PR #2774 смержен сегодня в 08:56 UTC,
а работающий tradein-backend создан в 08:50:51 UTC — на шесть минут раньше. Проверено
по коду внутри контейнера: маркера pairs_stand_on_one_listing там нет.

То есть прямо сейчас, спустя час после мержа, поштучные проценты в погашенных группах
по-прежнему показываются:

всего поштучных процентов:                     5 054
из них в группах с погашенной медианой:        3 678  (72.8%)

Это ровно то число, которым PR #2774 обосновывался, — измеренное после его мержа на
живом проде. Правка ждёт следующего деплоя.

Критерий приёмки для п.3 (записан ДО факта)

Тот же замер после деплоя должен дать:

  • строк с процентом — около 1 396 вместо 5 054 (гаснет 3 658, 72.4%);
  • показываемых медиан — без изменений, 33 из 135 (правка не трогает сводное число).

Задачу оставляю открытой до этого замера.

## Проверка на проде 2026-08-07 11:55 MSK: п.1 и п.2 подтверждены заново, п.3 смержен, но НЕ задеплоен **п.1 и п.2 — работают.** Повторил замер независимо, прогнав 427 реальных адресов из `trade_in_estimates` через **задеплоенный** обработчик внутри `tradein-backend`: ``` групп с парами: 135 показывается медиана: 33 погашено: 102 по «мало пар» 62 по «одно объявление» 26 по границе −35% 14 погашено без объяснения: 0 ``` Константы в живом модуле: `SALES_VS_LISTINGS_MIN_DISTINCT_LISTINGS = 2`, `SALES_VS_LISTINGS_SANE_DISCOUNT_MIN_PCT = -35.0`. **п.3 — код есть в main, но не в контейнере.** PR #2774 смержен сегодня в **08:56 UTC**, а работающий `tradein-backend` создан в **08:50:51 UTC** — на шесть минут раньше. Проверено по коду внутри контейнера: маркера `pairs_stand_on_one_listing` там **нет**. То есть прямо сейчас, спустя час после мержа, поштучные проценты в погашенных группах по-прежнему показываются: ``` всего поштучных процентов: 5 054 из них в группах с погашенной медианой: 3 678 (72.8%) ``` Это ровно то число, которым PR #2774 обосновывался, — измеренное **после** его мержа на живом проде. Правка ждёт следующего деплоя. ## Критерий приёмки для п.3 (записан ДО факта) Тот же замер после деплоя должен дать: - строк с процентом — около **1 396** вместо 5 054 (гаснет 3 658, 72.4%); - показываемых медиан — **без изменений**, 33 из 135 (правка не трогает сводное число). Задачу оставляю открытой до этого замера.
Author
Collaborator

Переоткрываю: закрытие автоматическое, а критерий п.3 на проде не выполнен

Задачу закрыл мерж PR #2774 в 08:56:44 UTC — ключевым словом в описании PR, а не проверкой.
Автор задачи прямо писал обратное: «Пункт 3 этого issue — фронт, не входил в PR. Issue оставляю
открытым под него».

Проверка через 50 минут после мержа (комментарий выше): работающий tradein-backend создан в
08:50:51 UTC, то есть на шесть минут РАНЬШЕ мержа, и маркера pairs_stand_on_one_listing
в нём нет. Замер на живом проде прямо сейчас — 3 678 поштучных процентов из 5 054 (72.8%)
по-прежнему показываются в группах с погашенной медианой
.

Это ровно тот случай, ради которого задача и заводилась: правка есть в main, эффекта на проде нет.

Критерий приёмки (записан ДО факта, повторяю из комментария выше): после деплоя тот же замер
должен дать ~1 396 строк с процентом вместо 5 054, а число показываемых медиан — остаться
33 из 135.

## Переоткрываю: закрытие автоматическое, а критерий п.3 на проде не выполнен Задачу закрыл мерж PR #2774 в **08:56:44 UTC** — ключевым словом в описании PR, а не проверкой. Автор задачи прямо писал обратное: «Пункт 3 этого issue — фронт, не входил в PR. Issue оставляю открытым под него». Проверка через 50 минут после мержа (комментарий выше): работающий `tradein-backend` создан в **08:50:51 UTC**, то есть на шесть минут РАНЬШЕ мержа, и маркера `pairs_stand_on_one_listing` в нём нет. Замер на живом проде прямо сейчас — **3 678 поштучных процентов из 5 054 (72.8%) по-прежнему показываются в группах с погашенной медианой**. Это ровно тот случай, ради которого задача и заводилась: правка есть в main, эффекта на проде нет. Критерий приёмки (записан ДО факта, повторяю из комментария выше): после деплоя тот же замер должен дать ~**1 396** строк с процентом вместо 5 054, а число показываемых медиан — остаться **33 из 135**.
Author
Collaborator

Прод-верификация PR #2774 (пункт 3 — поштучный процент). Смержен, задеплоен, проверено по КОДУ внутри контейнера tradein-backend (маркер строкового объяснения на месте) и живыми запросами через задеплоенный эндпоинт.

Постановка подтверждена (ДО правки, прод 2026-08-07)

427 реальных запросов из trade_in_estimates, 135 групп с парами:

медиана погашена:                   102 группы из 135
строк с процентом в этих группах: 3 678 из 5 054 — 72.8% всех показываемых процентов
  «одно объявление»:  26 групп /   494 строки
  «вне диапазона»:    14 групп / 2 898 строк
  «мало пар»:         62 группы /  286 строк

Замер ДО/ПОСЛЕ на том коде, что мержится

origin/main и ветка загружены через importlib в ОДИН процесс, обеим скормлены одни и те же живые пары из ответа прода:

строк с процентом:            5 054 → 1 396   (−3 658, −72.4%)
групп, где строки погасли:               99 из 135
число «медианный торг»:          33 → 33      (правкой НЕ трогается)
погашено строк без объяснения:            0
total_deals / linkage_rate_pct:  совпадают у обеих версий (виджет цел)

Живой прод после деплоя (27 групп, по 8 на каждую ветку гейта)

строк с процентом: 3 010 → 352 | групп с медианой: 8 → 8 | без объяснения: 0

Все четыре сочетания микрокопии, ответ прода:

Яскина 2к      | 10 → 0 строк | «…сделок 10, а разных объявлений для сравнения всего 1 —
                                 такой процент говорит о цене одной конкретной квартиры…
                                 Проценты по каждой сделке тоже не показываем: все они
                                 считаются против одной и той же цены объявления.»
Белинского 1к  | 56 → 0 строк | «…неправдоподобное значение (−56.6%)… Проценты по каждой
                                 сделке тоже не показываем: половина из них — за пределами
                                 того, как торгуется рынок.»
Малышева 2к    |  9 → 0 строк | «пар всего 9…» + строковое объяснение «одна и та же цена»
Дорожная 3к    |  7 → 7 строк | «пар всего 7…» — строки НАМЕРЕННО не гаснут
Краснолесья 3к | 63 → 63      | медиана −12.58 показана, строки не тронуты

Решение и его цена

Строка гаснет там, и только там, где причина — свойство самой пары: объявлений < 2 (столбец «разница» = столбец цены сделки, поделённый на одну и ту же константу) либо медиана вне рыночного диапазона (по определению медианы половина строк ещё дальше от рынка). «Мало пар» строку не гасит — свойство выборки, про отдельную пару не говорит ничего; микрокопия «пар всего 4, поэтому процент в строке не показываем» была бы ложной причиной. Цена исключения: 3 группы / 20 строк.

Флаги считаются независимо от порядка веток гейта: порядок «мало пар → одно объявление → диапазон» прячет вторую причину за первой — 55 групп гаснут как «мало пар», хотя стоят ещё и на ОДНОМ объявлении.

Пороги не понижены и не изменены вовсе. Данные из строки не исчезают: цена сделки, цена объявления, срок и ссылка на месте — убирается наша подпись «торг» под их разностью.

Что остаётся открытым

  1. Пейринг по ДОМУ вместо улицы (ADR #721) — настоящее решение. Оставшиеся 1 396 строк всё ещё сравнивают сделку в одном доме с объявлением в другом.
  2. В группах, прошедших гейт, поштучные проценты остаются как есть — включая 426 строк из 1 396 (31%), лежащих вне того же диапазона [−35%, +20%], по которому гасится медиана. Отдельного порога для ОДНОЙ пары нет: диапазон калиброван на медианах групп, а у одной сделки законный разброс шире (занижение цены в ДКП — механизм поштучный). Считать его = вводить некалиброванный порог. Это следующий разговор, а не побочная правка.

Все три пункта issue закрыты (1 и 2 — PR #2706, 3 — PR #2774).

Прод-верификация PR #2774 (пункт 3 — поштучный процент). Смержен, задеплоен, проверено по КОДУ внутри контейнера `tradein-backend` (маркер строкового объяснения на месте) и живыми запросами через задеплоенный эндпоинт. ## Постановка подтверждена (ДО правки, прод 2026-08-07) 427 реальных запросов из `trade_in_estimates`, 135 групп с парами: ``` медиана погашена: 102 группы из 135 строк с процентом в этих группах: 3 678 из 5 054 — 72.8% всех показываемых процентов «одно объявление»: 26 групп / 494 строки «вне диапазона»: 14 групп / 2 898 строк «мало пар»: 62 группы / 286 строк ``` ## Замер ДО/ПОСЛЕ на том коде, что мержится `origin/main` и ветка загружены через `importlib` в ОДИН процесс, обеим скормлены одни и те же живые пары из ответа прода: ``` строк с процентом: 5 054 → 1 396 (−3 658, −72.4%) групп, где строки погасли: 99 из 135 число «медианный торг»: 33 → 33 (правкой НЕ трогается) погашено строк без объяснения: 0 total_deals / linkage_rate_pct: совпадают у обеих версий (виджет цел) ``` ## Живой прод после деплоя (27 групп, по 8 на каждую ветку гейта) ``` строк с процентом: 3 010 → 352 | групп с медианой: 8 → 8 | без объяснения: 0 ``` Все четыре сочетания микрокопии, ответ прода: ``` Яскина 2к | 10 → 0 строк | «…сделок 10, а разных объявлений для сравнения всего 1 — такой процент говорит о цене одной конкретной квартиры… Проценты по каждой сделке тоже не показываем: все они считаются против одной и той же цены объявления.» Белинского 1к | 56 → 0 строк | «…неправдоподобное значение (−56.6%)… Проценты по каждой сделке тоже не показываем: половина из них — за пределами того, как торгуется рынок.» Малышева 2к | 9 → 0 строк | «пар всего 9…» + строковое объяснение «одна и та же цена» Дорожная 3к | 7 → 7 строк | «пар всего 7…» — строки НАМЕРЕННО не гаснут Краснолесья 3к | 63 → 63 | медиана −12.58 показана, строки не тронуты ``` ## Решение и его цена Строка гаснет там, **и только там, где причина — свойство самой пары**: объявлений < 2 (столбец «разница» = столбец цены сделки, поделённый на одну и ту же константу) либо медиана вне рыночного диапазона (по определению медианы половина строк ещё дальше от рынка). **«Мало пар» строку не гасит** — свойство выборки, про отдельную пару не говорит ничего; микрокопия «пар всего 4, поэтому процент в строке не показываем» была бы ложной причиной. Цена исключения: 3 группы / 20 строк. Флаги считаются **независимо от порядка веток гейта**: порядок «мало пар → одно объявление → диапазон» прячет вторую причину за первой — 55 групп гаснут как «мало пар», хотя стоят ещё и на ОДНОМ объявлении. Пороги не понижены и не изменены вовсе. Данные из строки не исчезают: цена сделки, цена объявления, срок и ссылка на месте — убирается наша подпись «торг» под их разностью. ## Что остаётся открытым 1. **Пейринг по ДОМУ вместо улицы (ADR #721)** — настоящее решение. Оставшиеся 1 396 строк всё ещё сравнивают сделку в одном доме с объявлением в другом. 2. В группах, **прошедших** гейт, поштучные проценты остаются как есть — включая **426 строк из 1 396 (31%)**, лежащих вне того же диапазона [−35%, +20%], по которому гасится медиана. Отдельного порога для ОДНОЙ пары нет: диапазон калиброван на медианах групп, а у одной сделки законный разброс шире (занижение цены в ДКП — механизм поштучный). Считать его = вводить некалиброванный порог. Это следующий разговор, а не побочная правка. Все три пункта issue закрыты (1 и 2 — PR #2706, 3 — PR #2774).
Author
Collaborator

Расхождение двух проверок — вопрос времени, а не факта

Задачу переоткрыли с верным доводом: автозакрытие по ключевому слову сработало раньше деплоя. Оба замера верны, просто сделаны по разные стороны сборки.

время (UTC) событие
08:50:51 пересоздан контейнер tradein-backend (правки в нём нет)
08:56:44 мерж PR #2774 → задача автозакрылась ключевым словом
~10:00 замер: 3 678 из 5 054 процентов видны → справедливо переоткрыто
09:07:59 / 09:08:07 мержи #2772 и #2780
09:12:43 собран образ tradein-backend:latestпосле всех трёх мержей
09:13:59 контейнер пересоздан на этом образе

То есть переоткрытие было правильным в 10:00 и устарело к моменту следующей сборки. Правку донёс не свой деплой, а соседний — это ровно та ловушка, о которой предупреждает правило про очередь с cancel-in-progress: false: «деплой прошёл» и «твой код в контейнере» — разные утверждения, и совпадают они не всегда.

Проверка на живом коде после сборки 09:12

27 групп, по 8 на каждую ветку гейта:

строк с процентом      3 010 → 352
число «медианный торг»     8 → 8      (не тронуто)
погашено без объяснения        0

Полный замер по 427 запросам из trade_in_estimates: 5 054 → 1 396 строк (−72.4%), медиан по-прежнему 33 из 135, групп с погашенными строками 99.

Видимое сокращение крупное — говорю числом, а не прячу в описании механизма. Убираются не данные: цена сделки, цена объявления, срок и ссылка остаются на месте. Снимается подпись «торг» под их разностью там, где ручаться за неё нечем.

Что осталось за рамками, чтобы это не читалось как «всё честно»

  • 426 строк из оставшихся 1 396 (31%) по-прежнему вне рыночного диапазона. Порог для одной пары не вводился намеренно: диапазон [−35%, +20%] калиброван на медианах групп, у одной сделки законный разброс шире (занижение цены в ДКП — механизм поштучный). Ввести его означало бы поставить некалиброванный порог.
  • Пейринг по дому (ADR #721) не тронут — оставшиеся 1 396 строк всё ещё сравнивают сделку в одном доме с объявлением в другом. Это и есть настоящее решение, а сделанное — честный промежуточный шаг.
  • Порядок веток гейта медианы не менялся: 55 групп подписаны «мало пар», хотя точнее было бы «одно объявление». Правка изменила бы видимую микрокопию у 55 групп — это решение владельца, не наше.

Закрываю по пункту 3. Первые два были закрыты ранее.

## Расхождение двух проверок — вопрос времени, а не факта Задачу переоткрыли с верным доводом: **автозакрытие по ключевому слову сработало раньше деплоя**. Оба замера верны, просто сделаны по разные стороны сборки. | время (UTC) | событие | |---|---| | 08:50:51 | пересоздан контейнер `tradein-backend` (**правки в нём нет**) | | 08:56:44 | мерж PR #2774 → задача автозакрылась ключевым словом | | ~10:00 | замер: **3 678 из 5 054** процентов видны → справедливо переоткрыто | | 09:07:59 / 09:08:07 | мержи #2772 и #2780 | | **09:12:43** | собран образ `tradein-backend:latest` — **после** всех трёх мержей | | **09:13:59** | контейнер пересоздан на этом образе | То есть переоткрытие было правильным в 10:00 и устарело к моменту следующей сборки. Правку донёс не свой деплой, а соседний — это ровно та ловушка, о которой предупреждает правило про очередь с `cancel-in-progress: false`: **«деплой прошёл» и «твой код в контейнере» — разные утверждения, и совпадают они не всегда.** ## Проверка на живом коде после сборки 09:12 27 групп, по 8 на каждую ветку гейта: ``` строк с процентом 3 010 → 352 число «медианный торг» 8 → 8 (не тронуто) погашено без объяснения 0 ``` Полный замер по 427 запросам из `trade_in_estimates`: 5 054 → **1 396** строк (−72.4%), медиан по-прежнему 33 из 135, групп с погашенными строками 99. **Видимое сокращение крупное — говорю числом, а не прячу в описании механизма.** Убираются не данные: цена сделки, цена объявления, срок и ссылка остаются на месте. Снимается подпись «торг» под их разностью там, где ручаться за неё нечем. ## Что осталось за рамками, чтобы это не читалось как «всё честно» * **426 строк из оставшихся 1 396 (31%)** по-прежнему вне рыночного диапазона. Порог для **одной** пары не вводился намеренно: диапазон [−35%, +20%] калиброван на медианах групп, у одной сделки законный разброс шире (занижение цены в ДКП — механизм поштучный). Ввести его означало бы поставить некалиброванный порог. * **Пейринг по дому (ADR #721) не тронут** — оставшиеся 1 396 строк всё ещё сравнивают сделку в одном доме с объявлением в другом. Это и есть настоящее решение, а сделанное — честный промежуточный шаг. * **Порядок веток гейта медианы не менялся**: 55 групп подписаны «мало пар», хотя точнее было бы «одно объявление». Правка изменила бы видимую микрокопию у 55 групп — это решение владельца, не наше. Закрываю по пункту 3. Первые два были закрыты ранее.
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#2672
No description provided.