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 CATALOG_PROJECTION_STATE_COLUMNS = { phase: "TEXT NOT NULL DEFAULT 'idle'", processed_count: 'INTEGER NOT NULL DEFAULT 0', total_count: 'INTEGER NOT NULL DEFAULT 0', laya_processed_count: 'INTEGER NOT NULL DEFAULT 0', laya_total_count: 'INTEGER NOT NULL DEFAULT 0' }; 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 iptv_org_channels ( id TEXT PRIMARY KEY, name TEXT NOT NULL, normalized_name TEXT NOT NULL, alt_names JSON NOT NULL DEFAULT '[]', country TEXT, languages JSON NOT NULL DEFAULT '[]', data JSON, updated_at INTEGER NOT NULL ); CREATE INDEX IF NOT EXISTS idx_iptv_org_channels_name ON iptv_org_channels(normalized_name); CREATE TABLE IF NOT EXISTS iptv_org_cache_state ( resource TEXT PRIMARY KEY, refreshed_at INTEGER NOT NULL, item_count INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE IF NOT EXISTS live_epg_matches ( id INTEGER PRIMARY KEY AUTOINCREMENT, logical_channel_id TEXT NOT NULL, language_code TEXT NOT NULL, source_id INTEGER, channel_id TEXT, match_confidence REAL NOT NULL DEFAULT 0, data JSON, updated_at INTEGER NOT NULL, UNIQUE (logical_channel_id, language_code) ); CREATE INDEX IF NOT EXISTS idx_live_epg_matches_channel ON live_epg_matches(logical_channel_id, language_code); 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, phase TEXT NOT NULL DEFAULT 'idle', processed_count INTEGER NOT NULL DEFAULT 0, total_count INTEGER NOT NULL DEFAULT 0, laya_processed_count INTEGER NOT NULL DEFAULT 0, laya_total_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 = { CATALOG_PROJECTION_STATE_COLUMNS, ensureColumns, LIVE_PROJECTION_COLUMNS, LIVE_PROJECTION_SCHEMA_SQL };