分析结论的「在验证什么」照实说谁写的:分析卡上那一条是模板作者写的,标「分析卡上写的『在验证什么』」;Agent 现场做的照旧「Agent 写的假设」(台账条目带 item 的是分析卡、带 call_id 的是 Agent)。服务端按 origin 给 hypothesis_label、hypothesis_note 两句,证据面板、导出页、可验证报告都照原样显示,不自己拼。测试:证据接口两种都测,分析卡的链、导出页、核对页各加一条断言

This commit is contained in:
ThearchyHelios
2026-10-21 15:52:19 +00:00
parent 1c117d78aa
commit 9461c03a63
12 changed files with 84 additions and 29 deletions
+13 -4
View File
@@ -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"),
+19 -5
View File
@@ -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) {
+5 -2
View File
@@ -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))
+1
View File
@@ -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"
+9 -3
View File
@@ -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):
+4 -1
View File
@@ -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
+3 -1
View File
@@ -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<string, string>,
/** 「在验证什么」那一行:服务端按谁写的给说法(Agent 写的是假设,分析卡上那一条是模板作者写的),照原样显示 */
hypothesis: 'Agent 写的假设',
refused: '这次分析没有做成',
renderOk: '与按结论渲染的结果一致',
+1 -1
View File
@@ -135,7 +135,7 @@ function JudgedPart({ step, recompute, seal, overall }: {
{step.caveats.map((c, i) => <li key={`${c.code ?? ''}-${i}`}>{c.text}</li>)}
</ul>
)] : null,
step.hypothesis ? [E.hypothesis, (
step.hypothesis ? [step.hypothesis_label || E.hypothesis, (
<span key="h" data-ev-analysis-hypothesis="">
{step.hypothesis}
{step.hypothesis_note && <span className="mt-0.5 block text-2xs text-faint">{step.hypothesis_note}</span>}
@@ -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"
}
}
}
}
+2
View File
@@ -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:方法、参数、实际用的阈值、现查结果 ----
+2 -1
View File
@@ -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('复算一致:按封存的输入重新计算,结论逐项相同')
+4
View File
@@ -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