Big changes
This commit is contained in:
@@ -11,11 +11,15 @@ CREATE TABLE IF NOT EXISTS sessions (
|
||||
server_id TEXT NOT NULL DEFAULT '',
|
||||
device_id TEXT NOT NULL DEFAULT '',
|
||||
device_name TEXT NOT NULL DEFAULT 'Memby TV',
|
||||
client_version TEXT NOT NULL DEFAULT '',
|
||||
client_protocol TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
last_seen_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS device_name TEXT NOT NULL DEFAULT 'Memby TV';
|
||||
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS client_version TEXT NOT NULL DEFAULT '';
|
||||
ALTER TABLE sessions ADD COLUMN IF NOT EXISTS client_protocol TEXT NOT NULL DEFAULT '';
|
||||
|
||||
-- Older builds could create more than one token for the same physical TV. Keep the most
|
||||
-- recently used row before adding the identity constraint.
|
||||
@@ -102,3 +106,116 @@ CREATE TABLE IF NOT EXISTS row_events (
|
||||
|
||||
CREATE INDEX IF NOT EXISTS row_events_time_idx ON row_events (occurred_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS row_events_row_idx ON row_events (row_id, occurred_at DESC);
|
||||
|
||||
-- Search terms are retained separately from row engagement so they can inform future
|
||||
-- ranking/recommendation work without coupling that analysis to rendered rows.
|
||||
CREATE TABLE IF NOT EXISTS search_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
emby_user_id TEXT NOT NULL,
|
||||
query TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS search_history_user_time_idx
|
||||
ON search_history (emby_user_id, occurred_at DESC);
|
||||
|
||||
-- Recommendation-relevant Tracearr history. The public Tracearr API has no user or
|
||||
-- since cursor, so stable source ids make these rows the durable deduplication boundary.
|
||||
-- Deliberately omit artwork, stream-detail blobs and other fields unused by ranking.
|
||||
CREATE TABLE IF NOT EXISTS tracearr_sessions (
|
||||
server_id TEXT NOT NULL DEFAULT '',
|
||||
tracearr_session_id TEXT NOT NULL,
|
||||
tracearr_user_id TEXT NOT NULL DEFAULT '',
|
||||
username TEXT NOT NULL DEFAULT '',
|
||||
state TEXT NOT NULL DEFAULT '',
|
||||
media_type TEXT NOT NULL DEFAULT '',
|
||||
media_title TEXT NOT NULL DEFAULT '',
|
||||
show_title TEXT NOT NULL DEFAULT '',
|
||||
season_number INT,
|
||||
episode_number INT,
|
||||
production_year INT,
|
||||
started_at TIMESTAMPTZ,
|
||||
stopped_at TIMESTAMPTZ,
|
||||
duration_ms BIGINT NOT NULL DEFAULT 0,
|
||||
progress_ms BIGINT NOT NULL DEFAULT 0,
|
||||
total_duration_ms BIGINT NOT NULL DEFAULT 0,
|
||||
watched BOOLEAN NOT NULL DEFAULT false,
|
||||
device TEXT NOT NULL DEFAULT '',
|
||||
player TEXT NOT NULL DEFAULT '',
|
||||
product TEXT NOT NULL DEFAULT '',
|
||||
platform TEXT NOT NULL DEFAULT '',
|
||||
is_transcode BOOLEAN NOT NULL DEFAULT false,
|
||||
video_decision TEXT NOT NULL DEFAULT '',
|
||||
audio_decision TEXT NOT NULL DEFAULT '',
|
||||
source_video_codec TEXT NOT NULL DEFAULT '',
|
||||
source_audio_codec TEXT NOT NULL DEFAULT '',
|
||||
emby_item_id TEXT NOT NULL DEFAULT '',
|
||||
emby_series_id TEXT NOT NULL DEFAULT '',
|
||||
source_fingerprint BYTEA NOT NULL,
|
||||
source_seen_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
imported_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (server_id, tracearr_session_id)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS tracearr_sessions_user_time_idx
|
||||
ON tracearr_sessions (tracearr_user_id, started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS tracearr_sessions_username_time_idx
|
||||
ON tracearr_sessions (lower(username), started_at DESC);
|
||||
CREATE INDEX IF NOT EXISTS tracearr_sessions_emby_item_idx
|
||||
ON tracearr_sessions (emby_item_id) WHERE emby_item_id <> '';
|
||||
CREATE INDEX IF NOT EXISTS tracearr_sessions_emby_series_idx
|
||||
ON tracearr_sessions (emby_series_id) WHERE emby_series_id <> '';
|
||||
|
||||
-- One compact derived profile per Emby user. Variable affinity maps stay together as
|
||||
-- JSON because the builder reads and replaces the whole profile; no request filters
|
||||
-- inside these maps.
|
||||
CREATE TABLE IF NOT EXISTS recommendation_user_profiles (
|
||||
emby_user_id TEXT PRIMARY KEY,
|
||||
tracearr_user_id TEXT NOT NULL DEFAULT '',
|
||||
tracearr_username TEXT NOT NULL DEFAULT '',
|
||||
source_session_count INT NOT NULL DEFAULT 0,
|
||||
mean_completion_ratio REAL NOT NULL DEFAULT 0,
|
||||
typical_session_minutes INT NOT NULL DEFAULT 0,
|
||||
genre_affinity JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
title_affinity JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
studio_affinity JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
codec_outcomes JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
signals_through TIMESTAMPTZ,
|
||||
built_at TIMESTAMPTZ,
|
||||
pool_built_at TIMESTAMPTZ,
|
||||
dirty_since TIMESTAMPTZ DEFAULT now(),
|
||||
last_error TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS recommendation_profiles_dirty_idx
|
||||
ON recommendation_user_profiles (dirty_since)
|
||||
WHERE dirty_since IS NOT NULL;
|
||||
|
||||
-- Every eligible ranked title is retained. At household scale this is only tens of
|
||||
-- thousands of compact rows and gives short runtime filters far more headroom than the
|
||||
-- old 240-title request pool.
|
||||
CREATE TABLE IF NOT EXISTS for_you_candidates (
|
||||
emby_user_id TEXT NOT NULL,
|
||||
item_id TEXT NOT NULL,
|
||||
base_rank INT NOT NULL,
|
||||
base_score REAL NOT NULL DEFAULT 0,
|
||||
runtime_minutes INT NOT NULL DEFAULT 0,
|
||||
affinity_score REAL NOT NULL DEFAULT 0,
|
||||
compatibility_score REAL NOT NULL DEFAULT 0,
|
||||
compatibility_label TEXT NOT NULL DEFAULT '',
|
||||
reason_kind TEXT NOT NULL DEFAULT '',
|
||||
reason_genre TEXT NOT NULL DEFAULT '',
|
||||
reason_source_session_id TEXT NOT NULL DEFAULT '',
|
||||
reason_source_item_id TEXT NOT NULL DEFAULT '',
|
||||
reason_source_title TEXT NOT NULL DEFAULT '',
|
||||
recommendation_reason TEXT NOT NULL DEFAULT '',
|
||||
built_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (emby_user_id, item_id),
|
||||
FOREIGN KEY (item_id) REFERENCES library_items(id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS for_you_candidates_user_rank_idx
|
||||
ON for_you_candidates (emby_user_id, base_rank);
|
||||
CREATE INDEX IF NOT EXISTS for_you_candidates_user_runtime_rank_idx
|
||||
ON for_you_candidates (emby_user_id, runtime_minutes, base_rank);
|
||||
|
||||
Reference in New Issue
Block a user