diff --git a/data/sql/90_user_weight_profiles.sql b/data/sql/90_user_weight_profiles.sql new file mode 100644 index 00000000..1e2158f3 --- /dev/null +++ b/data/sql/90_user_weight_profiles.sql @@ -0,0 +1,66 @@ +-- 90_user_weight_profiles.sql +-- User-defined POI weight profiles для site_finder/analyze_parcel. +-- Per #114 (Макс feedback): разные веса для разных категорий (садики ≠ Мегамарт). +-- +-- Apply order: после 89_drop_dead_brin_rosreestr_deals.sql +-- Dependencies: нет (standalone table, no FK to other tables) +-- +-- POI categories compatible with _POI_WEIGHTS in backend/app/api/v1/parcels.py: +-- school, kindergarten, pharmacy, hospital, shop_mall, shop_supermarket, +-- shop_small, park, bus_stop, metro_stop, tram_stop +-- +-- Idempotent: safe to run multiple times. + +BEGIN; + +CREATE TABLE IF NOT EXISTS user_weight_profiles ( + id SERIAL PRIMARY KEY, + user_id TEXT NOT NULL, -- пока admin token / простой ID; auth в Sprint 5 + profile_name TEXT NOT NULL, + weights JSONB NOT NULL DEFAULT '{}'::jsonb, + is_default BOOLEAN NOT NULL DEFAULT FALSE, + description TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (user_id, profile_name) +); + +CREATE INDEX IF NOT EXISTS user_weight_profiles_user_idx + ON user_weight_profiles (user_id); + +-- Partial index для быстрого fetch default profile per user +CREATE INDEX IF NOT EXISTS user_weight_profiles_default_idx + ON user_weight_profiles (user_id) WHERE is_default = TRUE; + +-- Trigger function для auto-update updated_at (shared, reusable across tables) +CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Drop trigger если ранее существовал (idempotent) +DROP TRIGGER IF EXISTS user_weight_profiles_updated_at ON user_weight_profiles; +CREATE TRIGGER user_weight_profiles_updated_at + BEFORE UPDATE ON user_weight_profiles + FOR EACH ROW EXECUTE FUNCTION set_updated_at(); + +COMMENT ON TABLE user_weight_profiles IS + 'POI weight profiles для site_finder. Per #114 (Макс feedback). ' + 'weights = {"school": 1.5, "kindergarten": 1.5, "shop_mall": 1.2, ...}'; + +COMMENT ON COLUMN user_weight_profiles.weights IS + 'JSONB map: poi_category → weight (float, typically -1.5..2.5). ' + 'Совместимо с _POI_WEIGHTS default в backend/app/api/v1/parcels.py. ' + 'Категории: school, kindergarten, pharmacy, hospital, shop_mall, ' + 'shop_supermarket, shop_small, park, bus_stop, metro_stop, tram_stop.'; + +COMMENT ON COLUMN user_weight_profiles.user_id IS + 'Простой строковый ID пользователя. Full auth integration — Sprint 5.'; + +COMMENT ON COLUMN user_weight_profiles.is_default IS + 'TRUE = профиль используется по умолчанию для этого user_id. ' + 'Partial index user_weight_profiles_default_idx ускоряет lookup.'; + +COMMIT;