MERA: карточка прогресса оценки показывает выдуманные стадии и ссылается на серверную механику, которой в этом пути нет #3081

Closed
opened 2026-08-24 16:51:37 +00:00 by bot-backend · 1 comment
Collaborator

Симптом

На странице оценки (tradein-mvp/frontend/src/app/page.tsx:251) рендерится карточка «Шаг B · Агрегация / Сбор данных по квартире» (SourcesProgress.tsx:75-76): пять строк-источников, у каждой своя полоска и свой статус. Выглядит как живой мониторинг сбора, но состояний ровно два. Пока идёт запрос — все источники разом «сбор...» (SourcesProgress.tsx:145), полоска жёстко 60% (:131); пришёл ответ — единовременный пересчёт по estimate.sources_used.

Чем грозит: прогресс не отражает реального состояния расчёта — пользователь не отличит «думает» от «завис», а подписи «timeout — нет ответа» (:146), «готово» (:144), «N лотов» (:141) читаются как факты по конкретному источнику. Отдельно: подпись :90 называет пользователю Celery group и таймаут — серверная механика течёт в UI-текст.

Почему так

  • Компонент без хуков, чистая функция от двух пропов (SourcesProgress.tsx:27); статус строки — тернарник от булева isPending (:47), overallPct — арифметика от doneCount (:69).
  • isPending = mutation.isPending одного блокирующего POST /api/v1/trade-in/estimate (page.tsx:175), промежуточных апдейтов нет: grep по EventSource / WebSocket / event-stream в tradein-mvp/frontend/src — 0 хитов.
  • Строки — не стадии расчёта, а ростер источников из lib/source-registry.ts:44-47 плюс захардкоженный «Росреестр (внутр.)» (SourcesProgress.tsx:55).
  • Эндпоинта статуса НЕТ: backend/app/api/v1/trade_in.py:408 — POST синхронный, 0 хитов по AsyncResult / task_id / StreamingResponse; роуты /quota:485, /estimate/{id}:499, /pdf:721. Опрашивать нечего.

Что сделать (чеклист)

Выбрать одну развилку.

Вариант A — честная неопределённая индикация (только фронт):

  • на время pending убрать пофайловые полоски и статусы (SourcesProgress.tsx:131,145,146), оставить один неопределённый индикатор на карточку
  • ростер держать нейтральным («ожидает запрос», :149) до ответа; раскрашивать строки только по estimate.sources_used
  • переписать подпись :90 без Celery и таймаута; референс тона — legacy-заглушка frontend/src/components/trade-in/EstimateProgress.tsx:40 («Считаем оценку…»)

Вариант B — реальный статус с бэкенда (cross-scope, дороже):

  • бэкенд: асинхронный запуск + task_id + эндпоинт статуса — сейчас его нет (trade_in.py:408)
  • фронт: polling/stream вместо isPending, статусы строк от реальных событий источников
  • подпись :90 оставлять только если механика действительно видна пользователю

Как проверить

  • запустить оценку: в первые секунды нет одновременных «сбор...» и 60%-полосок у всех пяти источников
  • в тексте карточки нет Celery/таймаута (:90); футер :159 про частичный результат сохранён
  • после ответа состояния строк соответствуют estimate.sources_used, «нет данных» (:149) — только у отсутствующих
  • проверить при правке (не проверялось): trade-in.css, .progress-card / .src-bar — JS-таймеров нет, но CSS-keyframes могут добавлять ложную анимацию
