Repository navigation
analytics: compareTo 在「日期维度本身就是网格维度」时从不对齐 —— 比较行按自己平移后的桶键落地,于是每行一半是真值一半是自信的 0,还多出窗口外的幻影行 #6007
Description
Activity
分诊结论:升维护者决策箱
needs-user-decision+domain:services分类 — ⛔ 不入
pm:queue。缺陷本身成立且证据完整(实测四行输出、两行落在筛选窗口之外),但修法的两个方向是产品语义之争,不是实现细节:- 方向 1(平移回当期)把网格读作「一条时间轴 × 两个数列」,产出 2 行 2 列;
- 方向 2(该形状下不产出
__compare列)把网格读作「两个窗口叠在同一条轴上」,并让消费端能区分「没有可比数据」与「对齐失败」。
两者给出的响应形状不同(列数不同 / 行数不同),而
<measure>__compare是 dataset 响应的公开契约面(objectui 侧 #3337 正在跟这条契约收敛)。方向 1 还额外要求定义「平移回」在previousPeriod(任意天数窗口)下的确切含义 —— 这是一条新的语义,不是既有语义的修补。立单 dev 已明确写「本单不代裁」,分诊同意:归维护者。域/路由 — 落点
packages/services/service-analytics/src/dataset-executor.ts(runCompare+mergeByDimensions)⇒domain:services。⛔ 不按「analytics / compareTo」的标题词汇猜:已在origin/main上定位到具体接缝 ——dataset-executor.ts:706-713即runCompare→mergeByDimensions(..., [...baseMeasures].map(m => \${m}__compare`))的合并处,以及:232的fillEmptyGroups(正文引用的那段注释在:195/:230`)。过时前提检查(对
origin/main44106d9,正文引用的628b028已前移):dataset-executor.ts在628b028(analytics dataset 路由:另有九处「作者/调用方形状」的 dataset 拒收仍答 500 —— 它们从来没进过 #5352 的正则名单,所以 #5367 的信封化也没覆盖到 #5716/PR fix(service-analytics): 十三处调用方形状的分析拒收改用自带信封的 4xx,不再答 500 (#5716) #5963)之后无新提交,正文所述接缝逐行仍在;- PR fix(service-analytics): 只当窗口用的 timeDimensions 条目不再被补默认粒度、不再静默拆网格 (#5688) #6003 仍 open、未合并。它把本单描述的四行行为作为「analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 未改变的长期行为」原样断言在
dataset-window-timedimension-bucketing.test.ts的「bucketed anchor」用例里 ——⚠️ 因此裁定后本单的实施会让那条用例有意转红,这既是现成靶子,也意味着本单必须排在 PR fix(service-analytics): 只当窗口用的 timeDimensions 条目不再被补默认粒度、不再静默拆网格 (#5688) #6003 落地之后,否则两边在同一测试文件上互相拆台。
查重 — 三仓 open issue/PR 全量缓存 + 本地正则(REST
search/issues本会话被网关按仓库范围拒绝)。compareTo/mergeByDimensions/dataset-executor命中三条,逐条排除:- analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 + PR fix(service-analytics): 只当窗口用的 timeDimensions 条目不再被补默认粒度、不再静默拆网格 (#5688) #6003(services 车道在飞)—— 不是重复,是另一层:analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 的裁决被明确限定为「回填判据收窄」,动的是分桶;本单动的是分桶之后的合并。analytics: 只用来限定「日期区间」的 timeDimension 被补上默认 dateGranularity,于是网格被静默按月拆分 —— 「按 Owner 统计」加个日期筛选就变成「按 Owner × 月」 #5688 修好的是「窗口-only 形状」,本单是「日期维度即网格维度」形状,正文已把这条边界写清并被我复核采信。
DashboardWidget.chartConfig在 dashboard 元数据面只有showLegend落地 —— 12 个已声明键里 11 个惰性,liveness 台账那一行高估了证据 #5175(DashboardWidget.chartConfig惰性键,repo:objectui)—— 无关,仅词汇撞。- objectui#3337(compareTo 收敛为
{ kind, dimension? },pm:blocked)—— 消费端形状单;本单裁定若走方向 2(不产出该列),会改变它要消费的响应形状,建议维护者拍板时一并看一眼,届时按跨座位转移协议在 objectui 侧补Blocked-by:。本轮不预先挂Blocked-by:—— 依赖方向取决于裁决结果,现在挂就是猜。
可达性(为决策提供轻重量级读数):需要
compareTo的锚定维度同时出现在selection.dimensions,即「趋势图 + 同比」这一标准形状;正文称examples/app-crm/app-showcase均有声明dateGranularity的 date 维度 —— 不是边角形状。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Ruling (maintainer, 2026-08-07): direction 1 — shift the comparison bucket key back onto the current period.
The compare pass keys its rows to the shifted window, so
mergeByDimensionsnever matches and appends instead: every row is half real value, half confident zero, plus phantom rows from outside the filtered window. Direction 1 restores the alignment every BI tool implements for "this period vs same period last year", and — decisively for a v17 blocker — the response shape does not change (still<measure>__comparecolumns), so objectui#3337's contract convergence is untouched and noBlocked-byis needed.previousYear/previousMonthwith an explicitdateGranularitydefine the shift-back uniquely. ForpreviousPeriod(arbitrary day windows) align by bucket ordinal / relative position — stating this closes the one semantic hole an implementing agent would otherwise have to invent an answer for.Acceptance: the "bucketed anchor" case in
dataset-window-timedimension-bucketing.test.ts:392is expected to flip deliberately (it was pinned pointing at this issue), plus paired expectations for the 2×2 grid. The serialization on PR #6003 is already released.Operator: PM session
session_01GcjbQLUQKysMU9uXB34iyv; maintainer ruling 2026-08-07 (decision-inbox round 2). Veto window open — comment or reopen to overturn.
Generated by Claude Code
认领:PM 循环第 6 轮(rc.5 发布后恢复)
会话:session_015a5qkLzpGXhLL2F5gvJ7dD
分支:claude/issue-6007-compareto-align
Worktree:objectstack-6007
域:domain:services
文件面:packages/services/service-analytics/src/(dataset-executor.ts 的 runCompare/mergeByDimensions +__tests__/)按 2026-08-07 03:19Z 维护者裁决(方向 1:比较桶键平移回当期;
previousPeriod按桶序/相对位置对齐)实施;串行前提 PR #6003 已合入,其钉住的「bucketed anchor」用例按裁决有意翻红换 2×2 成对断言。同包 #5918 本轮不派(同文件族串行),派发令含对它的成本重定价必答项。
Generated by Claude Code
ACCEPT → PR #6213(draft 转 ready 入队中)。
- 裁决(方向 1)照实落地:合并前把比较桶键用当期说法重述——
previousYear是shiftRange年运算的精确逆运算(含setUTCFullYear溢出行为,窗口与桶键不可能对「一年」有两种理解);previousPeriod按日历桶序对齐(⛔ 非数组下标——下标法在当期网格有空档时其后全部错位一格,该区别单独钉住)。响应形状未动,objectui#3337 不受影响。 - 不确定即保持原样(改前行为):空桶 / 未分桶锚 / 平移回落在窗外的桶,三形状各有对照钉。
- 验收主证:改前先在基线跑绿缺陷 pin(证明「活着被钉」),改后
:392按裁决翻红换 2×2 成对断言;逆向验证方向预判命中(禁用对齐 ⇒ 12 红全是新钉、3 条故意的对照钉与 55 条回归全绿——钉与修复不耦合的证明)。包内 1206 测全绿、build 72/72、远端 CI 24/24 绿。 - 对 analytics 自动推断路径:
measures上的关系穿越点号 member 仍被剥成基表列 ——owner.region_count_distinct静默聚合基表region(#5739 裁决未覆盖的第四个铸造点) #5918 的定价必答项:完全无影响,三路测量(不同代码路径:analytics 自动推断路径:measures上的关系穿越点号 member 仍被剥成基表列 ——owner.region_count_distinct静默聚合基表region(#5739 裁决未覆盖的第四个铸造点) #5918 在 ad-hoc 推断分支,本单在 compiled-dataset 路径;新增符号全仓零外部引用,analytics-service.ts 零触碰;词汇不相交:本单动 DIMENSION 列值,analytics 自动推断路径:measures上的关系穿越点号 member 仍被剥成基表列 ——owner.region_count_distinct静默聚合基表region(#5739 裁决未覆盖的第四个铸造点) #5918 是 MEASURES 映射)。analytics 自动推断路径:measures上的关系穿越点号 member 仍被剥成基表列 ——owner.region_count_distinct静默聚合基表region(#5739 裁决未覆盖的第四个铸造点) #5918 成本不变,两单甚至不共文件——排程照原价。
Generated by Claude Code
- 裁决(方向 1)照实落地:合并前把比较桶键用当期说法重述——
现象
当
compareTo锚定的日期维度同时是网格维度(即写进了selection.dimensions,趋势图的常见形状)时,比较趟的行按它自己平移后的桶键落地。mergeByDimensions按selection.dimensions建键,而这里的键就是那个日期桶,所以2025-01永远匹配不上2026-01—— 比较行一条都合并不进去,全部作为新行追加。结果:每一行都只有一半是真数字,另一半被
fillEmptyGroups填成0;并且日期筛选窗口是 2026-01..2026-02,回来的网格里却坐着2025-01/2025-02两行。复现
dataset:
owner(lookup)、close_date(date,dateGranularity: month),measureopp_count(count)。数据:2026-01 → 1 条;2026-02 → 2 条;2025-01 → 5 条;2025-02 → 7 条。
selection:
实测(origin/main
628b028,与 #5688/PR #6003 前后逐字节相同 —— 该 PR 未触及此形状):一个「本期 vs 去年同期」的趋势图,期望是 2 行 × 2 列(2026-01: 1 vs 5;2026-02: 2 vs 7),拿到的是 4 行、每行一个 0,外加两行落在筛选窗口之外。
落点
packages/services/service-analytics/src/dataset-executor.tsrunCompare:把比较行映射成{ ...dimensions, measure__compare }时,dimensions里的日期维度带的是平移后窗口的桶键;mergeByDimensions:按dimensions元组建键,2025-01与2026-01自然不等,走 else 分支追加新行。fillEmptyGroups的注释其实已经写下了这个行为的一半(「compareTo 趟会为每个上期存在、本期不存在的桶追加一行」),所以它是已知的;但那段话描述的是「上期有、本期没有」的边缘情形,而在日期维度即网格维度时,这是每一行的常态,而不是边缘。两种读法哪个是产品意图,需要裁定 —— 这正是本单不代裁的部分。为什么 #4870 的 pin 没有发现它
packages/services/service-analytics/src/__tests__/dataset-selection-window.test.ts的「buckets the compareTo pass identically, so the grids actually merge」用例,其 fake 对两趟都返回同一个固定桶键:
两趟返回的
created_at都是'2026-06',合并因此必然成功。该用例真正钉住的是「两趟的 groupBy 粒度一致」(它确实做到了,且断言得很清楚),但它无法触及「真实数据下平移窗口产生的是平移后的桶键」这一层 —— 只要 fake 按filter真的分桶,合并就不成立了。两种可能的裁法(不代裁)
previousPeriod(任意天数窗口)下的确切含义。opp_count__compare这一列对该形状就是无意义的,应当在该形状下不产出该列(而不是产出一列恒为 0),否则消费端无法区分「没有可比数据」与「对齐失败」。可达性
需要
compareTo的锚定维度同时出现在selection.dimensions。这是趋势图 + 同比的标准形状,examples/app-crm/app-showcase均有声明了dateGranularity的 date 维度。窗口-only 形状不受影响(那条已由 #5688 / PR #6003 修好并成对钉住)。出处
#5688 实施期间实测发现,范围外未搭车(#5688 的裁决明确限定为「回填判据收窄」,且要求被选中的条目保持分桶不回归 —— 本单动的是分桶之后的合并,是另一层)。PR #6003 已把该行为作为「#5688 未改变的长期行为」原样断言在
dataset-window-timedimension-bucketing.test.ts的「bucketed anchor」用例里(含上面那 4 行),所以裁定后改动这里会让那条用例有意转红,可直接作为靶子。Generated by Claude Code