提议卡重排:要紧的放前面,证据收进展开,并标出「建议档位」与「实下股数」不一致
一、重排。原先一张卡一口气铺十六行,实测二十七八个可视行,大半是「暂无」 「近三天没点名」这类占位句,要人在一屏读不完的文字里自己找哪条要紧。 现在按「决定要不要点采纳所必需」分两层: 卡面留——这一笔买多少股多少钱占总规模几成、建议档位、选股系统判决、 决策系统结论(截到 60 字)、公司深度(真有报告才占一行)、为什么选它。 展开里收——研报、安全边际、催化事件、定价状态、相关快讯、行业催化、 判决收敛、建议方案整句、择时层两个期限、候选榜信息、决策系统原话。 「这一笔买多少」原先排在第十四行,而它才是采纳按钮真正会执行的东西,提到第一行。 二、删掉重复行。propBasis 与 propVerdictLine 在判决为「候选」或「仅展示」时 读的是同一个 hard_numbers.basis,逐字相同,模板里又挨着摆两行,于是卡上出现 两行一模一样的话。今天 77 只票里 38 只判候选,每一只出提议都重复一次。 删 propBasis,留 09-04 专门写的收口函数 propVerdictLine。 三、「为什么选它」第一次上卡。propReasons 这个函数写好之后模板里一直没引用过 (grep 只有定义与导出两处),而这恰恰是人做决定最先要看的。顺带不再只取前两条。 四、【要紧】新增一行红字,标出建议档位与实下股数不一致。 研判把握度低或不可用时,advice_service.apply_judge 把档位降成「试探仓」、 把 advice.target_pct 改成 1%,但它明确不改数量(函数说明原文「不改数量, 数量交人」),而页面上采纳按钮没有填数量的地方,这个「交人」无处兑现—— 采纳时后端直接抄提议里的 qty 原样落单(web/main.py:485,只有卖出侧会重算)。 155 实测 8 张待确认提议,7 张写着「试探仓(目标 1.0%)」,而每一张实际都会 买 3.00%(标准仓 6% 的第一批),正好三倍: 002812.SZ 1200股×48.55 实占 3.00% 建议 1% 算量用 6% 300604.SZ 200股×264.81 实占 3.00% 建议 1% 算量用 6% 300638.SZ 3300股×18.11 实占 3.05% 建议 1% 算量用 6% 301525.SZ 1600股×37.37 实占 3.00% 建议 1% 算量用 6% 688698.SH 1400股×41.49 实占 3.00% 建议 1% 算量用 6% 605598.SH 2300股×25.24 实占 3.07% 建议 1% 算量用 6% 688556.SH 7300股×8.11 实占 3.02% 建议 1% 算量用 6% 只有 300118.SZ(标准仓)是自洽的。 根子上的修法是让数量跟着档位重算,那会改变每一笔采纳真正发出去的钱, 还要一并处理「1% 在高价票上凑不满一手」与「pms_position.target_pct 从未写过、 试探仓建的仓日后会被按 6% 补满」两件事,所以单独拍板、本次不做。 在那之前先把矛盾摆到人眼前——让人看错数字下单,比让页面难看严重得多。 判定用的键一个都没动,只动 DOM 与显示函数。两道页面守卫通过。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9359056d04
commit
d30440edb2
|
|
@ -1075,25 +1075,48 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;}
|
|||
<span class="muted mono" style="font-size:11px">{{ p.ts_code }}</span>
|
||||
<el-tag size="small" effect="dark" type="primary">{{ tx('action', p.action) }}</el-tag>
|
||||
<span class="spacer"></span><span class="muted" style="font-size:11px">{{ p.expire_at }} 前有效</span></div>
|
||||
<div class="st" v-if="p.judge_reason"><span class="muted">决策系统:</span>{{ p.judge_reason }}<span class="muted" v-if="(p.hard_numbers||{}).judge_conf!=null">(置信度 {{ (p.hard_numbers||{}).judge_conf }})</span></div>
|
||||
<!-- 2026-09-10 重排。原先这里一口气铺十六行、实测一张卡二十七八个可视行,
|
||||
其中大半是「暂无」「近三天没点名」这类占位句 —— 要人在一屏读不完的
|
||||
文字里自己找哪一条是要紧的。现在按「决定要不要点采纳所必需」分两层:
|
||||
必需的留在卡面, 证据类的收进展开。判定用的键一个都没动, 只动 DOM。 -->
|
||||
|
||||
<!-- 第一行就是这一笔到底买多少。原先它排在第十四行, 而它才是采纳按钮真正
|
||||
会执行的东西。 -->
|
||||
<div class="st"><b>{{ p.qty }} 股</b> · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }}
|
||||
· 约需 <b>{{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}</b>
|
||||
<span class="muted" v-if="propSizePct(p)">(占总规模 {{ propSizePct(p) }})</span></div>
|
||||
|
||||
<!-- 建议档位与实际下单量对不对得上。对不上就直接说, 别让人自己去算。 -->
|
||||
<div class="st" v-if="propAdviceTier(p)"><span class="muted">建议档位:</span>{{ propAdviceTier(p) }}</div>
|
||||
<div class="st" v-if="propSizeMismatch(p)" style="color:#d8574a">{{ propSizeMismatch(p) }}</div>
|
||||
|
||||
<div class="st" v-if="(p.hard_numbers||{}).verdict"><span class="muted">选股系统:</span>{{ propVerdictLine(p) }}</div>
|
||||
<div class="st muted" v-if="propBasis(p)">{{ propBasis(p) }}</div>
|
||||
<div class="st" v-if="propLogic(p)"><span class="muted">研报说:</span>{{ propLogic(p) }}<span class="muted" v-if="propLogicMore(p)">(另有 {{ propLogicMore(p) }} 条)</span></div>
|
||||
<!-- 三句整句都是选股系统写好的中文 (2026-09-07 安全边际、2026-09-08 催化事件与定价状态),
|
||||
只展示不进判定; 缺就不显示这一行, 不留空壳。 -->
|
||||
<div class="st muted" v-if="propText(p, 'valuation_text')">{{ propText(p, 'valuation_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'events_text')">{{ propText(p, 'events_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'pricing_text')">{{ propText(p, 'pricing_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'news_text')">{{ propText(p, 'news_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'industry_catalyst_text')">{{ propText(p, 'industry_catalyst_text') }}</div>
|
||||
<!-- 公司深度与建议方案 (2026-09-09 接入方案): 质地档、评议摘要、报告链接; 建议方案与机械方案并排。 -->
|
||||
<div class="st" v-if="propText(p, 'company_review_text')"><span class="muted">公司深度:</span>{{ propText(p, 'company_review_text') }}</div>
|
||||
<div class="st" v-if="propText(p, 'company_gate')"><span class="muted">判决收敛:</span>{{ propText(p, 'company_gate') }}</div>
|
||||
<div class="st" v-if="propText(p, 'advice_text')">{{ propText(p, 'advice_text') }}</div>
|
||||
<div class="st" v-if="propHeads(p)"><span class="muted">择时层两个期限:</span>{{ propHeads(p) }}</div>
|
||||
<div class="st muted">{{ propWhy(p) }}</div>
|
||||
<div class="st">约 {{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }}
|
||||
· 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}</div>
|
||||
<div class="st" v-if="p.judge_reason"><span class="muted">决策系统:</span>{{ propJudgeShort(p) }}<span class="muted" v-if="(p.hard_numbers||{}).judge_conf!=null">(把握度 {{ (p.hard_numbers||{}).judge_conf }})</span></div>
|
||||
|
||||
<!-- 公司深度只在真有报告时才占一行。判据用结构化的 company_review 而不是
|
||||
那句整句 —— 没报告时上游下发的是一句 21 字的占位, 实测 77 只票里 64 只
|
||||
都是它, 占着位置又不说事。 -->
|
||||
<div class="st" v-if="(p.hard_numbers||{}).company_review"><span class="muted">公司深度:</span>{{ propText(p, 'company_review_text') }}</div>
|
||||
|
||||
<!-- 为什么选它。这三条一直算好放在 hard_numbers.reasons 里, 但模板从来没用过。 -->
|
||||
<div class="st muted" v-for="(x, ri) in propReasons(p)" :key="'r'+ri">{{ x }}</div>
|
||||
|
||||
<div class="st muted clickable" @click="togglePropMore(p.proposal_id)">
|
||||
{{ propMoreOpen.has(p.proposal_id) ? '▾ 收起证据' : '▸ 展开证据(研报、安全边际、催化、定价、快讯、行业、择时层)' }}
|
||||
</div>
|
||||
<template v-if="propMoreOpen.has(p.proposal_id)">
|
||||
<div class="st" v-if="propLogic(p)"><span class="muted">研报说:</span>{{ propLogic(p) }}<span class="muted" v-if="propLogicMore(p)">(另有 {{ propLogicMore(p) }} 条)</span></div>
|
||||
<div class="st muted" v-if="propText(p, 'valuation_text')">{{ propText(p, 'valuation_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'events_text')">{{ propText(p, 'events_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'pricing_text')">{{ propText(p, 'pricing_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'news_text')">{{ propText(p, 'news_text') }}</div>
|
||||
<div class="st muted" v-if="propText(p, 'industry_catalyst_text')">{{ propText(p, 'industry_catalyst_text') }}</div>
|
||||
<div class="st" v-if="propText(p, 'company_gate')"><span class="muted">判决收敛:</span>{{ propText(p, 'company_gate') }}</div>
|
||||
<div class="st" v-if="propText(p, 'advice_text')">{{ propText(p, 'advice_text') }}</div>
|
||||
<div class="st" v-if="propHeads(p)"><span class="muted">择时层两个期限:</span>{{ propHeads(p) }}</div>
|
||||
<div class="st muted">{{ propWhy(p) }}</div>
|
||||
<div class="st muted" v-if="p.judge_reason && propJudgeShort(p) !== p.judge_reason">决策系统原话:{{ p.judge_reason }}</div>
|
||||
</template>
|
||||
<div style="margin-top:6px">
|
||||
<el-button type="success" size="small" @click="decide(p,'ACCEPTED')">采纳</el-button>
|
||||
<el-button type="danger" size="small" @click="decide(p,'DECLINED')">驳回</el-button>
|
||||
|
|
@ -2733,15 +2756,69 @@ createApp({
|
|||
if (h.heat != null) bits.push('热度 ' + (typeof h.heat === 'number' ? h.heat.toFixed(2) : h.heat));
|
||||
return bits.join(' · ') || '—';
|
||||
}
|
||||
// 选股系统候选卡的理由 (硬数字 reasons, 后端已截到四条), 提议卡上只显示前两条
|
||||
// 选股系统候选卡的理由 (硬数字 reasons, 后端已截到四条)。
|
||||
// 2026-09-10: 这个函数写好之后模板里一直没引用过, 卡上从来没显示过「为什么选它」——
|
||||
// 而这恰恰是人做决定最先要看的。现在放到卡面上, 并且不再只取前两条 (实测每票 2 到 3 条)。
|
||||
function propReasons(p) {
|
||||
const r = (p.hard_numbers || {}).reasons;
|
||||
return Array.isArray(r) ? r.slice(0, 2) : [];
|
||||
return Array.isArray(r) ? r.filter(x => typeof x === 'string' && x.trim()) : [];
|
||||
}
|
||||
// 2026-09-10 删掉了 propBasis。它 return 的是 hard_numbers.basis, 而 propVerdictLine
|
||||
// 在判决为「候选」或「仅展示」时 return 的也是同一个 basis —— 两个函数逐字相同,
|
||||
// 模板里又挨着摆了两行, 于是卡上出现两行一模一样的话 (今天 77 只票里 38 只判候选,
|
||||
// 每一只出提议都重复一次)。留 propVerdictLine 那一个, 它是 09-04 专门写的收口函数,
|
||||
// 历史提议表格那一处 (「选股判决」列) 本来就是单独用它、没配 basis 行。
|
||||
// 这一笔占总规模多少。hard_numbers 里有 target_amount 与 target_pct 两个数,
|
||||
// 相除就是总规模, 不必再从参数表取一遍 (取一遍还有取不到的可能)。
|
||||
function propScale(p) {
|
||||
const h = p.hard_numbers || {};
|
||||
const amt = Number(h.target_amount), pct = Number(h.target_pct);
|
||||
return (isFinite(amt) && isFinite(pct) && pct > 0) ? amt / pct : null;
|
||||
}
|
||||
function propSizePct(p) {
|
||||
const h = p.hard_numbers || {};
|
||||
const scale = propScale(p), price = Number(h.price), qty = Number(p.qty);
|
||||
if (!scale || !isFinite(price) || !isFinite(qty) || qty <= 0) return '';
|
||||
return (price * qty / scale * 100).toFixed(2) + '%';
|
||||
}
|
||||
// 建议档位的短版。原先卡上摆的是 advice_text 那一整句 (实测 90 多个字, 把档位、
|
||||
// 机械方案、条件、依据、研判结论全串在一行), 读的人要在里面找「到底建议买多少」。
|
||||
// 整句仍在展开里, 这里只留结论。
|
||||
function propAdviceTier(p) {
|
||||
const a = (p.hard_numbers || {}).advice;
|
||||
if (!a || typeof a !== 'object') return '';
|
||||
const tier = String(a.tier || '').trim();
|
||||
const pct = Number(a.target_pct);
|
||||
if (!tier) return '';
|
||||
return tier + (isFinite(pct) ? '(目标 ' + (pct * 100).toFixed(1) + '%)' : '');
|
||||
}
|
||||
// 【要紧】建议档位与真正会下的股数对不对得上。
|
||||
//
|
||||
// 2026-09-10 查出来的问题: 研判把握度低或不可用时, advice_service.apply_judge 会把
|
||||
// 档位降成「试探仓」并把 advice.target_pct 改成 1%, 但它明确不改数量 (函数说明原文
|
||||
// 「不改数量, 数量交人」), 而页面上采纳按钮没有填数量的地方, 这个「交人」无处兑现——
|
||||
// 采纳时后端直接抄提议里的 qty 原样落单。于是卡上写着「试探仓, 目标 1.0%」,
|
||||
// 点下去买的却是标准仓 6% 的第一批。实测恩捷股份那张卡: 卡面写 1.0%,
|
||||
// 实下 1200 股 48.55 元共 58,260 元 = 总规模的 2.91%, 差 2.9 倍。
|
||||
//
|
||||
// 根子上的修法是让数量跟着档位重算, 那会改变每一笔采纳真正发出去的钱, 要单独拍板。
|
||||
// 在那之前, 这一行先把矛盾摆到人眼前 —— 让人看错数字下单, 比让页面难看严重得多。
|
||||
function propSizeMismatch(p) {
|
||||
const h = p.hard_numbers || {};
|
||||
const a = h.advice;
|
||||
if (!a || typeof a !== 'object') return '';
|
||||
const advPct = Number(a.target_pct), usedPct = Number(h.target_pct);
|
||||
if (!isFinite(advPct) || !isFinite(usedPct) || usedPct <= 0) return '';
|
||||
if (Math.abs(advPct - usedPct) < 1e-9) return '';
|
||||
return '注意:上面那个股数是按 ' + (usedPct * 100).toFixed(1) + '% 那一档算的,'
|
||||
+ '不是按建议档位的 ' + (advPct * 100).toFixed(1) + '%。采纳后下的就是上面那个数。';
|
||||
}
|
||||
// 决策系统那句话最长到 500 字, 而它常常是一句技术报错 (例如研判超时那句)。
|
||||
// 卡面只留前 60 字, 原话收进展开。
|
||||
function propJudgeShort(p) {
|
||||
const r = String(p.judge_reason || '').trim();
|
||||
return r.length > 60 ? r.slice(0, 60) + '…' : r;
|
||||
}
|
||||
// 研究理由 (2026-09-03): 判决依据是上游对这条判决的一句话解释; 因果论断是研报里说这家
|
||||
// 公司好在哪, 每行自带「——出处:《研报标题》 披露日」。卡片窄, 只摆第一条论断,
|
||||
// 但那一条必须原样显示到出处与日期为止 —— 一条 2024 年的论断与一条上周的, 人得一眼分得出。
|
||||
function propBasis(p) { return (p.hard_numbers || {}).basis || ''; }
|
||||
// 硬数字里由上游写好的整句 (安全边际 / 催化事件 / 定价状态): 原样显示, 不是字符串就不显示。
|
||||
function propText(p, key) {
|
||||
const v = (p.hard_numbers || {})[key];
|
||||
|
|
@ -3820,6 +3897,14 @@ createApp({
|
|||
const lFold = ref(!!_rf.l), rFold = ref(!!_rf.r);
|
||||
const railOpen = ref(null); // null / 'l' / 'r'
|
||||
const sectClosed = reactive({ prop:false, strat:false, pool:false, ops:false, set:true });
|
||||
// 提议卡上「展开证据」的状态, 按提议号记。用整体替换而不是原地增删,
|
||||
// 因为 Vue 对 Set 的方法调用不作响应, 替换引用才会重画。
|
||||
const propMoreOpen = ref(new Set());
|
||||
const togglePropMore = (id) => {
|
||||
const next = new Set(propMoreOpen.value);
|
||||
if (next.has(id)) next.delete(id); else next.add(id);
|
||||
propMoreOpen.value = next;
|
||||
};
|
||||
function toggleSect(k) { sectClosed[k] = !sectClosed[k]; }
|
||||
function _railSave() {
|
||||
try { sessionStorage.setItem(RAIL_STORE_KEY, JSON.stringify({ l:lFold.value, r:rFold.value })); } catch (e) {}
|
||||
|
|
@ -3910,8 +3995,11 @@ createApp({
|
|||
// 但从 2026-09-04 那批文案改造起就一直没写进这份清单, 模板拿到的是 undefined。
|
||||
// 平时提议队列为空、那几行 v-if 不成立所以从不调用, 09-10 早上队列里出现
|
||||
// 八条新建仓提议, 第一次真的调到它, 整页渲染当场炸掉 —— 表现是整个页面白屏。
|
||||
propVerdictLine, propBasis, propLogic, propLogicMore, propText, propHeads,
|
||||
propVerdictLine, propLogic, propLogicMore, propText, propHeads,
|
||||
hardCn, opCn, paramsCn, dropCn, plainCheck,
|
||||
// 2026-09-10 提议卡重排新增
|
||||
propScale, propSizePct, propAdviceTier, propSizeMismatch, propJudgeShort,
|
||||
propMoreOpen, togglePropMore,
|
||||
pHaltBuy, pResumeBuy, pHaltAll, pResumeAll, pReduce, pIncrease, pLiquidate, pSectorExit,
|
||||
pctOf, tgtPos, posMoveValid, posMovePreview, doPosMove, heldSectors, secSel, doSectorExit,
|
||||
pmap, pval, dcaOn, sumPosition, sumDca, sumAutonomy,
|
||||
|
|
|
|||
Loading…
Reference in New Issue