1
This commit is contained in:
parent
24859a6acf
commit
2e99af7486
@ -766,6 +766,17 @@ def get_onchain_token_detail(symbol, hours=72):
|
||||
""",
|
||||
(symbol, cutoff),
|
||||
).fetchall()
|
||||
raw_events = conn.execute(
|
||||
"""
|
||||
SELECT * FROM onchain_raw_events
|
||||
WHERE mapped_symbol=%s AND detected_at >= %s
|
||||
AND mapping_status='mapped'
|
||||
AND source=%s
|
||||
ORDER BY detected_at::timestamp DESC, importance DESC, id DESC
|
||||
LIMIT 100
|
||||
""",
|
||||
(symbol, cutoff, STANDARD_SIGNAL_SOURCE),
|
||||
).fetchall()
|
||||
rec = conn.execute(
|
||||
"""
|
||||
SELECT id, rec_time, action_status, execution_status, display_bucket, entry_price, current_price
|
||||
@ -781,6 +792,8 @@ def get_onchain_token_detail(symbol, hours=72):
|
||||
"hours": int(hours or 72),
|
||||
"mappings": [_with_raw(row) for row in mappings],
|
||||
"events": [_with_raw(row) for row in events],
|
||||
"raw_events": [_format_raw_event(row) for row in raw_events],
|
||||
"raw_event_count": len(raw_events),
|
||||
"metrics": [_with_raw(row) for row in metrics],
|
||||
"recommendation": dict(rec) if rec else None,
|
||||
}
|
||||
|
||||
@ -75,7 +75,7 @@ async function reloadRawEvents(offset){state.rawOffset=offset||0;$('rawFeed').in
|
||||
async function reloadTokens(offset){state.offset=offset||0;$('tokenTable').innerHTML='<tr><td colspan="8" class="loading">加载中...</td></tr>';try{var qs='hours='+$('hoursSel').value+'&limit='+state.limit+'&offset='+state.offset+'&chain='+encodeURIComponent($('chainSel').value)+'&signal='+encodeURIComponent($('signalSel').value);var d=await (await fetch(API+'/api/onchain/tokens?'+qs)).json();state.total=d.total||0;var items=d.items||[];if(!items.length){$('tokenTable').innerHTML='<tr><td colspan="8" class="empty">暂无链上异动 token</td></tr>'}else{$('tokenTable').innerHTML=items.map(function(t){return '<tr onclick="loadDetail(\''+esc(t.symbol)+'\')"><td class="sym">'+esc(t.symbol)+'</td><td>'+esc(t.chain)+'</td><td class="num">'+Number(t.onchain_score||0).toFixed(0)+'</td><td class="num">'+Number(t.risk_score||0).toFixed(0)+'</td><td class="num">'+Number(t.event_count||t.mapped_event_count||0).toFixed(0)+'</td><td>'+esc(t.latest_event_at?fmtTime(t.latest_event_at):'--')+'</td><td>'+esc(t.source||'nodereal')+'</td><td>'+recLabel(t.recommendation)+'</td></tr>'}).join('');if(!state.selected&&items[0])loadDetail(items[0].symbol)}updatePager()}catch(e){$('tokenTable').innerHTML='<tr><td colspan="8" class="empty">加载失败</td></tr>'}}
|
||||
function updatePager(){var page=Math.floor(state.offset/state.limit)+1,totalPages=Math.max(1,Math.ceil((state.total||0)/state.limit));$('pageInfo').textContent='第 '+page+' / '+totalPages+' 页,共 '+state.total+' 个';$('prevBtn').disabled=state.offset<=0;$('nextBtn').disabled=state.offset+state.limit>=state.total}
|
||||
function page(step){var next=state.offset+step*state.limit;if(next<0||next>=state.total)return;reloadTokens(next)}
|
||||
async function loadDetail(symbol){state.selected=symbol;$('detailNote').textContent=symbol;$('detailBody').innerHTML='<div class="loading">加载详情...</div>';try{var d=await (await fetch(API+'/api/onchain/tokens/'+encodeURIComponent(symbol)+'?hours=168')).json();var latest=(d.metrics||[])[0]||{};var rec=d.recommendation;var metrics='<div class="metric-grid">'+[['链上分',Number(latest.onchain_score||0).toFixed(0)],['风险分',Number(latest.risk_score||0).toFixed(0)],['映射事件',Number(latest.event_count||latest.mapped_event_count||0).toFixed(0)],['数据源',latest.source||'nodereal']].map(function(x){return '<div class="metric"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')+'</div>';var recHtml=rec?'<div class="hint">主链路状态:'+esc(rec.action_status||rec.execution_status||'观察')+' · 推荐 #'+esc(rec.id)+'</div>':'<div class="hint">尚未形成主链路推荐;若链上信号质量足够,会先进入技术检查。</div>';var events=(d.events||[]).slice(0,20).map(function(e){var cls=e.direction==='risk'?'risk':e.direction==='positive'?'pos':'blue';return '<div class="event"><div class="event-top"><div class="event-title">'+esc(e.signal_label||e.signal_code)+'</div><span class="badge '+cls+'">'+esc(e.severity||e.direction)+'</span></div><div class="event-meta">'+fmtTime(e.detected_at)+' · '+esc(e.chain)+' · '+fmtUsd(e.value_usd)+'<br>'+esc(e.wallet_label||e.counterparty_label||e.source||'链上事件')+'</div></div>'}).join('')||'<div class="empty">暂无事件明细</div>';$('detailBody').innerHTML='<div class="detail-title">'+esc(d.symbol)+'</div><div class="detail-sub">'+(d.mappings||[]).length+' 个合约映射 · 近 7 天</div>'+metrics+recHtml+'<div class="event-feed">'+events+'</div>'}catch(e){$('detailBody').innerHTML='<div class="empty">详情加载失败</div>'}}
|
||||
async function loadDetail(symbol){state.selected=symbol;$('detailNote').textContent=symbol;$('detailBody').innerHTML='<div class="loading">加载详情...</div>';try{var d=await (await fetch(API+'/api/onchain/tokens/'+encodeURIComponent(symbol)+'?hours=168')).json();var latest=(d.metrics||[])[0]||{};var rec=d.recommendation;var rawCount=Number(d.raw_event_count||latest.event_count||latest.mapped_event_count||0);var metrics='<div class="metric-grid">'+[['链上分',Number(latest.onchain_score||0).toFixed(0)],['风险分',Number(latest.risk_score||0).toFixed(0)],['映射事件',rawCount.toFixed(0)],['数据源',latest.source||'nodereal']].map(function(x){return '<div class="metric"><span>'+x[0]+'</span><b>'+x[1]+'</b></div>'}).join('')+'</div>';var recHtml=rec?'<div class="hint">主链路状态:'+esc(rec.action_status||rec.execution_status||'观察')+' · 推荐 #'+esc(rec.id)+'</div>':'<div class="hint">尚未形成主链路推荐;若链上信号质量足够,会先进入技术检查。</div>';var standardEvents=(d.events||[]).slice(0,10).map(function(e){var cls=e.direction==='risk'?'risk':e.direction==='positive'?'pos':'blue';return '<div class="event"><div class="event-top"><div class="event-title">'+esc(e.signal_label||e.signal_code)+'</div><span class="badge '+cls+'">'+esc(e.severity||e.direction)+'</span></div><div class="event-meta">'+fmtTime(e.detected_at)+' · '+esc(e.chain)+' · '+fmtUsd(e.value_usd)+'<br>'+esc(e.wallet_label||e.counterparty_label||e.source||'链上事件')+'</div></div>'});var rawEvents=(d.raw_events||[]).slice(0,20).map(function(e){var amount=e.total_amount||e.amount||0;var link=e.url?' · <a class="raw-link" href="'+esc(e.url)+'" target="_blank" rel="noopener">查看来源</a>':'';return '<div class="event"><div class="event-top"><div class="event-title">'+esc(e.event_label||e.title||e.event_type||'NodeReal 原始事件')+'</div><span class="badge mapped">已映射</span></div><div class="event-meta">'+fmtTime(e.detected_at)+' · '+esc(e.chain)+' · '+esc(e.mapped_symbol||d.symbol)+' · 热度 '+fmtAmount(amount)+link+'<br>'+esc(e.pipeline_note||'已映射,可进入后续链上信号分析。')+'</div></div>'});var events=standardEvents.concat(rawEvents).join('')||'<div class="empty">暂无事件明细</div>';$('detailBody').innerHTML='<div class="detail-title">'+esc(d.symbol)+'</div><div class="detail-sub">'+(d.mappings||[]).length+' 个合约映射 · 近 7 天</div>'+metrics+recHtml+'<div class="event-feed">'+events+'</div>'}catch(e){$('detailBody').innerHTML='<div class="empty">详情加载失败</div>'}}
|
||||
function reloadAll(){state.offset=0;state.rawOffset=0;state.selected='';loadOverview();reloadRawEvents(0);reloadTokens(0)}
|
||||
reloadAll();
|
||||
setInterval(reloadAll,300000);
|
||||
|
||||
@ -232,6 +232,32 @@ def test_overview_ignores_legacy_signals_and_surfaces_mapped_raw_feed(monkeypatc
|
||||
assert overview["signals"] == []
|
||||
|
||||
|
||||
def test_token_detail_includes_mapped_raw_events(monkeypatch, tmp_path):
|
||||
_temp_db(monkeypatch, tmp_path)
|
||||
onchain_db.insert_onchain_raw_event(
|
||||
{
|
||||
"source": "nodereal",
|
||||
"chain": "bsc",
|
||||
"event_type": "evm_transfer",
|
||||
"token_address": "0xbeam",
|
||||
"title": "NodeReal ERC-20 原始转账",
|
||||
"amount": 300,
|
||||
"total_amount": 300,
|
||||
"importance": 78,
|
||||
"mapped_symbol": "BEAM/USDT",
|
||||
"mapping_status": "mapped",
|
||||
"detected_at": datetime.now().isoformat(),
|
||||
}
|
||||
)
|
||||
|
||||
detail = onchain_db.get_onchain_token_detail("BEAM/USDT", hours=24)
|
||||
|
||||
assert detail["events"] == []
|
||||
assert detail["raw_event_count"] == 1
|
||||
assert detail["raw_events"][0]["mapped_symbol"] == "BEAM/USDT"
|
||||
assert detail["raw_events"][0]["pipeline_note"] == "已映射,可进入后续链上信号分析。"
|
||||
|
||||
|
||||
def test_nodereal_events_generate_metrics_and_normalized_event(monkeypatch, tmp_path):
|
||||
_temp_db(monkeypatch, tmp_path)
|
||||
monkeypatch.setenv("ALPHAX_NODEREAL_API_KEY", "test-key")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user