Move variant grouping work out of Live TV rendering into the catalog pipeline: a liveProjectionService builds the logical channel projection (schema in liveProjectionSchema) whenever a source is added, synced or modified (name change, new address), and the projection is served through a /live route. ChannelList now consumes the precomputed projection via a liveChannelProjection client service, recalculating only when the catalog changes. Cover projection building, incremental recompute and client consumption with tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.7 KiB
JavaScript
50 lines
1.7 KiB
JavaScript
const LIVE_PROJECTION_COLUMNS = {
|
|
logical_channel_id: 'TEXT',
|
|
grouping_fingerprint: 'TEXT',
|
|
grouping_input_hash: 'TEXT',
|
|
quality_label: 'TEXT',
|
|
quality_rank: 'INTEGER',
|
|
variant_rank: 'INTEGER',
|
|
language_code: 'TEXT',
|
|
language_confidence: 'REAL',
|
|
language_scores: 'JSON',
|
|
language_evidence: 'JSON',
|
|
language_detector: 'TEXT',
|
|
language_input_hash: 'TEXT'
|
|
};
|
|
|
|
const LIVE_PROJECTION_SCHEMA_SQL = `
|
|
CREATE INDEX IF NOT EXISTS idx_items_logical_channel ON playlist_items(logical_channel_id);
|
|
CREATE INDEX IF NOT EXISTS idx_items_grouping_fingerprint ON playlist_items(grouping_fingerprint);
|
|
|
|
CREATE TABLE IF NOT EXISTS logical_channels (
|
|
id TEXT PRIMARY KEY,
|
|
fingerprint TEXT NOT NULL UNIQUE,
|
|
display_name TEXT NOT NULL,
|
|
preferred_variant_id TEXT,
|
|
variant_count INTEGER NOT NULL DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
CREATE INDEX IF NOT EXISTS idx_logical_channels_fingerprint ON logical_channels(fingerprint);
|
|
|
|
CREATE TABLE IF NOT EXISTS catalog_projection_state (
|
|
projection TEXT PRIMARY KEY,
|
|
status TEXT NOT NULL,
|
|
started_at INTEGER,
|
|
completed_at INTEGER,
|
|
input_hash TEXT,
|
|
projected_count INTEGER NOT NULL DEFAULT 0,
|
|
error TEXT
|
|
);
|
|
`;
|
|
|
|
function ensureColumns(database, table, columns) {
|
|
const existing = new Set(database.prepare(`PRAGMA table_info(${table})`).all().map(column => column.name));
|
|
for (const [name, definition] of Object.entries(columns)) {
|
|
if (!existing.has(name)) database.exec(`ALTER TABLE ${table} ADD COLUMN ${name} ${definition}`);
|
|
}
|
|
}
|
|
|
|
module.exports = { ensureColumns, LIVE_PROJECTION_COLUMNS, LIVE_PROJECTION_SCHEMA_SQL };
|