бэктест: выборка вразброс — ORDER BY id DESC садится на несколько улиц #3289

Merged
bot-backend merged 1 commit from feat/backtest-scattered-sampling into main 2026-08-31 09:14:42 +00:00
Collaborator

Владелец заметил на лендинге: «почти все сделки из Чкаловского района». Разбор дал причину глубже.

Что нашлось

ORDER BY id DESC берёт последние вставленные строки, а Росреестр грузится пачками по домам — соседние id это один дом и одна улица.

Замер на проде 31.08.2026, Екатеринбург, выборка 200:

выборка разных улиц макс с одной улицы
последние 200 по id 38 22
случайные 200 129 6

На этой выборке стоит витрина: 20 строк с четырёх улиц, 16 из них с двух (Данилы Зверева — 9, Лучистая — 7).

Что замер показал, а чего НЕ показал

Пять прогонов по 200 сделок:

режим MAPE покрытие коридором
recent (текущий) 15,18 % 88,75 %
scattered · mera 14,96 % 83,13 %
scattered · alpha 15,97 % 88,34 %
scattered · bravo 13,69 % 83,85 %
scattered · charlie 14,36 % 86,23 %

Заголовочная точность устояла. Опубликованные 14,5 % лежат внутри разброса представительной выборки — кластеризация их не раздувала. Я шёл проверять обратную гипотезу и получил опровержение; говорю прямо, потому что иначе это выглядело бы как «нашли и починили».

А вот покрытие коридором опубликовано как 88 % — это верх диапазона. При пересборке выборки величина гуляет 83–88 при медиане около 86. То есть печатается лучший прогон из пяти как единственный.

Отсюда следствие для страницы, более общее, чем одно число: лендинг публикует точечные значения разового прогона, а они гуляют на несколько пунктов при пересборке выборки. Правка самих чисел — отдельным заходом, здесь только инструмент.

Решения

Умолчание не тронуто. Докстринг _sample_sql обещает дефолтному пути побайтово тот же SQL ради замороженного регресс-гейта; смена умолчания молча обнулила бы сравнимость всей истории замеров. Режим выбирается флагом --spread scattered.

Порядок по md5(id || seed), а не random(). Нужен воспроизводимый порядок: с random() два прогона отличались бы и из-за правки, и из-за состава выборки, и разделить вклады было бы нечем. Тот же seed → та же выборка.

Проверки

6 тестов, обе стороны. Фальсификация:

  • сделать вразброс умолчанием → падает identity-проверка (is _SAMPLE_SQL); сравнение текстов её бы пропустило, а докстринг обещает именно побайтовую неизменность;
  • заменить md5 на random() → падает проверка воспроизводимости.

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

ruff check и ruff format --check чисто.

