Витрина лендинга: прогон с нулём строк завершается успехом, гасит часы свежести и снимает со страницы весь блок вместе с датой #3511

Open
opened 2026-09-12 16:38:46 +00:00 by bot-backend · 0 comments
Collaborator

Найдено при ревью PR #3509 (#3469). Становится актуальным ровно с того момента, как витрина начнёт пересчитываться автоматически.

Механика

app/tasks/landing_showcase_deals.py делает DELETE + INSERT одной транзакцией. Если после правки оценщика в полосу −5…+20 % не попадёт ни одна строка (или спайн не даст прогноза), прогон честно завершится done с written = 0, и витрина станет пустой.

Сводка просроченных источников при этом промолчит. Замер на живой схеме:

run_brought_data('done', {'considered': 200, 'eligible': 0, 'written': 0}) → True

Счётчики витрины (considered / eligible / written) не пересекаются с _RESULT_COUNTER_KEYS кита (total_seen / lots_fetched / unique_fetched / succeeded), поэтому _run_result_count возвращает None, и единственной мерой «принёс ли прогон данные» остаётся СТАТУС. Нулевой прогон обнуляет часы свежести ровно так же, как удачный.

Это тот же класс, что #1948 и «сторож, слепой по построению»: провал пишет тот же признак, что и успех.

Почему на странице этого не будет видно

AccuracyV3.tsx рендерит и таблицу, и подпись со счётчиками, и новую дату пересчёта внутри одного {deals.length > 0 && ...}. То есть пустой прогон снимает с экрана и сам признак возраста, который PR #3509 туда добавляет: посетитель увидит не «витрина пуста, пересчитана вчера», а отсутствие блока целиком.

Почему это не горело раньше

До #3509 задача запускалась только руками — за всю жизнь считанные разы. С суточным расписанием это становится ежесуточной возможностью: одна правка оценщика, сдвинувшая распределение расхождений за границы полосы, тихо опустошает блок публичного лендинга, и ни тревоги, ни следа.

Чинить

Самое дешёвое — в обработчике (app/services/product_handlers.py): при counters["written"] == 0 помечать прогон failed (или оставлять done, но писать logger.error со стабильным маркером). Тогда трое суток нулей дадут ту же тревогу, что и трое суток молчания.

Смежно стоит подумать, надо ли вообще давать полосе опустошать витрину: вариант «полоса не набрала ни одной строки → показываем прежний набор и говорим об этом» честнее пустого экрана. Это решение продукта, не инженерное.

Приёмка

Прогон с written = 0 не гасит часы свежести: сводка просроченных называет landing_showcase_deals через 3× такта, считая от последнего прогона, принёсшего строки, а не от последнего завершившегося. Тест по значению: прогон с нулём строк → источник в списке просроченных; прогон с непустым набором → не в списке.

Refs #3469, PR #3509, #1948.

Найдено при ревью PR #3509 (#3469). Становится актуальным ровно с того момента, как витрина начнёт пересчитываться автоматически. ## Механика `app/tasks/landing_showcase_deals.py` делает `DELETE` + `INSERT` одной транзакцией. Если после правки оценщика в полосу −5…+20 % не попадёт ни одна строка (или спайн не даст прогноза), прогон честно завершится `done` с `written = 0`, и витрина станет пустой. **Сводка просроченных источников при этом промолчит.** Замер на живой схеме: ``` run_brought_data('done', {'considered': 200, 'eligible': 0, 'written': 0}) → True ``` Счётчики витрины (`considered` / `eligible` / `written`) не пересекаются с `_RESULT_COUNTER_KEYS` кита (`total_seen` / `lots_fetched` / `unique_fetched` / `succeeded`), поэтому `_run_result_count` возвращает `None`, и единственной мерой «принёс ли прогон данные» остаётся СТАТУС. Нулевой прогон обнуляет часы свежести ровно так же, как удачный. Это тот же класс, что #1948 и «сторож, слепой по построению»: провал пишет тот же признак, что и успех. ## Почему на странице этого не будет видно `AccuracyV3.tsx` рендерит и таблицу, и подпись со счётчиками, и **новую дату пересчёта** внутри одного `{deals.length > 0 && ...}`. То есть пустой прогон снимает с экрана и сам признак возраста, который PR #3509 туда добавляет: посетитель увидит не «витрина пуста, пересчитана вчера», а отсутствие блока целиком. ## Почему это не горело раньше До #3509 задача запускалась только руками — за всю жизнь считанные разы. С суточным расписанием это становится ежесуточной возможностью: одна правка оценщика, сдвинувшая распределение расхождений за границы полосы, тихо опустошает блок публичного лендинга, и ни тревоги, ни следа. ## Чинить Самое дешёвое — в обработчике (`app/services/product_handlers.py`): при `counters["written"] == 0` помечать прогон `failed` (или оставлять `done`, но писать `logger.error` со стабильным маркером). Тогда трое суток нулей дадут ту же тревогу, что и трое суток молчания. Смежно стоит подумать, надо ли вообще давать полосе опустошать витрину: вариант «полоса не набрала ни одной строки → показываем прежний набор и говорим об этом» честнее пустого экрана. Это решение продукта, не инженерное. ## Приёмка Прогон с `written = 0` не гасит часы свежести: сводка просроченных называет `landing_showcase_deals` через 3× такта, считая от последнего прогона, **принёсшего строки**, а не от последнего завершившегося. Тест по значению: прогон с нулём строк → источник в списке просроченных; прогон с непустым набором → не в списке. Refs #3469, PR #3509, #1948.
Sign in to join this conversation.
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#3511
No description provided.