feat(site-finder): weight profiles service — Pydantic + CRUD (#114 sub-PR 2/4) #137
No reviewers
Labels
No labels
admin
analytics
auth
automation
bug
business
chore
ci
compliance
data
data-moat
docs
duplicate
dx
enhancement
Fable 5 ревью
feedback/max
generative
GG-форсайт
needs-discussion
needs-human
observability
pause-bots
performance
priority/p0
priority/p1
priority/p2
priority/p3
scope/backend
scope/db
scope/devops
scope/frontend
scope/qa
scrapers
security
site-finder
stage/1
stage/2
status/blocked
status/done
status/needs-analysis
status/needs-fix
status/qa
status/ready
status/review
status/wip
tech-debt
tradein
ux
week ревью 1
wontfix
вторичка
ИРД
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: lekss361/gendesign#137
Loading…
Add table
Reference in a new issue
No description provided.
Delete branch "feat/114-weight-profiles-service"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
#114 sub-PR 2 of 4. Backend Pydantic v2 models + CRUD service.
API (
backend/app/services/site_finder/weight_profiles.py)list/get/get_default/create/update/delete_profileresolve_weights(db, user_id?, profile_id?)→ falls back to_SYSTEM_POI_WEIGHTSValidation
_POI_WEIGHTSkeys)Tests
14 passing (mock-based, no DB).
Vault
code/modules/Module_Weight_Profiles_Service.mdNEWRefs: #114
Bot review (SHA
be3b60e, CI ✅)Чистая foundation для #114 sub-PR 2/4. Service module + 14 unit-тестов, inert до sub-PR 3 (endpoints).
Validation surface
✅ Pydantic v2 idiomatic —
field_validatorс@classmethod,model_config = {"from_attributes": True}✅ Shared
_validate_weights_dict— DRY междуWeightProfileCreateиWeightProfileUpdate✅
ALLOWED_CATEGORIES(11 keys) mirrors_POI_WEIGHTSвparcels.py— единый source of truth✅ Bounds
[-2.0, 3.0]покрывают и penalty (tram_stop = −0.5) и boost (park = +2.5)Transaction safety
✅
is_defaultatomic toggle —_UNSET_DEFAULT+INSERT/UPDATE+db.commit()в одной tx. Race из «два profile is_default=TRUE одновременно» невозможна.✅
update_profilePATCH semantics — только заданные поля вsets; если ничего кромеupdated_at = NOW()— пропускается UPDATE.resolve_weights priority chain
Корректный приоритет:
profile_id+user_id→ конкретный профильuser_idonly → default-профиль_SYSTEM_POI_WEIGHTS✅ Empty
weightsfallback — если профиль найден ноweights = {}, возвращаются system defaults (covered тестомtest_resolve_weights_empty_profile_weights_fallback)✅
dict(profile.weights)— возвращается копия, не reference (coveredtest_resolve_weights_system_default_returns_copy)Тесты (14)
Покрытие хорошее: validation (unknown cat / >MAX / <MIN / valid / empty / boundary / update None / update unknown) + resolve_weights (no args / copy semantics / default profile / specific profile / not-found fallback / empty-weights fallback).
Минор (не блокеры — fix в sub-PR 3 или ignore)
db: Anyв сигнатурах — лучшеSession(sqlalchemy.orm). Type-hint hygiene, не runtime issue.update_profileделаетget_profileдважды — start для existence check + end для return. Slightly wasteful, но readable. Если станет hot path — можно вернуть результатRETURNING *от UPDATE.wp_module.get_profile = lambda ...+ try/finally restore. Работает корректно, ноpytest-mockmocker.patch.objectчище и auto-restoring. Не влияет на корректность.approve merge