function openDb() { return new Promise((resolve, reject) => { const req = indexedDB.open('paper_trading_viz', 2) req.onupgradeneeded = () => { const db = req.result if (!db.objectStoreNames.contains('meta')) db.createObjectStore('meta', { keyPath: 'id' }) if (!db.objectStoreNames.contains('raw_decisions')) { const raw = db.createObjectStore('raw_decisions', { keyPath: 'id' }) if (!raw.indexNames.contains('group')) raw.createIndex('group', 'group', { unique: false }) if (!raw.indexNames.contains('updated_at')) raw.createIndex('updated_at', 'updated_at', { unique: false }) } } req.onsuccess = () => resolve(req.result) req.onerror = () => reject(req.error) }) } // removed aggregates helpers export async function readSyncMeta() { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('meta', 'readonly') const store = tx.objectStore('meta') const req = store.get('raw_sync') req.onsuccess = () => resolve(req.result || null) req.onerror = () => reject(req.error) }) } export async function writeSyncMeta(remoteUpdatedAt, lastSyncedAt) { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('meta', 'readwrite') const store = tx.objectStore('meta') store.put({ id: 'raw_sync', remoteUpdatedAt: remoteUpdatedAt || null, lastSyncedAt: lastSyncedAt || new Date().toISOString() }) tx.oncomplete = () => resolve(true) tx.onerror = () => reject(tx.error) }) } export async function writeRawDecisions(rows) { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('raw_decisions', 'readwrite') const store = tx.objectStore('raw_decisions') const clearReq = store.clear() clearReq.onsuccess = () => { for (const r of rows) { const key = r.id const group = `${r.agent_name}|${r.asset}|${r.model}` store.put({ ...r, id: key, group }) } tx.oncomplete = () => resolve(true) tx.onerror = () => reject(tx.error) } clearReq.onerror = () => reject(clearReq.error) }) } export async function upsertRawDecisions(rows) { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('raw_decisions', 'readwrite') const store = tx.objectStore('raw_decisions') for (const r of rows) { const key = r.id const group = `${r.agent_name}|${r.asset}|${r.model}` store.put({ ...r, id: key, group }) } tx.oncomplete = () => resolve(true) tx.onerror = () => reject(tx.error) }) } export async function readRawByGroup(groupKey) { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('raw_decisions', 'readonly') const store = tx.objectStore('raw_decisions') const idx = store.index('group') const req = idx.openCursor(IDBKeyRange.only(groupKey)) const rows = [] req.onsuccess = e => { const cursor = e.target.result if (cursor) { rows.push(cursor.value); cursor.continue() } else { resolve(rows) } } req.onerror = () => reject(req.error) }) } export async function readAllRawDecisions() { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('raw_decisions', 'readonly') const store = tx.objectStore('raw_decisions') const rows = [] const cursorReq = store.openCursor() cursorReq.onsuccess = e => { const cursor = e.target.result if (cursor) { rows.push(cursor.value); cursor.continue() } else { resolve(rows) } } cursorReq.onerror = () => reject(cursorReq.error) }) } export async function readRawUpdatedAtMax() { const db = await openDb() return new Promise((resolve, reject) => { const tx = db.transaction('raw_decisions', 'readonly') const store = tx.objectStore('raw_decisions') const index = store.index('updated_at') const req = index.openCursor(null, 'prev') req.onsuccess = e => { const cursor = e.target.result resolve(cursor ? cursor.value.updated_at : null) } req.onerror = () => reject(req.error) }) } export async function clearAllStores() { const db = await openDb() return new Promise((resolve, reject) => { const stores = [] if (db.objectStoreNames.contains('meta')) stores.push('meta') if (db.objectStoreNames.contains('raw_decisions')) stores.push('raw_decisions') if (stores.length === 0) { resolve(true); return } const tx = db.transaction(stores, 'readwrite') if (db.objectStoreNames.contains('meta')) tx.objectStore('meta').clear() if (db.objectStoreNames.contains('raw_decisions')) tx.objectStore('raw_decisions').clear() tx.oncomplete = () => resolve(true) tx.onerror = () => reject(tx.error) }) }