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 '';}).join('')+'
时间阶段币种分数/收益价格/涨幅状态信号与说明
'+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||'--')+'
';} 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")