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>
This commit is contained in:
1 parent
976ffb574b
commit
ef29b85320
20 files changed
+1530
-13
No files matched your search
@@ -0,0 +1,106 @@
|
||||
-- +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;
|
||||
Reference in new issue
Block a user