fix(service-analytics): compareTo 带上 measure 自己的 filter,__compare 列不再是另一个 measure (#4820) - #4870
Conversation
…<measure>__compare is the same measure as its neighbour (#4820) A measure declared with its own `filter` was scoped by a supplementary grouped sub-query in the current period, but the `compareTo` pass issued ONE shifted query over every base measure with only the base filter — `measureFilters` was never read on that path. `won_count` counted won deals while `won_count__compare` counted every deal, side by side under one label, biased so the comparison window always looks better. Both windows now run the same `runMeasurePass`: unfiltered measures in one query plus one sub-query per filter-scoped measure, merged by dimension key. The split lives in one exported `splitMeasuresByFilter` so the two paths cannot re-diverge; the only difference between them is the shifted dateRange. Tests build a fake service that really evaluates `where` and groups seed rows, so the pinned number changes with the fix (won_count__compare 5 -> 1), not just the query shape. Selections whose measures carry no filter still compare in a single shifted query. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…w NUL byte `check:nul-bytes` (run inside the ESLint job) rejected a literal 0x00 written into the fake service's group key. A raw NUL makes grep/ripgrep treat the whole file as binary and return zero matches, so the file drops out of code search and every grep-based lint; git only scans the first 8000 bytes, so at offset 5440 it still diffed as text. Keyed with `JSON.stringify(...)` joined by `|` instead, which needs no exotic byte at all and is unambiguous for the multi-dimension case. Deliberately NOT the production `mergeByDimensions` key, whose delimiter-free concatenation is #4821 — the fake must not import a second defect into a test for this one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
复核通过 —— ACCEPT,已标 ready 并送合并队列1. 没有长出第二套规则 —— 这是本单最重要的要求我派发时写的是:「如果两条路径各自实现一遍『怎么应用 measure filter』,下一个缺陷就在它们再次分叉的地方。」 实际做法是抽出 2. 假 service 真的在求值 —— 这是「数值可区分」要求的正确做法
而且种子设计得很巧:让 反向验证给出的是用户真正看到的东西:
一个「本月赢单 vs 上月」的卡片,修复前显示的是暴跌,实际是增长。断言查询形状证明不了这个,断言数字才行。 无 filter 的 measure 用例修复前后都通过(仍只发 2 条查询),主路径未误伤。 3. 与 #4708 的接缝处理对了基于 PR #4822 合并后的 main 工作, 4. NUL 字节首轮 CI 的 ESLint 红是 顺带记一个今天学到的东西:裸 NUL 会让 git 把文件判为二进制,于是 GitHub 上这个测试文件的 diff 显示成 5. changeset
|
Fixes #4820
问题
带
filter的 measure,在当前期是靠一条补充分组子查询(combineFilters(baseFilter, compiled.measureFilters[m]))来限定范围的;但compareTo那条路径只发一条涵盖全部 base measure 的位移查询,where里只有baseFilter——compiled.measureFilters在那条路径上从未被查阅过。于是同一个 measure,当前期这一列被限定了,对比列没有:
revenuewon_count{"stage":"closed_won"}revenue,won_countwon_count__compare数的是上个窗口的全部商机,被这个 measure 本来要排除的行精确地放大了。误差只往一个方向走:对比期永远显得更好,所以「本月赢单 vs 上月」的卡片会在什么都没变坏的时候读作暴跌。只有带 filter 的 measure 错,旁边不带 filter 的照常正确 —— 这正是它能一直活着的原因。修法
没有在 compare 路径上再写一遍「怎么应用 measure filter」。两条路径现在走同一个
runMeasurePass:不带 filter 的 measure 一条查询,带 filter 的每个一条子查询,按维度键合并;splitMeasuresByFilter是「这个 measure 有没有自己的 filter」的唯一答案。两个窗口之间唯一的差别,就是位移过的dateRange。长出第二套规则正是这类缺陷复发的地方 —— 它们下一次改动就会再次分叉。所以抽的是公共函数,不是补丁。
数据集 filter、presentation 的
runtimeFilter、measure 自己的 filter,在两个窗口里以完全相同的方式组合。代价:设了
compareTo时,每个带 filter 的 measure 多一条查询。measure 都不带 filter 的 selection 完全不受影响,仍然只发一条位移查询(测试钉住了这一条)。与 #4708 的接缝:compare 路径现在也会扇出,所以它的补充子查询同样可能整组缺失。
fillEmptyGroups已经覆盖__compare列,上个窗口被 filter 清空的分组读作0(count/sum)而不是空白 —— 和当前期的行为一致,已加测试。测试
测试的假 service 不是按查询形状返回硬编码数字,而是一个小的内存库:它真的对种子行求值
where和dateRange,真的分组。所以丢掉 measure filter,数字会变 —— 这是本 PR 唯一值得钉的东西,查询形状证明不了用户看到的数是对的。种子刻意让
revenue在两个窗口都是 300(3×100 = 100 + 4×50),于是能让won_count__compare动的只剩「filter 有没有到位移查询」。改动前后跑同一组用例:
won_count查询whereclosed_won{ stage: 'closed_won' }won_count__compare修复前的实际报错(把 fix 临时回退成原来那条扁平查询跑出来的):
命令与结果:
changeset 等级:
patchsplitMeasuresByFilter没有加进src/index.ts,包的公开入口面一字未变,可观察到的变化就是__compare列的数字被改对了。作为对照,#4708 的 PR #4822 是minor,因为它确实新增了公开面(fillEmptyGroups进了 index、emptyGroupValueFor进了 spec);本 PR 没有对应的东西。范围
packages/spec/**零改动;未触碰packages/lint、skills/**、content/docs/**、content/docs/releases/。mergeByDimensions用无分隔符拼接维度键)就在本 PR 改的同一个文件里,本 PR 刻意不碰,留给它自己那一单。🤖 Generated with Claude Code
https://claude.ai/code/session_015Br2xsJsczFsTR9bvbh2Ny
Generated by Claude Code