Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions .changeset/compare-to-bucket-alignment.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
---
"@objectstack/service-analytics": patch
---

fix(service-analytics): `compareTo` 在「日期维度本身就是网格维度」时把比较桶键平移回当期 (#6007)

趋势图 + 同比是 `compareTo` 最常见的形状:日期维度既写进 `selection.dimensions`
(它就是图表的时间轴),又被 `compareTo` 用作锚点。这个形状下比较趟从来没有对齐过。

比较趟查询的是**平移后**的窗口,所以它的行按平移后的桶键落地;而
`mergeByDimensions` 按 `selection.dimensions` 元组建键 —— `2025-01` 不等于
`2026-01`,于是**没有一条**比较行合并得进去,全部作为新行追加。两趟各自只报告了自己
那一半,`fillEmptyGroups` 把另一半填成自信的 `0`,再加上平移后的桶键坐在网格里,而它们
落在调用方筛选窗口之外。一个 2 桶窗口的「今年 vs 去年同期」回来是这样的:

```
[{"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}]
```

四行、每行一个 0、两行在窗口外;期望是 2 行 × 2 列。

**修法(维护者裁决 2026-08-07,方向 1):合并之前,把每个比较桶键用当期的说法重述一遍。**
上例现在返回 `[{close_date:'2026-01',opp_count:1,opp_count__compare:5},
{close_date:'2026-02',opp_count:2,opp_count__compare:7}]`。

- `previousYear` —— 窗口是按日历年平移的,所以逆运算就是按日历年往前推一年:对桶自己的
首日做平移再重新分桶。`2025-01` → `2026-01`、`2025-Q1` → `2026-Q1`、
`2025-W03` → `2026-W03`。它刻意是 `shiftRange` 那套年运算的精确逆运算(含
`setUTCFullYear` 的溢出行为),窗口与桶键因此不可能对「一年」有两种理解。
- `previousPeriod` —— 任意天数窗口没有日历对应物,所以按**桶序(bucket ordinal)**对齐:
上一窗口的第 n 个桶对上本窗口的第 n 个桶,n 各自从自己窗口的起点数起。序号由**日历**算出
而不是数组下标,所以本期网格里某个桶没有数据(存在空档)不会让其后每个桶都错位一格。

**响应形状不变** —— 仍然是 `<measure>__compare` 列,行仍然是网格维度元组,所以消费端
(objectui#3337 正在收敛的那条契约)不受影响。

不确定时一律**保持原样**(即改动前的行为),而不是猜:空桶(两条聚合路径上键都是 `null`,
两趟本来就互相合并)、未分桶的日期维度(分组的是原始时间戳,不是桶键)、以及平移回来落在
当期窗口之外的桶(两个等长的天数窗口可以切出不同的桶数)。

范围严格限定在坏掉的那个形状:锚点必须是**网格维度**(仅作窗口的锚点两趟都不是列,#5688
之后本来就对齐)且必须**被分桶**。两趟通过同一个 `granularityOf` 读取桶大小,所以这里重述
的桶大小按构造就是查询分组用的桶大小。
Loading
Loading