From d30440edb2608b50a10be7eda3e696f58529b180 Mon Sep 17 00:00:00 2001 From: zlt Date: Thu, 10 Sep 2026 13:00:03 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E8=AE=AE=E5=8D=A1=E9=87=8D=E6=8E=92?= =?UTF-8?q?=EF=BC=9A=E8=A6=81=E7=B4=A7=E7=9A=84=E6=94=BE=E5=89=8D=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E8=AF=81=E6=8D=AE=E6=94=B6=E8=BF=9B=E5=B1=95=E5=BC=80?= =?UTF-8?q?=EF=BC=8C=E5=B9=B6=E6=A0=87=E5=87=BA=E3=80=8C=E5=BB=BA=E8=AE=AE?= =?UTF-8?q?=E6=A1=A3=E4=BD=8D=E3=80=8D=E4=B8=8E=E3=80=8C=E5=AE=9E=E4=B8=8B?= =?UTF-8?q?=E8=82=A1=E6=95=B0=E3=80=8D=E4=B8=8D=E4=B8=80=E8=87=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 一、重排。原先一张卡一口气铺十六行,实测二十七八个可视行,大半是「暂无」 「近三天没点名」这类占位句,要人在一屏读不完的文字里自己找哪条要紧。 现在按「决定要不要点采纳所必需」分两层: 卡面留——这一笔买多少股多少钱占总规模几成、建议档位、选股系统判决、 决策系统结论(截到 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 --- app/web/static/index.html | 138 +++++++++++++++++++++++++++++++------- 1 file changed, 113 insertions(+), 25 deletions(-) diff --git a/app/web/static/index.html b/app/web/static/index.html index ff1874a..dced3d6 100644 --- a/app/web/static/index.html +++ b/app/web/static/index.html @@ -1075,25 +1075,48 @@ body.dock-r:not(.r-fold) .side-r .strip{display:none;} {{ p.ts_code }} {{ tx('action', p.action) }} {{ p.expire_at }} 前有效 -
决策系统:{{ p.judge_reason }}(置信度 {{ (p.hard_numbers||{}).judge_conf }})
+ + + +
{{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }} + · 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }} + (占总规模 {{ propSizePct(p) }})
+ + +
建议档位:{{ propAdviceTier(p) }}
+
{{ propSizeMismatch(p) }}
+
选股系统:{{ propVerdictLine(p) }}
-
{{ propBasis(p) }}
-
研报说:{{ propLogic(p) }}(另有 {{ propLogicMore(p) }} 条)
- -
{{ propText(p, 'valuation_text') }}
-
{{ propText(p, 'events_text') }}
-
{{ propText(p, 'pricing_text') }}
-
{{ propText(p, 'news_text') }}
-
{{ propText(p, 'industry_catalyst_text') }}
- -
公司深度:{{ propText(p, 'company_review_text') }}
-
判决收敛:{{ propText(p, 'company_gate') }}
-
{{ propText(p, 'advice_text') }}
-
择时层两个期限:{{ propHeads(p) }}
-
{{ propWhy(p) }}
-
约 {{ p.qty }} 股 · 参考价 {{ (p.hard_numbers||{}).price==null ? '—' : (p.hard_numbers||{}).price }} - · 约需 {{ money(((p.hard_numbers||{}).price||0)*(p.qty||0)) }}
+
决策系统:{{ propJudgeShort(p) }}(把握度 {{ (p.hard_numbers||{}).judge_conf }})
+ + +
公司深度:{{ propText(p, 'company_review_text') }}
+ + +
{{ x }}
+ +
+ {{ propMoreOpen.has(p.proposal_id) ? '▾ 收起证据' : '▸ 展开证据(研报、安全边际、催化、定价、快讯、行业、择时层)' }} +
+
采纳 驳回 @@ -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,