import { createContext, useContext, useEffect, useState, useCallback } from 'react' const DataContext = createContext(null) const KEYS = [ 'models', 'recs', 'ms', 'vol_salience', 'val_share', 'freq_anchors', 'ppa_mt', 'ppa_tt', 'comp_ms', 'vtm', 'interaction', 'growth_decomp', 'pgi', 'stats', 'trend', 'recs_full', ] async function fetchKey(key) { // 1. Try FastAPI backend (/api/data/{key}) — live pipeline-computed data try { const res = await fetch(`/api/data/${key}`) if (res.ok) return res.json() } catch (_) { // upload server not reachable — fall through to static fallback } // 2. Fallback: static pre-generated file served by Vite from public/data/ // Works when upload server is not running (e.g. first open, view-only mode) const res = await fetch(`/data/${key}.json`) if (!res.ok) throw new Error(`Failed to load ${key}: ${res.status}`) return res.json() } export function DataProvider({ children }) { const [data, setData] = useState({}) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [progress, setProgress] = useState(0) const loadAll = useCallback(async () => { setLoading(true) setProgress(0) setError(null) let cancelled = false const results = {} const errors = [] for (let i = 0; i < KEYS.length; i++) { const key = KEYS[i] try { results[key] = await fetchKey(key) } catch (e) { results[key] = null errors.push(`${key}: ${e.message}`) } if (!cancelled) setProgress(Math.round(((i + 1) / KEYS.length) * 100)) } if (!cancelled) { setData(results) setLoading(false) if (errors.length === KEYS.length) { setError('All data failed to load. Is the server running?\n' + errors[0]) } } return () => { cancelled = true } }, []) useEffect(() => { const cleanup = loadAll() return () => { cleanup.then?.(fn => fn?.()) } }, [loadAll]) return ( {children} ) } export function useData() { return useContext(DataContext) }