Задача landing_showcase_deals не запускалась вообще: строки в
scrape_schedules не было (0 строк по '%showcase%' на проде 12.09), а в
реестре product_handlers — обработчика. Пересчёт был ручным шагом, и
лэндинг показывал прогон от 30.08 — тринадцать суток.
Что сделано:
- Handler `landing_showcase_deals` в product_handlers: тело задачи
писалось под `python -m` и про run_id не знает, поэтому done/failed
ставит обработчик (как у refresh_search_matview).
- Миграция 303 сеет расписание: enabled=true, окно 06:00–07:00 UTC,
interval_days=1. Такт суточный не из-за данных — сделки Росреестра
квартальные, — а из-за кода: прогноз считает тот же спайн оценщика,
что и боевой расчёт, и любой деплой меняет числа на витрине, не
трогая ни одной сделки.
- Эта же строка заводит витрину в СУЩЕСТВУЮЩИЙ монитор свежести:
сводка просроченных источников (emit_stale_digest, #2670) ходит по
включённым расписаниям и бьёт ERROR → GlitchTip, когда источник
молчит дольше 3× своего такта. Своего монитора не заводим: витрина
была невидима не потому, что сводка не умеет про неё говорить, а
потому, что источника для сводки не существовало.
- На странице под таблицей — дата прогона рядом со счётчиками:
«Витрина пересчитана 30.08.2026». computed_at ручка /showcase отдавала
и раньше, фронт его не показывал; даты нет — предложения нет.
Тесты: обработчик резолвится тем же resolve_handler, что и боевой
_dispatch; миграция на живой БД реально кладёт строку и не задваивает
её при повторе; настоящий запрос сводки видит витрину и отдаёт её
просроченной после 3× такта (число тактов — литерал из приёмки, не
константа кита: взятое из неё ожидание уезжало вместе с ней и держало
тесты зелёными при факторе 3650).
Closes#3469
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>