diff --git a/frontend/src/components/StockExplainView.jsx b/frontend/src/components/StockExplainView.jsx index 68b067e..34e3243 100644 --- a/frontend/src/components/StockExplainView.jsx +++ b/frontend/src/components/StockExplainView.jsx @@ -153,6 +153,71 @@ function resolveEventCategory(event) { return 'signal'; } +function normalizeTradeRow(row, fallbackIndex = 0) { + if (!row || typeof row !== 'object') return null; + const timestamp = row.timestamp || row.ts || row.created_at || null; + const ticker = row.ticker || ''; + const side = row.side || ''; + const qtyValue = Number(row.qty ?? row.quantity ?? 0); + const priceValue = Number(row.price ?? 0); + return { + id: row.id || `trade-${ticker}-${timestamp || fallbackIndex}-${fallbackIndex}`, + timestamp, + trading_date: row.trading_date || row.trade_date || null, + ticker, + side, + qty: Number.isFinite(qtyValue) ? qtyValue : 0, + price: Number.isFinite(priceValue) ? priceValue : 0 + }; +} + +function normalizeSignalRow(row, fallbackIndex = 0) { + if (!row || typeof row !== 'object') return null; + const timestamp = row.timestamp || row.created_at || null; + const date = row.date || row.trade_date || eventDateKey(timestamp) || ''; + const rawSignal = row.signal || row.title || ''; + const normalizedDirection = normalizeSignalDirection(rawSignal); + const confidenceValue = Number(row.confidence); + const realReturnValue = Number(row.real_return); + const parsedCorrect = typeof row.is_correct === 'string' + ? row.is_correct.toLowerCase() === 'true' + ? true + : row.is_correct.toLowerCase() === 'false' + ? false + : null + : typeof row.is_correct === 'boolean' + ? row.is_correct + : null; + + return { + id: row.id || `signal-${row.agent_id || row.agentId || 'agent'}-${date || fallbackIndex}-${fallbackIndex}`, + timestamp, + date, + ticker: row.ticker || '', + signal: rawSignal, + confidence: Number.isFinite(confidenceValue) ? confidenceValue : null, + real_return: Number.isFinite(realReturnValue) ? realReturnValue : null, + is_correct: parsedCorrect, + agentId: row.agent_id || row.agentId || '', + agentName: row.agent_name || row.agentName || row.meta || '未知分析师', + role: row.role || row.meta || '', + normalizedDirection + }; +} + +function normalizeMentionRow(row, fallbackIndex = 0) { + if (!row || typeof row !== 'object') return null; + return { + id: row.id || `mention-${fallbackIndex}`, + feedId: row.id || `mention-${fallbackIndex}`, + timestamp: row.timestamp || null, + agent: row.agent || row.agentName || '未知角色', + content: row.body || row.content || '', + conferenceTitle: row.meta || '', + feedType: 'sqlite' + }; +} + const EVENT_CATEGORY_META = { all: { label: '全部事件', color: '#111111' }, discussion: { label: '讨论', color: '#555555' }, @@ -210,7 +275,7 @@ export default function StockExplainView({ [holdings, selectedSymbol] ); - const tickerTrades = useMemo( + const fallbackTrades = useMemo( () => trades .filter((trade) => trade.ticker === selectedSymbol) .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()), @@ -218,6 +283,12 @@ export default function StockExplainView({ ); const tickerSignals = useMemo(() => { + const snapshotSignals = Array.isArray(explainEventsSnapshot?.signals) + ? explainEventsSnapshot.signals.map((signal, index) => normalizeSignalRow(signal, index)).filter(Boolean) + : []; + if (snapshotSignals.length > 0) { + return snapshotSignals.sort((a, b) => new Date(b.timestamp || b.date).getTime() - new Date(a.timestamp || a.date).getTime()); + } if (!selectedSymbol) return []; return (Array.isArray(leaderboard) ? leaderboard : []).flatMap((agent) => { const signals = Array.isArray(agent.signals) ? agent.signals : []; @@ -231,7 +302,7 @@ export default function StockExplainView({ normalizedDirection: normalizeSignalDirection(signal.signal) })); }).sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - }, [leaderboard, selectedSymbol]); + }, [explainEventsSnapshot, leaderboard, selectedSymbol]); const signalSummary = useMemo(() => { const summary = { bullish: 0, bearish: 0, neutral: 0 }; @@ -241,7 +312,7 @@ export default function StockExplainView({ return summary; }, [tickerSignals]); - const recentMentions = useMemo(() => { + const fallbackRecentMentions = useMemo(() => { const flattened = flattenFeedMessages(feed); return flattened .filter((message) => message.agent !== 'System' && includesTicker(message.content, selectedSymbol)) @@ -249,6 +320,29 @@ export default function StockExplainView({ .slice(0, 8); }, [feed, selectedSymbol]); + const tickerTrades = useMemo(() => { + const snapshotTrades = Array.isArray(explainEventsSnapshot?.trades) + ? explainEventsSnapshot.trades.map((trade, index) => normalizeTradeRow(trade, index)).filter(Boolean) + : []; + if (snapshotTrades.length > 0) { + return snapshotTrades.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + } + return fallbackTrades; + }, [explainEventsSnapshot, fallbackTrades]); + + const recentMentions = useMemo(() => { + const snapshotMentions = Array.isArray(explainEventsSnapshot?.events) + ? explainEventsSnapshot.events + .map((event, index) => normalizeMentionRow(event, index)) + .filter(Boolean) + .slice(0, 8) + : []; + if (snapshotMentions.length > 0) { + return snapshotMentions.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()); + } + return fallbackRecentMentions; + }, [explainEventsSnapshot, fallbackRecentMentions]); + const latestSignal = tickerSignals[0] || null; const priceColor = selectedTicker?.change > 0 ? '#00C853' : selectedTicker?.change < 0 ? '#FF1744' : '#000000'; const exposureWeight = holding && Number.isFinite(Number(holding.weight)) ? Number(holding.weight) * 100 : null; @@ -769,7 +863,7 @@ export default function StockExplainView({
- 这版解释以运行中的 agent 输出为主,不依赖外部新闻库或单股历史事件数据库。 + 这版解释优先读取当前 run 的 SQLite 历史快照,拿不到时再回退到运行中的内存态事件。
@@ -809,7 +903,7 @@ export default function StockExplainView({ : '暂无'}
- {latestSignal ? `${latestSignal.agentName} · ${latestSignal.date}` : '还没有历史信号'} + {latestSignal ? `${latestSignal.agentName} · ${latestSignal.date || eventDateKey(latestSignal.timestamp)}` : '还没有历史信号'}
@@ -846,8 +940,8 @@ export default function StockExplainView({ : '#666666'; return ( - - {signal.date} + + {signal.date || eventDateKey(signal.timestamp) || '-'}
{signal.agentName}
{signal.role}