api-server/src/modules/learning-activity/learning-activity.service.ts

117 lines
4.4 KiB
TypeScript
Raw Normal View History

import { Injectable } from '@nestjs/common';
import { LearningActivityRepository } from './learning-activity.repository';
import { LearningTrendWorkflow } from '../ai/workflows/learning-trend.workflow';
@Injectable()
export class LearningActivityService {
constructor(
private readonly repository: LearningActivityRepository,
private readonly trendWorkflow: LearningTrendWorkflow,
) {}
async getHeatmap(userId: string) {
const activities = await this.repository.findAll(userId);
const heatmap: Record<string, number> = {};
for (const a of activities) {
const dateStr = a.activityDate instanceof Date
? a.activityDate.toISOString().split('T')[0]
: String(a.activityDate).split('T')[0];
heatmap[dateStr] = a.durationSeconds;
}
return heatmap;
}
async getSummary(userId: string) {
const activities = await this.repository.findAll(userId);
const totalMinutes = Math.round(
activities.reduce((s, a) => s + a.durationSeconds, 0) / 60,
);
const totalCards = activities.reduce((s, a) => s + a.reviewCount, 0);
const activeDays = activities.filter((a) => a.durationSeconds > 0).length;
const dailyAverage = activeDays > 0 ? Math.round(totalMinutes / activeDays) : 0;
return { totalMinutes, totalCardsReviewed: totalCards, activeDays, dailyAverage };
}
async getTrend(userId: string, periodDays: number = 7) {
const activities = await this.repository.findAll(userId);
const cutoff = new Date();
cutoff.setDate(cutoff.getDate() - periodDays);
const recent = activities.filter((a) =>
new Date(a.activityDate) >= cutoff
);
const previousCutoff = new Date(cutoff);
previousCutoff.setDate(previousCutoff.getDate() - periodDays);
const previous = activities.filter((a) => {
const d = new Date(a.activityDate);
return d >= previousCutoff && d < cutoff;
});
const sum = (items: typeof activities, key: keyof typeof activities[0]) =>
items.reduce((s, a) => s + (Number(a[key]) || 0), 0);
const recentTotalMinutes = Math.round(sum(recent, 'durationSeconds') / 60);
const recentSessions = sum(recent, 'sessionsCount');
const recentRecall = sum(recent, 'activeRecallCount');
const recentReview = sum(recent, 'reviewCount');
const recentAiAnalysis = sum(recent, 'aiAnalysisCount');
const recentLoops = sum(recent, 'completedLoopCount');
const recentActiveDays = recent.filter((a) => a.durationSeconds > 0).length;
const recentDailyAvg = recentActiveDays > 0
? Math.round(recentTotalMinutes / recentActiveDays) : 0;
const recentActivityLevel = recent.length > 0
? Math.round(recent.reduce((s, a) => s + a.activityLevel, 0) / recent.length)
: 0;
const prevTotalMinutes = Math.round(sum(previous, 'durationSeconds') / 60);
// Build daily time-series for chart rendering
const dailySeries = this.buildDailySeries(recent, periodDays);
const trendInput = {
userId,
periodDays,
totalMinutes: recentTotalMinutes,
sessionsCount: recentSessions,
activeRecallCount: recentRecall,
reviewCount: recentReview,
aiAnalysisCount: recentAiAnalysis,
completedLoopCount: recentLoops,
activityLevel: recentActivityLevel,
activeDays: recentActiveDays,
dailyAverage: recentDailyAvg,
previousPeriod: previous.length > 0 ? {
totalMinutes: prevTotalMinutes,
activeRecallCount: sum(previous, 'activeRecallCount'),
reviewCount: sum(previous, 'reviewCount'),
activeDays: previous.filter((a) => a.durationSeconds > 0).length,
} : undefined,
};
const aiResult = await this.trendWorkflow.execute(trendInput);
return { ...aiResult, dailySeries };
}
private buildDailySeries(activities: any[], days: number) {
const series: { date: string; value: number; label: string }[] = [];
const now = new Date();
for (let i = days - 1; i >= 0; i--) {
const d = new Date(now);
d.setDate(d.getDate() - i);
const dateStr = d.toISOString().split('T')[0];
const dayActs = activities.filter((a) => {
const ad = a.activityDate instanceof Date ? a.activityDate : new Date(a.activityDate);
return ad.toISOString().split('T')[0] === dateStr;
});
const minutes = Math.round(dayActs.reduce((s: number, a: any) => s + a.durationSeconds, 0) / 60);
series.push({
date: dateStr,
value: minutes,
label: `${minutes}分钟`,
});
}
return series;
}
}