## Симптом На странице оценки (`tradein-mvp/frontend/src/app/page.tsx:251`) рендерится карточка «Шаг B · Агрегация / Сбор данных по квартире» (`SourcesProgress.tsx:75-76`): пять строк-источников, у каждой своя полоска и свой статус. Выглядит как живой мониторинг сбора, но состояний ровно два. Пока идёт запрос — все источники разом «сбор...» (`SourcesProgress.tsx:145`), полоска жёстко 60% (`:131`); пришёл ответ — единовременный пересчёт по `estimate.sources_used`. Чем грозит: прогресс не отражает реального состояния расчёта — пользователь не отличит «думает» от «завис», а подписи «timeout — нет ответа» (`:146`), «готово» (`:144`), «N лотов» (`:141`) читаются как факты по конкретному источнику. Отдельно: подпись `:90` называет пользователю Celery group и таймаут — серверная механика течёт в UI-текст. ## Почему так - Компонент без хуков, чистая функция от двух пропов (`SourcesProgress.tsx:27`); статус строки — тернарник от булева `isPending` (`:47`), `overallPct` — арифметика от `doneCount` (`:69`). - `isPending` = `mutation.isPending` одного блокирующего `POST /api/v1/trade-in/estimate` (`page.tsx:175`), промежуточных апдейтов нет: grep по EventSource / WebSocket / event-stream в `tradein-mvp/frontend/src` — 0 хитов. - Строки — не стадии расчёта, а ростер источников из `lib/source-registry.ts:44-47` плюс захардкоженный «Росреестр (внутр.)» (`SourcesProgress.tsx:55`). - Эндпоинта статуса НЕТ: `backend/app/api/v1/trade_in.py:408` — POST синхронный, 0 хитов по AsyncResult / task_id / StreamingResponse; роуты `/quota:485`, `/estimate/{id}:499`, `/pdf:721`. Опрашивать нечего. ## Что сделать (чеклист) Выбрать одну развилку. Вариант A — честная неопределённая индикация (только фронт): - [ ] на время pending убрать пофайловые полоски и статусы (`SourcesProgress.tsx:131,145,146`), оставить один неопределённый индикатор на карточку - [ ] ростер держать нейтральным («ожидает запрос», `:149`) до ответа; раскрашивать строки только по `estimate.sources_used` - [ ] переписать подпись `:90` без Celery и таймаута; референс тона — legacy-заглушка `frontend/src/components/trade-in/EstimateProgress.tsx:40` («Считаем оценку…») Вариант B — реальный статус с бэкенда (cross-scope, дороже): - [ ] бэкенд: асинхронный запуск + task_id + эндпоинт статуса — сейчас его нет (`trade_in.py:408`) - [ ] фронт: polling/stream вместо `isPending`, статусы строк от реальных событий источников - [ ] подпись `:90` оставлять только если механика действительно видна пользователю ## Как проверить - запустить оценку: в первые секунды нет одновременных «сбор...» и 60%-полосок у всех пяти источников - в тексте карточки нет Celery/таймаута (`:90`); футер `:159` про частичный результат сохранён - после ответа состояния строк соответствуют `estimate.sources_used`, «нет данных» (`:149`) — только у отсутствующих - проверить при правке (не проверялось): `trade-in.css`, `.progress-card` / `.src-bar` — JS-таймеров нет, но CSS-keyframes могут добавлять ложную анимацию
bot-backend added the
priority/p3
ux
tech-debt
tradein
scope/frontend
labels 2026-08-24 16:51:37 +00:00
Author
Collaborator

Сделано вариантом A (PR #3101, на проде с 26.08, строка нового текста проверена в бандле контейнера).

На pending: нейтральный ростер «ожидает ответ» без пофайловых полосок, «опрашиваем…» вместо «0/5», честная неопределённая анимация вместо Math.min(95, …), подпись без Celery/таймаута. После ответа поведение прежнее (раскраска по sources_used). Недостижимые ветки error/loading у строк удалены — «timeout — нет ответа» больше не существует как текст. Preview-страница /ui-preview/estimate рендерит оба состояния.

Вариант B отклонён сознательно: /estimate остаётся синхронным по решениям #3082/#3083 — async-инфраструктура ради прогресс-бара шла бы против них. Если когда-нибудь появится реальный статус с бэкенда, карточка к нему готова (раскраска уже данные-ориентированная).

Скриншот-проверка обоих состояний на dev сделана. Закрываю.

Сделано вариантом A (PR #3101, на проде с 26.08, строка нового текста проверена в бандле контейнера). На pending: нейтральный ростер «ожидает ответ» без пофайловых полосок, «опрашиваем…» вместо «0/5», честная неопределённая анимация вместо `Math.min(95, …)`, подпись без Celery/таймаута. После ответа поведение прежнее (раскраска по `sources_used`). Недостижимые ветки `error`/`loading` у строк удалены — «timeout — нет ответа» больше не существует как текст. Preview-страница `/ui-preview/estimate` рендерит оба состояния. Вариант B отклонён сознательно: `/estimate` остаётся синхронным по решениям #3082/#3083 — async-инфраструктура ради прогресс-бара шла бы против них. Если когда-нибудь появится реальный статус с бэкенда, карточка к нему готова (раскраска уже данные-ориентированная). Скриншот-проверка обоих состояний на dev сделана. Закрываю.
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#3081
No description provided.