From 609768a3bdef117155e10b93057a15fb760f482f Mon Sep 17 00:00:00 2001 From: bot-backend Date: Thu, 13 Aug 2026 22:01:49 +0300 Subject: [PATCH] =?UTF-8?q?fix(tradein/sql):=20=D0=BE=D0=BA=D0=BD=D0=BE=20?= =?UTF-8?q?23=E2=86=920=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=2023?= =?UTF-8?q?=E2=86=9224=20=E2=80=94=20=D0=BC=D0=B8=D0=B3=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=B8=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=8F=D0=BB=D0=B0=D1=81=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI поймал то, что статические проверки пропустили: пять строк получили window_start_hour=23, window_end_hour=24, а констрейнт из 052_ требует BETWEEN 0 AND 23. Весь INSERT падал, миграция не применялась. Час 23 заворачивается в 0 — планировщик поддерживает окна через полночь явно (window_end_hour <= window_start_hour → cross-midnight). Тест test_migration_262_windows_are_one_hour и пропустил ошибку: он требовал end == start + 1, что для 23 даёт ровно 24. Теперь сверяет (start + 1) % 24, плюс отдельная проверка на диапазон 0..23 — то есть на то самое условие прод-констрейнта. Проверено сухим прогоном на боевой схеме: BEGIN → миграция → ROLLBACK, psql exit 0, число строк не изменилось. --- ...chedules_seed_oblast_city_sweeps_wave2.sql | 10 ++++----- ..._migration_262_oblast_city_sweeps_wave2.py | 21 ++++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/tradein-mvp/backend/data/sql/262_scrape_schedules_seed_oblast_city_sweeps_wave2.sql b/tradein-mvp/backend/data/sql/262_scrape_schedules_seed_oblast_city_sweeps_wave2.sql index 8a0c0520..e03493d7 100644 --- a/tradein-mvp/backend/data/sql/262_scrape_schedules_seed_oblast_city_sweeps_wave2.sql +++ b/tradein-mvp/backend/data/sql/262_scrape_schedules_seed_oblast_city_sweeps_wave2.sql @@ -280,7 +280,7 @@ VALUES 'avito_city_sweep_verkhnyaya_tura', false, 23, - 24, + 0, ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 23)) AT TIME ZONE 'UTC', '{"pages_per_anchor": 3, "detail_top_n": 0, "request_delay_sec": 7.0, "enrich_houses": true, "radius_m": 3000, "interval_days": 3, "city": "verkhnyaya_tura"}'::jsonb ), @@ -427,7 +427,7 @@ VALUES 'cian_city_sweep_sysert', false, 23, - 24, + 0, ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 23)) AT TIME ZONE 'UTC', '{"pages_per_anchor": 3, "request_delay_sec": 5, "radius_m": 3000, "detail_top_n": 10, "enrich_houses": true, "interval_days": 3, "city": "sysert"}'::jsonb ), @@ -555,7 +555,7 @@ VALUES 'cian_city_sweep_lesnoy', false, 23, - 24, + 0, ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 23)) AT TIME ZONE 'UTC', '{"pages_per_anchor": 3, "request_delay_sec": 5, "radius_m": 3000, "detail_top_n": 10, "enrich_houses": true, "interval_days": 3, "city": "lesnoy"}'::jsonb ), @@ -766,7 +766,7 @@ VALUES 'yandex_city_sweep_degtyarsk', false, 23, - 24, + 0, ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 23)) AT TIME ZONE 'UTC', '{"pages_per_anchor": 3, "request_delay_sec": 9, "radius_m": 25000, "interval_days": 3, "city": "degtyarsk"}'::jsonb ), @@ -910,7 +910,7 @@ VALUES 'yandex_city_sweep_verkhnyaya_tura', false, 23, - 24, + 0, ((CURRENT_DATE + INTERVAL '1 day') + make_interval(hours => 23)) AT TIME ZONE 'UTC', '{"pages_per_anchor": 3, "request_delay_sec": 9, "radius_m": 25000, "interval_days": 3, "city": "verkhnyaya_tura"}'::jsonb ), diff --git a/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py b/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py index 23cf7656..75da91a6 100644 --- a/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py +++ b/tradein-mvp/backend/tests/test_migration_262_oblast_city_sweeps_wave2.py @@ -393,8 +393,27 @@ def _row_windows(path: Path) -> list[tuple[str, str, int, int]]: def test_migration_262_windows_are_one_hour() -> None: + """Окно ровно на час. Час 23 заворачивается в 0 (cross-midnight) — scheduler + это поддерживает явно (`window_end_hour <= window_start_hour` → окно через + полночь, см. scraper_kit.orchestration.scheduler).""" for provider, slug, start, end in _row_windows(_MIGRATION_262): - assert end == start + 1, f"{provider}_{slug}: window [{start},{end}) не 1-часовое" + expected = (start + 1) % 24 + assert ( + end == expected + ), f"{provider}_{slug}: window [{start},{end}) не 1-часовое (ожидали end={expected})" + + +def test_migration_262_window_hours_satisfy_db_check_constraint() -> None: + """Оба часа обязаны попадать в 0..23 — иначе миграция не применится вовсе. + + Регресс: первая версия давала start=23, end=24 у пяти строк. Проверка + «end == start + 1» это пропускала, а прод-констрейнт `window_end_range` + (052_scrape_schedules.sql: CHECK (window_end_hour BETWEEN 0 AND 23)) — + нет, и весь INSERT падал в CI на реальном postgres. + """ + for provider, slug, start, end in _row_windows(_MIGRATION_262): + assert 0 <= start <= 23, f"{provider}_{slug}: window_start_hour={start} вне 0..23" + assert 0 <= end <= 23, f"{provider}_{slug}: window_end_hour={end} вне 0..23" def test_migration_262_windows_avoid_ekb_and_wave1_occupied_hours() -> None: