fix(v2): 修复沙盘面板在真渲染下暴露的三处缺陷(浏览器实测)
这些缺陷源码守卫与构建都查不出,只有真正渲染才暴露(正是 F11 要防的情况): 1. 雷达图没有尺寸约束:<svg> 只有 viewBox,撑满整个容器(实测约 1000px 高), 且轴上没有维度标签。现按仓库既有约定给显式 width/height 并补轴标签。 2. 首屏不显示演示值:真实运行视图按 AC-30 不携带演示估计,导致首屏显示 "暂不显示",与 13 §7「首屏并列概率(演示值)+ 主审意见」不一致。 现载入演示域运行后取一次基线情景(空选择 → 服务端演示表条目 00), production 域不请求演示表。 3. 未勾选任何假设时也画出实线情景轮廓(等于把基线复制成"所选情景"); 现只在存在被假设的维度时画实线。另外维度阶段补上序号,便于对照图例。 守卫脚本相应补 4 条断言(显式尺寸、轴标签、基线虚线、无勾选不画实线), 并修正被本次改动影响的旧断言。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -35,7 +35,11 @@ for (const [name, source] of [["panel", panel], ["lib", lib]]) {
|
||||
assert.match(panel, /simulation\.probability_view/, "情景数值必须取自服务端响应");
|
||||
|
||||
// 4. 演示值只在演示域显示,且紧邻"人工设定"说明(AC-12/30、DESIGN §38)
|
||||
assert.match(panel, /demoValue\(run\?\.data_domain/, "演示值必须按数据域判定后才显示");
|
||||
assert.match(panel, /const domainName = run\?\.data_domain/, "演示值必须按数据域判定后才显示");
|
||||
assert.match(panel, /demoValue\(domainName, simulation\?\.baseline_probability_view \|\| run\?\.probability_view\)/,
|
||||
"基线演示值必须取自服务端基线视图,不能由前端合成");
|
||||
assert.match(panel, /if \(first && first\.data_domain === "demo"\) await applyScenario\(\[\], first\)/,
|
||||
"首屏演示值只对演示域取值,production 域不得请求演示表");
|
||||
assert.match(panel, /未完成真实历史校准/, "未校准时必须说明为何不显示概率");
|
||||
assert.match(panel, /正式审批由人工决定/, "演示值旁必须有用途边界说明");
|
||||
assert.match(lib, /domain !== "demo"/, "lib 必须在非演示域返回 null");
|
||||
@@ -49,6 +53,12 @@ assert.match(lib, /EMPTY_DIMENSIONS = "当前方案尚未配置核查维度"/);
|
||||
assert.match(panel, /shown\.layout === "radar" && <RadarChart/, "只有判定为雷达时才画雷达");
|
||||
assert.match(panel, /shown\.layout === "empty" && <div className="ra-empty">/, "空维度用空状态,不补六轴");
|
||||
assert.match(panel, /文字列表与图等价/, "图必须有等价文字列表");
|
||||
assert.match(panel, /width=\{\d+\} height=\{\d+\} viewBox=/, "雷达必须给显式尺寸,否则会撑满容器(真渲染验证过的缺陷)");
|
||||
assert.match(panel, /<text key=\{`label-\$\{item\.id\}`\}/, "雷达必须有轴标签,图不能只有形状");
|
||||
assert.match(panel, /strokeDasharray="4 4"/, "基线必须用虚线,不能只靠颜色区分");
|
||||
assert.match(panel, /hasScenario = Array\.from\(scenarioStages\.values\(\)\)\.some\(\(item\) => item\.assumed\)/,
|
||||
"没有勾选假设时不得画出实线情景轮廓");
|
||||
assert.match(panel, /阶段 \{stage \?\? "—"\} · \{stageName\(stage\)\}/, "维度必须同时给出阶段序号与名称");
|
||||
|
||||
// 6. 乱序与过期响应必须丢弃(AC-31)
|
||||
assert.match(panel, /ticket !== seq\.current/, "旧请求晚到必须丢弃");
|
||||
|
||||
@@ -50,6 +50,9 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
} else {
|
||||
setActions([]);
|
||||
}
|
||||
// 演示域的基线演示值来自服务端演示表:首屏就要能看到"演示值 + 人工设定说明",
|
||||
// 而不是等用户勾选后才出现数字(13 §7)。production 域不取,避免越域数值。
|
||||
if (first && first.data_domain === "demo") await applyScenario([], first);
|
||||
} catch (e: any) {
|
||||
setError(e.message);
|
||||
} finally {
|
||||
@@ -69,15 +72,16 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
.catch((e: any) => setError(e.message));
|
||||
}
|
||||
|
||||
async function applyScenario(keys: string[]) {
|
||||
if (!run) return;
|
||||
async function applyScenario(keys: string[], target?: RunView | null) {
|
||||
const current = target || run;
|
||||
if (!current) return;
|
||||
const ticket = ++seq.current;
|
||||
setBusy(true); setError(""); setNotice("");
|
||||
setScenarioState(keys.length ? "computing" : "baseline");
|
||||
try {
|
||||
const result: Simulation = await request(
|
||||
`${PRESCREEN}${projectPath}/runs/${encodeURIComponent(run.id)}/simulations`,
|
||||
{assumptions: keys, expected_revision: run.revision});
|
||||
`${PRESCREEN}${projectPath}/runs/${encodeURIComponent(current.id)}/simulations`,
|
||||
{assumptions: keys, expected_revision: current.revision});
|
||||
if (ticket !== seq.current) return; // 旧请求晚到:丢弃,不覆盖新结果
|
||||
const returned = (result.assumptions || []).map((item) => item.key).sort().join(",");
|
||||
if (returned !== [...keys].sort().join(",")) return; // 结果与当前选择不一致:丢弃
|
||||
@@ -111,9 +115,10 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
opener.current?.focus();
|
||||
}
|
||||
|
||||
const baseline = demoValue(run?.data_domain || "demo", run?.probability_view);
|
||||
const scenario = simulation && simulation.status === "ready"
|
||||
? demoValue(run?.data_domain || "demo", simulation.probability_view) : null;
|
||||
const domainName = run?.data_domain || "demo";
|
||||
const baseline = demoValue(domainName, simulation?.baseline_probability_view || run?.probability_view);
|
||||
const scenario = simulation && simulation.status === "ready" && selected.length
|
||||
? demoValue(domainName, simulation.probability_view) : null;
|
||||
const dimensions = run?.dimension_results || [];
|
||||
// 刻度信息来自呈现层:运行视图只带阶段序数,因此默认同一刻度;方案带 scale_id 时按实际判断。
|
||||
const shown = presentationFor(dimensions.map((item: any) => ({scale_id: item.scale_id || "stage"})));
|
||||
@@ -170,7 +175,7 @@ export default function PrescreenPanel({project, request, busy, setBusy, setErro
|
||||
const overlay = scenarioStages.get(item.id);
|
||||
const stage = overlay ? overlay.scenario_stage : item.stage;
|
||||
return <p key={item.id}>
|
||||
<b>{item.id}</b> · {stateLabel(item.state)} · 阶段 {stageName(stage)}
|
||||
<b>{item.id}</b> · {stateLabel(item.state)} · 阶段 {stage ?? "—"} · {stageName(stage)}
|
||||
{overlay?.assumed ? "(本情景假设该维度交叉核验成立;原项目仍待核实)" : ""}
|
||||
{" "}
|
||||
<button className="ra-button" aria-label={`查看 ${item.id} 的依据`}
|
||||
@@ -228,9 +233,11 @@ function RadarChart({dimensions, scenarioStages}: {dimensions: any[]; scenarioSt
|
||||
const overlay = scenarioStages.get(item.id);
|
||||
return overlay ? overlay.scenario_stage : item.stage;
|
||||
});
|
||||
const hasScenario = scenarioStages.size > 0;
|
||||
// 只有真的存在被假设的维度才画实线:没勾选时把基线复制成"所选情景"是误导
|
||||
const hasScenario = Array.from(scenarioStages.values()).some((item) => item.assumed);
|
||||
return <div>
|
||||
<svg role="group" aria-label="证据核验阶段雷达,详细内容见下方维度按钮" viewBox="-140 -120 280 250">
|
||||
<svg role="group" aria-label="证据核验阶段雷达,详细内容见下方维度按钮"
|
||||
width={330} height={260} viewBox="-150 -130 300 270">
|
||||
{[1, 2, 3, 4].map((step) => <polygon key={step} className="radar-grid" fill="none" stroke="currentColor" opacity={0.2}
|
||||
points={polygon(Array(count).fill(step))} />)}
|
||||
{dimensions.map((item, index) => {
|
||||
@@ -241,6 +248,11 @@ function RadarChart({dimensions, scenarioStages}: {dimensions: any[]; scenarioSt
|
||||
points={polygon(baselineStages)} />
|
||||
{hasScenario && <polygon data-series="scenario" fill="currentColor" fillOpacity={0.15} stroke="currentColor"
|
||||
points={polygon(scenarioStagesList)} />}
|
||||
{dimensions.map((item, index) => {
|
||||
const [x, y] = point(index, 4.7);
|
||||
return <text key={`label-${item.id}`} x={x} y={y} fontSize={11} textAnchor="middle"
|
||||
fill="currentColor">{item.id}</text>;
|
||||
})}
|
||||
</svg>
|
||||
<p>虚线为基线,实线为所选假设;两者都只是阶段,不是分数。</p>
|
||||
</div>;
|
||||
|
||||
Reference in New Issue
Block a user