feat(weights): finish #114 Custom POI weights — inline pass-through + Edit/Delete UI + live preview #201

Closed
opened 2026-05-16 10:16:29 +00:00 by lekss361 · 0 comments
Owner

Source: Re-audit #114 на 2026-05-16 — backend + UI panel есть, но критические usability gaps. Issue #114 был closed 2026-05-14 преждевременно.

Что НЕ работает сейчас

Gap Impact Where
🔴 Inline weights не передаются в /analyze если профиль не сохранён User двигает слайдеры, жмёт «Применить» + «Анализировать» — backend ignores draft weights и берёт system defaults. UI создаёт ложное впечатление работающей feature. WeightProfilePanel.tsxuseSiteAnalysis.tsactiveProfileId=null path
🔴 CRUD требует admin_token Public user не может save/load/delete свои профили вообще. Profile save/load gated {!!adminToken && ...} WeightProfilePanel.tsx — adminToken из localStorage.getItem("admin_token")
🟠 Update/Delete UI отсутствует useUpdateProfile + useDeleteProfile hooks реализованы в lib/api/weightProfiles.ts, но ни одна кнопка их не вызывает. Только Create wired. WeightProfilePanel.tsx
🟡 Нет live preview После «Применить» — ещё нужен click «Анализировать». User experience disconnected. page.tsx — handleApply просто сохраняет state, не re-fetches
🟡 11 категорий вместо 25 Issue #114 acceptance claim "25 sliders". Реализовано 11 (school/kindergarten/pharmacy/hospital/shop_mall/shop_supermarket/shop_small/park/bus_stop/metro_stop/tram_stop). Backend ALLOWED_CATEGORIES тоже 11. lib/api/weightProfiles.ts + backend weight_profiles.py

Scope для нового PR (frontend + minor backend)

Phase 1 — Critical inline pass-through (must)

Backend (backend/app/api/v1/parcels.py):

  • POST /analyze принимает optional weights: dict[str, float] | None в body (или query)
  • Если задан — применяет именно его, без обязательного profile_id
  • Бэкенд validation: same MIN_WEIGHT / MAX_WEIGHT / ALLOWED_CATEGORIES гарды

Frontend (WeightProfilePanel.tsx + useSiteAnalysis.ts):

  • При «Применить» (или auto-trigger) — передавать draft weights inline в /analyze request
  • Убрать обязательность admin_token для inline draft (только для save/load named profile)
  • activeProfileId=null НЕ должно блокировать применение draft weights

Phase 2 — Live preview UX

  • При drag slider → debounced 500ms → auto re-fetch analyze с inline weights
  • Visible delta: «Score было 21.3 → стало 23.7» badge

Phase 3 — Update/Delete UI

  • Per-profile row: «✎ Изменить» + «🗑 Удалить» buttons
  • Modal/inline edit для weights + name + description
  • Hooks уже готовы (useUpdateProfile, useDeleteProfile)

Phase 4 (optional) — 25 categories расширение

