diff --git a/static/pipeline.html b/static/pipeline.html
index 338ffa6..8382f80 100644
--- a/static/pipeline.html
+++ b/static/pipeline.html
@@ -72,7 +72,7 @@ function changePage(step){if(!state.totalPages)return;var next=state.page+step;i
function setHoursAndReload(){state.offset=0;state.page=1;resetSelection();loadRuns();}
function renderDetail(){var d=state.detail||{},s=d.summary||{};$('detailTitle').textContent='批次 #'+(s.run_id||s.id||'--')+' · '+fmtTime(s.started_at);$('detailWindow').textContent=fmtTime(s.window_start)+' - '+fmtTime(s.window_end);var timeline=(d.timeline||[]).map(function(t){return '
'+esc(t.stage)+''+fmtTime(t.started_at)+' · '+fmtDur(t.duration_ms)+''+esc(t.result_status||t.run_status)+'
';}).join('')||'暂无阶段日志
';var counts=d.stage_counts||{};var chips=[['all','全部',0],['universe_gate','宇宙过滤',counts.universe_gate||0],['discovery','异动发现',counts.discovery||0],['quality_filter','质量验证',(counts.quality_pass||0)+(counts.quality_reject||0)],['trade_confirm','交易确认',counts.trade_confirm||0],['tracking','跟踪',counts.tracking||0],['review','复盘',counts.review||0],['missed','漏选',counts.missed||0]];var chipHtml=chips.map(function(c){return '';}).join('');var minis=[['覆盖漏斗',(s.usdt_pair_count||0)+' / '+(s.tradable_universe_count||0)+' / '+(s.kline_attempt_count||0)+' / '+(s.rough_candidates||0)],['缓存过滤',s.cached_exclusion_count||0],['K线成功',pct(s.kline_h1_success_count,s.kline_attempt_count)+' / '+pct(s.kline_h4_success_count,s.kline_attempt_count)],['流程漏斗',(s.rough_candidates||0)+' / '+(s.fine_qualified||0)+' / '+(s.confirm_hits||0)+' / '+(s.recommendations||0)],['推荐转化',pct(s.recommendations,s.fine_qualified)],['复盘成功/失败',(s.perf_success||0)+' / '+(s.perf_failed||0)],['漏选',s.missed_count||0]].map(function(x){return ''+x[0]+''+x[1]+'
';}).join('');$('detailBody').innerHTML=''+timeline+'
'+minis+'
'+chipHtml+'
'+renderRows();}
function setFilter(f){state.filter=f;renderDetail();}
-function rowStage(item){if(item.kind==='screening')return item.funnel_stage||item.layer||'discovery';if(item.kind==='recommendation')return (item.performance_status==='success'||item.performance_status==='failed')?'tracking':'trade_confirm';if(item.kind==='review')return 'review';if(item.kind==='missed')return 'discovery';return 'all';}
+function rowStage(item){if(item.kind==='missed')return 'missed';if(item.kind==='screening')return item.funnel_stage||item.layer||'discovery';if(item.kind==='recommendation')return (item.performance_status==='success'||item.performance_status==='failed')?'tracking':'trade_confirm';if(item.kind==='review')return 'review';return 'all';}
function collectRows(){var d=state.detail||{},rows=[];(d.screening_items||[]).forEach(function(x){rows.push({kind:'screening',layer:x.layer,funnel_stage:x.funnel_stage,time:x.scan_time,symbol:x.symbol,stage:x.candidate_stage_label||x.funnel_stage_label||x.stage_label,score:x.score,price:x.price,status:x.state,signals:x.signals||[],note:(x.detail_json&&x.detail_json.reason_label)||(x.detail_json&&x.detail_json.reason)||'',stage_code:x.candidate_stage});});(d.recommendations||[]).forEach(function(x){rows.push({kind:'recommendation',time:x.rec_time,symbol:x.symbol,stage:x.stage_label||'交易推荐',score:x.rec_score,price:x.entry_price,status:x.execution_label||x.action_status||x.status,signals:x.signal_labels&&x.signal_labels.length?x.signal_labels:(x.signals||[]),note:x.execution_reason||x.state_reason||'',performance_status:x.performance_status});});(d.reviews||[]).forEach(function(x){rows.push({kind:'review',time:x.review_time,symbol:x.symbol,stage:x.outcome==='爆发'?'复盘命中':(x.outcome==='失败'?'复盘失败':'复盘未验证'),score:'',price:x.pnl_48h,status:x.outcome,signals:x.hit_signals&&x.hit_signals.length?x.hit_signals:(x.triggered_signals||[]),note:x.lesson||''});});(d.missed_explosions||[]).forEach(function(x){rows.push({kind:'missed',time:x.detect_time,symbol:x.symbol,stage:'漏选',score:'',price:x.gain_pct,status:x.reason_missed||'漏选',signals:[],note:x.lesson||''});});rows.sort(function(a,b){return String(a.time||'').localeCompare(String(b.time||''));});return rows;}
function renderRows(){var rows=collectRows().filter(function(r){return state.filter==='all'||rowStage(r)===state.filter||(state.filter==='review'&&r.kind==='review');});if(!rows.length)return '当前阶段暂无明细
';return '| 时间 | 阶段 | 币种 | 分数/收益 | 价格/涨幅 | 状态 | 信号与说明 |
'+rows.map(function(r){var cls=r.kind==='recommendation'?'rec':r.stage==='复盘命中'?'win':(r.stage==='复盘失败'||r.kind==='missed')?'fail':'';var parts=uniq((r.signals||[]).slice(0,5).concat([r.note]));var desc=parts.map(friendlyReason).join(' · ');return '| '+fmtTime(r.time)+' | '+label(r.stage,cls)+' | '+esc(r.symbol||'--')+' | '+esc(r.score===''?'--':r.score)+' | '+esc(r.price==null?'--':r.price)+' | '+esc(r.status||'--')+' | '+esc(desc||'--')+' |
';}).join('')+'
';}
loadRuns();
diff --git a/tests/test_pipeline_runs_api.py b/tests/test_pipeline_runs_api.py
index 3ffc948..0b57ad6 100644
--- a/tests/test_pipeline_runs_api.py
+++ b/tests/test_pipeline_runs_api.py
@@ -328,13 +328,23 @@ def test_pipeline_page_nav_hides_watchlist_entry_and_watchlist_route_survives(te
assert pipeline_resp.status_code == 200
html = pipeline_resp.text
assert "链路日志" in html
- assert 'href="/pipeline"' in html
assert 'href="/watchlist"' not in html
watch_resp = client.get("/watchlist")
assert watch_resp.status_code == 200
+def test_pipeline_page_filters_missed_rows_as_missed(temp_db):
+ client = TestClient(web_server.app)
+
+ resp = client.get("/pipeline")
+ assert resp.status_code == 200
+ html = resp.text
+
+ assert "if(item.kind==='missed')return 'missed'" in html
+ assert "['missed','漏选'" in html
+
+
def test_user_nav_keeps_review_center_but_hides_legacy_research_pages(temp_db):
client = TestClient(web_server.app)
resp = client.get("/app")