feat(site-finder): weight profiles endpoints + analyze integration (#114 sub-PR 3/4) #138

Merged
lekss361 merged 1 commit from feat/114-weight-profiles-endpoints into main 2026-05-14 21:40:07 +00:00
lekss361 commented 2026-05-14 21:34:38 +00:00 (Migrated from github.com)

Summary

#114 sub-PR 3 of 4. Admin CRUD endpoints + minimal analyze_parcel integration.

Changes

  • NEW: backend/app/api/v1/admin_weight_profiles.py (102 lines) — full CRUD under AdminTokenAuth
  • MODIFIED: parcels.py — 3 minimal changes (new optional query params, resolve_weights call, response field)
  • MODIFIED: main.py — router registration
  • NEW: tests/test_admin_weight_profiles.py (351 lines, 13 tests)

Endpoints

Method Path Action
GET /api/v1/admin/site-finder/weight-profiles?user_id=X list
POST /api/v1/admin/site-finder/weight-profiles create
GET /api/v1/admin/site-finder/weight-profiles/{id}?user_id=X get
PUT /api/v1/admin/site-finder/weight-profiles/{id}?user_id=X update
DELETE /api/v1/admin/site-finder/weight-profiles/{id}?user_id=X delete

analyze_parcel integration (non-breaking)

New optional query params:

  • profile_id: int | None
  • profile_user_id: str | None

If both omitted → source: \"system\" (defaults from _POI_WEIGHTS).
If profile_id given → load profile, use weights override.
Else if profile_user_id → use user's default profile.

Response gains:

\"weights_profile\": {
  \"source\": \"system\" | \"user_default\" | \"profile\",
  \"profile_id\": int | null,
  \"user_id\": str | null,
  \"weights_applied\": { \"school\": 1.5, ... }
}

Tests

46/46 pass (33 pre-existing + 13 new). Covers:

  • list/create/get/update/delete happy paths
  • 422 на unknown category / out-of-bounds weight
  • 404 на missing profile
  • 401 без X-Admin-Token

Refs: #114

## Summary #114 sub-PR 3 of 4. Admin CRUD endpoints + minimal analyze_parcel integration. ## Changes - NEW: `backend/app/api/v1/admin_weight_profiles.py` (102 lines) — full CRUD under AdminTokenAuth - MODIFIED: `parcels.py` — 3 minimal changes (new optional query params, resolve_weights call, response field) - MODIFIED: `main.py` — router registration - NEW: `tests/test_admin_weight_profiles.py` (351 lines, 13 tests) ## Endpoints | Method | Path | Action | |---|---|---| | GET | `/api/v1/admin/site-finder/weight-profiles?user_id=X` | list | | POST | `/api/v1/admin/site-finder/weight-profiles` | create | | GET | `/api/v1/admin/site-finder/weight-profiles/{id}?user_id=X` | get | | PUT | `/api/v1/admin/site-finder/weight-profiles/{id}?user_id=X` | update | | DELETE | `/api/v1/admin/site-finder/weight-profiles/{id}?user_id=X` | delete | ## analyze_parcel integration (non-breaking) New optional query params: - `profile_id: int | None` - `profile_user_id: str | None` If both omitted → `source: \"system\"` (defaults from `_POI_WEIGHTS`). If `profile_id` given → load profile, use weights override. Else if `profile_user_id` → use user's default profile. Response gains: ```json \"weights_profile\": { \"source\": \"system\" | \"user_default\" | \"profile\", \"profile_id\": int | null, \"user_id\": str | null, \"weights_applied\": { \"school\": 1.5, ... } } ``` ## Tests **46/46 pass** (33 pre-existing + 13 new). Covers: - list/create/get/update/delete happy paths - 422 на unknown category / out-of-bounds weight - 404 на missing profile - 401 без X-Admin-Token Refs: #114
lekss361 commented 2026-05-14 21:39:51 +00:00 (Migrated from github.com)

Bot review (SHA c8e510c, CI )

Чистая интеграция #114 sub-PR 3/4. Non-breaking, full CRUD, transparent response field.

Endpoints

