diff --git a/backend/app/api/evidence.py b/backend/app/api/evidence.py index 5deb102d..4a8c4582 100644 --- a/backend/app/api/evidence.py +++ b/backend/app/api/evidence.py @@ -1015,8 +1015,12 @@ async def _chain(seg: dict[str, Any], doc: dict[str, Any], sealed: _Sealed, # 分析结论的出处链 # -------------------------------------------------------------------------- -#: Agent 写的假设(hypothesis)旁边那句:它不是结论,只在证据面板显示(细案 01 §7.3) -HYPOTHESIS_NOTE = "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" +#: 「在验证什么」(hypothesis)旁边那句:它不是结论,只在证据面板显示(细案 01 §7.3)。谁写的照实说:Agent 现场做的 +#: (台账条目带 call_id)写「Agent 写的假设」,分析卡做的(带 item)是模板作者写的,写「分析卡上写的『在验证什么』」 +HYPOTHESIS_LABEL = "Agent 写的假设" +HYPOTHESIS_NOTE = f"{HYPOTHESIS_LABEL}:说明这次在验证什么,不是分析结论,也不进报告正文" +CARD_HYPOTHESIS_LABEL = "分析卡上写的『在验证什么』" +CARD_HYPOTHESIS_NOTE = f"{CARD_HYPOTHESIS_LABEL}:模板作者写的那一句,不是分析结论,也不进报告正文" UNSEALED_ANALYSIS = "这份分析结论不属于封存范围内的任何事件(封存前的台账里没有它),不能作为证据展示" TAMPERED_ANALYSIS = "分析结论与哈希不一致,疑似被修改,不展示其中的判定和数" MISSING_ANALYSIS = "无法读取分析结论" @@ -1104,7 +1108,8 @@ async def _analysis_chain(seg: dict[str, Any], cite: dict[str, Any], entry: dict """分析结论的出处链(00-总方案 §3.3):判定 → 方法 → 计算 → 输入 → 复算 → 封存。 - 判定(analysis):判定语、判定标准、判定码和点名的段、附带说明、算不了的原因,这一处引用的是哪一项(判定语、具名标量、 - 结果表的一格);Agent 写的假设另列,标成「Agent 写的假设」(HYPOTHESIS_NOTE) + 结果表的一格);「在验证什么」另列,谁写的照实标:Agent 现场做的写「Agent 写的假设」 + (HYPOTHESIS_NOTE),分析卡做的写「分析卡上写的『在验证什么』」(CARD_HYPOTHESIS_NOTE,模板作者写的) - 方法(analysis_method):方法与版本、规范化后的参数、实际用的阈值(只列这个算子用到的)、现查的表范围(probes) - 计算(analysis_calc):结果表(前 table_top_k 段,加上被引用的那一行附近)、具名的数、关键数的代入式 (evidence.analysis_formulas) @@ -1154,7 +1159,11 @@ async def _analysis_chain(seg: dict[str, Any], cite: dict[str, Any], entry: dict phrase=result.get("phrase"), criterion=result.get("criterion"), verdict=verdict, caveats=[{k: c.get(k) for k in ("code", "text")} for c in result.get("caveats") or [] if isinstance(c, dict)]) if isinstance(result.get("hypothesis"), str) and result["hypothesis"].strip(): - judged.update(hypothesis=result["hypothesis"], hypothesis_note=HYPOTHESIS_NOTE) + # 谁写的照实说:分析卡(台账条目带 item)是模板作者写的,Agent 工具(带 call_id)是现场写的 + card = judged["origin"] == "card" + judged.update(hypothesis=result["hypothesis"], + hypothesis_label=CARD_HYPOTHESIS_LABEL if card else HYPOTHESIS_LABEL, + hypothesis_note=CARD_HYPOTHESIS_NOTE if card else HYPOTHESIS_NOTE) settings = result.get("settings") if isinstance(result.get("settings"), dict) else {} method = { "step": "analysis_method", "alias": alias, "method": result.get("method"), diff --git a/backend/app/bundle/verifier.js b/backend/app/bundle/verifier.js index a0ad030a..31f0129e 100644 --- a/backend/app/bundle/verifier.js +++ b/backend/app/bundle/verifier.js @@ -259,7 +259,7 @@ dCell: '快照里的值', dShown: '正文显示', dShownSame: '与按显示规则渲染的结果一致', dExpected: '按显示规则应为', dArtifact: '所在工件', dReport: '所在报告', dPhrase: '判定语', dCriterion: '判定标准', dMethod: '方法与版本', dAnalysis: '分析结论里的值', dRecheck: '复算', - dAnalysisInputs: '输入', dHypothesis: 'Agent 写的假设', dCaveats: '附带说明', + dAnalysisInputs: '输入', dHypothesis: 'Agent 写的假设', dCardHypothesis: '分析卡上写的『在验证什么』', dCaveats: '附带说明', recheckBy: '导出时服务端按封存的输入复算的结论;本页只核哈希,不在浏览器里重算统计', recheckNone: '导出时没有复算(结论未随文件附带)', analysisInput: (role, alias) => `${role}${alias ? ` · ${alias}` : ''}`, @@ -1212,7 +1212,7 @@ async function verify(data, digest, shownSeal) { const out = { events: [], manifest: [], artifacts: [], chain: [], numbers: [], docs: [], meta: [], checked: new Map(), order: [], - arts: new Map(), queryAliases: new Map(), + arts: new Map(), queryAliases: new Map(), analysisEntries: new Map(), } const events = Array.isArray(data.events) ? data.events.filter(isObj) : [] const meta = data.meta @@ -1373,6 +1373,15 @@ for (const [id, refs] of byDoc) { if (refs.length && !out.queryAliases.has(id)) out.queryAliases.set(id, refs[0].alias) } + // 工件 → 报告目录里这条分析结论的条目(item、call_id):谁做的那一行、「在验证什么」的说法照它认 + out.analysisEntries = new Map() + for (const [id, refs] of byDoc) { + for (const ref of refs) { + const doc = trusted.get(ref.report) + const entry = own(isObj(doc) ? doc.catalog : null, ref.alias) + if (isObj(entry) && entry.kind === 'analysis' && !out.analysisEntries.has(id)) out.analysisEntries.set(id, entry) + } + } out.arts = arts out.chained = chained // 已核对的口径卡记着的输入 → 输入指向的查询快照。卡本身核过哈希(③ 通过),它记的引用和文档的证据目录 @@ -2032,12 +2041,17 @@ return parts } - /** 数字详情里分析结论一节:判定语、判定标准、方法与版本、附带说明、Agent 写的假设、输入(各自的核对状态)、导出时服务端 - * 的复算结论。结论没随文件附带(遮罩、文件不在)时只写复算一句 */ + /** 数字详情里分析结论一节:判定语、判定标准、方法与版本、附带说明、「在验证什么」(谁写的照实标:Agent 写的假设 / + * 分析卡上写的『在验证什么』,照报告目录里这条结论的条目认)、输入(各自的核对状态)、导出时服务端的复算结论。 + * 结论没随文件附带(遮罩、文件不在)时只写复算一句 */ function analysisBlock(number) { const result = isObj(number.analysis) ? number.analysis : null const art = view.out.artifacts.find((a) => a.a.id === number.artifact) const recheck = art && isObj(art.a.recheck) ? art.a.recheck : null + // 谁做的:报告目录里这条结论的条目(带 item 的是分析卡做的,带 call_id 的是 Agent 现场做的)。「在验证什么」的说法照它认, + // 不把模板作者写的那一句标成 Agent 写的假设 + const entry = view.out.analysisEntries.get(number.artifact) + const card = isObj(entry) ? !!entry.item && !entry.call_id : false const parts = [] if (result) { parts.push(kv([ @@ -2048,7 +2062,7 @@ const caveats = (Array.isArray(result.caveats) ? result.caveats : []).filter(isObj).map((c) => String(c.text || '')) if (caveats.length) parts.push(h('p', { class: 'sub' }, T.dCaveats), h('ul', { class: 'items' }, caveats.map((c) => h('li', {}, c)))) if (typeof result.hypothesis === 'string' && result.hypothesis.trim()) { - parts.push(h('p', { class: 'sub' }, T.dHypothesis), h('p', {}, result.hypothesis)) + parts.push(h('p', { class: 'sub' }, card ? T.dCardHypothesis : T.dHypothesis), h('p', {}, result.hypothesis)) } const inputs = (Array.isArray(result.inputs) ? result.inputs : []).filter((i) => isObj(i) && typeof i.artifact === 'string') if (inputs.length) { diff --git a/backend/app/report_export/runtime.js b/backend/app/report_export/runtime.js index 1d574e44..fb62e174 100644 --- a/backend/app/report_export/runtime.js +++ b/backend/app/report_export/runtime.js @@ -461,7 +461,8 @@ }, v == null ? NONE : textOf(v)))))))) } - /** 分析结论 · 判定:判定语、判定标准、判定码和点名的段、附带说明、算不了的原因、这一处引用的是哪一项、谁做的、Agent 写的假设 */ + /** 分析结论 · 判定:判定语、判定标准、判定码和点名的段、附带说明、算不了的原因、这一处引用的是哪一项、谁做的、 + * 「在验证什么」(谁写的照实标:Agent 写的假设 / 分析卡上写的『在验证什么』) */ function analysisPart(step) { const body = [] const verdict = isObj(step.verdict) ? step.verdict : null @@ -484,7 +485,9 @@ h('ul', { style: 'margin:4px 0 0;padding-left:1.2em;font-size:13px' }, step.caveats.map((c) => h('li', {}, String(c.text || ''))))) } if (step.hypothesis) { - body.push(h('p', { class: 'sub' }, T.dHypothesis), h('p', { style: 'margin:4px 0 0;font-size:13px' }, String(step.hypothesis))) + // 「在验证什么」:谁写的服务端照实给(Agent 写的假设 / 分析卡上写的『在验证什么』),这里照原样显示 + body.push(h('p', { class: 'sub' }, String(step.hypothesis_label || T.dHypothesis)), + h('p', { style: 'margin:4px 0 0;font-size:13px' }, String(step.hypothesis))) if (step.hypothesis_note) body.push(muted(step.hypothesis_note)) } if (step.note) body.push(waiting(step.note)) diff --git a/backend/tests/test_analysis_bundle.py b/backend/tests/test_analysis_bundle.py index 6572040a..f8794167 100644 --- a/backend/tests/test_analysis_bundle.py +++ b/backend/tests/test_analysis_bundle.py @@ -100,6 +100,7 @@ def verdicts(bundle: dict[str, Any]) -> dict[str, Any]: def test_the_analysis_and_its_input_travel_with_the_file_and_verify(): + """Agent 现场做的结论(台账条目带 call_id):那一句「在验证什么」标成「Agent 写的假设」。""" bundle, files, a = bundle_of() analysis_item = item(bundle, a) assert analysis_item["kind"] == ARTIFACT_KIND and "b64" in analysis_item and analysis_item["node_id"] == "an" diff --git a/backend/tests/test_analysis_evidence_api.py b/backend/tests/test_analysis_evidence_api.py index 87e403da..3bb75709 100644 --- a/backend/tests/test_analysis_evidence_api.py +++ b/backend/tests/test_analysis_evidence_api.py @@ -5,7 +5,8 @@ 真的 recompute:按 sha256 取回封存的输入快照、下钻的上一层结论,逐字段比。只有要它出错、升版的几处用 monkeypatch。 - 证据图:A1 条目带算子、判定;「派生自」的边 A1 → Q1 -- 片段接口:判定 → 方法 → 计算 → 输入(接查询步骤)→ 复算 → 封存;Agent 写的假设标出来;复算不了照实写,不让接口出错 +- 片段接口:判定 → 方法 → 计算 → 输入(接查询步骤)→ 复算 → 封存;「在验证什么」谁写的照实标(Agent 写的假设 / 分析卡上 + 写的『在验证什么』);复算不了照实写,不让接口出错 - 审计:分析判定、分析结论的叫法;分析说法核对的违规单列一行 - 报告导出:数据块里有分析结论那几步;文档带「分析范围」的,正文后面渲染、编号点得开 - 指标步骤带可比性:口径卡指标上的可比性结论(不进台账)照卡上记的 sha256 取回,点得开(判定 → … → 复算 → 封存) @@ -135,7 +136,9 @@ async def test_the_phrase_opens_judgment_method_calculation_inputs_recompute_and assert judged["verdict"] == {"code": "driver", "subjects": [["华东"]], "direction": "down"} assert judged["cited"] == {"what": "phrase"} and judged["render_ok"] is True and judged["eid_ok"] is True assert judged["origin"] == "card" and judged["item"] == "drop" and judged["label"] == "分析" - assert judged["hypothesis"] == "华东拖累" and judged["hypothesis_note"].startswith("Agent 写的假设") + # 分析卡做的:模板作者写的「在验证什么」,不标成 Agent 写的假设 + assert judged["hypothesis"] == "华东拖累" and judged["hypothesis_label"] == "分析卡上写的『在验证什么』" + assert judged["hypothesis_note"].startswith("分析卡上写的『在验证什么』") assert {c["code"] for c in judged["caveats"]} == {"measure_unlabeled", "headline_basis", "comparability"} assert method["method"] == "decompose.additive@1" and method["method_label"] == "变化拆解(可加)" assert {s["key"]: s["label"] for s in method["settings"]}["main_share"] == "「主要来源」的占比" @@ -307,9 +310,12 @@ def test_suspicious_name_hints_do_not_read_the_analysis_results(monkeypatch): async def test_agent_analyses_are_marked_as_such(client): + """Agent 现场做的:谁做的写 Agent,「在验证什么」照旧标「Agent 写的假设」(分析卡上那一句是模板作者写的,另说)""" made = await sealed_run(TEXT, item=None, call_id="call-9") body = (await client.get(f"/api/runs/{made['run']}/evidence/segments/{seg_id(made['doc'], 'a:A1')}")).json() - assert body["chain"][0]["origin"] == "agent" and body["chain"][0]["call_id"] == "call-9" + judged = body["chain"][0] + assert judged["origin"] == "agent" and judged["call_id"] == "call-9" + assert judged["hypothesis_label"] == "Agent 写的假设" and judged["hypothesis_note"].startswith("Agent 写的假设") async def test_headline_inputs_show_the_closure(client): diff --git a/backend/tests/test_analysis_pages.py b/backend/tests/test_analysis_pages.py index 6d086b2f..4f2452e1 100644 --- a/backend/tests/test_analysis_pages.py +++ b/backend/tests/test_analysis_pages.py @@ -147,7 +147,8 @@ async def test_the_export_page_opens_the_analysis_steps_and_the_scope_numbers(): "analysis_recompute", "analysis_seal"] assert opened["title"].startswith("判定「销售额下降") body = opened["body"] - for text in ("判定标准一段占总变化的一半以上算主要来源", "Agent 写的假设华东拖累", "实际用的阈值", "「主要来源」的占比", + # 分析卡做的结论(台账条目带 item):那一句是模板作者写的,照实写「分析卡上写的『在验证什么』」,不标成 Agent 写的假设 + for text in ("判定标准一段占总变化的一半以上算主要来源", "分析卡上写的『在验证什么』华东拖累", "实际用的阈值", "「主要来源」的占比", "华东的占比 = 华东的变化 ÷ 总变化 = -120 ÷ (-194) = 61.9%", "结果表(列出 4 行,共 4 行)", "复算一致:按封存的输入重新计算,结论逐项相同", "封存范围内的台账记过这份分析结论", "按运行当天现算的界 date('now', '-7 days') = 2026-10-14(现查 · 2026-10-21T09:00:00)", @@ -186,6 +187,8 @@ async def test_the_bundle_page_shows_the_analysis_detail_and_the_scope(): # 方法写服务端带来的叫法和 id(和导出页同一个写法),不只写 id assert "判定语销售额下降 194(-8.7%)" in phrase and "方法与版本变化拆解(可加) · decompose.additive@1" in phrase assert "输入数据 · Q1" in phrase # 输入的角色写叫法,不写 data + # 这份结论是分析卡做的(台账条目带 item):那一句「在验证什么」写「分析卡上写的『在验证什么』」,不标成 Agent 写的假设 + assert "分析卡上写的『在验证什么』" in phrase and "Agent 写的假设" not in phrase assert "复算一致:按封存的输入重新计算,结论逐项相同(导出时服务端按封存的输入复算的结论;本页只核哈希" in phrase value = next(text for text in out["opened"].values() if "具名的数「top_share」" in text) assert "引用的值0.618556701031" in value and "正文显示61.9%(与按显示规则渲染的结果一致)" in value diff --git a/frontend/src/lib/termsAnalysis.ts b/frontend/src/lib/termsAnalysis.ts index 0995f3ed..6ff5d9a6 100644 --- a/frontend/src/lib/termsAnalysis.ts +++ b/frontend/src/lib/termsAnalysis.ts @@ -8,7 +8,8 @@ * 的数、说法违规的原话都是服务端写好的,界面照原样显示,不按判定码、原因码自己拼。规矩同 terms.ts 开头的术语表。 * * 术语:一次分析的结果叫「分析结论」,编号 A1、A2……;系统写的那一句叫「判定语」;判定的依据叫「判定标准」;做不成的叫「没有做成」 - * (服务端判定语的写法),原因叫「算不了的原因」;写错的调用叫「调用有误」,不记成结论;Agent 写的那句叫「Agent 写的假设」。 + * (服务端判定语的写法),原因叫「算不了的原因」;写错的调用叫「调用有误」,不记成结论;「这次在验证什么」那一句谁写的照实说: + * Agent 现场做的那句叫「Agent 写的假设」,分析卡上那条是模板作者写的,叫「分析卡上写的『在验证什么』」。 */ import { formatNumber } from './format' @@ -261,6 +262,7 @@ export const ANALYSIS_TEXT = { citedCell: (row: number, column: string) => `结果表第 ${formatNumber(row + 1)} 行「${column}」`, who: '谁做的', origin: { card: '分析卡(模板定好的分析)', agent: 'Agent 现场做的分析', metric: '口径卡执行时自动做的可比性检查' } as Record, + /** 「在验证什么」那一行:服务端按谁写的给说法(Agent 写的是假设,分析卡上那一条是模板作者写的),照原样显示 */ hypothesis: 'Agent 写的假设', refused: '这次分析没有做成', renderOk: '与按结论渲染的结果一致', diff --git a/frontend/src/run/EvidenceAnalysis.tsx b/frontend/src/run/EvidenceAnalysis.tsx index 7eec12f5..af57f8d9 100644 --- a/frontend/src/run/EvidenceAnalysis.tsx +++ b/frontend/src/run/EvidenceAnalysis.tsx @@ -135,7 +135,7 @@ function JudgedPart({ step, recompute, seal, overall }: { {step.caveats.map((c, i) =>
  • {c.text}
  • )} )] : null, - step.hypothesis ? [E.hypothesis, ( + step.hypothesis ? [step.hypothesis_label || E.hypothesis, ( {step.hypothesis} {step.hypothesis_note && {step.hypothesis_note}} diff --git a/frontend/src/run/__tests__/evidence-analysis.json b/frontend/src/run/__tests__/evidence-analysis.json index 0131364f..e0490cec 100644 --- a/frontend/src/run/__tests__/evidence-analysis.json +++ b/frontend/src/run/__tests__/evidence-analysis.json @@ -673,7 +673,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -1326,7 +1327,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -1984,7 +1986,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -3280,7 +3283,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -3871,7 +3875,8 @@ "verdict": null, "caveats": [], "hypothesis": "这周的下降算不算异常", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -4075,7 +4080,8 @@ }, "caveats": [], "hypothesis": "两周能不能直接比", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文", + "hypothesis_label": "Agent 写的假设" }, { "step": "analysis_method", @@ -5189,7 +5195,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "分析卡上写的『在验证什么』:模板作者写的那一句,不是分析结论,也不进报告正文", + "hypothesis_label": "分析卡上写的『在验证什么』" }, { "step": "analysis_method", @@ -6082,7 +6089,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "分析卡上写的『在验证什么』:模板作者写的那一句,不是分析结论,也不进报告正文", + "hypothesis_label": "分析卡上写的『在验证什么』" }, { "step": "analysis_method", @@ -6740,7 +6748,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "分析卡上写的『在验证什么』:模板作者写的那一句,不是分析结论,也不进报告正文", + "hypothesis_label": "分析卡上写的『在验证什么』" }, { "step": "analysis_method", @@ -7924,7 +7933,8 @@ } ], "hypothesis": "下降是不是集中在某个地区", - "hypothesis_note": "Agent 写的假设:说明这次在验证什么,不是分析结论,也不进报告正文" + "hypothesis_note": "分析卡上写的『在验证什么』:模板作者写的那一句,不是分析结论,也不进报告正文", + "hypothesis_label": "分析卡上写的『在验证什么』" }, { "step": "analysis_method", @@ -9453,4 +9463,4 @@ "declared_at": "2026-10-21T10:36:37.326911+00:00" } } -} \ No newline at end of file +} diff --git a/frontend/src/types.ts b/frontend/src/types.ts index d86672a1..dd2c9353 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -2448,6 +2448,8 @@ export interface EvidenceAnalysisStep { verdict?: AnalysisVerdict | null caveats?: AnalysisCaveat[] hypothesis?: string + /** 「在验证什么」那一行的说法:谁写的照实说,服务端按台账条目给(Agent 写的假设 / 分析卡上写的『在验证什么』) */ + hypothesis_label?: string hypothesis_note?: string note?: string // ---- analysis_method:方法、参数、实际用的阈值、现查结果 ---- diff --git a/scripts/check-bundle.mjs b/scripts/check-bundle.mjs index 3598421b..7185471a 100644 --- a/scripts/check-bundle.mjs +++ b/scripts/check-bundle.mjs @@ -927,8 +927,9 @@ await section('样本页:各种形状', async () => { check('analysis:点开判定语:判定语、判定标准、方法与版本(服务端带的叫法)', detail.includes('判定语') && detail.includes('一段占总变化的一半以上算主要来源') && detail.includes('变化拆解(可加) · decompose.additive@1'), detail.replace(/\s+/g, ' ').slice(0, 300)) + // 这三份分析都是 Agent 现场做的(台账条目带 call_id):那一句照旧标「Agent 写的假设」 check('analysis:……附带说明、Agent 写的假设', detail.includes('附带说明') && detail.includes('Agent 写的假设') - && detail.includes('下降是不是集中在某个地区'), detail.replace(/\s+/g, ' ').slice(0, 400)) + && !detail.includes('分析卡上写的『在验证什么』') && detail.includes('下降是不是集中在某个地区'), detail.replace(/\s+/g, ' ').slice(0, 400)) check('analysis:……输入(角色、编号、各自的核对状态)', ['基期总数 · Q1', '本期总数 · Q1', '数据 · Q2'].every((t) => detail.includes(t)), detail.replace(/\s+/g, ' ').slice(0, 500)) check('analysis:……复算写导出时服务端复算的结论、本页不重算统计', detail.includes('复算一致:按封存的输入重新计算,结论逐项相同') diff --git a/scripts/check-evidence.mjs b/scripts/check-evidence.mjs index 2d5f4688..509bd89e 100644 --- a/scripts/check-evidence.mjs +++ b/scripts/check-evidence.mjs @@ -645,6 +645,10 @@ await section('analysis', '分析结论:判定语、具名的数、结果表 await p.locator('[data-ev-analysis="A2"]').waitFor({ timeout: 8000 }) check('分析卡的结论:谁做的写「分析卡(模板定好的分析)」', (await p.locator('[data-ev-analysis-origin]').getAttribute('data-ev-analysis-origin')) === 'card' && (await p.locator('[data-ev-analysis="A2"]').innerText()).includes('分析卡(模板定好的分析)')) + // 「在验证什么」那一句是模板作者写的:照实写「分析卡上写的『在验证什么』」,不标成 Agent 写的假设 + const cardBody = await p.locator('[data-ev-analysis="A2"]').innerText() + check('……「在验证什么」写「分析卡上写的『在验证什么』」,不说 Agent 写的假设', cardBody.includes('分析卡上写的『在验证什么』') + && !cardBody.includes('Agent 写的假设'), cardBody.slice(-160)) await card.locator('[data-seg="s6"]').click() await p.locator('[data-ev-analysis="A1"]').waitFor({ timeout: 8000 }) check('算不了的结论:标「这次分析没有做成」、写算不了的原因', await p.locator('[data-ev-analysis-refused]').count() === 1