site-finder §4.1: «Применить» у весов POI ни к чему не приводит + системные пресеты не видны #2790

Closed
opened 2026-08-07 10:37:43 +00:00 by bot-backend · 2 comments
Collaborator

Найдено при разборе #2782 (там чинилась недостижимость CRUD профилей весов). Обе находки — на том же пути, но к снятой авторизации отношения не имеют, поэтому вынесены отдельно.

1. «Применить» в §4.1 нового Site Finder ничего не применяет

frontend/src/components/site-finder/analysis/Section3SettingsAndCompetitors.tsx:93weights это write-only state:

const [weights, setWeights] = useState<Record<PoiCategoryKey, number>>(…)
function handleWeightsChange(newWeights, _profileId) { setWeights(newWeights); }

Единственный читатель — currentWeights={weights} обратно в ту же панель (строка 262). До analyze значение не доезжает: в AnalysisPageContent.tsx слова weights нет вообще. То есть на /site-finder/analysis/[cad] ползунки POI-весов и кнопка «Применить» декоративны — score не пересчитывается.

На легаси-странице (/legacy/site-finder) тот же обработчик ставит pendingWeightsChange и запускает повторный analyze, т.е. там всё работает. Расхождение появилось при переносе панели в новый отчёт.

Что решить: либо дотянуть проводку до re-analyze (как в легаси), либо убрать кнопку «Применить» из §4.1 и оставить панель только для сохранения профилей. Второе — не UI-косметика: пользователь сейчас двигает ползунки и думает, что видит другой расчёт.

2. Системные пресеты не видны никому

В user_weight_profiles на проде лежат три пресета под user_id='__system__' (Эконом / Комфорт / Бизнес, засеяны 16.05.2026, data/sql/100_user_weight_profiles_default_seed.sql). Бэкенд умеет их отдавать — GET …/weight-profiles?include_system=truelist_profiles_with_system(), проверено на проде, возвращает 4 записи вместо одной.

Фронт этот параметр не передаёт никогда (frontend/src/lib/api/weightProfiles.ts), поэтому у пользователя без своих профилей дропдаун пустой.

Просто добавить include_system=true нельзя — получится новая ложь на экране. resolve_weights() (backend/app/services/site_finder/weight_profiles.py:349) ищет профиль как get_profile(db, user_id, profile_id), то есть scoped к владельцу. Пресет с user_id='__system__', выбранный пользователем admin, не найдётся → тихий откат на системные веса, при этом ответ отрапортует weights_profile.source = "profile". Ровно тот же класс бага, что чинили в #2782 (там было profile_id без profile_user_id).

Порядок: сначала научить resolve_weights резолвить __system__-профили (или отдавать пресеты с признаком, по которому фронт шлёт веса inline, а не profile_id), потом включать include_system в UI.

3. Мелочь рядом

useUpdateProfile / useDeleteProfile в weightProfiles.ts не вызываются ниоткуда. В UI сейчас есть только list + create — переименовать или удалить сохранённый профиль нельзя, хотя эндпоинты живы и покрыты тестами.

Найдено при разборе #2782 (там чинилась недостижимость CRUD профилей весов). Обе находки — на том же пути, но к снятой авторизации отношения не имеют, поэтому вынесены отдельно. ## 1. «Применить» в §4.1 нового Site Finder ничего не применяет `frontend/src/components/site-finder/analysis/Section3SettingsAndCompetitors.tsx:93` — `weights` это write-only state: ``` const [weights, setWeights] = useState<Record<PoiCategoryKey, number>>(…) function handleWeightsChange(newWeights, _profileId) { setWeights(newWeights); } ``` Единственный читатель — `currentWeights={weights}` обратно в ту же панель (строка 262). До `analyze` значение не доезжает: в `AnalysisPageContent.tsx` слова `weights` нет вообще. То есть на `/site-finder/analysis/[cad]` ползунки POI-весов и кнопка «Применить» декоративны — score не пересчитывается. На легаси-странице (`/legacy/site-finder`) тот же обработчик ставит `pendingWeightsChange` и запускает повторный analyze, т.е. там всё работает. Расхождение появилось при переносе панели в новый отчёт. Что решить: либо дотянуть проводку до re-analyze (как в легаси), либо убрать кнопку «Применить» из §4.1 и оставить панель только для сохранения профилей. Второе — не UI-косметика: пользователь сейчас двигает ползунки и думает, что видит другой расчёт. ## 2. Системные пресеты не видны никому В `user_weight_profiles` на проде лежат три пресета под `user_id='__system__'` (Эконом / Комфорт / Бизнес, засеяны 16.05.2026, `data/sql/100_user_weight_profiles_default_seed.sql`). Бэкенд умеет их отдавать — `GET …/weight-profiles?include_system=true` → `list_profiles_with_system()`, проверено на проде, возвращает 4 записи вместо одной. Фронт этот параметр не передаёт никогда (`frontend/src/lib/api/weightProfiles.ts`), поэтому у пользователя без своих профилей дропдаун пустой. **Просто добавить `include_system=true` нельзя** — получится новая ложь на экране. `resolve_weights()` (`backend/app/services/site_finder/weight_profiles.py:349`) ищет профиль как `get_profile(db, user_id, profile_id)`, то есть scoped к владельцу. Пресет с `user_id='__system__'`, выбранный пользователем `admin`, не найдётся → тихий откат на системные веса, при этом ответ отрапортует `weights_profile.source = "profile"`. Ровно тот же класс бага, что чинили в #2782 (там было `profile_id` без `profile_user_id`). Порядок: сначала научить `resolve_weights` резолвить `__system__`-профили (или отдавать пресеты с признаком, по которому фронт шлёт веса inline, а не `profile_id`), потом включать `include_system` в UI. ## 3. Мелочь рядом `useUpdateProfile` / `useDeleteProfile` в `weightProfiles.ts` не вызываются ниоткуда. В UI сейчас есть только list + create — переименовать или удалить сохранённый профиль нельзя, хотя эндпоинты живы и покрыты тестами.
Author
Collaborator

