-- 277: итог пересчёта витрины лэндинга — счётчики рядом со строками. -- -- ЗАЧЕМ ОТДЕЛЬНАЯ ТАБЛИЦА, А НЕ КОЛОНКИ В landing_showcase_deals. Счётчики — -- факт ПРОГОНА, а не строки: на 20 строк они были бы продублированы 20 раз, а -- в самом важном случае — когда показывать оказалось нечего — исчезли бы -- вместе со строками. «Рассмотрено 200, показывать нечего» обязано доезжать до -- фронта ровно так же, как «рассмотрено 200, показано 20». -- -- ЗАЧЕМ ВООБЩЕ. Витрина показывает 20 сделок «МЕРА сказала X — продали за Y». -- Без чисел рядом эти 20 строк читаются как «столько и было»: посетитель не -- может отличить выборку из работы оценщика от её лучшего хвоста. Поэтому -- ручка /api/public/mera/showcase отдаёт вместе со строками, сколько сделок -- рассмотрено, сколько годных строк не поместилось и по какому правилу -- отбраковано остальное. -- -- rejection_rule — ТЕКСТ, А НЕ КОД ПРАВИЛА. Правило живёт в -- app/tasks/landing_showcase_deals.REJECTION_RULE и пишется сюда тем прогоном, -- который эти числа и посчитал: подпись под витриной обязана описывать ТОТ -- отбор, что дал эти строки, а не тот, что задеплоен сегодня. -- -- computed_at совпадает со строками батча: задача пишет строки и этот итог -- ОДНОЙ транзакцией, а now() в Postgres — время начала транзакции. BEGIN; -- Конвенция проекта (#2752): блокирующий DDL идёт под lock_timeout, иначе он -- встанет в очередь за чужой сессией и утащит за собой запросы приложения. SET LOCAL lock_timeout = '5s'; CREATE TABLE IF NOT EXISTS landing_showcase_runs ( id bigserial PRIMARY KEY, computed_at timestamptz NOT NULL DEFAULT now(), considered integer NOT NULL, priced integer NOT NULL, no_prediction integer NOT NULL, incomplete integer NOT NULL, eligible integer NOT NULL, written integer NOT NULL, with_district integer NOT NULL, rejection_rule text NOT NULL ); CREATE INDEX IF NOT EXISTS idx_landing_showcase_runs_computed_at ON landing_showcase_runs (computed_at DESC); COMMIT;