Владелец заметил на лендинге: «почти все сделки из Чкаловского района». Разбор дал причину глубже. ## Что нашлось `ORDER BY id DESC` берёт последние **вставленные** строки, а Росреестр грузится пачками по домам — соседние id это один дом и одна улица. Замер на проде 31.08.2026, Екатеринбург, выборка 200: | выборка | разных улиц | макс с одной улицы | |---|---|---| | последние 200 по id | **38** | **22** | | случайные 200 | **129** | 6 | На этой выборке стоит витрина: **20 строк с четырёх улиц**, 16 из них с двух (Данилы Зверева — 9, Лучистая — 7). ## Что замер показал, а чего НЕ показал Пять прогонов по 200 сделок: | режим | MAPE | покрытие коридором | |---|---|---| | recent (текущий) | 15,18 % | **88,75 %** | | scattered · mera | 14,96 % | 83,13 % | | scattered · alpha | 15,97 % | 88,34 % | | scattered · bravo | 13,69 % | 83,85 % | | scattered · charlie | 14,36 % | 86,23 % | **Заголовочная точность устояла.** Опубликованные 14,5 % лежат внутри разброса представительной выборки — кластеризация их не раздувала. Я шёл проверять обратную гипотезу и получил опровержение; говорю прямо, потому что иначе это выглядело бы как «нашли и починили». **А вот покрытие коридором опубликовано как 88 % — это верх диапазона.** При пересборке выборки величина гуляет 83–88 при медиане около 86. То есть печатается лучший прогон из пяти как единственный. Отсюда следствие для страницы, более общее, чем одно число: **лендинг публикует точечные значения разового прогона, а они гуляют на несколько пунктов при пересборке выборки.** Правка самих чисел — отдельным заходом, здесь только инструмент. ## Решения **Умолчание не тронуто.** Докстринг `_sample_sql` обещает дефолтному пути побайтово тот же SQL ради замороженного регресс-гейта; смена умолчания молча обнулила бы сравнимость всей истории замеров. Режим выбирается флагом `--spread scattered`. **Порядок по `md5(id || seed)`, а не `random()`.** Нужен **воспроизводимый** порядок: с `random()` два прогона отличались бы и из-за правки, и из-за состава выборки, и разделить вклады было бы нечем. Тот же seed → та же выборка. ## Проверки 6 тестов, обе стороны. Фальсификация: - сделать вразброс умолчанием → падает **identity**-проверка (`is _SAMPLE_SQL`); сравнение текстов её бы пропустило, а докстринг обещает именно побайтовую неизменность; - заменить `md5` на `random()` → падает проверка воспроизводимости. Отдельный тест держит, что оба режима отбирают по **одним и тем же условиям** — иначе разница между прогонами объяснялась бы не порядком, а другим набором сделок, и сравнение ничего бы не значило. `ruff check` и `ruff format --check` чисто.
bot-backend added 1 commit 2026-08-31 06:28:42 +00:00
бэктест: режим выборки вразброс — ORDER BY id DESC садится на несколько улиц
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI / changes (pull_request) Successful in 11s
CI Trade-In / frontend-checks (pull_request) Has been skipped
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
CI Trade-In / backend-tests (pull_request) Successful in 5m12s
884a6b9cfe
ORDER BY id DESC берёт последние ВСТАВЛЕННЫЕ строки, а Росреестр грузится
пачками по домам: соседние id это один дом и одна улица. Замер на проде
31.08.2026 по Екатеринбургу, выборка 200:

    последние 200 по id : 38 разных улиц, до 22 сделок с ОДНОЙ улицы
    случайные 200       : 129 разных улиц, максимум 6 с одной

На этой выборке стоит витрина лэндинга: 20 строк с ЧЕТЫРЁХ улиц, 16 из них с
двух. Владелец заметил это как «почти все сделки из одного района».

ЧТО ЗАМЕР ПОКАЗАЛ, а что нет. Пять прогонов по 200 сделок:

    recent            MAPE 15.18%  покрытие 88.75%
    scattered mera    MAPE 14.96%  покрытие 83.13%
    scattered alpha   MAPE 15.97%  покрытие 88.34%
    scattered bravo   MAPE 13.69%  покрытие 83.85%
    scattered charlie MAPE 14.36%  покрытие 86.23%

Заголовочная точность УСТОЯЛА — опубликованные 14,5% лежат внутри разброса
представительной выборки. Кластеризация её не раздувала.

А покрытие коридором опубликовано как 88% — это верх диапазона: при пересборке
выборки величина гуляет 83-88 при медиане около 86. Публикуется лучший прогон
из пяти как единственный. Правка самих чисел лэндинга — отдельным заходом.

УМОЛЧАНИЕ НЕ ТРОНУТО. Докстринг _sample_sql обещает дефолтному пути побайтово
тот же SQL ради замороженного регресс-гейта; смена умолчания молча обнулила бы
сравнимость всей истории замеров. Режим выбирается флагом --spread.

Порядок по md5(id||seed), а не random(): нужен ВОСПРОИЗВОДИМЫЙ порядок, иначе
два прогона отличаются и из-за правки, и из-за состава выборки, и разделить
вклады нечем.

Тест держит обе стороны и проверен фальсификацией: сделать вразброс умолчанием
— падает identity-проверка (сравнение текстов её бы пропустило), заменить md5
на random() — падает проверка воспроизводимости.
bot-backend merged commit d20b844561 into main 2026-08-31 09:14:42 +00:00
bot-backend deleted branch feat/backtest-scattered-sampling 2026-08-31 09:14:42 +00:00
Sign in to join this conversation.
No reviewers
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#3289
No description provided.