Skip to content

analytics: compareTo 在「日期维度本身就是网格维度」时从不对齐 —— 比较行按自己平移后的桶键落地,于是每行一半是真值一半是自信的 0,还多出窗口外的幻影行 #6007

Description

@hotlong

现象

当 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),measure opp_count(count)。

数据:2026-01 → 1 条;2026-02 → 2 条;2025-01 → 5 条;2025-02 → 7 条。

selection:

{ dimensions: ['close_date'],
  measures: ['opp_count'],
  timeDimensions: [{ dimension: 'close_date', dateRange: ['2026-01-01', '2026-02-28'] }],
  compareTo: { kind: 'previousYear', dimension: 'close_date' } }

实测(origin/main 628b028,与 #5688/PR #6003 前后逐字节相同 —— 该 PR 未触及此形状):

rows [{"close_date":"2025-01","opp_count__compare":5,"opp_count":0},
      {"close_date":"2025-02","opp_count__compare":7,"opp_count":0},
      {"close_date":"2026-01","opp_count":1,"opp_count__compare":0},
      {"close_date":"2026-02","opp_count":2,"opp_count__compare":0}]

一个「本期 vs 去年同期」的趋势图,期望是 2 行 × 2 列(2026-01: 1 vs 5;2026-02: 2 vs 7),拿到的是 4 行、每行一个 0,外加两行落在筛选窗口之外。

落点

  • packages/services/service-analytics/src/dataset-executor.ts runCompare:把比较行映射成 { ...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 对两趟都返回同一个固定桶键:

executeAggregate: async (object, options) => {
  calls.push({ object, options });
  return [{ created_at: '2026-06', account_count: calls.length }];
}

两趟返回的 created_at 都是 '2026-06',合并因此必然成功。该用例真正钉住的是「两趟的 groupBy 粒度一致」(它确实做到了,且断言得很清楚),但它无法触及「真实数据下平移窗口产生的是平移后的桶键」这一层 —— 只要 fake 按 filter 真的分桶,合并就不成立了。

两种可能的裁法(不代裁)

  1. 按相对位置对齐:比较行的日期桶键在合并前被「平移回」当期(2025-01 → 2026-01),于是 2 行 2 列。语义清晰,但需要定义「平移回」在 previousPeriod(任意天数窗口)下的确切含义。
  2. 维持现状并明说:承认这是「两个窗口叠在同一条时间轴上」的呈现,那么 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

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊结论:升维护者决策箱 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/main 44106d9,正文引用的 628b028 已前移):

    查重 — 三仓 open issue/PR 全量缓存 + 本地正则(REST search/issues 本会话被网关按仓库范围拒绝)。compareTo / mergeByDimensions / dataset-executor 命中三条,逐条排除:

    可达性(为决策提供轻重量级读数):需要 compareTo 的锚定维度同时出现在 selection.dimensions,即「趋势图 + 同比」这一标准形状;正文称 examples/app-crm / app-showcase 均有声明 dateGranularity 的 date 维度 —— 不是边角形状。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. claude commented on Aug 7, 2026

    @claude
    Contributor

    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 mergeByDimensions never 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>__compare columns), so objectui#3337's contract convergence is untouched and no Blocked-by is needed.

    previousYear / previousMonth with an explicit dateGranularity define the shift-back uniquely. For previousPeriod (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:392 is 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

  3. self-assigned this
    on Aug 7, 2026
  4. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    认领: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

  5. hotlong commented on Aug 7, 2026

    @hotlong
    ContributorAuthor

    ACCEPT → PR #6213(draft 转 ready 入队中)。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions