mirror of
https://github.com/ThearchyHelios/AgentLab.git
synced 2026-11-15 01:51:57 +00:00
双盲复算前端复核修正(第 3 轮):同一次裁决里几个指标落到数据目录的同一项上时,卡上按其余指标当前的选择说——说法不同的改说都不记,有效记录条件说合成一句
问题:服务端写目录前把同一次裁决里落到同一张表同一项上的说法归拢(dual_check_facts._merged):业务日期、去重规则只要说法不同就一项都不写,有效记录条件用 AND 合成一句写。审批卡上的预告是每个指标单独算的,卡上不看别的指标选了什么:一个指标采用原流程(paid_at)、另一个采用盲算(created_at),两块里各自加粗说「记为……」,服务端一项都不写;两个指标都采用去重的一边、去重的列不同,也是这样。结尾只说按发起审批时的数据目录预告,看不出选择的组合也会让写入作废。审核人可能为了不让同一张表出现两个业务日期,把本该分开的选择改成一致。 审批卡(run/DualCheckApproval.tsx) - 按其余指标当前的选择,给每个指标的两种选择各找出落到同一数据源、同一张表、同一项上的指标(peersOf)。只看预告会写的一边:选「两边都不对」、还没选的,服务端不会并进来。说法照服务端的 _same_fact 比:业务日期只看哪一列,其余压掉多余的空白比。 - 业务日期、去重规则说法不同时,那一句改说「……与「退款率」的选择说法不同,都不记」,不再算作会写。有效记录条件在「记为……」后面补一句「与「退款率」采用的条件用 AND 合成一句」。 - 选择一变,各块的说法跟着变。没选中的那几句也按别的指标当前的选择说,审核人改选之前就看得出这一选会不会让整项作废。 - 结尾改成「写入的项记为人工确认的事实;以上按发起审批时的数据目录和各指标当前的选择预告……」。原来是「两边都不对时不记。记为人工确认的事实」,读起来像在说不记的那一项。 - 没有预告时的结尾同样改。时间列、去重两句补上「同一次裁决里几个指标给同一张表记的业务日期(去重规则)不同时都不记」。 夹具与检查 - 夹具加 peers 和 merges。peers 换掉「退款率」对不上的那个输入,和「销售额」落到 orders 的业务日期、去重规则、有效记录条件上。merges 是四种组合:业务日期分开选、选同一列、去重的列不同、有效记录条件不同。 - peers 的差异说明和预告是服务端的真实输出。merges 的 written 是 record_choices 在空目录上真写一遍的结果。 - test_dual_check_fixture.py 新增两项。一项不依赖服务端,查每种组合真的落到同一项上。另一项在服务端合入后建临时数据源真写一遍,逐项比对。 - 在 vs/f1a 的f7d1a2e和233afd1上都跑过:8 项全过,regenerate 逐字节不变。 - 预览用例 dual-check-approval 认 peer 参数。 - check-verify 新增一节: - 分开选时,两边加粗的那一句都说都不记,写明哪张表的哪一项、和哪个指标说法不同。 - 没选中的那一句按别的指标当前的选择说;一个指标改选两边都不对后,另一个回到「记为」。 - 选同一列照常记;去重的列不同都不记;有效记录条件说合成一句。 - 390px 下深浅色都不横向溢出。 - check-verify 没有预告的一节补两项:「人工确认」说的是写入的项;说法不同时都不记。 - 变红验证:去掉实现(peersOf 返回空),check-verify 变红 6 项;把有效记录条件也当成说法不同,变红 1 项;把还没选、两边都不对也当成采用了一边,变红 2 项。 - 改动后 297 项全部通过(原来 274 项)。check-all 里 check-verify 的耗时估计按实测改成 71 秒。
This commit is contained in:
@@ -96,7 +96,7 @@ const ESTIMATE = {
|
||||
stream: 173, chat: 160, studio: 149, manage: 145, runs: 112, publish: 92, shell: 87,
|
||||
evidence: 91, versions: 60, catalog: 45, 'ui-kit': 70, ui: 61, guards: 8, 'canvas-layout': 2, copy: 2, tokens: 1, trace: 1, decode: 1,
|
||||
// 占位,换成真检查时按实测改
|
||||
schedules: 60, compare: 45, verify: 65, inquiry: 1, bundle: 16, whatif: 33,
|
||||
schedules: 60, compare: 45, verify: 71, inquiry: 1, bundle: 16, whatif: 33,
|
||||
}
|
||||
// 最慢的两项按段拆成几份。用的是它们自己的段过滤:最后一份用 *_SKIP 兜住「其余全部」,以后新加的段
|
||||
// 不用改这里也跑得到(拆出去的那几份要是一段都没对上,那一份会报「一项都没跑」)。
|
||||
|
||||
+120
-5
@@ -6,9 +6,11 @@
|
||||
// 原文按行比出的不同行高亮,规范化后不同的行(text_diff)分两栏高亮——夹具里的差异说明是服务端的真实输出
|
||||
// (backend/tests/test_dual_check_fixture.py 核对),规范化的行和原文对不上,高亮不能指望两边逐行相等;
|
||||
// 「记入数据目录」不能说得比服务端宽:服务端在摘要里预告了采用哪一边写什么(payload.summary[].catalog)时照它说,
|
||||
// 两边都不写就不提,当前选的那一句加重;老服务端没有预告时只在可能写的形状下(恰好一条具体差异,且是时间列、筛选
|
||||
// 条件或聚合)说必要条件,只有一边按时间筛、多出的是指标自己范围的条件、采用不去重的一边这几种不写的照实说——
|
||||
// 夹具里每个变体都带着服务端对这一对 SQL 的真实预告,两种说法都按它核对;
|
||||
// 两边都不写就不提,当前选的那一句加重;同一次裁决里别的指标按当前的选择落到同一张表的同一项上时,照服务端写目录前
|
||||
// 的归拢说——业务日期、去重规则说法不同的改说都不记,有效记录条件说合成一句;老服务端没有预告时只在可能写的形状下
|
||||
// (恰好一条具体差异,且是时间列、筛选条件或聚合)说必要条件,只有一边按时间筛、多出的是指标自己范围的条件、采用
|
||||
// 不去重的一边这几种不写的照实说——夹具里每个变体都带着服务端对这一对 SQL 的真实预告,几种组合带着服务端真写一遍的
|
||||
// 结果,两种说法都按它核对;
|
||||
// 三个选择缺一个都不能提交,提交的回复是 {choices, note};卡上不能再冒出通用的输入框和「提交 / 驳回并终止」
|
||||
// (mode 是 input,通用那套会把一个字符串当 value 发出去),认不出指标时另给「驳回并终止」;后果按运行类别和节点的
|
||||
// 「未采用原流程时」说(payload 带了实际生效的处置就照它说,老服务端从运行的图快照里读),探索运行不提出具,读不到
|
||||
@@ -326,7 +328,7 @@ await section('审批卡:并排对照与高亮(服务端真实的差异输
|
||||
|
||||
/**
|
||||
* 把「销售额」换成 variants 里的一对 SQL 及其真实的差异说明(取值照旧不同),别的指标原样;diff 给了就用给的
|
||||
* (拼几个输入的、兜底的原话)
|
||||
* (拼几个输入的、兜底的原话)。peer 给了就把「退款率」对不上的那个输入同样换成 peers 里的一对 SQL
|
||||
*/
|
||||
function cardWith(name, patch = {}) {
|
||||
const v = fx.variants[name]
|
||||
@@ -337,6 +339,14 @@ function cardWith(name, patch = {}) {
|
||||
gmv.diff = JSON.parse(JSON.stringify(v?.diff ?? gmv.diff))
|
||||
Object.assign(gmv, patch.metric ?? {})
|
||||
if (patch.diff) gmv.diff = patch.diff
|
||||
if (patch.peer) {
|
||||
const p = fx.peers[patch.peer]
|
||||
const rr = card.metrics[1]
|
||||
const k = rr.original.inputs.findIndex((i, n) => i.value !== rr.blind.inputs[n].value)
|
||||
rr.original.inputs[k].sql = p.sql[0]
|
||||
rr.blind.inputs[k].sql = p.sql[1]
|
||||
rr.diff = JSON.parse(JSON.stringify(p.diff))
|
||||
}
|
||||
return async () => ({ json: { id: CARD_ID, content: card } })
|
||||
}
|
||||
|
||||
@@ -377,6 +387,13 @@ await section('审批卡:没有预告时,「记入数据目录」只说必
|
||||
// 服务端会写的那一边,卡上的必要条件里得说到写的是哪一项
|
||||
const items = writes.map((s) => LABEL[expect[s].item])
|
||||
check(`……服务端会写的(${items.join('、') || '无'})卡上说到了`, items.every((i) => note.includes(i)), note)
|
||||
// 「不记」后面紧跟一句「记为人工确认的事实」,读起来像在说不记的那一项
|
||||
check('……「人工确认」说的是写入的项', note.includes('写入的项记为人工确认的事实') && !note.includes('不记。记为'), note)
|
||||
}
|
||||
if (name === 'time_one_side' || name === 'dedup') {
|
||||
// 同一次裁决里几个指标给同一张表记的业务日期、去重规则不同时,服务端一项都不写(有效记录条件是合成一句写)。
|
||||
// 没有预告时卡上看不出别的指标落到哪一项上,照实说这一条
|
||||
check('……同一次裁决里几个指标给同一张表记的说法不同时都不记', note.includes('同一次裁决里') && note.includes('不同时都不记'), note)
|
||||
}
|
||||
if (name === 'time_one_side') {
|
||||
// 正上方那条差异说「盲算没有按时间筛选」:卡上不能再说两边只差在按哪一列筛时间
|
||||
@@ -420,10 +437,14 @@ await section('审批卡:没有预告时,「记入数据目录」只说必
|
||||
check('几个输入都差在同一方面(合并后两条):不说会记入数据目录', await metric(two.page, 'gmv').locator('[data-dual-catalog]').count() === 0)
|
||||
})
|
||||
|
||||
/** 一块里预告的三句:{a, b, neither} → {text, write, current} */
|
||||
/**
|
||||
* 一块里预告的三句:{a, b, neither} → {text, write, current, conflict, joined}。conflict:同一次裁决里别的指标对同一项
|
||||
* 说法不同、都不记;joined:有效记录条件要和别的指标的合成一句
|
||||
*/
|
||||
const previewClauses = (loc) => loc.evaluate((el) => Object.fromEntries([...el.querySelectorAll('[data-dual-catalog="preview"] [data-dual-catalog-side]')]
|
||||
.map((s) => [s.dataset.dualCatalogSide, {
|
||||
text: s.textContent.trim(), write: s.dataset.write != null, current: s.dataset.current != null, weight: Number(getComputedStyle(s).fontWeight),
|
||||
conflict: s.dataset.conflict != null, joined: s.dataset.joined != null,
|
||||
}])))
|
||||
|
||||
await section('审批卡:服务端预告了写目录时照它说,当前选的那一句加重', async () => {
|
||||
@@ -537,6 +558,100 @@ await section('审批卡:服务端预告了写目录时照它说,当前选
|
||||
}
|
||||
})
|
||||
|
||||
await section('审批卡:同一次裁决里几个指标落到数据目录的同一项上,按其余指标当前的选择说', async () => {
|
||||
// 预告是每个指标单独算的,服务端写目录前却把同一次裁决里落到同一张表同一项上的说法归拢(dual_check_facts._merged):
|
||||
// 业务日期、去重规则说法不同的一项都不写,有效记录条件用 AND 合成一句写。两个指标分开选时卡上要是各自加粗说
|
||||
// 「记为……」,审核人会以为同一张表要记两个业务日期,可能为了避开它把本该分开的选择改成一致(2026-10 第 3 轮复核)。
|
||||
// 卡上按其余指标当前的选择说,选择变了说法跟着变。夹具 merges 是这几种组合在服务端真写一遍的结果
|
||||
// (test_dual_check_fixture.py 用 record_choices 核对),peers 换掉「退款率」,和「销售额」落到同一项上
|
||||
const LABEL = { a: '采用原流程的结果', b: '采用盲算的结果', neither: '两边都不对' }
|
||||
const open = async (name, opts = {}) => {
|
||||
const m = fx.merges[name]
|
||||
const query = ['catalog=1', `peer=${m.refund_rate}`, ...(m.gmv ? [`variant=${m.gmv}`] : [])].join('&')
|
||||
const out = await preview('dual-check-approval', { query, artifact: cardWith(m.gmv, { peer: m.refund_rate }), ...opts })
|
||||
await out.page.waitForSelector('[data-dual-check][data-state="ready"]')
|
||||
return out
|
||||
}
|
||||
const choose = (page, id, c) => metric(page, id).locator('label', { hasText: LABEL[c] }).click()
|
||||
const chooseAll = async (page, name) => {
|
||||
for (const [id, c] of Object.entries(fx.merges[name].choices)) await choose(page, id, c)
|
||||
}
|
||||
/** 两个指标当前选中的那一句 */
|
||||
const current = async (page) => Object.fromEntries(await Promise.all(['gmv', 'refund_rate'].map(async (id) => (
|
||||
[id, Object.values(await previewClauses(metric(page, id))).find((x) => x.current) ?? null]))))
|
||||
|
||||
// 业务日期:销售额采用原流程(paid_at)、退款率采用盲算(created_at)
|
||||
check('夹具:分开选时服务端一项都不写(record_choices 的真实结果)', fx.merges.time_split.written.length === 0)
|
||||
const { page, errors } = await open('time_split')
|
||||
let g = await previewClauses(metric(page, 'gmv'))
|
||||
check('还没选:退款率没有选择,销售额照自己的预告说', g.a?.text === '采用原流程的结果时,「orders」(shop)的业务日期记为「paid_at」'
|
||||
&& g.a.write && !g.a.conflict, g.a?.text)
|
||||
await choose(page, 'gmv', 'a')
|
||||
let r = await previewClauses(metric(page, 'refund_rate'))
|
||||
check('销售额选了原流程:退款率选原流程(同一列)照常记', r.a?.text === '采用原流程的结果时,「orders」(shop)的业务日期记为「paid_at」'
|
||||
&& r.a.write && !r.a.conflict, r.a?.text)
|
||||
check('……退款率选盲算(另一列)说清和销售额说法不同、都不记,不再说「记为」', !!r.b?.conflict && !r.b.write
|
||||
&& r.b.text.includes('「销售额」') && r.b.text.endsWith('都不记') && !r.b.text.includes('记为'), r.b?.text)
|
||||
await chooseAll(page, 'time_split')
|
||||
let cur = await current(page)
|
||||
check('分开选:两边加粗的那一句都说都不记,和服务端一致', ['gmv', 'refund_rate'].every((id) => cur[id]?.conflict && !cur[id].write
|
||||
&& cur[id].weight >= 500), JSON.stringify(cur))
|
||||
check('……销售额那一句写明是哪张表的哪一项、和哪个指标说法不同',
|
||||
cur.gmv?.text === '采用原流程的结果时,「orders」(shop)的业务日期与「退款率」的选择说法不同,都不记', cur.gmv?.text)
|
||||
check('……退款率那一句是同一项,不再重复写表名', cur.refund_rate?.text === '采用盲算的结果时与「销售额」的选择说法不同,都不记',
|
||||
cur.refund_rate?.text)
|
||||
g = await previewClauses(metric(page, 'gmv'))
|
||||
check('……销售额那一块里,改选盲算(和退款率同一列)的那一句照常说记', g.b?.text === '采用盲算的结果时记为「created_at」'
|
||||
&& g.b.write && !g.b.conflict, g.b?.text)
|
||||
const note = await text(metric(page, 'gmv').locator('[data-dual-catalog]'))
|
||||
check('……结尾说清预告按各指标当前的选择算,「人工确认」说的是写入的项', note.includes('各指标当前的选择')
|
||||
&& note.includes('写入的项记为人工确认的事实') && !note.includes('不记。记为'), note)
|
||||
// 退款率改选两边都不对:不再落到同一项上,销售额照常记
|
||||
await choose(page, 'refund_rate', 'neither')
|
||||
g = await previewClauses(metric(page, 'gmv'))
|
||||
check('退款率改选两边都不对:销售额加粗的那一句回到记为「paid_at」', g.a?.current && g.a.write && !g.a.conflict
|
||||
&& g.a.text.endsWith('记为「paid_at」'), g.a?.text)
|
||||
check('页面没有报错', errors.length === 0, errors.join(';'))
|
||||
|
||||
// 选同一边、落到同一列:服务端写一次
|
||||
const same = await open('time_same')
|
||||
await chooseAll(same.page, 'time_same')
|
||||
cur = await current(same.page)
|
||||
const sameValue = previewValue(fx.merges.time_same.written[0]?.value)
|
||||
check('选同一列:两边加粗的那一句都说记为这一列,和服务端写的一致', fx.merges.time_same.written.length === 1
|
||||
&& ['gmv', 'refund_rate'].every((id) => cur[id]?.write && !cur[id].conflict && cur[id].text.endsWith(`记为「${sameValue}」`)), JSON.stringify(cur))
|
||||
|
||||
// 去重规则:两个指标都采用去重的一边,去重的列不同(user_id、id)
|
||||
const dd = await open('dedup_columns')
|
||||
await chooseAll(dd.page, 'dedup_columns')
|
||||
cur = await current(dd.page)
|
||||
check('去重的列不同:服务端一项都不写,两边加粗的那一句都说都不记', fx.merges.dedup_columns.written.length === 0
|
||||
&& cur.gmv?.text === '采用原流程的结果时,「orders」(shop)的去重规则与「退款率」的选择说法不同,都不记'
|
||||
&& cur.refund_rate?.text === '采用原流程的结果时,「orders」(shop)的去重规则与「销售额」的选择说法不同,都不记'
|
||||
&& !cur.gmv.write && !cur.refund_rate.write, `${cur.gmv?.text} ‖ ${cur.refund_rate?.text}`)
|
||||
|
||||
// 有效记录条件:两个指标各自多出的条件不同(status <> 'test'、status <> 'cancelled'),服务端合成一句写
|
||||
const ff = await open('filters')
|
||||
await chooseAll(ff.page, 'filters')
|
||||
cur = await current(ff.page)
|
||||
const own = { gmv: fx.variants.filter_extra.catalog.a.value, refund_rate: fx.peers.filter.catalog.a.value }
|
||||
const merged = fx.merges.filters.written[0]?.value ?? ''
|
||||
check('夹具:服务端把两边的条件合成一句写', own.gmv !== own.refund_rate && merged.includes(own.gmv) && merged.includes(own.refund_rate), merged)
|
||||
check('有效记录条件不同:两边加粗的那一句都说记、并和另一个指标采用的条件合成一句',
|
||||
cur.gmv?.text === `采用原流程的结果时,「orders」(shop)的有效记录条件记为「${own.gmv}」,与「退款率」采用的条件用 AND 合成一句`
|
||||
&& cur.refund_rate?.text === `采用原流程的结果时,「orders」(shop)的有效记录条件记为「${own.refund_rate}」,与「销售额」采用的条件用 AND 合成一句`
|
||||
&& cur.gmv.write && cur.gmv.joined && cur.refund_rate.write && cur.refund_rate.joined, `${cur.gmv?.text} ‖ ${cur.refund_rate?.text}`)
|
||||
|
||||
// 窄屏:说法不同的那一句更长,也不撑宽
|
||||
for (const theme of ['dark', 'light']) {
|
||||
const narrow = await open('time_split', { width: 390, height: 844, theme })
|
||||
await chooseAll(narrow.page, 'time_split')
|
||||
const w = await narrow.page.evaluate(() => document.documentElement.scrollWidth - document.documentElement.clientWidth)
|
||||
check(`390px(${theme}):说法不同的那一句不横向溢出`, w <= 0, `${w}px`)
|
||||
await shot(narrow.page, `approval-merge-narrow-${theme}`)
|
||||
}
|
||||
})
|
||||
|
||||
await section('审批卡:只有解析失败、兜底原话另有原因时', async () => {
|
||||
// 只有解析失败:和摘要阶段(服务端把它算作兜底)说法一致,默认展开对照,解析的原话附在下面
|
||||
const { page } = await preview('dual-check-approval', { artifact: cardWith('parse') })
|
||||
|
||||
Reference in New Issue
Block a user