API-INFO-014 P0 | 聚合到 DailyLearningActivity 【status:todo】 #113

Closed
opened 2026-06-07 11:03:39 +08:00 by wangdl · 2 comments
Owner

目标

按 userId+activityDate 聚合每日学习活动。

规则

  • durationSeconds += countedActiveSecondsDelta
  • readingSeconds += countedActiveSecondsDelta
  • 同一 clientSessionId 当天只计一次 sessionsCount
  • 同一 material 当天只计一次 materialsReadCount
  • activityLevel 自动重算
  • 使用 clientTimezoneOffsetMinutes 计算 activityDate

详见设计文档 API-INFO-000。

## 目标 按 userId+activityDate 聚合每日学习活动。 ### 规则 - durationSeconds += countedActiveSecondsDelta - readingSeconds += countedActiveSecondsDelta - 同一 clientSessionId 当天只计一次 sessionsCount - 同一 material 当天只计一次 materialsReadCount - activityLevel 自动重算 - 使用 clientTimezoneOffsetMinutes 计算 activityDate 详见设计文档 API-INFO-000。
wangdl added this to the M8:学习信息收集与基础分析闭环 milestone 2026-06-07 11:03:39 +08:00
wangdl changed title from API-INFO-008 P0 | 聚合 ReadingEvent 到 DailyLearningActivity to API-INFO-014 P0 | 聚合到 DailyLearningActivity 2026-06-07 11:22:17 +08:00
wangdl changed title from API-INFO-014 P0 | 聚合到 DailyLearningActivity to API-INFO-014 P0 | 聚合到 DailyLearningActivity 【status:todo】 2026-06-07 19:04:14 +08:00
Author
Owner

审查结论:当前 API 项目学习信息收集体系基本为全新建设。可复用:JWT Guard、LearningSession 基础表/CRUD、DailyLearningActivity 基础表、ActivityController 部分接口、LearningRecord schema。其余 ReadingEvent/TemporaryMaterial/Progress/批量上报/Processor/聚合/查询接口/错误码/去重/权限/测试/文档均不存在或仅部分存在。

本 Issue: 聚合逻辑不存在。依赖 upsert/increment。blocked-by:processor

标签: audit:reviewed audit:api-info status:todo work:aggregation

## 审查结论:当前 API 项目学习信息收集体系基本为全新建设。可复用:JWT Guard、LearningSession 基础表/CRUD、DailyLearningActivity 基础表、ActivityController 部分接口、LearningRecord schema。其余 ReadingEvent/TemporaryMaterial/Progress/批量上报/Processor/聚合/查询接口/错误码/去重/权限/测试/文档均不存在或仅部分存在。 **本 Issue**: 聚合逻辑不存在。依赖 upsert/increment。blocked-by:processor **标签**: audit:reviewed audit:api-info status:todo work:aggregation
Author
Owner

完成报告

交付

1. LearningActivityRepository.upsertFromReadingEvent(tx, data) — 时区感知的每日聚合:

// computeActivityDate: clientTimestampMs + offsetMinutes → UTC date
// UPSERT (userId, activityDate)
//   durationSeconds   += activeSecondsDelta
//   readingSeconds    += activeSecondsDelta
//   materialsReadCount += (material_opened ? 1 : 0)
//   markedReadCount   += (marked_as_read ? 1 : 0)

2. 时区计算

private computeActivityDate(timestampMs: bigint, offsetMinutes: number | null): Date {
  const offsetMs = (offsetMinutes ?? 0) * 60 * 1000;
  const localMs = Number(timestampMs) + offsetMs;
  const date = new Date(localMs);
  // 截断到 UTC 日期
  return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()));
}

3. ProcessorService 事务扩展 — Step 5c:

await this.prisma.$transaction(async (tx) => {
  await insertReadingEvent(tx, ...);          // 4
  await sessionRepo.upsertFromReadingEvent();  // 5a
  await progressSvc.upsertFromReadingEvent();  // 5b
  await activityRepo.upsertFromReadingEvent(); // 5c
  await tx.readingEvent.update(...);           // 6
});
## 完成报告 ### 交付 **1. `LearningActivityRepository.upsertFromReadingEvent(tx, data)`** — 时区感知的每日聚合: ```typescript // computeActivityDate: clientTimestampMs + offsetMinutes → UTC date // UPSERT (userId, activityDate) // durationSeconds += activeSecondsDelta // readingSeconds += activeSecondsDelta // materialsReadCount += (material_opened ? 1 : 0) // markedReadCount += (marked_as_read ? 1 : 0) ``` **2. 时区计算**: ```typescript private computeActivityDate(timestampMs: bigint, offsetMinutes: number | null): Date { const offsetMs = (offsetMinutes ?? 0) * 60 * 1000; const localMs = Number(timestampMs) + offsetMs; const date = new Date(localMs); // 截断到 UTC 日期 return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); } ``` **3. ProcessorService 事务扩展** — Step 5c: ```typescript await this.prisma.$transaction(async (tx) => { await insertReadingEvent(tx, ...); // 4 await sessionRepo.upsertFromReadingEvent(); // 5a await progressSvc.upsertFromReadingEvent(); // 5b await activityRepo.upsertFromReadingEvent(); // 5c await tx.readingEvent.update(...); // 6 }); ```
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: wangdl/api-server#113
No description provided.