Если acceptance требует — расширить ALLOWED_CATEGORIES до 25 (per #114 original spec):

  • Backend _POI_WEIGHTS extend
  • Frontend POI_CATEGORIES array extend + POI_LABELS
  • Backward-compat: existing 11-key profiles остаются valid

Phase 5 — Public CRUD (если решим что user-facing)

Если weight profiles должны быть user-scoped (без admin):

  • Auth-based user_id вместо admin_token guard
  • Per-user RLS на user_weight_profiles (schema уже есть)

Acceptance

  • Inline draft weights ВЛИЯЮТ на score без обязательного profile save
  • Edit existing profile через UI (PATCH endpoint)
  • Delete existing profile через UI
  • Live preview (debounced auto re-fetch) при drag слайдера
  • (опц) 25 categories
  • (опц) Public CRUD без admin token

Owner

backend-engineer (Phase 1 backend) + frontend-engineer (Phase 1-3 UI) + database-expert (если Phase 4-5 нужны schema changes)

Effort

  • Phase 1: ~1-1.5 дня (backend +20 LOC, frontend +40 LOC, tests)
  • Phase 2: 0.5 день
  • Phase 3: 0.5-1 день
  • Phase 4-5: opt, ~1-2 дня

Total minimum (Phase 1-3): ~2-3 дня.

  • Supersedes: часть acceptance из #114 (закрыто преждевременно)
  • Audit: session 2026-05-16
  • Related: #43 I2 (3-preset → flexible custom)
**Source:** Re-audit #114 на 2026-05-16 — backend + UI panel есть, но критические usability gaps. Issue #114 был closed 2026-05-14 преждевременно. ## Что НЕ работает сейчас | Gap | Impact | Where | |---|---|---| | 🔴 **Inline weights не передаются в `/analyze` если профиль не сохранён** | User двигает слайдеры, жмёт «Применить» + «Анализировать» — backend ignores draft weights и берёт system defaults. UI создаёт ложное впечатление работающей feature. | `WeightProfilePanel.tsx` → `useSiteAnalysis.ts` — `activeProfileId=null` path | | 🔴 **CRUD требует admin_token** | Public user не может save/load/delete свои профили вообще. Profile save/load gated `{!!adminToken && ...}` | `WeightProfilePanel.tsx` — adminToken из `localStorage.getItem("admin_token")` | | 🟠 **Update/Delete UI отсутствует** | `useUpdateProfile` + `useDeleteProfile` hooks реализованы в `lib/api/weightProfiles.ts`, но ни одна кнопка их не вызывает. Только Create wired. | `WeightProfilePanel.tsx` | | 🟡 **Нет live preview** | После «Применить» — ещё нужен click «Анализировать». User experience disconnected. | `page.tsx` — handleApply просто сохраняет state, не re-fetches | | 🟡 **11 категорий вместо 25** | Issue #114 acceptance claim "25 sliders". Реализовано 11 (school/kindergarten/pharmacy/hospital/shop_mall/shop_supermarket/shop_small/park/bus_stop/metro_stop/tram_stop). Backend `ALLOWED_CATEGORIES` тоже 11. | `lib/api/weightProfiles.ts` + backend `weight_profiles.py` | ## Scope для нового PR (frontend + minor backend) ### Phase 1 — Critical inline pass-through (must) **Backend** (`backend/app/api/v1/parcels.py`): - `POST /analyze` принимает optional `weights: dict[str, float] | None` в body (или query) - Если задан — применяет именно его, без обязательного `profile_id` - Бэкенд validation: same `MIN_WEIGHT` / `MAX_WEIGHT` / `ALLOWED_CATEGORIES` гарды **Frontend** (`WeightProfilePanel.tsx` + `useSiteAnalysis.ts`): - При «Применить» (или auto-trigger) — передавать `draft` weights inline в `/analyze` request - Убрать обязательность admin_token для inline draft (только для save/load named profile) - `activeProfileId=null` НЕ должно блокировать применение draft weights ### Phase 2 — Live preview UX - При drag slider → debounced 500ms → auto re-fetch analyze с inline weights - Visible delta: «Score было 21.3 → стало 23.7» badge ### Phase 3 — Update/Delete UI - Per-profile row: «✎ Изменить» + «🗑 Удалить» buttons - Modal/inline edit для weights + name + description - Hooks уже готовы (`useUpdateProfile`, `useDeleteProfile`) ### Phase 4 (optional) — 25 categories расширение Если acceptance требует — расширить `ALLOWED_CATEGORIES` до 25 (per #114 original spec): - Backend `_POI_WEIGHTS` extend - Frontend `POI_CATEGORIES` array extend + `POI_LABELS` - Backward-compat: existing 11-key profiles остаются valid ### Phase 5 — Public CRUD (если решим что user-facing) Если weight profiles должны быть user-scoped (без admin): - Auth-based user_id вместо admin_token guard - Per-user RLS на `user_weight_profiles` (schema уже есть) ## Acceptance - [ ] Inline draft weights ВЛИЯЮТ на score без обязательного profile save - [ ] Edit existing profile через UI (PATCH endpoint) - [ ] Delete existing profile через UI - [ ] Live preview (debounced auto re-fetch) при drag слайдера - [ ] (опц) 25 categories - [ ] (опц) Public CRUD без admin token ## Owner `backend-engineer` (Phase 1 backend) + `frontend-engineer` (Phase 1-3 UI) + `database-expert` (если Phase 4-5 нужны schema changes) ## Effort - Phase 1: ~1-1.5 дня (backend +20 LOC, frontend +40 LOC, tests) - Phase 2: 0.5 день - Phase 3: 0.5-1 день - Phase 4-5: opt, ~1-2 дня Total minimum (Phase 1-3): ~2-3 дня. ## Links - **Supersedes:** часть acceptance из #114 (закрыто преждевременно) - **Audit:** session 2026-05-16 - **Related:** #43 I2 (3-preset → flexible custom)
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#201
No description provided.