Files
pipu-music/services/api/migrations/00006_wave.sql
T
mrleo1nidandClaude Opus 5.5 ef29b85320
ci / api / api (push) Successful in 27s
ci / recommender / recommender (push) Successful in 1m0s
ci / docker / docker (образы собираются — их сервер собирает сам при обновлении) (push) Successful in 1m26s
feat(rec): audio analyzer and My Wave v1 engine (M4)
- migration 00006: track features and embeddings (pgvector, HNSW),
  analysis queue filled by a trigger on new tracks, wave sessions and
  impressions, recommender profiles, model versions
- analyzer basic-1 (ADR-014): ffmpeg decode, three 30 s excerpts,
  256-d timbre/rhythm embedding, tempo by onset autocorrelation, key,
  loudness, energy, danceability, acousticness, intro length; worker
  with SKIP LOCKED claims, retries and re-analysis on version change
- wave engine v1: candidates (session/taste similarity, fresh,
  liked artists, rediscover, popular, exploration, seed), filters
  (dislikes, played in 3 h, already served), heuristic score, activity
  and mood fit from contexts.yaml with relaxing thresholds, MMR with
  one track per artist, exploration share, session vector updated by
  reactions and a change of course after three skips
- POST /internal/wave/batch with the catalog index cached in memory
  and the session kept in Redis

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 12:20:55 +03:00

107 lines
3.5 KiB
PL/PgSQL

-- +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;