-- +goose Up CREATE TABLE track_features ( track_id uuid PRIMARY KEY REFERENCES tracks(id) ON DELETE CASCADE, analyzer_ver text NOT NULL, bpm real, bpm_confidence real, key text, scale text, loudness_lufs real, energy real, danceability real, valence real, arousal real, acousticness real, instrumentalness real, mood_happy real, mood_sad real, mood_relaxed real, mood_aggressive real, mood_party real, intro_ms int, analyzed_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE track_embeddings ( track_id uuid PRIMARY KEY REFERENCES tracks(id) ON DELETE CASCADE, audio vector(256), cf vector(64), updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX track_embeddings_audio_idx ON track_embeddings USING hnsw (audio vector_cosine_ops); CREATE TABLE analysis_jobs ( track_id uuid PRIMARY KEY REFERENCES tracks(id) ON DELETE CASCADE, status text NOT NULL DEFAULT 'queued', attempts int NOT NULL DEFAULT 0, error text, updated_at timestamptz NOT NULL DEFAULT now() ); CREATE INDEX analysis_jobs_queued_idx ON analysis_jobs (updated_at) WHERE status = 'queued'; -- Каждый новый трек сразу встаёт в очередь анализа; существующие — тоже. -- +goose StatementBegin CREATE FUNCTION enqueue_analysis() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN INSERT INTO analysis_jobs (track_id) VALUES (NEW.id) ON CONFLICT DO NOTHING; RETURN NEW; END $$; -- +goose StatementEnd CREATE TRIGGER tracks_enqueue_analysis AFTER INSERT ON tracks FOR EACH ROW EXECUTE FUNCTION enqueue_analysis(); INSERT INTO analysis_jobs (track_id) SELECT id FROM tracks ON CONFLICT DO NOTHING; CREATE TABLE wave_sessions ( id uuid PRIMARY KEY, user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE, activity text, mood text, seed_type text, seed_id uuid, started_at timestamptz NOT NULL DEFAULT now(), ended_at timestamptz, tracks_served int NOT NULL DEFAULT 0 ); CREATE INDEX wave_sessions_user_idx ON wave_sessions (user_id, started_at DESC); CREATE TABLE wave_impressions ( wave_session_id uuid REFERENCES wave_sessions(id) ON DELETE CASCADE, position int NOT NULL, track_id uuid NOT NULL, candidate_source text NOT NULL, score real, model_ver text, served_at timestamptz NOT NULL DEFAULT now(), outcome text, PRIMARY KEY (wave_session_id, position) ); CREATE INDEX wave_impressions_track_idx ON wave_impressions (wave_session_id, track_id); CREATE TABLE user_profiles_rec ( user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE, taste_audio vector(256), taste_cf vector(64), genre_prefs jsonb NOT NULL DEFAULT '{}', bandit jsonb NOT NULL DEFAULT '{}', exploration real NOT NULL DEFAULT 0.15, updated_at timestamptz NOT NULL DEFAULT now() ); CREATE TABLE model_versions ( id text PRIMARY KEY, kind text NOT NULL, metrics jsonb, is_active boolean NOT NULL DEFAULT false, created_at timestamptz NOT NULL DEFAULT now() ); -- +goose Down DROP TABLE model_versions; DROP TABLE user_profiles_rec; DROP TABLE wave_impressions; DROP TABLE wave_sessions; DROP TRIGGER tracks_enqueue_analysis ON tracks; DROP FUNCTION enqueue_analysis(); DROP TABLE analysis_jobs; DROP TABLE track_embeddings; DROP TABLE track_features;