feat(frontend): 决议O③卡角标☠n/N(部分死=卡留活跃池)+死因子行态(划线/☠前缀/死因tooltip/不可点修404) [vps]
This commit is contained in:
@@ -34,6 +34,7 @@ export interface HypothesisFactor {
|
||||
origin: string
|
||||
createdAt: string | null
|
||||
status: string
|
||||
cause?: string | null // 决议O③:graveyard 死因(死因子行 tooltip)
|
||||
batch?: { jobId: string; startedAt: string } | null // 出生批次(台账反查,null=早期/无记录)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,13 @@ export const todosMock: TodoItem[] = [
|
||||
]
|
||||
|
||||
export const hypothesesMock: Hypothesis[] = [
|
||||
{ id: 'h01', title: '高管增持公告后 60 日跑赢', source: '直觉+UZI 底稿', logic: '真金白银增持=内部人信号', expectedSign: 'positive', falsifiable: '若增持公告后 60 日超额收益均值<=0 则证伪', dataNeeds: ['高管增持公告事件'], state: 'data_check', updatedAt: '2026-09-22' },
|
||||
{ id: 'h01', title: '高管增持公告后 60 日跑赢', source: '直觉+UZI 底稿', logic: '真金白银增持=内部人信号', expectedSign: 'positive', falsifiable: '若增持公告后 60 日超额收益均值<=0 则证伪', dataNeeds: ['高管增持公告事件'], state: 'data_check', updatedAt: '2026-09-22',
|
||||
factors: [
|
||||
{ name: 'fa高管增持超额', expression: 'ts_rank(close, 60)', origin: 'decomposer',
|
||||
createdAt: '2026-10-09', status: 'graveyard', cause: 'IC 两窗反号(2026-10 日报)', batch: null },
|
||||
{ name: 'fa高管增持量比', expression: 'volume / ts_mean(volume, 20)', origin: 'decomposer',
|
||||
createdAt: '2026-10-09', status: 'incubating', batch: null },
|
||||
] },
|
||||
{ id: 'h02', title: '龙虎榜游资席位反向(杀猪盘 8 信号)', source: '七项目调研', logic: '快变量频率错配→只作反向风险因子', expectedSign: 'negative', falsifiable: '若游资席位主导个股 T+5 后并未跑输则证伪', dataNeeds: ['龙虎榜席位明细(存量)'], state: 'data_check', updatedAt: '2026-09-22' },
|
||||
{ id: 'h03', title: '送转≠现金股息谬误修正', source: 'FinRobot 调研', logic: 'E 族口径:E11 送转剔除,只认现金股息', expectedSign: 'positive', falsifiable: '若现金股息率因子 IC 连续两窗为负则证伪', dataNeeds: ['dividend 域(存量)'], state: 'building', updatedAt: '2026-09-22', factorId: 'E14_dividend_yield' },
|
||||
{ id: 'h04', title: '分析师目标价上调共识拐点', source: '研报共识域', logic: 'ratingChange 0=上调(EM 编码),共识拐点领先收益', expectedSign: 'positive', falsifiable: '若上调共识拐点后 20 日超额收益无显著为正(t<2)则证伪', dataNeeds: ['research_report 域'], state: 'promoted', updatedAt: '2026-09-22', factorId: 'res_target_upside' },
|
||||
|
||||
@@ -52,6 +52,22 @@ describe('HypothesisPool.vue', () => {
|
||||
expect(w.text()).toContain('概念与 fa_xxx 重复,考试两次未过')
|
||||
expect(w.text()).toContain('无假设卡(存量/早期)')
|
||||
})
|
||||
it('决议O:部分死角标☠n/N+死因子划线不可点+死因tooltip', async () => {
|
||||
vi.mocked(api.getHypotheses).mockResolvedValue(hypothesesMock) // mock 已含 1 死 1 活
|
||||
const w = mount(HypothesisPool, { global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
const badge = w.find('.skull-badge')
|
||||
expect(badge.exists()).toBe(true)
|
||||
expect(badge.text()).toContain('☠1/2')
|
||||
// 因子清单默认折叠,展开后死因子行才渲染(同 D7 件①用例)
|
||||
const head = w.findAll('.sec-head').find((s) => s.text().includes('已产因子'))
|
||||
await head!.trigger('click')
|
||||
await flushPromises()
|
||||
const dead = w.findAll('.fac-name.dead')
|
||||
expect(dead.length).toBe(1)
|
||||
expect(dead[0].text()).toContain('fa高管增持超额')
|
||||
expect(dead[0].attributes('title')).toContain('IC 两窗反号')
|
||||
})
|
||||
})
|
||||
|
||||
describe('向导区(P4-1)', () => {
|
||||
|
||||
@@ -54,6 +54,9 @@ async function confirmSave() {
|
||||
const router = useRouter()
|
||||
const DECOMPOSABLE: Hypothesis['state'][] = ['queued', 'data_check', 'building']
|
||||
const canDecompose = (h: Hypothesis): boolean => DECOMPOSABLE.includes(h.state)
|
||||
// 决议O:部分死=卡留活跃池+角标☠n/N(n=死因子数,0 不显示)
|
||||
const deadFactorCount = (h: Hypothesis): number =>
|
||||
(h.factors || []).filter((f) => f.status === 'graveyard').length
|
||||
// 在飞态=服务端真相(卡片 decomposeJob),前端只轮询不登记(10-09 job 化)
|
||||
const isDecomposing = (h: Hypothesis): boolean => h.decomposeJob?.status === 'running'
|
||||
|
||||
@@ -225,6 +228,10 @@ function progText(h: Hypothesis): string {
|
||||
<div class="hcard-head">
|
||||
<span class="hcard-title">{{ h.title }}</span>
|
||||
<StatusChip :label="HYPOTHESIS_LABEL[h.state]" :tone="HYPOTHESIS_TONE[h.state]" />
|
||||
<span v-if="deadFactorCount(h)" class="skull-badge"
|
||||
:title="`卡下 ${deadFactorCount(h)}/${h.factors!.length} 因子已判死(卡留活跃池)`">
|
||||
☠{{ deadFactorCount(h) }}/{{ h.factors!.length }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="hcard-row"><span class="k">来源</span><span>{{ h.source }}</span></div>
|
||||
<div class="hcard-row"><span class="k">逻辑</span><span>{{ h.logic }}</span></div>
|
||||
@@ -263,7 +270,9 @@ function progText(h: Hypothesis): string {
|
||||
── 早期(无批次记录) ──
|
||||
</div>
|
||||
<div class="fac-row">
|
||||
<span class="fac-name" title="看出生档案"
|
||||
<span v-if="f.status === 'graveyard'" class="fac-name dead"
|
||||
:title="`☠ ${f.cause ?? '未注死因'}`">☠ {{ f.name }}</span>
|
||||
<span v-else class="fac-name" title="看出生档案"
|
||||
@click="router.push(`/pipeline/factors/${f.name}`)">{{ f.name }}</span>
|
||||
<code class="fac-expr">{{ f.expression }}</code>
|
||||
<span class="fac-date">{{ f.createdAt }}</span>
|
||||
@@ -390,6 +399,10 @@ function progText(h: Hypothesis): string {
|
||||
.fac-row:last-child { border-bottom: none; }
|
||||
.fac-name { font-family: var(--mono); color: var(--brand); cursor: pointer; white-space: nowrap; }
|
||||
.fac-name:hover { text-decoration: underline; }
|
||||
.fac-name.dead { text-decoration: line-through; color: var(--text-3); cursor: default; }
|
||||
.skull-badge { font-family: var(--mono); font-size: 10px; color: #ff5f6d;
|
||||
border: 1px solid rgba(255, 95, 109, 0.4); border-radius: var(--r-sm);
|
||||
padding: 0 5px; line-height: 16px; flex-shrink: 0; }
|
||||
.fac-expr { flex: 1; min-width: 0; font-family: var(--mono); font-size: 10px; color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.fac-date { color: var(--text-3); font-size: 10px; white-space: nowrap; }
|
||||
.fac-batch { font-family: var(--mono); font-size: 9.5px; color: var(--cyan-dim); border-top: 1px dashed rgba(0, 184, 204, 0.35); margin-top: 4px; padding-top: 3px; }
|
||||
|
||||
Reference in New Issue
Block a user