fix(site-finder): §4.1 «Применить» действительно применяет веса POI (#2790) #2810

Merged
bot-backend merged 1 commit from fix/2790-weights-apply into main 2026-08-10 08:40:00 +00:00
Collaborator

Summary

П.1 (видимая поломка). Постановка верна. weights в §4.1 был write-only: жил в Section31Settings и читался только обратно в ту же панель. Прод-доказательство (Playwright, участок 66:41:0702017:131): двигаю Парки 1.8→3.0 и Трамвайные −0.5→−2.0, жму «Применить», жду 120 с — скор 18.91 до и 18.91 после, за всё время один POST /analyze и тот без тела.

Веса подняты на страницу и уходят в запрос через AnalyzeWeightsContext, а не пропом: useParcelAnalyzeQuery зовут шесть мест (§1, §2, §4, §5, страница, /ptica) на общем ключе кэша ["parcel-analyze", cad, horizon]. Проп, забытый в одной секции, развёл бы ключи — половина страницы считала бы по одним весам, половина по другим, и дорогой (10-30 c) analyze ушёл бы дважды. Контекст держит всех потребителей одного ключа на одном значении по построению.

Веса шлём inline, а не через profile_id: тело запроса равно ползункам, ответ рапортует source="inline" — расхождению между показанным и посчитанным взяться неоткуда.

После правки (тот же сценарий на собранном фронте против прод-бэкенда): 18.91 → 17.95, второй POST несёт {"weights":{…,"park":3,…,"tram_stop":-2}}. Живой прод-запрос с теми же весами даёт ровно 17.95.

П.2 (системные пресеты). Постановка верна, но требование «сначала бэкенд» снимается: пресет не обязательно адресовать через profile_id. Теперь include_system=true в списке, а «Применить» на пресете отдаёт profileId = null → уходят inline-веса пресета. Тихому откату неоткуда взяться, бэкенд не тронут.

Прод-проверка: дропдаун стал По умолчанию · admin ★ · Эконом · пресет · Комфорт · пресет · Бизнес · пресет; выбор «Бизнес» → тело metro_stop 2.5 / shop_mall 2.0 / park 2.0 (совпадает с сидом 100_user_weight_profiles_default_seed.sql) → скор 13.13, source="inline", profile_id=null.

П.3. useUpdateProfile / useDeleteProfile удалены. За три месяца в проде 1 профиль на всю базу (admin, создан 15.05.2026, updated_at = created_at) + 3 системных пресета: ни одного обновления, ни одной попытки удаления. Достраивать UI под нулевой спрос дороже, чем убрать мёртвый код; PUT/DELETE на бэкенде живы и покрыты тестами.

Test plan

  • AnalysisPageContent.weights.test.tsx — рендерит настоящую страницу с настоящей §4.1 и настоящим useParcelAnalyzeQuery, двигает ползунки, жмёт «Применить», проверяет тело второго POST /analyze. На коде до фикса красный: expected 2, received 1 — второго запроса нет вовсе.
  • npx vitest run — 271/271 (в useParcelAnalyzeQuery.test.ts хук теперь зовётся через renderHook: он читает контекст, вне рендера у React нет dispatcher'а).
  • npx tsc --noEmit, eslint, prettier — чисто.
  • npm run build — проходит (контекст в site-finder-api.ts не утекает в серверный граф).
  • Прод, живой запрос: inline park 3.0 / tram −2.0 → score 17.95, source="inline"; пресет «Бизнес» → 13.13.

Refs #2790

## Summary **П.1 (видимая поломка).** Постановка верна. `weights` в §4.1 был write-only: жил в `Section31Settings` и читался только обратно в ту же панель. Прод-доказательство (Playwright, участок 66:41:0702017:131): двигаю Парки 1.8→3.0 и Трамвайные −0.5→−2.0, жму «Применить», жду 120 с — скор **18.91 до и 18.91 после**, за всё время **один** POST /analyze и тот без тела. Веса подняты на страницу и уходят в запрос через `AnalyzeWeightsContext`, а не пропом: `useParcelAnalyzeQuery` зовут шесть мест (§1, §2, §4, §5, страница, /ptica) на общем ключе кэша `["parcel-analyze", cad, horizon]`. Проп, забытый в одной секции, развёл бы ключи — половина страницы считала бы по одним весам, половина по другим, и дорогой (10-30 c) analyze ушёл бы дважды. Контекст держит всех потребителей одного ключа на одном значении по построению. Веса шлём **inline**, а не через `profile_id`: тело запроса равно ползункам, ответ рапортует `source="inline"` — расхождению между показанным и посчитанным взяться неоткуда. После правки (тот же сценарий на собранном фронте против прод-бэкенда): **18.91 → 17.95**, второй POST несёт `{"weights":{…,"park":3,…,"tram_stop":-2}}`. Живой прод-запрос с теми же весами даёт ровно 17.95. **П.2 (системные пресеты).** Постановка верна, но требование «сначала бэкенд» снимается: пресет не обязательно адресовать через `profile_id`. Теперь `include_system=true` в списке, а «Применить» на пресете отдаёт `profileId = null` → уходят inline-веса пресета. Тихому откату неоткуда взяться, бэкенд не тронут. Прод-проверка: дропдаун стал `По умолчанию · admin ★ · Эконом · пресет · Комфорт · пресет · Бизнес · пресет`; выбор «Бизнес» → тело `metro_stop 2.5 / shop_mall 2.0 / park 2.0` (совпадает с сидом `100_user_weight_profiles_default_seed.sql`) → скор **13.13**, `source="inline"`, `profile_id=null`. **П.3.** `useUpdateProfile` / `useDeleteProfile` удалены. За три месяца в проде 1 профиль на всю базу (`admin`, создан 15.05.2026, `updated_at = created_at`) + 3 системных пресета: ни одного обновления, ни одной попытки удаления. Достраивать UI под нулевой спрос дороже, чем убрать мёртвый код; PUT/DELETE на бэкенде живы и покрыты тестами. ## Test plan - [x] `AnalysisPageContent.weights.test.tsx` — рендерит настоящую страницу с настоящей §4.1 и настоящим `useParcelAnalyzeQuery`, двигает ползунки, жмёт «Применить», проверяет тело второго POST /analyze. **На коде до фикса красный**: `expected 2, received 1` — второго запроса нет вовсе. - [x] `npx vitest run` — 271/271 (в `useParcelAnalyzeQuery.test.ts` хук теперь зовётся через `renderHook`: он читает контекст, вне рендера у React нет dispatcher'а). - [x] `npx tsc --noEmit`, eslint, prettier — чисто. - [x] `npm run build` — проходит (контекст в `site-finder-api.ts` не утекает в серверный граф). - [x] Прод, живой запрос: inline park 3.0 / tram −2.0 → score 17.95, `source="inline"`; пресет «Бизнес» → 13.13. Refs #2790
bot-backend added 1 commit 2026-08-10 08:30:37 +00:00
fix(site-finder): §4.1 «Применить» действительно применяет веса POI (#2790)
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 9s
CI / backend-tests (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Has been skipped
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / frontend-tests (pull_request) Successful in 1m12s
CI / openapi-codegen-check (pull_request) Successful in 2m15s
12bc9f346f
Состояние ползунков было write-only: `Section31Settings` держал его у себя и
отдавал обратно в ту же панель, до /analyze оно не доезжало. Пользователь
двигал веса, жал «Применить» и получал ТОТ ЖЕ скор по системным весам —
проверено на проде: park 1.8→3.0, tram −0.5→−2.0, скор 18.91 до и после, за
две минуты ни одного нового POST /analyze.

Веса подняты на страницу и уходят в analyze через `AnalyzeWeightsContext`, а не
пропом: `useParcelAnalyzeQuery` зовут шесть мест (§1, §2, §4, §5, страница,
/ptica) на общем ключе кэша. Проп, забытый в одной секции, развёл бы ключи —
половина страницы считала бы по одним весам, половина по другим, и дорогой
analyze ушёл бы дважды. Контекст держит всех потребителей ключа на одном
значении по построению.

Веса уходят inline, а не через profile_id: тело запроса равно ползункам, ответ
рапортует source="inline". Прод-проверка: park 3.0 / tram −2.0 → 18.91 → 17.95.

Заодно (#2790 п.2): системные пресеты (Эконом / Комфорт / Бизнес) лежали в
проде с 16.05.2026 и не были видны никому — фронт не слал include_system.
Просто включить его было нельзя: пресет с user_id='__system__', выбранный как
profile_id, resolve_weights() ищет в области владельца, не находит и молча
берёт дефолты, отвечая source="profile" (#2782). Поэтому пресет теперь не
адресуется по profile_id — его веса уходят inline. Бэкенд не тронут.
Прод-проверка: «Бизнес» → inline metro_stop 2.5 / shop_mall 2.0 → скор 13.13.

П.3: useUpdateProfile / useDeleteProfile удалены. Их не звали ниоткуда, а
спрос за три месяца — 1 профиль на всю базу (admin, updated_at = created_at):
ни одного изменения, ни одной попытки удаления. Эндпоинты живы и покрыты
тестами бэкенда.

Refs #2790
bot-backend merged commit 84a65d40dd into main 2026-08-10 08:40:00 +00:00
bot-backend deleted branch fix/2790-weights-apply 2026-08-10 08:40:00 +00:00
Sign in to join this conversation.
No reviewers
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#2810
No description provided.