5 RESTful endpoints под /api/v1/admin/site-finder/weight-profiles — все под AdminTokenAuth (consistent с PR #133)
User-scoped CRUDuser_id Query на list/get/update/delete защищает cross-user access
HTTP semantics: 201 для POST, 204 для DELETE, 404 для missing — RESTful
response_model per endpoint — proper OpenAPI schema
min_length=1 на user_id Query — отсечь empty string

parcels.py integration

Non-breaking — 2 optional query params (profile_id, profile_user_id); если оба None → source: "system" (backward-compatible behavior)
Late import resolve_weights — circular-safe
Smart fallback chain в scoring loop:

w = _effective_weights.get(cat, _POI_WEIGHTS.get(cat, 0.0))

Partial override работает корректно — если профиль задаёт только school, остальные категории берутся из системных defaults.
Response transparency — новое поле weights_profile.source ("profile" | "user_default" | "system") + weights_applied для отладки из UI

main.py

Router зарегистрирован c tags=["admin", "site-finder"] — корректно для OpenAPI группировки

Минор (UX, не блокер)

resolve_weights возвращает либо user dict, либо system dict — НЕ merged. Если профиль партиальный ({"school": 2.0}), то:

  • _effective_weights = {"school": 2.0}
  • Scoring корректен благодаря .get(cat, _POI_WEIGHTS.get(...)) fallback'у в parcels.py ✓
  • НО response weights_applied = {"school": 2.0} — показывает только customized keys, не отражает реально применённые веса (system defaults для остальных категорий невидимы в response)

Не блокер:

  1. Sub-PR 4 (UI с 25 sliders) вероятно форсит полный набор
  2. Behaviour корректен, только display misleading
  3. Тривиальный follow-up: merge _SYSTEM_POI_WEIGHTS + user weights в resolve_weights до возврата
# Будущий улучшенный resolve_weights:
merged = dict(_SYSTEM_POI_WEIGHTS)
merged.update(profile.weights)
return merged

Это сделает weights_applied truth-telling и позволит убрать fallback в parcels.py scoring loop.

Tests

46/46 (33 pre-existing + 13 new) — coverage CRUD happy paths + 422 validation + 404 missing + 401 auth, per PR body. CI green.

approve merge

## Bot review (SHA `c8e510c`, CI ✅) Чистая интеграция #114 sub-PR 3/4. Non-breaking, full CRUD, transparent response field. ### Endpoints ✅ **5 RESTful endpoints** под `/api/v1/admin/site-finder/weight-profiles` — все под `AdminTokenAuth` (consistent с PR #133) ✅ **User-scoped CRUD** — `user_id` Query на list/get/update/delete защищает cross-user access ✅ **HTTP semantics**: 201 для POST, 204 для DELETE, 404 для missing — RESTful ✅ **`response_model` per endpoint** — proper OpenAPI schema ✅ **`min_length=1` на user_id Query** — отсечь empty string ### parcels.py integration ✅ **Non-breaking** — 2 optional query params (`profile_id`, `profile_user_id`); если оба None → `source: "system"` (backward-compatible behavior) ✅ **Late import `resolve_weights`** — circular-safe ✅ **Smart fallback chain в scoring loop**: ```python w = _effective_weights.get(cat, _POI_WEIGHTS.get(cat, 0.0)) ``` Partial override работает корректно — если профиль задаёт только `school`, остальные категории берутся из системных defaults. ✅ **Response transparency** — новое поле `weights_profile.source` ("profile" | "user_default" | "system") + `weights_applied` для отладки из UI ### main.py ✅ Router зарегистрирован c `tags=["admin", "site-finder"]` — корректно для OpenAPI группировки ### Минор (UX, не блокер) `resolve_weights` возвращает либо user dict, либо system dict — **НЕ merged**. Если профиль партиальный (`{"school": 2.0}`), то: - `_effective_weights = {"school": 2.0}` - Scoring корректен благодаря `.get(cat, _POI_WEIGHTS.get(...))` fallback'у в parcels.py ✓ - НО response `weights_applied = {"school": 2.0}` — показывает только customized keys, **не отражает реально применённые веса** (system defaults для остальных категорий невидимы в response) Не блокер: 1. Sub-PR 4 (UI с 25 sliders) вероятно форсит полный набор 2. Behaviour корректен, только display misleading 3. Тривиальный follow-up: merge `_SYSTEM_POI_WEIGHTS` + user weights в `resolve_weights` до возврата ```python # Будущий улучшенный resolve_weights: merged = dict(_SYSTEM_POI_WEIGHTS) merged.update(profile.weights) return merged ``` Это сделает `weights_applied` truth-telling и позволит убрать fallback в parcels.py scoring loop. ### Tests 46/46 (33 pre-existing + 13 new) — coverage CRUD happy paths + 422 validation + 404 missing + 401 auth, per PR body. CI green. approve merge
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#138
No description provided.