"""Выборка бэктеста: режим «вразброс» и неприкосновенность умолчания. ЗАЧЕМ. `ORDER BY id DESC` берёт последние ВСТАВЛЕННЫЕ строки, а Росреестр грузится пачками по домам: соседние id — это один дом и одна улица. Замер на проде 31.08.2026 по Екатеринбургу, выборка 200: последние 200 по id : 38 разных улиц, до 22 сделок с ОДНОЙ улицы случайные 200 : 129 разных улиц, максимум 6 с одной Выборка втрое беднее случайной — а на ней стояла витрина лэндинга (20 строк с ЧЕТЫРЁХ улиц, 16 из них с двух). ЧТО ЭТОТ ТЕСТ ДЕРЖИТ. Две вещи, и вторая не менее важна первой: 1. Режим «вразброс» действительно меняет порядок и воспроизводим по seed. 2. УМОЛЧАНИЕ НЕ ТРОНУТО — дефолтный путь обязан отдавать ТОТ ЖЕ объект SQL. Замороженный регресс-гейт сравнивает прогоны между собой; сменить умолчание значило бы молча обнулить сравнимость всей истории замеров. """ from __future__ import annotations import re from scripts.backtest_estimator import ( _SAMPLE_SQL, _SAMPLE_SQL_SCATTERED, _SAMPLE_SQL_SCATTERED_CITY, _sample_sql, ) def _sql(obj) -> str: return str(obj.text if hasattr(obj, "text") else obj) class TestУмолчаниеНеТронуто: def test_дефолтный_путь_отдаёт_тот_же_объект(self): # Именно identity, а не равенство текста: докстринг `_sample_sql` # обещает побайтовую неизменность, и «текст совпал» это обещание не # проверяет — объект мог быть пересобран с другими bind-параметрами. assert _sample_sql(None) is _SAMPLE_SQL assert _sample_sql(None, scattered=False) is _SAMPLE_SQL def test_умолчание_сортирует_по_id_и_не_знает_про_seed(self): sql = _sql(_SAMPLE_SQL) assert "ORDER BY id DESC" in sql assert ":seed" not in sql, "в умолчание протёк параметр режима вразброс" class TestРежимВразброс: def test_выбирается_явно_и_меняет_порядок(self): assert _sample_sql(None, scattered=True) is _SAMPLE_SQL_SCATTERED assert _sample_sql("Екатеринбург", scattered=True) is _SAMPLE_SQL_SCATTERED_CITY def test_порядок_воспроизводим_а_не_случаен(self): # random() дал бы разную выборку на каждом прогоне, и два замера # подряд отличались бы и из-за правки, и из-за состава выборки — # разделить вклады было бы нечем. for obj in (_SAMPLE_SQL_SCATTERED, _SAMPLE_SQL_SCATTERED_CITY): sql = _sql(obj) assert "md5(" in sql, "порядок обязан считаться от id и seed" assert ":seed" in sql assert re.search(r"\brandom\s*\(", sql) is None, ( "random() делает выборку неповторимой между прогонами" ) assert "ORDER BY id DESC" not in sql def test_городской_вариант_фильтрует_город_а_общий_нет(self): assert "city = CAST(:city AS text)" in _sql(_SAMPLE_SQL_SCATTERED_CITY) assert "city = CAST(:city AS text)" not in _sql(_SAMPLE_SQL_SCATTERED) def test_оба_режима_отбирают_по_одним_и_тем_же_условиям(self): # Иначе разница между режимами объяснялась бы не порядком, а другим # набором сделок — и сравнение двух прогонов ничего бы не значило. общие = [ "source = 'rosreestr'", "geom IS NOT NULL", "price_per_m2 BETWEEN", "rooms IS NOT NULL", "area_m2 > 0", "deal_date >= CAST(:since AS date)", ] база = _sql(_SAMPLE_SQL) вразброс = _sql(_SAMPLE_SQL_SCATTERED) for условие in общие: assert условие in база, условие assert условие in вразброс, условие