mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 00:51:59 +00:00
证据接口的核查状态类型挪进 types.ts,证据面板的反方审稿改用单句展示:EvidenceDualCheck、EvidenceUnitChallenge、输入步骤的 masked / mask_unverified、证据图的 challenge_verdicts 照服务端逐字核对;审批卡上的概况类型补上 unreviewed、degrades。ChallengeSummary 加单句模式(scope=claim,只画这一句的判断、它可能没反查成的说明、展开看理由和查询,不画报告级的标题和计数)和显式的「不谈出具」开关(issuance=false,不预告降档、也不为此取运行类别),证据面板改用这两样,去掉传 runId 为空的做法;check-evidence 的核查一节跟着改成单句的断言
This commit is contained in:
@@ -3954,6 +3954,20 @@ export const CHALLENGE_TEXT = {
|
||||
/** 同上,没有可跳的查询时(双盲复算的理由)只念出处 */
|
||||
citedPlain: (source: string) => `(出处:${source})`,
|
||||
|
||||
// ---- 证据面板的单句展示(ChallengeSummary 的 scope="claim")与正文的句末标记(run/EvidenceDoc)----
|
||||
/** 证据面板里这一节的标题 */
|
||||
claimTitle: '反方审稿',
|
||||
/** 单句展示:展开反方对这一句的理由和它执行过的查询 */
|
||||
claimDetail: '反方的理由与查询',
|
||||
/** 这一句判为无法判断,而这份报告无法判断的句子全是没反查成的:这一句就是 */
|
||||
claimUnreviewed: '这一句未能反查,已记为无法判断',
|
||||
/** 同上,但无法判断的句子里还有查过了、数据不够的:说不准是不是这一句 */
|
||||
claimMaybeUnreviewed: (n: number) => `这份报告有 ${formatNumber(n)} 句未能反查,已记为无法判断,这一句可能在其中`,
|
||||
/** 正文句末标记的悬停说明:判断是反方模型给的 */
|
||||
markTitle: (verdict: string) => `反方审稿:${verdict}(反方模型的判断,并非系统核对)`,
|
||||
/** 句末标记读屏念的那一段:跟在句子后面(只有反方审稿时),或接在裁判的判定后面(两样都有时,并在一个标记里) */
|
||||
markLabel: (verdict: string) => `反方审稿:${verdict}`,
|
||||
|
||||
// ---- 时间线(run/decode.ts explainLog):按日志的 code 和结构化字段说,不从原话里认
|
||||
log: {
|
||||
skippedDisabled: '反方审稿未启用,已跳过',
|
||||
|
||||
@@ -11,58 +11,73 @@ import type {
|
||||
import { BlockView, CopyChip, Markdown, StyledSlice, inlineStyles, numericColumns } from './Markdown'
|
||||
|
||||
export interface ChallengeSummaryProps {
|
||||
/** 这张审批卡审的报告(payload.reports,和 ApprovalReports 拿到的是同一份) */
|
||||
/**
|
||||
* 审的报告:审批卡是 payload.reports(和 ApprovalReports 拿到的是同一份);证据面板是按片段接口的 unit.challenge 拼的
|
||||
* 一份,条目只有这一句
|
||||
*/
|
||||
reports: ApprovalReport[]
|
||||
runId: string
|
||||
/** 运行 id:要谈出具、又没给 runClass 时按它取一次运行类别 */
|
||||
runId?: string
|
||||
/**
|
||||
* 这次运行的类别(审批自带的 approval.run_class,同 ApprovalReports 的 runClass)。给了就照它说、不再另取;没给
|
||||
* (老服务端的审批没有这一项)才按运行 id 取一次
|
||||
*/
|
||||
runClass?: string | null
|
||||
/**
|
||||
* report(默认):报告级的一块,审批卡用——标题、四种判断各几句、逐句列出、点开一句看理由和查询。
|
||||
* claim:单句,证据面板用——只有这一句的判断、它可能没反查成的说明、展开看理由和查询,不画报告级的标题、计数和别的句子
|
||||
* (面板里这一句就在正文上方;计数说的是整份报告,放在一句话的面板里读着像这一句的结果)。只取条目里的第一条
|
||||
*/
|
||||
scope?: 'report' | 'claim'
|
||||
/**
|
||||
* 谈不谈出具(「出具会降档」、只标注、不在出具路径上)。默认谈:审批卡上运行还没出具,预告有用。证据面板看的是已经结束
|
||||
* 的运行,降没降档、出没出具以报告的出具横幅(封存的出具判定)为准,再说一遍将来时的预告只会和横幅对不上(驳回了、
|
||||
* 不予出具、反方审稿不在成果节点上游……):传 false,一句和出具有关的话都不说,也不为此去取运行类别
|
||||
*/
|
||||
issuance?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* 审批卡里反方审稿的摘要,紧跟在报告核对概况(ApprovalReports)后面:一行概况(四种判断各几句),出具相关的那一句,
|
||||
* 有句子反查没完成时再说一行「N 句未能反查」,逐句列出结论原文和判断,点开一句再按 challenge_artifact 取完整内容,看反方
|
||||
* 的理由和它执行过的查询。审稿记录没存上(没有工件)时只列判断、不能点开,整块说一次。
|
||||
* 反方审稿的摘要。两种摆法:
|
||||
* - 审批卡(scope 为 report),紧跟在报告核对概况(ApprovalReports)后面:一行概况(四种判断各几句),出具相关的那一句,
|
||||
* 有句子反查没完成时再说一行「N 句未能反查」,逐句列出结论原文和判断,点开一句再按 challenge_artifact 取完整内容,看
|
||||
* 反方的理由和它执行过的查询。审稿记录没存上(没有工件)时只列判断、不能点开,整块说一次。
|
||||
* - 证据面板(scope 为 claim、issuance 为 false):一句话的判断,展开同样按 challenge_artifact 取这一句的理由和查询。
|
||||
*
|
||||
* 判断是反方模型给的,不是系统核对:颜色照结论句裁判的那一套,「经受住」也不用确定性的绿。
|
||||
* 「出具会降档」只在正式运行、有被推翻的结论、被推翻的真会让出具降档时才写:服务端给了 degrades 就照它(按降档处置、又接
|
||||
* 在带出具契约的成果节点上游才为真),老服务端没给时按「没写只标注」算。只标注的写一句不影响出具,按降档处置却不在出具
|
||||
* 路径上的写一句为什么不影响;探索运行不出具,和出具有关的话一句不说。运行类别照审批卡给的(runClass,审批自带的那一项,
|
||||
* 卡顶写的也是它);没给时按运行 id 从证据 store 取(loadRunClass),取到之前、取不到时都不说和出具有关的话(窗口重新
|
||||
* 拿到焦点时再取)。
|
||||
* 「出具会降档」只在谈出具(issuance)、正式运行、有被推翻的结论、被推翻的真会让出具降档时才写:服务端给了 degrades 就照它
|
||||
* (按降档处置、又接在带出具契约的成果节点上游才为真),老服务端没给时按「没写只标注」算。只标注的写一句不影响出具,按降档
|
||||
* 处置却不在出具路径上的写一句为什么不影响;探索运行不出具,和出具有关的话一句不说。运行类别照审批卡给的(runClass,审批
|
||||
* 自带的那一项,卡顶写的也是它);没给时按运行 id 从证据 store 取(loadRunClass),取到之前、取不到时都不说和出具有关的话
|
||||
* (窗口重新拿到焦点时再取)。
|
||||
*
|
||||
* 审的报告都没有经过反方审稿时什么都不画,也不发任何请求,卡片和以前一样。
|
||||
*/
|
||||
/**
|
||||
* 审批卡上的反方审稿概况,约定之外服务端另带两项(期 4):unreviewed 未能反查的句数,degrades 被推翻的会不会让出具降档。
|
||||
* 老服务端都不给
|
||||
*/
|
||||
type Challenge = ApprovalChallenge & { unreviewed?: number; degrades?: boolean }
|
||||
type RunClass = 'formal' | 'exploratory'
|
||||
const runClassOf = (v: unknown): RunClass | null => (v === 'formal' || v === 'exploratory' ? v : null)
|
||||
|
||||
export function ChallengeSummary({ reports, runId, runClass }: ChallengeSummaryProps) {
|
||||
export function ChallengeSummary({ reports, runId = '', runClass, scope = 'report', issuance = true }: ChallengeSummaryProps) {
|
||||
const reviewed = useMemo(() => reports.flatMap((r) => {
|
||||
const challenge = challengeOf(r)
|
||||
return challenge ? [{ report: r, challenge }] : []
|
||||
}), [reports])
|
||||
if (!reviewed.length) return null
|
||||
return <ChallengeList reviewed={reviewed} runId={runId} given={runClassOf(runClass)} />
|
||||
return <ChallengeList reviewed={reviewed} runId={runId} given={runClassOf(runClass)} scope={scope} issuance={issuance} />
|
||||
}
|
||||
|
||||
function ChallengeList({ reviewed, runId, given }: {
|
||||
reviewed: { report: ApprovalReport; challenge: Challenge }[]; runId: string; given: RunClass | null
|
||||
function ChallengeList({ reviewed, runId, given, scope, issuance }: {
|
||||
reviewed: { report: ApprovalReport; challenge: ApprovalChallenge }[]; runId: string; given: RunClass | null
|
||||
scope: 'report' | 'claim'; issuance: boolean
|
||||
}) {
|
||||
// 审批卡给了运行类别就照它,不另取:另取的那一次失败了,正式运行里的「出具会降档」会无端消失,而卡顶照样写着正式运行
|
||||
const fetched = useRunClass(given ? '' : runId)
|
||||
const run = given ?? fetched
|
||||
// 审批卡给了运行类别就照它,不另取:另取的那一次失败了,正式运行里的「出具会降档」会无端消失,而卡顶照样写着正式运行。
|
||||
// 不谈出具的连取都不取:运行类别只用来决定说不说出具
|
||||
const fetched = useRunClass(given || !issuance ? '' : runId)
|
||||
const run = issuance ? given ?? fetched : null
|
||||
return (
|
||||
<>
|
||||
{reviewed.map(({ report, challenge }) => (
|
||||
<ChallengeBlock key={report.node_id} report={report} challenge={challenge} run={run} />
|
||||
))}
|
||||
{reviewed.map(({ report, challenge }) => (scope === 'claim'
|
||||
? <ClaimBlock key={report.node_id} report={report} challenge={challenge} run={run} />
|
||||
: <ChallengeBlock key={report.node_id} report={report} challenge={challenge} run={run} />))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -92,7 +107,7 @@ const count = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) && v
|
||||
* payload.reports[i].challenge 认不认得:要么有计数、要么有条目。计数缺了按条目数(条目最多 10 条,计数才是全部);
|
||||
* 条目缺了 unit 的留空(行的键带序号,区分得开),不补假的:补出来的键会被当成真的 unit 去和工件里的句子比
|
||||
*/
|
||||
function challengeOf(report: ApprovalReport): Challenge | null {
|
||||
function challengeOf(report: ApprovalReport): ApprovalChallenge | null {
|
||||
const raw = report.challenge as unknown
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
const c = raw as Record<string, unknown>
|
||||
@@ -113,6 +128,19 @@ function challengeOf(report: ApprovalReport): Challenge | null {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 出具相关的那一句说什么(run 为 null——不谈出具、不是正式运行、类别还不知道——时一句都不说):
|
||||
* degrade 会降档(写「出具会降档」和原因);off 按降档处置却不在出具路径上(写为什么不影响);flag 只标注(写不影响出具档位)
|
||||
*/
|
||||
type Issuance = 'degrade' | 'off' | 'flag' | null
|
||||
function issuanceOf(challenge: ApprovalChallenge, run: RunClass | null): Issuance {
|
||||
if (run !== 'formal' || challenge.counts.refuted <= 0) return null
|
||||
// 没给 on_refuted(老服务端)按降档;报错的节点到不了审批,不会是 fail
|
||||
if (challenge.on_refuted === 'flag') return 'flag'
|
||||
// 被推翻的真会让出具降档:服务端给了就照它,老服务端没给时按「不是只标注」算
|
||||
return (challenge.degrades ?? true) ? 'degrade' : 'off'
|
||||
}
|
||||
|
||||
/**
|
||||
* 这次运行的类别(审批卡没给时才取,runId 为空就不取):取到之前、取不到时为 null——不知道是不是正式运行,就一句和出具
|
||||
* 有关的话都不说。取失败了不就此作罢:窗口重新拿到焦点时再取一次(store 对出错的槽会重发),取到了正式运行,降档的提示
|
||||
@@ -222,7 +250,7 @@ const focusLost = () => !document.activeElement || document.activeElement === do
|
||||
// ---------------------------------------------------------------- 卡片
|
||||
|
||||
function ChallengeBlock({ report, challenge, run }: {
|
||||
report: ApprovalReport; challenge: Challenge; run: RunClass | null
|
||||
report: ApprovalReport; challenge: ApprovalChallenge; run: RunClass | null
|
||||
}) {
|
||||
const [open, setOpen] = useState<Set<string>>(() => new Set())
|
||||
const [all, setAll] = useState(false)
|
||||
@@ -231,11 +259,7 @@ function ChallengeBlock({ report, challenge, run }: {
|
||||
const total = VERDICTS.reduce((n, v) => n + counts[v], 0)
|
||||
// 审批里没带工件:服务端保存审稿记录失败了。每句都看不了理由和查询,不画成能点开的样子,整块说一次
|
||||
const noRecord = !challenge.artifact
|
||||
const refuted = counts.refuted > 0 && run === 'formal'
|
||||
// 没给 on_refuted(老服务端)按降档;报错的节点到不了审批,不会是 fail
|
||||
const flagged = challenge.on_refuted === 'flag'
|
||||
// 被推翻的真会让出具降档:服务端给了就照它,老服务端没给时按「不是只标注」算
|
||||
const degrades = challenge.degrades ?? !flagged
|
||||
const said = issuanceOf(challenge, run)
|
||||
// 未能反查的每一句都记为无法判断(服务端的约定),不会多于概况里的「无法判断」:对不上时按概况的数说,两个数不打架
|
||||
const unreviewed = Math.min(challenge.unreviewed ?? 0, counts.inconclusive)
|
||||
// 条目之外的几句(条目最多 10 条):要看全部时从工件里补上,排在后面,顺序照工件
|
||||
@@ -284,27 +308,14 @@ function ChallengeBlock({ report, challenge, run }: {
|
||||
</span>
|
||||
</Fragment>
|
||||
))}
|
||||
{refuted && !flagged && degrades && (
|
||||
<span className="chip shrink-0 font-semibold" data-challenge-degrade=""
|
||||
style={{ color: 'var(--st-failed)', borderColor: 'var(--st-failed)' }}>
|
||||
<AlertTriangle size={11} aria-hidden /> {CHALLENGE_TEXT.degrade}
|
||||
</span>
|
||||
)}
|
||||
{said === 'degrade' && <DegradeChip />}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-2xs text-dim" data-challenge-empty="">{CHALLENGE_TEXT.none}</div>
|
||||
)}
|
||||
{/* 降档的原因常显,不放在悬停提示里:触屏、键盘用户也得看得到为什么、会怎样(只标注那一档同样常显一行)。
|
||||
紧跟在概况那一行(「出具会降档」的标记)下面,中间不插别的话 */}
|
||||
{refuted && !flagged && degrades && (
|
||||
<div className="mt-0.5 text-2xs leading-relaxed text-dim" data-challenge-degrade-note="">{CHALLENGE_TEXT.degradeHint}</div>
|
||||
)}
|
||||
{refuted && !flagged && !degrades && (
|
||||
<div className="mt-0.5 text-2xs leading-relaxed text-dim" data-challenge-degrade-off="">{CHALLENGE_TEXT.degradeOff}</div>
|
||||
)}
|
||||
{refuted && flagged && (
|
||||
<div className="mt-0.5 text-2xs leading-relaxed text-dim" data-challenge-flag="">{CHALLENGE_TEXT.flagNote}</div>
|
||||
)}
|
||||
<IssuanceNote said={said} />
|
||||
{/* 反查没完成的几句:「无法判断」里分不出查了数据不够和反查没做完,单独说一次 */}
|
||||
{unreviewed > 0 && (
|
||||
<p className="mt-0.5 text-2xs leading-relaxed" data-challenge-unreviewed={unreviewed}>
|
||||
@@ -365,6 +376,79 @@ function ChallengeBlock({ report, challenge, run }: {
|
||||
)
|
||||
}
|
||||
|
||||
/** 「出具会降档」的醒目标记:跟在概况(单句时跟在判断)后面,原因由 IssuanceNote 常显在下一行 */
|
||||
function DegradeChip() {
|
||||
return (
|
||||
<span className="chip shrink-0 font-semibold" data-challenge-degrade=""
|
||||
style={{ color: 'var(--st-failed)', borderColor: 'var(--st-failed)' }}>
|
||||
<AlertTriangle size={11} aria-hidden /> {CHALLENGE_TEXT.degrade}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
/** 出具相关的那一行:会降档的原因、不在出具路径上为什么不影响、只标注不影响出具档位。不谈出具时什么都不画 */
|
||||
function IssuanceNote({ said }: { said: Issuance }) {
|
||||
const note = 'mt-0.5 text-2xs leading-relaxed text-dim'
|
||||
if (said === 'degrade') return <div className={note} data-challenge-degrade-note="">{CHALLENGE_TEXT.degradeHint}</div>
|
||||
if (said === 'off') return <div className={note} data-challenge-degrade-off="">{CHALLENGE_TEXT.degradeOff}</div>
|
||||
if (said === 'flag') return <div className={note} data-challenge-flag="">{CHALLENGE_TEXT.flagNote}</div>
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* 单句(证据面板):这一句的判断;判为无法判断、这份报告又有句子未能反查时说一句它(可能)没反查成;展开看反方的理由和
|
||||
* 它执行过的查询(按 challenge_artifact 取,和审批卡共用一份缓存);最后写明判断出自反方模型。不画报告级的标题、四种
|
||||
* 判断的计数和别的句子,也不列这一句的原文(面板上方就是这一句)。审稿记录没存上时不给展开,说一次为什么
|
||||
*/
|
||||
function ClaimBlock({ report, challenge, run }: {
|
||||
report: ApprovalReport; challenge: ApprovalChallenge; run: RunClass | null
|
||||
}) {
|
||||
const [open, setOpen] = useState(false)
|
||||
const detail = useChallengeArtifact(challenge.artifact ?? '', open)
|
||||
const detailId = useId()
|
||||
const item = challenge.items[0]
|
||||
if (!item) return null
|
||||
const noRecord = !challenge.artifact
|
||||
const said = issuanceOf(challenge, run)
|
||||
// 未能反查的每一句都记为无法判断(服务端的约定):这一句判为无法判断、报告里又有没反查成的,才可能是它。无法判断的
|
||||
// 句子全是没反查成的(未能反查的不少于无法判断的),那就一定是它;还有查过了、数据不够的,说不准,照实说「可能」
|
||||
const unreviewed = item.verdict === 'inconclusive' ? Math.min(challenge.unreviewed ?? 0, challenge.counts.inconclusive) : 0
|
||||
const surely = unreviewed > 0 && unreviewed >= challenge.counts.inconclusive
|
||||
return (
|
||||
<div className="min-w-0 space-y-1 text-2xs leading-relaxed" data-challenge={report.node_id} data-challenge-scope="claim"
|
||||
data-challenge-verdict={item.verdict}>
|
||||
<div className="flex flex-wrap items-center gap-x-1.5 gap-y-1">
|
||||
<VerdictTag verdict={item.verdict} />
|
||||
{said === 'degrade' && <DegradeChip />}
|
||||
{!noRecord && (
|
||||
<button type="button" className="btn btn-xs" aria-expanded={open} aria-controls={open ? detailId : undefined}
|
||||
data-challenge-toggle="" onClick={() => setOpen((v) => !v)}>
|
||||
<ChevronRight size={11} aria-hidden style={{ transform: open ? 'rotate(90deg)' : 'none', transition: 'transform .15s' }} />
|
||||
{CHALLENGE_TEXT.claimDetail}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<IssuanceNote said={said} />
|
||||
{unreviewed > 0 && (
|
||||
<p data-challenge-unreviewed={surely ? 'claim' : 'maybe'}>
|
||||
<span className="font-medium" style={{ color: 'var(--st-waiting)' }}>
|
||||
{surely ? CHALLENGE_TEXT.claimUnreviewed : CHALLENGE_TEXT.claimMaybeUnreviewed(unreviewed)}
|
||||
</span>
|
||||
<span className="text-dim">。{CHALLENGE_TEXT.unreviewedHint}</span>
|
||||
</p>
|
||||
)}
|
||||
{open && (
|
||||
<div id={detailId} className="min-w-0 space-y-2 rounded border p-2" data-challenge-detail={item.unit}>
|
||||
<ClaimDetail item={item} detail={detail} />
|
||||
</div>
|
||||
)}
|
||||
{noRecord && <p style={{ color: 'var(--st-waiting)' }} data-challenge-norecord="">{CHALLENGE_TEXT.noRecord}</p>}
|
||||
{/* 判断标签看着像结论,得说清是谁判的:展开取到审稿记录之后带上模型名 */}
|
||||
<p className="text-faint" data-challenge-judged="">{CHALLENGE_TEXT.judgedBy(detail.data?.model ?? null)}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
/** 判断标签:文字加颜色(去掉颜色也读得出),不铺底色——浅色主题下底色会把小字的对比度压到 4.5 以下 */
|
||||
function VerdictTag({ verdict }: { verdict: string }) {
|
||||
const color = isVerdict(verdict) ? VERDICT_COLOR[verdict] : 'var(--text-dim)'
|
||||
|
||||
@@ -19,14 +19,15 @@ import {
|
||||
import { humanizeError } from '../lib/errors'
|
||||
import { formatDateTime, formatNumber, NONE, shortId } from '../lib/format'
|
||||
import {
|
||||
ABSENCE_CLAIM_TEXT, DUAL_CHECK_TEXT, EVIDENCE_TEXT, JUDGE_TEXT, MERGE_TEXT, NODE_TYPE_LABEL, RUN_SQL_CHECK_TEXT, SQL_CHECK_TEXT,
|
||||
nodeTypeLabel,
|
||||
ABSENCE_CLAIM_TEXT, CHALLENGE_TEXT, DUAL_CHECK_TEXT, EVIDENCE_TEXT, JUDGE_TEXT, MERGE_TEXT, NODE_TYPE_LABEL, RUN_SQL_CHECK_TEXT,
|
||||
SQL_CHECK_TEXT, nodeTypeLabel,
|
||||
} from '../lib/terms'
|
||||
import { useCatalog } from '../store/catalog'
|
||||
import { askKey, segmentKey, useEvidence, useExplore, useVerdicts } from '../store/evidence'
|
||||
import type {
|
||||
ApprovalReport, ChallengeCounts, EvidenceBlock, EvidenceDocData, EvidenceInput, EvidenceMerge, EvidenceOnDemand,
|
||||
EvidenceQuoteSource, EvidenceSeal, EvidenceSegment, EvidenceStep, EvidenceUnit, EvidenceVerdict, EvidenceViolation,
|
||||
ApprovalReport, EvidenceBlock, EvidenceDocData, EvidenceDualCheck, EvidenceInput, EvidenceMerge, EvidenceOnDemand,
|
||||
EvidenceQuoteSource, EvidenceSeal, EvidenceSegment, EvidenceSegmentDetail, EvidenceStep, EvidenceUnit, EvidenceUnitChallenge,
|
||||
EvidenceVerdict, EvidenceViolation,
|
||||
} from '../types'
|
||||
import { ArtifactViewer, ResultTable } from './AssistantStream'
|
||||
import { ChallengeSummary } from './ChallengeSummary'
|
||||
@@ -76,10 +77,12 @@ import {
|
||||
* 表格底下带着推演结果。
|
||||
*
|
||||
* 核查状态(期 4):指标步骤多一行双盲复算的结果(一致、不一致和审核人选了什么、未能完成、已跳过),选了盲算、两边都
|
||||
* 不对、没选、未能完成的写明报告里的数没换、已记为缺口;句子被反方审过的(点开整句、点开句中的数字都一样)多一块反方
|
||||
* 审稿,复用审批卡上的展示(run/ChallengeSummary),概况是整份报告真实的计数,点开这一句看反方的理由和查询,不说出具会
|
||||
* 不会降档(运行已经结束,看报告的出具横幅)。取自遮罩列的输入,服务端已把值换成「已遮罩」,标签照样写「已遮罩」,输入
|
||||
* 来源里只说核对结果、不写值;遮没遮核对不了的(快照读不出来)同样不显示值,写明原因。
|
||||
* 不对、没选、未能完成的写明报告里的数没换、已记为缺口;句子被反方审过的(点开整句、点开句中的数字都一样)多一节反方
|
||||
* 审稿,用审批卡那一块的单句展示(run/ChallengeSummary 的 scope="claim"):这一句的判断、它可能没反查成的说明,展开看
|
||||
* 反方的理由和查询,不画报告级的标题和计数,不说出具会不会降档(运行已经结束,看报告的出具横幅)。正文里被审过的句子
|
||||
* 挂句末标记(run/EvidenceDoc),点开就是整句面板。取自遮罩列的输入,服务端已把值换成「已遮罩」,标签照样写「已遮罩」,
|
||||
* 输入来源里只说核对结果、不写值;遮没遮核对不了的(快照读不出来)同样不显示值,写明原因。类型都在 types.ts
|
||||
* (EvidenceDualCheck、EvidenceUnitChallenge、EvidenceInput 的 masked / mask_unverified)。
|
||||
*
|
||||
* 四种摆法:side 从右侧弹出(宽屏),drawer 从底部抽出(窄屏),inline 在 360px 的
|
||||
* 画布右栏里直接栏内展开,dock 放进页面给的一块常驻位置(记录页的「证据」页签)。
|
||||
@@ -87,51 +90,24 @@ import {
|
||||
*/
|
||||
|
||||
/**
|
||||
* 期 4:证据接口在指标步骤上给的双盲复算状态(api/evidence.py 的 _dual_check_of)。没有双盲复算复核过这份指标集时
|
||||
* 没有这个键。只有证据面板读它,类型就近写在这里
|
||||
* 期 4:证据接口在指标步骤上给的双盲复算状态(类型 EvidenceDualCheck,照 api/evidence.py 的 _dual_check_of)。没有双盲
|
||||
* 复算复核过这份指标集时没有这个键;状态不是一个词的不认
|
||||
*/
|
||||
interface DualCheckState {
|
||||
status: 'agreed' | 'disagreed' | 'unchecked' | 'skipped' | (string & {})
|
||||
node_id?: string
|
||||
/** 不一致时审核人的选择:a 采用原流程、b 采用盲算、neither 两边都不对。没选的没有 */
|
||||
choice?: string
|
||||
/** 一致、不一致时的盲算值,服务端按口径卡的格式渲染好的 */
|
||||
blind_rendered?: string
|
||||
/** 未能完成(unchecked):原因(人话);已跳过:disabled / no_inputs / not_targeted(按这个指标说),认不出的代码不显示 */
|
||||
reason?: string
|
||||
}
|
||||
|
||||
/** 期 4:证据接口在句子上给的反方审稿判断(api/evidence.py 的 _challenge_of)。没审到这一句时没有这个键 */
|
||||
interface UnitChallenge {
|
||||
verdict: string
|
||||
node_id?: string
|
||||
/** 审稿记录(challenge_artifact)。服务端保存失败时为 null:判断照样有,理由和查询看不了 */
|
||||
artifact?: string | null
|
||||
/** 实际生效的处置:degrade 被推翻的计入出具缺口;flag 只标注;fail 节点报错、运行停了 */
|
||||
on_refuted?: string
|
||||
/** 报告撰写节点的名字 */
|
||||
report_label?: string
|
||||
/** 整份报告的审稿计数(节点产出里的那一份),不是这一句的 */
|
||||
counts: ChallengeCounts
|
||||
/** 没能反查的句数(期 4 起的产出才有) */
|
||||
unreviewed?: number
|
||||
}
|
||||
|
||||
function dualCheckOf(step?: EvidenceStep): DualCheckState | null {
|
||||
const raw = (step as (EvidenceStep & { dual_check?: unknown }) | undefined)?.dual_check
|
||||
return raw && typeof raw === 'object' && typeof (raw as DualCheckState).status === 'string' ? raw as DualCheckState : null
|
||||
function dualCheckOf(step?: EvidenceStep): EvidenceDualCheck | null {
|
||||
const raw: unknown = step?.dual_check
|
||||
return raw && typeof raw === 'object' && typeof (raw as EvidenceDualCheck).status === 'string' ? raw as EvidenceDualCheck : null
|
||||
}
|
||||
|
||||
const CHALLENGE_VERDICTS = ['stands', 'weakened', 'refuted', 'inconclusive'] as const
|
||||
|
||||
/**
|
||||
* 句子上的反方审稿判断认不认得:判断是一个词,计数四项都是数。没有计数的不画——审批卡的展示缺了计数会按列出的这一句
|
||||
* 拼一个「被推翻 1 句、其余 0 句」,读着像整份报告的结果
|
||||
* 句子上的反方审稿判断(类型 EvidenceUnitChallenge,照 _challenge_of)认不认得:判断是一个词,计数四项都是数。单句展示
|
||||
* 不画计数,可要拿它和 unreviewed 对一下「无法判断」是不是没反查成;缺了计数不是服务端的形状,宁可不画也不半截地画
|
||||
*/
|
||||
function challengeOf(unit: unknown): UnitChallenge | null {
|
||||
const raw = unit && typeof unit === 'object' ? (unit as { challenge?: unknown }).challenge : undefined
|
||||
function challengeOf(unit: EvidenceSegmentDetail['unit']): EvidenceUnitChallenge | null {
|
||||
const raw: unknown = unit?.challenge
|
||||
if (!raw || typeof raw !== 'object') return null
|
||||
const c = raw as UnitChallenge
|
||||
const c = raw as EvidenceUnitChallenge
|
||||
const counts = c.counts && typeof c.counts === 'object' ? c.counts : null
|
||||
if (typeof c.verdict !== 'string' || !c.verdict || !counts
|
||||
|| !CHALLENGE_VERDICTS.every((v) => typeof counts[v] === 'number' && Number.isFinite(counts[v]))) return null
|
||||
@@ -140,13 +116,13 @@ function challengeOf(unit: unknown): UnitChallenge | null {
|
||||
|
||||
/** 取自遮罩列的输入:服务端把值换成「已遮罩」,masked 写明是哪几列 */
|
||||
function maskedOf(input: EvidenceInput): string[] {
|
||||
const raw = (input as EvidenceInput & { masked?: unknown }).masked
|
||||
const raw: unknown = input.masked
|
||||
return Array.isArray(raw) ? raw.filter((c): c is string => typeof c === 'string' && !!c) : []
|
||||
}
|
||||
|
||||
/** 遮没遮核对不了的输入(快照、取值链上的产出读不出来):值同样换成「已遮罩」,mask_unverified 是原因 */
|
||||
function unverifiedOf(input: EvidenceInput): string {
|
||||
const raw = (input as EvidenceInput & { mask_unverified?: unknown }).mask_unverified
|
||||
const raw: unknown = input.mask_unverified
|
||||
return typeof raw === 'string' ? raw : ''
|
||||
}
|
||||
|
||||
@@ -660,28 +636,27 @@ function ClaimBody({ doc, artifact, runId, runClass, unit, block }: {
|
||||
}
|
||||
|
||||
/**
|
||||
* 反方审稿对这一句的判断(期 4),整句面板、片段面板共用。复用审批卡上的展示(run/ChallengeSummary,只读它的导出):
|
||||
* 那一块是报告级的——标题写报告名,概况是整份报告四种判断各几句(服务端给的真实计数,不凭这一句拼),条目只列这一句,
|
||||
* 点开看反方的理由和它执行的查询,「显示全部」补出这份报告别的句子。判断是反方模型给的,那一块自己写明。
|
||||
* 反方审稿对这一句的判断(期 4),整句面板、片段面板共用。用审批卡那一块的单句展示(ChallengeSummary 的 scope="claim"):
|
||||
* 只画这一句的判断、它可能没反查成的说明(透传 unreviewed,和整份报告的计数对一下),展开看反方的理由和它执行的查询;
|
||||
* 不画报告级的标题、四种判断的计数和别的句子。判断是反方模型给的,那一块自己写明。
|
||||
*
|
||||
* 不给运行 id:ChallengeSummary 只拿它查运行类别,好在审批卡上预告「出具会降档」。证据面板看的是已经结束的运行,降没
|
||||
* 降档、出没出具以报告的出具横幅(封存的出具判定)为准,这里再说一遍将来时的预告只会和横幅对不上(驳回了、不予出具、
|
||||
* 反方审稿不在成果节点上游……)。ChallengeSummary 取不到运行类别时一句和出具有关的话都不说。等它有了单句的展示
|
||||
* (只画这一句的判断和理由,不画报告级的标题、计数),换用那个
|
||||
* 不谈出具(issuance={false},也不传 degrades):审批卡上预告「出具会降档」,是因为那时运行还没出具。证据面板看的是已经
|
||||
* 结束的运行,降没降档、出没出具以报告的出具横幅(封存的出具判定)为准,再说一遍将来时的预告只会和横幅对不上(驳回了、
|
||||
* 不予出具、反方审稿不在成果节点上游……)
|
||||
*/
|
||||
function ChallengePart({ challenge, unit, report }: { challenge: UnitChallenge; unit: EvidenceUnit; report?: string }) {
|
||||
function ChallengePart({ challenge, unit, report }: { challenge: EvidenceUnitChallenge; unit: EvidenceUnit; report?: string }) {
|
||||
const reports = useMemo<ApprovalReport[]>(() => [{
|
||||
node_id: report ?? '', label: challenge.report_label || report || '',
|
||||
challenge: {
|
||||
counts: challenge.counts, items: [{ unit: unit.id, text: unitText(unit).trim(), verdict: challenge.verdict }],
|
||||
artifact: challenge.artifact ?? null, on_refuted: challenge.on_refuted,
|
||||
...(typeof challenge.unreviewed === 'number' ? { unreviewed: challenge.unreviewed } : {}),
|
||||
} as ApprovalReport['challenge'],
|
||||
},
|
||||
}], [challenge, unit, report])
|
||||
return (
|
||||
<div data-ev-challenge={challenge.verdict}>
|
||||
<ChallengeSummary reports={reports} runId="" />
|
||||
</div>
|
||||
<Part title={CHALLENGE_TEXT.claimTitle} data-ev-challenge={challenge.verdict}>
|
||||
<ChallengeSummary reports={reports} scope="claim" issuance={false} />
|
||||
</Part>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1380,12 +1355,12 @@ function MetricPart({ step, entry, inputs, runId, runClass, seal, pending, faile
|
||||
* 不一致而审核人采用了原流程用提醒色,采用盲算、两边都不对、没有选的用失败色(记了缺口);未能完成用提醒色(同样记了
|
||||
* 缺口);跳过的中性色。认不出的跳过原因不写原因代码
|
||||
*/
|
||||
function DualCheckLine({ check, formal }: { check: DualCheckState; formal: boolean }) {
|
||||
function DualCheckLine({ check, formal }: { check: EvidenceDualCheck; formal: boolean }) {
|
||||
const T = EVIDENCE_TEXT.dualCheck
|
||||
const host = useEvidenceHost()
|
||||
const { status } = check
|
||||
const blind = (status === 'agreed' || status === 'disagreed') && check.blind_rendered ? T.blind(check.blind_rendered) : ''
|
||||
const picked = check.choice ? DUAL_CHECK_TEXT.approval.choices[check.choice as 'a' | 'b' | 'neither'] : undefined
|
||||
const picked = check.choice ? DUAL_CHECK_TEXT.approval.choices[check.choice] : undefined
|
||||
const detail = status === 'disagreed' ? (picked ? T.choice(picked) : T.noChoice)
|
||||
: status === 'skipped' ? (check.reason ? T.skip[check.reason] ?? '' : '')
|
||||
: status === 'unchecked' ? check.reason ?? '' : ''
|
||||
|
||||
+71
-2
@@ -2037,7 +2037,12 @@ export interface EvidenceGraph {
|
||||
/** 封存的裁判摘要(文档的 judge) */
|
||||
judge?: EvidenceJudgeSummary | null
|
||||
/** 最近一次按需裁判用的模型:和写作模型相同没有、在不在价格表里 */
|
||||
judge_meta?: EvidenceJudgeMeta | null }[]
|
||||
judge_meta?: EvidenceJudgeMeta | null
|
||||
/**
|
||||
* 期 4:被反方审过的句子 → 判断(_challenge_verdicts,和片段接口的 unit.challenge 同一个读法,每句的判断
|
||||
* 一字不差)。正文据此给被审过的句子挂句末标记、点开整句面板。一句都没审、文档读不出来、老服务端都没有这个键
|
||||
*/
|
||||
challenge_verdicts?: Record<string, ChallengeVerdict | (string & {})> }[]
|
||||
evidence?: { alias?: string; eid?: string; kind?: string; label?: string; node_id?: string; artifact?: string
|
||||
sealed?: boolean; cited_by?: string[]; report?: string }[]
|
||||
edges?: { from: string; to: string; rel: string }[]
|
||||
@@ -2090,6 +2095,59 @@ export interface EvidenceInput {
|
||||
cell?: string
|
||||
/** agent 的数组字段:引用的行段到了截断查询结果的末行,后面还有没取回的行(query 步骤上是快照本身截断了) */
|
||||
truncated?: boolean | null
|
||||
/**
|
||||
* 期 4:取自数据源设置了遮罩的列(_withheld):服务端已把值换成「已遮罩」、拿掉 model_value / snapshot_value,这里写明
|
||||
* 是哪几列。没遮的没有这个键
|
||||
*/
|
||||
masked?: string[]
|
||||
/**
|
||||
* 期 4:遮没遮核对不了(查询快照、取值链上的产出读不出来):值同样换成「已遮罩」,这是原因(人话,服务端写好的一句)。
|
||||
* 不进 masked:面板底下「这些列设置了遮罩」不把它们算进去
|
||||
*/
|
||||
mask_unverified?: string
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 期 4:证据接口里的核查状态(api/evidence.py)。是核查,不是证据:没核查过的指标、句子不带这些键。形状照服务端的
|
||||
// 真实输出逐字核对过,前端认的时候仍按「认不出就不画」写(run/EvidencePanel.tsx 的 dualCheckOf / challengeOf)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/** 双盲复算跳过一个指标的原因(_not_checked):关闭了;这个指标没有来自查询的输入;不在复算范围内(或值不是数) */
|
||||
export type EvidenceDualCheckSkip = 'disabled' | 'no_inputs' | 'not_targeted'
|
||||
|
||||
/**
|
||||
* 指标步骤上的双盲复算状态(_dual_check_of / _dual_status)。没有双盲复算复核过这份指标集时指标步骤没有 dual_check。
|
||||
* 产出读不出来、退回台账条目时只有 status、node_id 和 choice
|
||||
*/
|
||||
export interface EvidenceDualCheck {
|
||||
/** agreed 与盲算一致;disagreed 不一致;unchecked 未能完成(盲算触顶、出错,或输入没法单独复算);skipped 没复算这个指标 */
|
||||
status: 'agreed' | 'disagreed' | 'unchecked' | 'skipped'
|
||||
/** 双盲复算节点 */
|
||||
node_id: string
|
||||
/** 只有 disagreed:审核人的选择(a 采用原流程、b 采用盲算、neither 两边都不对)。没选的没有 */
|
||||
choice?: DualCheckChoice
|
||||
/** 只有 agreed / disagreed:盲算值,服务端按口径卡的格式渲染好的 */
|
||||
blind_rendered?: string
|
||||
/** unchecked:给人看的原因;skipped:EvidenceDualCheckSkip 里的代码(界面认不出的代码不显示) */
|
||||
reason?: string
|
||||
}
|
||||
|
||||
/** 句子上的反方审稿判断(_challenge_of):片段接口答的 unit.challenge。没被反方审过的句子没有这个键 */
|
||||
export interface EvidenceUnitChallenge {
|
||||
/** 这一句的判断(反方模型给的,不是系统核对) */
|
||||
verdict: ChallengeVerdict | (string & {})
|
||||
/** 反方审稿节点(几个都审了这一句时取最后跑完的那个) */
|
||||
node_id: string
|
||||
/** 审稿记录(challenge_artifact)。服务端保存失败时为 null:判断照样有,理由和查询看不了 */
|
||||
artifact: string | null
|
||||
/** 实际生效的处置:degrade 被推翻的计入出具缺口;flag 只标注;fail 节点报错、运行停了 */
|
||||
on_refuted: 'degrade' | 'flag' | 'fail'
|
||||
/** 报告撰写节点的名字(画布上的标题,取不到时是节点 id) */
|
||||
report_label: string
|
||||
/** 这次审稿四种判断各几句:整份报告的(节点产出里的那一份),不是这一句的 */
|
||||
counts: ChallengeCounts
|
||||
/** 没能反查的句数(都记为无法判断,不会多于 counts.inconclusive)。节点产出里有才带 */
|
||||
unreviewed?: number
|
||||
}
|
||||
|
||||
/** 口径卡钉在另一个已发布工作流某个版本里的口径卡上 */
|
||||
@@ -2213,6 +2271,8 @@ export interface EvidenceStep extends Omit<EvidenceInput, 'status'> {
|
||||
sql_check_failed?: boolean
|
||||
/** metric:没通过的原因(人话,「所依据的查询未通过 SQL 检查(…),结果不可靠」) */
|
||||
sql_check_reason?: string
|
||||
/** metric(期 4):这个指标的双盲复算状态。没有双盲复算复核过这份指标集时没有这个键 */
|
||||
dual_check?: EvidenceDualCheck
|
||||
/** query:这次查询对照数据目录查出的问题。没查出问题时没有这个键 */
|
||||
checks?: SqlCheckItem[]
|
||||
/** query:查询当时冻结的表结构快照(工件 id)。合并查询、老快照没有 */
|
||||
@@ -2366,7 +2426,9 @@ export interface EvidenceSegmentDetail {
|
||||
/** 这一句的判定:封存的文档里的,或者按最新的 evidence.judged 叠上的(post_seal) */
|
||||
verdict?: EvidenceVerdict
|
||||
/** 这一句能不能请模型判断、不能的话为什么 */
|
||||
on_demand?: EvidenceOnDemand }
|
||||
on_demand?: EvidenceOnDemand
|
||||
/** 期 4:反方审稿对这一句的判断。没审到这一句的没有这个键 */
|
||||
challenge?: EvidenceUnitChallenge }
|
||||
block?: { id?: string; type?: string }
|
||||
chain?: EvidenceStep[]
|
||||
/** 片段为什么是现在这个状态,一句人话 */
|
||||
@@ -3666,6 +3728,13 @@ export interface ApprovalChallenge {
|
||||
/** challenge_artifact 工件 id。服务端保存审稿记录失败时为 null:判断照样有,理由和查询看不了 */
|
||||
artifact: string | null
|
||||
on_refuted?: 'degrade' | 'flag' | (string & {})
|
||||
/** 期 4(nodes/challenge.challenge_summary):没能反查的句数,都记为无法判断。升级前的产出、老服务端没有 */
|
||||
unreviewed?: number
|
||||
/**
|
||||
* 期 4:被推翻的会不会让出具降档(按降档处置、又接在带出具契约的成果节点上游才为真)。升级前的产出、老服务端没有,
|
||||
* 审批卡按「没写只标注就降档」的旧规则说
|
||||
*/
|
||||
degrades?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3489,7 +3489,8 @@ await section('verify', '期 4:指标步骤的双盲复算、取自遮罩列
|
||||
check('输入来源:没遮罩的字段照旧', verified.includes('与快照一致') && !verified.includes('遮罩'), verified.replace(/\s+/g, ' '))
|
||||
await q.ctx.close()
|
||||
|
||||
// ---- 反方审稿:整句面板、片段面板都看得到这一句的判断。复用审批卡的展示,概况是整份报告的真实计数,不预告出具
|
||||
// ---- 反方审稿:整句面板、片段面板都看得到这一句的判断。用审批卡那一块的单句展示:只画这一句的判断,不画报告级的
|
||||
// 标题和计数,不预告出具
|
||||
const u = fxj.formal.units
|
||||
const challenge = (verdict) => ({ verdict, node_id: 'chal', artifact: CH_ART, on_refuted: 'degrade', report_label: '写周报',
|
||||
counts: CH_COUNTS })
|
||||
@@ -3528,10 +3529,12 @@ await section('verify', '期 4:指标步骤的双盲复算、取自遮罩列
|
||||
await c.page.waitForSelector('[data-evidence-panel] [data-ev-challenge] [data-challenge-item]', { timeout: 4000 }).catch(() => {})
|
||||
const text = await blockText()
|
||||
if (w === 1280) {
|
||||
check('整句面板:反方审稿的判断(被推翻)挂在这一句下,写明是哪份报告的审稿', await block.getAttribute('data-ev-challenge', { timeout: 3000 }).catch(() => null) === 'refuted'
|
||||
&& text.includes('报告「写周报」的反方审稿') && text.includes('被推翻') && text.includes('新客首单'), text)
|
||||
check('……概况是整份报告的真实计数(服务端给的),不凭这一句拼', text.includes('经受住 3 句') && text.includes('被削弱 1 句')
|
||||
&& text.includes('被推翻 2 句') && text.includes('无法判断 0 句') && await block.locator('[data-challenge-more="5"]').count() === 1, text)
|
||||
check('整句面板:「反方审稿」一节写这一句的判断(被推翻),用单句展示', await block.getAttribute('data-ev-challenge', { timeout: 3000 }).catch(() => null) === 'refuted'
|
||||
&& text.startsWith('反方审稿') && await block.locator('[data-challenge-scope="claim"]').count() === 1
|
||||
&& (await block.locator('[data-challenge-tag="refuted"]').innerText().catch(() => '')).trim() === '被推翻', text)
|
||||
check('……单句只画这一句:不画报告级的标题、四种判断的计数、别的句子,也不再抄一遍这一句的原文',
|
||||
await block.locator('[data-challenge-counts], [data-challenge-items], [data-challenge-more], [data-challenge-show-all]').count() === 0
|
||||
&& !text.includes('报告「写周报」的反方审稿') && !/\d+ 句/.test(text) && !text.includes('增长主要来自新客首单'), text)
|
||||
check('……运行已经结束:不预告「出具会降档」,降没降档看报告的出具横幅', await block.locator(ISSUANCE_LINES).count() === 0
|
||||
&& !text.includes('降档'), text)
|
||||
check('……判断由反方模型给出,并非系统核对', text.includes('并非系统核对'), text)
|
||||
@@ -3539,12 +3542,12 @@ await section('verify', '期 4:指标步骤的双盲复算、取自遮罩列
|
||||
await c.page.waitForSelector('[data-evidence-panel] [data-challenge-detail-state="ok"]', { timeout: 4000 }).catch(() => {})
|
||||
const why = await block.locator('[data-challenge-rationale]').innerText().catch(() => '')
|
||||
check('……点开看得到反方的理由(按审稿记录取)', why.includes(CH_RATIONALE) && artifactHits.length === 1, why)
|
||||
// 被削弱的那一句:概况照样是整份报告的数,不出现凭这一句拼的「被推翻 0 句」
|
||||
// 被削弱的那一句:只写它自己的判断,不写整份报告的计数(以前借报告级的展示,读着像这一句「被推翻 0 句」)
|
||||
await badge(c.page, J, u.ok).click()
|
||||
await c.page.waitForSelector('[data-evidence-panel] [data-ev-challenge="weakened"]', { timeout: 4000 }).catch(() => {})
|
||||
const weak = await blockText()
|
||||
check('被削弱的那一句:概况写整份报告的计数(被推翻 2 句),不写凭这一句拼的「被推翻 0 句」', weak.includes('被推翻 2 句')
|
||||
&& !weak.includes('被推翻 0 句') && !weak.includes('被削弱 1 句 · 被推翻 0 句'), weak)
|
||||
check('被削弱的那一句:只写这一句的判断(被削弱),不写整份报告的计数', weak.includes('被削弱') && !weak.includes('被推翻')
|
||||
&& !/\d+ 句/.test(weak), weak)
|
||||
// 片段面板:点这一句里的数字,同样看得到判断;正式运行里双盲复算记的是出具缺口
|
||||
await c.page.locator(`${J} [data-seg="s6"]`).click()
|
||||
await c.page.waitForSelector('[data-evidence-panel] [data-ev-challenge="weakened"]', { timeout: 4000 }).catch(() => {})
|
||||
@@ -3567,7 +3570,7 @@ await section('verify', '期 4:指标步骤的双盲复算、取自遮罩列
|
||||
await c.page.locator(`${JP} [data-seg="s14"]`).click()
|
||||
await c.page.waitForSelector('[data-evidence-panel] [data-ev-challenge]', { timeout: 4000 }).catch(() => {})
|
||||
check('没开结论句裁判的报告:点句中的数字看得到反方审稿判断', await block.getAttribute('data-ev-challenge', { timeout: 3000 }).catch(() => null) === 'refuted'
|
||||
&& (await blockText()).includes('退款 29 单'), await blockText())
|
||||
&& (await block.locator('[data-challenge-tag="refuted"]').count()) === 1, await blockText())
|
||||
} else {
|
||||
const wide = await c.page.evaluate(() => {
|
||||
const el = document.querySelector('[data-evidence-panel]')
|
||||
|
||||
Reference in New Issue
Block a user