Working on this in PR #2810 — п.1 починен (веса из §4.1 доезжают до /analyze), п.2 сделан без правок бэкенда (пресет уходит inline, а не profile_id), п.3 мёртвые хуки удалены.

Working on this in PR #2810 — п.1 починен (веса из §4.1 доезжают до /analyze), п.2 сделан без правок бэкенда (пресет уходит inline, а не profile_id), п.3 мёртвые хуки удалены.
Author
Collaborator

Прод 12.08: PR #2810 смержен и доехал — закрываю все три пункта

п.1 «Применить» действительно применяет. Write-only состояния больше нет: веса подняты в AnalysisPageContent (appliedWeights), розданы через AnalyzeWeightsContext, и — главное — контекст читается в lib/site-finder-api.ts:525-558:

const weights = useContext(AnalyzeWeightsContext);
const weightsKey = weights ? JSON.stringify(Object.entries(weights).sort()) : ...
queryKey: ["parcel-analyze", cad, horizon, weightsKey],
const analyzeInit: RequestInit = weights
  ? { method: "POST", signal, body: JSON.stringify({ weights }) } : ...

Веса меняют и ключ кэша запроса, и тело /analyze — score пересчитывается. Специально проверил, что новая проводка не оказалась вторым write-only состоянием того же класса: у контекста есть потребитель вне файла-провайдера, и он не тестовый.

п.2 системные пресеты видны, и без ловушки, которой задача опасалась. lib/api/weightProfiles.ts:123 шлёт include_system=true, а выбранный пресет уходит inline весами, а не profile_id. Значит resolve_weights() не идёт в get_profile(db, user_id, profile_id) и не может тихо откатиться на дефолт, отрапортовав weights_profile.source = "profile" — тот самый класс бага из #2782 не воспроизводится. Правок бэкенда не потребовалось, порядок из задачи («сначала научить резолвер, потом включать в UI») обойдён законно.

На проде в user_weight_profiles: __system__ — 3 записи (Эконом / Комфорт / Бизнес), admin — 1.

п.3 мёртвые useUpdateProfile / useDeleteProfile удалены, на их месте комментарий со ссылкой на #2790 п.3.

Доехало до прода, а не только смержено: в работающем gendesign-frontend-1 (BUILD_ID fxwAPQaC967xs3YC6KZ_w, контейнер поднят 10.08) строка include_system присутствует в .next/static. До #2810 фронт этого параметра не отправлял никогда — значит в бандле именно эта правка, а не «похожий код».

## Прод 12.08: PR #2810 смержен и доехал — закрываю все три пункта **п.1 «Применить» действительно применяет.** Write-only состояния больше нет: веса подняты в `AnalysisPageContent` (`appliedWeights`), розданы через `AnalyzeWeightsContext`, и — главное — контекст **читается** в `lib/site-finder-api.ts:525-558`: ```ts const weights = useContext(AnalyzeWeightsContext); const weightsKey = weights ? JSON.stringify(Object.entries(weights).sort()) : ... queryKey: ["parcel-analyze", cad, horizon, weightsKey], const analyzeInit: RequestInit = weights ? { method: "POST", signal, body: JSON.stringify({ weights }) } : ... ``` Веса меняют и ключ кэша запроса, и тело `/analyze` — score пересчитывается. Специально проверил, что новая проводка не оказалась вторым write-only состоянием того же класса: у контекста есть потребитель **вне** файла-провайдера, и он не тестовый. **п.2 системные пресеты видны, и без ловушки, которой задача опасалась.** `lib/api/weightProfiles.ts:123` шлёт `include_system=true`, а выбранный пресет уходит **inline весами**, а не `profile_id`. Значит `resolve_weights()` не идёт в `get_profile(db, user_id, profile_id)` и не может тихо откатиться на дефолт, отрапортовав `weights_profile.source = "profile"` — тот самый класс бага из #2782 не воспроизводится. Правок бэкенда не потребовалось, порядок из задачи («сначала научить резолвер, потом включать в UI») обойдён законно. На проде в `user_weight_profiles`: `__system__` — 3 записи (Эконом / Комфорт / Бизнес), `admin` — 1. **п.3** мёртвые `useUpdateProfile` / `useDeleteProfile` удалены, на их месте комментарий со ссылкой на #2790 п.3. **Доехало до прода, а не только смержено:** в работающем `gendesign-frontend-1` (`BUILD_ID fxwAPQaC967xs3YC6KZ_w`, контейнер поднят 10.08) строка `include_system` присутствует в `.next/static`. До #2810 фронт этого параметра не отправлял никогда — значит в бандле именно эта правка, а не «похожий код».
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#2790
No description provided.