Some checks failed
CI / verify (push) Has been cancelled
- lock profile task tracking scope to user - add analytics date dimension module support and tests - regenerate SpacetimeDB Rust bindings with private APIs
725 lines
21 KiB
Markdown
725 lines
21 KiB
Markdown
# 埋点系统新增周、月、季、年维度映射表计划
|
||
|
||
## 目标
|
||
|
||
在 Genarrative 的埋点/统计系统中新增“周、月、季、年”维度映射表,让后续统计查询可以按不同时间粒度稳定聚合,而不是只依赖运行时临时计算日期范围。
|
||
|
||
本计划只做设计与落地步骤,不直接修改业务代码。
|
||
|
||
## 当前上下文与初步发现
|
||
|
||
1. 当前仓库根目录为 `/home/dsk/workspace/Genarrative`。
|
||
2. 远端更新后已定位到当前真实埋点/任务系统:
|
||
- 原始埋点表:`tracking_event`
|
||
- 日聚合投影表:`tracking_daily_stat`
|
||
- 任务配置表:`profile_task_config`
|
||
- 任务进度表:`profile_task_progress`
|
||
- 领奖记录表:`profile_task_reward_claim`
|
||
3. 相关文件:
|
||
- `server-rs/crates/spacetime-module/src/runtime/profile.rs`
|
||
- `server-rs/crates/module-runtime/src/domain.rs`
|
||
- `server-rs/crates/module-runtime/src/application.rs`
|
||
- `server-rs/crates/api-server/src/runtime_profile.rs`
|
||
- `apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
|
||
- `apps/admin-web/src/api/adminApiTypes.ts`
|
||
- `apps/admin-web/src/config/trackingEventDefinitions.ts`
|
||
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
||
- `docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md`
|
||
4. 当前 `tracking_event` 是明细表,`tracking_daily_stat` 是统一日汇总表,不按范围拆表;它通过 `event_key + scope_kind + scope_id + day_key` 区分不同聚合桶。
|
||
5. 当前任务系统是“个人任务系统”,首版任务配置均面向用户维度;后台暴露“埋点范围”选择会导致运营误配。
|
||
6. 已决定采用任务配置方案 B:
|
||
- 后台任务配置不再让运营手动选择埋点范围。
|
||
- 后端个人任务配置统一限制为 `RuntimeTrackingScopeKind::User`。
|
||
- 若 API 收到非 `user` 的 `scopeKind`,应拒绝或兼容忽略但最终落库必须为 `User`;推荐直接拒绝并返回清晰错误。
|
||
7. 已发现一个需要纳入本计划修复的语义问题:`profile_task_tracking_scope_id` 里 `RuntimeTrackingScopeKind::Work` 当前返回 `user_id`,这不符合 work 维度语义。即使个人任务暂不支持 work,也应避免错误映射继续存在。
|
||
8. 当前日期桶使用北京时间自然日:`day_key = floor((occurred_at_micros + 8h) / 1d)`;周/月/季/年映射表应优先沿用这一业务日口径,除非产品另行确认。
|
||
9. 如果新增正式后端表,需要同步:
|
||
- 表定义
|
||
- reducer/procedure
|
||
- migration.rs
|
||
- 生成绑定
|
||
- spacetime-client facade
|
||
- shared-contracts / API DTO,如有接口暴露
|
||
|
||
## 关键假设
|
||
|
||
在未定位现有埋点模块前,先按以下假设规划:
|
||
|
||
1. 当前已有某种事件明细表或统计事实表,例如:
|
||
- `telemetry_event`
|
||
- `analytics_event`
|
||
- `metric_event`
|
||
- `narrative_telemetry`
|
||
- 或类似命名
|
||
2. 新增的“映射表”用于把具体日期或事件时间映射到时间维度 bucket。
|
||
3. 映射维度包括:
|
||
- 周:week
|
||
- 月:month
|
||
- 季:quarter
|
||
- 年:year
|
||
4. 已明确选择“一张通用日期维度映射表”方案:`analytics_date_dimension`。
|
||
5. 统计口径需要明确:
|
||
- 周从周一还是周日开始
|
||
- 是否使用 ISO week
|
||
- 季度是自然季度还是财务季度
|
||
- 时区使用 UTC 还是业务本地时区
|
||
|
||
## 推荐设计方向
|
||
|
||
### 方案 A:单一时间维度映射表,推荐
|
||
|
||
新增一张日历维度表,每一行对应一个自然日,并包含它归属的周、月、季、年。
|
||
|
||
表概念:
|
||
|
||
```text
|
||
analytics_date_dimension
|
||
```
|
||
|
||
建议字段:
|
||
|
||
```text
|
||
date_key string 例如 2026-05-04
|
||
calendar_date string 真实日期,按 YYYY-MM-DD 存储
|
||
weekday u8 1-7 或 0-6,需要统一约定
|
||
iso_week_key string 例如 2026-W19
|
||
week_start_date_key string 例如 2026-05-04
|
||
week_end_date_key string 例如 2026-05-10
|
||
month_key string 例如 2026-05
|
||
month_start_date_key string
|
||
month_end_date_key string
|
||
quarter_key string 例如 2026-Q2
|
||
year_key string 例如 2026
|
||
created_at timestamp/string
|
||
updated_at timestamp/string
|
||
```
|
||
|
||
优点:
|
||
|
||
- 一张表即可支持日、周、月、季、年映射。
|
||
- 便于后续新增半月、财年、节假日、自然周等维度。
|
||
- 查询逻辑简单:事件日期 join/date_key 映射到目标粒度。
|
||
- 数据量很小,按 20 年也只有约 7300 行。
|
||
|
||
缺点:
|
||
|
||
- 需要在事件时间写入或统计查询时把 timestamp 归一为 date_key。
|
||
- 如果要支持多时区,可能需要增加 timezone 字段或多套 calendar。
|
||
|
||
### 方案 B:四张独立映射表
|
||
|
||
分别新增:
|
||
|
||
```text
|
||
analytics_week_dimension
|
||
analytics_month_dimension
|
||
analytics_quarter_dimension
|
||
analytics_year_dimension
|
||
```
|
||
|
||
优点:
|
||
|
||
- 每个粒度表结构更纯粹。
|
||
- 查询时可以直接针对目标粒度表。
|
||
|
||
缺点:
|
||
|
||
- 表更多,维护复杂。
|
||
- 日期归属关系仍然需要额外处理。
|
||
- 容易出现周/月/季/年口径漂移。
|
||
|
||
### 最终选择
|
||
|
||
本计划采用方案 A:单一 `analytics_date_dimension` 日期维表,而不是四张独立映射表。
|
||
|
||
如业务未来明确要求“周、月、季、年各自有独立映射表”,也应优先在日期维表基础上派生视图或物化派生表,而不是一开始拆成四张重复表。
|
||
|
||
## 后端设计建议
|
||
|
||
### 1. 明确埋点领域归属
|
||
|
||
先定位现有埋点模块。如果没有独立模块,建议新增或归入:
|
||
|
||
```text
|
||
server-rs/crates/module-analytics/
|
||
```
|
||
|
||
或如果当前项目已有 telemetry 命名,则保持已有命名,例如:
|
||
|
||
```text
|
||
server-rs/crates/module-telemetry/
|
||
```
|
||
|
||
领域层职责:
|
||
|
||
- 时间粒度定义
|
||
- date_key/week_key/month_key/quarter_key/year_key 生成规则
|
||
- 时间维度校验
|
||
- 事件聚合查询输入的纯规则
|
||
|
||
不应包含:
|
||
|
||
- SpacetimeDB 表读写
|
||
- Axum handler
|
||
- HTTP response
|
||
|
||
### 2. SpacetimeDB 表设计
|
||
|
||
在 `spacetime-module` 中新增时间维度表。
|
||
|
||
建议表名:
|
||
|
||
```text
|
||
analytics_date_dimension
|
||
```
|
||
|
||
建议主键:
|
||
|
||
```text
|
||
date_key
|
||
```
|
||
|
||
建议索引:
|
||
|
||
```text
|
||
iso_week_key
|
||
month_key
|
||
quarter_key
|
||
year_key
|
||
```
|
||
|
||
如果 SpacetimeDB 表定义已有统一命名规范,应按现有规范命名。
|
||
|
||
### 3. 初始化/补全 reducer
|
||
|
||
新增 reducer 或内部 procedure,用于生成指定日期范围内的维度数据。
|
||
|
||
建议能力:
|
||
|
||
```text
|
||
seed_analytics_date_dimensions(start_date, end_date)
|
||
ensure_analytics_date_dimension_for_date(date_key)
|
||
ensure_analytics_date_dimensions_for_range(start_date, end_date)
|
||
```
|
||
|
||
设计原则:
|
||
|
||
- 可幂等执行。
|
||
- 已存在 date_key 时不重复插入。
|
||
- 支持一次补一段日期。
|
||
- 避免一次补太大范围导致事务过重。
|
||
- 生产环境建议按年份或月份分批。
|
||
|
||
### 4. 事件表和映射表关系
|
||
|
||
如果事件表目前只有 timestamp,建议新增或计算出:
|
||
|
||
```text
|
||
event_date_key
|
||
```
|
||
|
||
可选策略:
|
||
|
||
1. 写入事件时同步写 `event_date_key`。
|
||
2. 查询统计时从 timestamp 临时计算 date_key。
|
||
3. 后台迁移为历史事件补 `event_date_key`。
|
||
|
||
推荐:
|
||
|
||
- 新事件写入时保存 `event_date_key`。
|
||
- 历史事件通过批量迁移 reducer 分批补齐。
|
||
|
||
### 5. 聚合查询设计
|
||
|
||
支持按粒度查询时,API 或 facade 可以接收:
|
||
|
||
```text
|
||
granularity = day | week | month | quarter | year
|
||
start_date
|
||
end_date
|
||
metric/event_name
|
||
filters
|
||
```
|
||
|
||
内部根据粒度选择 bucket key:
|
||
|
||
```text
|
||
day -> date_key
|
||
week -> iso_week_key 或 week_key
|
||
month -> month_key
|
||
quarter -> quarter_key
|
||
year -> year_key
|
||
```
|
||
|
||
返回结构建议统一:
|
||
|
||
```text
|
||
bucket_key
|
||
bucket_label
|
||
bucket_start_date
|
||
bucket_end_date
|
||
value
|
||
```
|
||
|
||
## 可能涉及的文件
|
||
|
||
由于当前尚未定位明确埋点模块,以下是预计文件范围。
|
||
|
||
### 必查文件/目录
|
||
|
||
```text
|
||
./Genarrative/server-rs/crates/
|
||
./Genarrative/server-rs/crates/spacetime-module/src/
|
||
./Genarrative/server-rs/crates/spacetime-client/src/
|
||
./Genarrative/server-rs/crates/shared-contracts/src/
|
||
./Genarrative/server-rs/crates/api-server/src/
|
||
./Genarrative/packages/shared/src/contracts/
|
||
./Genarrative/src/services/
|
||
```
|
||
|
||
### 可能新增文件
|
||
|
||
如果采用 analytics 命名:
|
||
|
||
```text
|
||
server-rs/crates/module-analytics/src/domain.rs
|
||
server-rs/crates/module-analytics/src/commands.rs
|
||
server-rs/crates/module-analytics/src/application.rs
|
||
server-rs/crates/module-analytics/src/errors.rs
|
||
server-rs/crates/module-analytics/src/events.rs
|
||
server-rs/crates/shared-contracts/src/analytics.rs
|
||
server-rs/crates/spacetime-client/src/analytics.rs
|
||
server-rs/crates/api-server/src/analytics.rs
|
||
packages/shared/src/contracts/analytics.ts
|
||
```
|
||
|
||
如果只是新增 SpacetimeDB 映射表且暂不暴露 API,则可能只需:
|
||
|
||
```text
|
||
server-rs/crates/spacetime-module/src/**
|
||
server-rs/crates/spacetime-module/src/migration.rs
|
||
server-rs/crates/spacetime-client/src/** # 如果查询会被 api-server 使用
|
||
```
|
||
|
||
## 详细实施步骤
|
||
|
||
### Step 1:复核现有埋点系统与任务配置链路
|
||
|
||
当前已定位真实链路,实施前再做一次只读复核,确认远端最新代码没有继续变化。
|
||
|
||
已知核心表:
|
||
|
||
```text
|
||
tracking_event # 原始埋点明细
|
||
tracking_daily_stat # 日聚合投影
|
||
profile_task_config # 个人任务配置
|
||
profile_task_progress # 个人任务进度
|
||
profile_task_reward_claim # 领奖记录
|
||
```
|
||
|
||
已知核心文件:
|
||
|
||
```text
|
||
server-rs/crates/spacetime-module/src/runtime/profile.rs
|
||
server-rs/crates/module-runtime/src/domain.rs
|
||
server-rs/crates/module-runtime/src/application.rs
|
||
server-rs/crates/api-server/src/runtime_profile.rs
|
||
apps/admin-web/src/pages/AdminTaskConfigPage.tsx
|
||
apps/admin-web/src/api/adminApiTypes.ts
|
||
apps/admin-web/src/config/trackingEventDefinitions.ts
|
||
docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md
|
||
docs/tracking/TRACKING_QUERY_PLAYBOOK_2026-05-03.md
|
||
```
|
||
|
||
重点确认:
|
||
|
||
1. `tracking_event` 是否仍包含 `event_key/scope_kind/scope_id/day_key/user_id/occurred_at`。
|
||
2. `tracking_daily_stat` 是否仍按 `event_key + scope_kind + scope_id + day_key` 生成 `stat_id`。
|
||
3. `profile_task_config` 是否仍包含 `scope_kind` 和 `sort_order`。
|
||
4. 后台 `AdminTaskConfigPage` 是否仍暴露“埋点范围”下拉。
|
||
5. `profile_task_tracking_scope_id` 中 `Work => user_id` 的错误映射是否仍存在。
|
||
|
||
### Step 1.5:先收紧个人任务配置的埋点范围,采用方案 B
|
||
|
||
在做周/月/季/年维度映射前,先修正个人任务配置边界,避免后续在错误配置模型上继续扩展。
|
||
|
||
目标行为:
|
||
|
||
```text
|
||
个人任务配置只支持用户维度埋点。
|
||
后台页面不再展示“埋点范围”。
|
||
后端不允许 profile_task_config 被写入 site/work/module 维度。
|
||
```
|
||
|
||
建议实现:
|
||
|
||
1. 前端隐藏 `AdminTaskConfigPage` 的“埋点范围”选择。
|
||
- 文件:`apps/admin-web/src/pages/AdminTaskConfigPage.tsx`
|
||
- 移除或隐藏:`scopeKinds` 下拉 UI。
|
||
- 保存请求仍可兼容传 `scopeKind: 'user'`,避免一次性改动 API contract。
|
||
2. 后端 upsert 校验 `scopeKind` 必须为 `RuntimeTrackingScopeKind::User`。
|
||
- 文件:`server-rs/crates/api-server/src/runtime_profile.rs`
|
||
- 或更底层:`server-rs/crates/module-runtime/src/domain.rs` / `server-rs/crates/spacetime-module/src/runtime/profile.rs` 的输入构造函数。
|
||
- 推荐在领域输入构造处兜底校验,API 层返回清晰错误。
|
||
3. 若暂不改 API DTO,则保持字段存在但限定值只能是 `user`。
|
||
- 文件:`apps/admin-web/src/api/adminApiTypes.ts`
|
||
- `AdminUpsertProfileTaskConfigRequest.scopeKind` 可保留,前端固定传 `user`。
|
||
4. 更新后台埋点定义注册表的语义:
|
||
- 文件:`apps/admin-web/src/config/trackingEventDefinitions.ts`
|
||
- 当前每个 event definition 包含 `scopeKind`,如果个人任务统一 `user`,可以保留为只读内部默认值;但不要让运营在页面改。
|
||
5. 更新技术文档:
|
||
- `docs/technical/PROFILE_TASK_AND_TRACKING_SYSTEM_2026-05-03.md`
|
||
- 明确“个人任务首版只支持用户维度埋点,后台不开放 scope_kind 配置”。
|
||
|
||
验收:
|
||
|
||
```text
|
||
后台任务配置页不再出现“埋点范围”选择。
|
||
保存 daily_login 后落库 scope_kind 仍为 User。
|
||
直接调用后台 upsert 接口传 site/work/module 时被拒绝,或最终不会落库为非 User;推荐拒绝。
|
||
```
|
||
|
||
### Step 1.6:修复 Work 范围错误返回 user_id 的语义问题
|
||
|
||
当前函数:
|
||
|
||
```text
|
||
server-rs/crates/spacetime-module/src/runtime/profile.rs
|
||
profile_task_tracking_scope_id(user_id, config)
|
||
```
|
||
|
||
当前问题:
|
||
|
||
```rust
|
||
RuntimeTrackingScopeKind::Work => user_id.to_string()
|
||
```
|
||
|
||
这会把 work 维度错误映射为用户 ID。虽然个人任务将限制为 User,但保留这个分支会误导后续扩展。
|
||
|
||
推荐修复策略:
|
||
|
||
1. 对个人任务进度计算来说,`Work` 不应进入该函数。
|
||
2. 将 `profile_task_tracking_scope_id` 改为返回 `Result<String, String>` 或 `Option<String>`。
|
||
3. 对不支持的范围返回错误,而不是伪造 scope_id:
|
||
|
||
```text
|
||
Site -> "site",如果个人任务仍不允许 site,则上游先拒绝
|
||
Module -> "profile",如果个人任务仍不允许 module,则上游先拒绝
|
||
User -> user_id
|
||
Work -> error: personal task progress does not support work scope without work_id
|
||
```
|
||
|
||
更严格的推荐:
|
||
|
||
```text
|
||
个人任务链路只接受 User。
|
||
Work/Site/Module 在 profile_task_progress_count 前就被拒绝。
|
||
profile_task_tracking_scope_id 只保留 User 分支,或者非 User 返回错误。
|
||
```
|
||
|
||
需要同步调整调用点:
|
||
|
||
```text
|
||
profile_task_progress_count
|
||
refresh_profile_task_progress
|
||
build_profile_task_center_snapshot
|
||
claim_profile_task_reward
|
||
```
|
||
|
||
避免因为函数返回 `Result` 后调用链未处理错误。
|
||
|
||
验收:
|
||
|
||
```text
|
||
不存在 Work => user_id 的映射。
|
||
个人任务配置非 User 时不会静默算出错误进度。
|
||
相关测试覆盖:User 正常;Work/Site/Module 被拒绝。
|
||
|
||
```
|
||
|
||
### Step 2:确定时间口径
|
||
|
||
必须先确认:
|
||
|
||
1. 周维度是否使用 ISO week。
|
||
2. 周开始日是周一还是周日。
|
||
3. 月/季/年是否按自然日历。
|
||
4. 统计时区是 UTC、服务器时区,还是用户本地时区。
|
||
5. 跨年周如何命名,例如 `2025-W01` 可能开始于 2024 年末。
|
||
|
||
推荐默认:
|
||
|
||
```text
|
||
时区:UTC,除非产品明确要求中国时区
|
||
周:ISO week,周一开始
|
||
月:自然月
|
||
季:自然季度
|
||
年:自然年
|
||
```
|
||
|
||
如果业务面向国内用户,建议考虑:
|
||
|
||
```text
|
||
时区:Asia/Shanghai
|
||
周:周一开始
|
||
```
|
||
|
||
### Step 3:设计 date dimension 表
|
||
|
||
设计字段和 key 格式,写入技术文档。
|
||
|
||
建议 key 格式:
|
||
|
||
```text
|
||
date_key: 2026-05-04
|
||
week_key: 2026-W19
|
||
month_key: 2026-05
|
||
quarter_key: 2026-Q2
|
||
year_key: 2026
|
||
```
|
||
|
||
注意:
|
||
|
||
- `week_key` 建议使用 ISO week-year,不一定等于 calendar year。
|
||
- `quarter_key` 使用 calendar year。
|
||
|
||
### Step 4:新增领域纯函数
|
||
|
||
在领域层或 shared-kernel 中实现纯函数:
|
||
|
||
```text
|
||
resolve_date_dimension(date, timezone) -> AnalyticsDateDimension
|
||
resolve_bucket_key(date_dimension, granularity) -> String
|
||
resolve_bucket_range(bucket_key, granularity) -> start/end date
|
||
```
|
||
|
||
要求:
|
||
|
||
- 有单元测试。
|
||
- 覆盖跨年周。
|
||
- 覆盖闰年 2 月。
|
||
- 覆盖季度边界。
|
||
|
||
### Step 5:新增 SpacetimeDB 表
|
||
|
||
在 `spacetime-module` 中新增表。
|
||
|
||
遵守约束:
|
||
|
||
- 新增表通常安全。
|
||
- 不修改已有表字段。
|
||
- 如果必须给已有事件表加 `event_date_key`,必须加在表定义末尾并提供 default。
|
||
- 若要补历史数据,使用 reducer 分批迁移。
|
||
|
||
### Step 6:新增 seed/ensure reducer
|
||
|
||
新增幂等 reducer:
|
||
|
||
```text
|
||
seed_analytics_date_dimensions(start_date, end_date)
|
||
ensure_analytics_date_dimension_for_date(date_key)
|
||
```
|
||
|
||
验证点:
|
||
|
||
- 重复执行不会重复插入。
|
||
- 日期范围非法时返回稳定错误。
|
||
- 单次范围过大时拒绝或分页。
|
||
|
||
### Step 7:接入事件写入链路
|
||
|
||
如果现有事件写入链路存在,新增:
|
||
|
||
```text
|
||
event_date_key
|
||
```
|
||
|
||
策略:
|
||
|
||
- 新事件写入时同步计算并保存。
|
||
- 写入前确保对应 date dimension 存在。
|
||
- 历史事件通过迁移 reducer 补齐。
|
||
|
||
如果暂不改事件表,也可以在查询阶段临时映射,但性能和一致性较差。
|
||
|
||
### Step 8:接入聚合查询
|
||
|
||
如已有统计接口,扩展请求参数:
|
||
|
||
```text
|
||
granularity: day | week | month | quarter | year
|
||
```
|
||
|
||
查询逻辑改为:
|
||
|
||
```text
|
||
事件/事实表
|
||
→ event_date_key
|
||
→ analytics_date_dimension
|
||
→ 取对应 bucket key
|
||
→ group by bucket key
|
||
```
|
||
|
||
返回 bucket 时包含:
|
||
|
||
```text
|
||
bucket_key
|
||
bucket_start_date
|
||
bucket_end_date
|
||
value
|
||
```
|
||
|
||
### Step 9:补 shared contracts 和前端 contracts
|
||
|
||
如果有 API 暴露,需要补:
|
||
|
||
```text
|
||
server-rs/crates/shared-contracts/src/analytics.rs
|
||
packages/shared/src/contracts/analytics.ts
|
||
```
|
||
|
||
建议 DTO:
|
||
|
||
```text
|
||
AnalyticsGranularity = day | week | month | quarter | year
|
||
AnalyticsBucketMetric
|
||
AnalyticsMetricQueryRequest
|
||
AnalyticsMetricQueryResponse
|
||
```
|
||
|
||
### Step 10:补测试
|
||
|
||
测试范围:
|
||
|
||
1. 领域日期映射测试
|
||
2. SpacetimeDB reducer 幂等测试
|
||
3. API 查询维度测试
|
||
4. 历史事件迁移测试,如涉及
|
||
5. 跨边界日期测试
|
||
6. 个人任务配置 scope 限制测试
|
||
7. `Work => user_id` 错误映射回归测试
|
||
|
||
重点用例:
|
||
|
||
```text
|
||
2024-02-29 闰年
|
||
2025-12-29 ISO week 可能属于 2026-W01
|
||
2026-01-01 跨年周
|
||
2026-03-31 Q1 结束
|
||
2026-04-01 Q2 开始
|
||
2026-12-31 年末
|
||
```
|
||
|
||
任务配置重点用例:
|
||
|
||
```text
|
||
admin upsert daily_login + scopeKind=user -> 成功
|
||
admin upsert daily_login + scopeKind=site -> 失败,错误信息说明个人任务仅支持 user
|
||
admin upsert daily_login + scopeKind=module -> 失败
|
||
admin upsert daily_login + scopeKind=work -> 失败
|
||
任务中心读取 daily_login -> 按 User + 当前 user_id 查询进度
|
||
代码中不存在 Work => user_id 的静默映射
|
||
```
|
||
|
||
## 测试与验证命令
|
||
|
||
具体命令需在定位模块后确认。初步建议:
|
||
|
||
```text
|
||
npm run typecheck
|
||
npm test
|
||
```
|
||
|
||
后端如涉及 Rust:
|
||
|
||
```text
|
||
cargo test -p module-analytics
|
||
cargo test -p spacetime-module
|
||
cargo test -p api-server
|
||
```
|
||
|
||
涉及 API smoke:
|
||
|
||
```text
|
||
npm run api-server
|
||
```
|
||
|
||
然后验证:
|
||
|
||
```text
|
||
GET /healthz
|
||
```
|
||
|
||
涉及 SpacetimeDB schema:
|
||
|
||
- 需要生成绑定。
|
||
- 需要确认 migration.rs 对齐。
|
||
- 需要确认 publish 不触发不安全 schema 变更。
|
||
|
||
## 风险与权衡
|
||
|
||
### 风险 1:个人任务 scope_kind 被误配置导致进度异常
|
||
|
||
当前个人任务系统本质上按用户维度计算进度。如果允许运营配置 `site/work/module`,可能导致任务进度查错 `tracking_daily_stat` 聚合桶,出现任务永远不可领取或错误可领取。
|
||
|
||
缓解:
|
||
|
||
```text
|
||
采用方案 B:后台隐藏埋点范围,后端限制个人任务配置只能写入 User。
|
||
```
|
||
|
||
### 风险 2:Work 维度缺少 work_id,上游却静默用 user_id 代替
|
||
|
||
当前 `profile_task_tracking_scope_id` 中 `Work => user_id` 是错误语义。若后续扩展作品任务,会把作品维度统计错误映射到用户维度。
|
||
|
||
缓解:
|
||
|
||
```text
|
||
移除 Work => user_id 映射;非 User 的个人任务配置应被拒绝。未来做作品任务时新增明确 work_id 来源和任务类型。
|
||
```
|
||
|
||
### 风险 3:时区口径影响统计结果
|
||
|
||
周/月/季/年映射对时区敏感。当前日桶使用北京时间自然日:`floor((occurred_at_micros + 8h) / 1d)`。新增映射表应明确沿用北京时间业务日,还是切换为 UTC/用户本地时区。
|
||
|
||
### 风险 4:ISO week 跨年
|
||
|
||
ISO week-year 与自然年不同。若前端展示按自然年理解,可能产生认知差异。
|
||
|
||
### 风险 5:修改已有事件表可能触发 SpacetimeDB 迁移限制
|
||
|
||
如果已有事件表需要新增字段:
|
||
|
||
- 字段必须加末尾。
|
||
- 必须提供 default。
|
||
- 历史数据要分批迁移。
|
||
|
||
### 风险 5:表设计过早绑定单一业务
|
||
|
||
建议用通用 date dimension,而不是为某个单一埋点写死周/月/季/年表,避免后续复用困难。
|
||
|
||
## 待确认问题
|
||
|
||
1. 周维度使用 ISO week 还是自然周?周一开始还是周日开始?
|
||
2. 周/月/季/年映射是否沿用当前北京时间业务日口径?
|
||
3. 这个映射表服务的是所有埋点,还是只服务个人任务/运营后台统计?
|
||
4. 是否需要 API 暴露这些映射关系,还是只用于后端聚合?
|
||
5. 是否需要回填历史事件?历史数据规模多大?
|
||
6. 未来是否会存在非个人任务,例如整站任务、模块任务、作品任务?如果会,应另行设计任务类型和 `scope_id` 来源,不应复用当前个人任务配置页直接开放 scope。
|
||
|
||
## 建议结论
|
||
|
||
优先采用“一张通用日期维度映射表”的设计:
|
||
|
||
```text
|
||
analytics_date_dimension
|
||
```
|
||
|
||
通过字段同时提供:
|
||
|
||
```text
|
||
day / week / month / quarter / year
|
||
```
|
||
|
||
后续统计按 `granularity` 选择 bucket key 聚合。这样比直接新增四张独立映射表更稳定、更容易复用,也更容易处理跨年周、季度边界和历史回填。
|