Skip to content

Commit af9a867

Browse files
committed
feat(dashboard): 学习脉搏加入非番茄钟折算(笔记/复习/费曼)(内测反馈)
1 parent 410d582 commit af9a867

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

‎client/src/features/dashboard/hooks/useLearningAnalytics.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ export function useLearningAnalytics(days = 30): UseLearningAnalyticsReturn {
3232
const fetchData = useCallback(async (worker: Worker) => {
3333
setLoading(true); setError(null);
3434
try {
35+
// 竞态修复:挂载时 notes/feynmanNotes 可能尚未加载完成(loadNotes 异步),
36+
// 直接聚合会按空数据计算——学习画像出勤率/持续性恒 0。先确保数据就绪。
37+
const noteState = useNoteStore.getState();
38+
if (noteState.notes.length === 0 && !noteState.isLoading) {
39+
await noteState.loadNotes();
40+
}
41+
const feynmanState = useFeynmanStore.getState();
42+
if (feynmanState.notes.length === 0 && !feynmanState.isLoading) {
43+
await feynmanState.loadNotes();
44+
}
3545
const notes = useNoteStore.getState().notes;
3646
const feynmanNotes = useFeynmanStore.getState().notes;
3747
const [sessions, flashcards, reviews] = await Promise.all([

‎client/src/features/dashboard/utils/aggregator.ts‎

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ const toISO = (d: Date) => `${d.getFullYear()}-${String(d.getMonth() + 1).padSta
88
const recent = <T>(arr: T[], dt: (i: T) => Date, days: number) => { const c = Date.now() - days * DAY_MS; return arr.filter((i) => dt(i).getTime() >= c); };
99
const norm = (v: number, m: number) => (m === 0 ? 0 : Math.min(100, Math.round((v / m) * 100)));
1010

11+
// ── 非番茄钟学习分钟折算(学习脉搏反映全部学习行为,内测反馈)──
12+
const NOTE_EDIT_MINUTES = 10; // 每篇笔记当天更新折算 10 分钟(按 id+日期去重防自动保存虚高)
13+
const REVIEW_MINUTES = 2; // 每次闪卡复习折算 2 分钟
14+
const FEYNMAN_MINUTES = 15; // 每篇费曼输出折算 15 分钟
15+
1116
/** 聚合输入类型 */
1217
export interface AggregateInput {
1318
sessions: PomodoroSession[]; notes: Note[]; flashcards: Flashcard[];
@@ -93,15 +98,43 @@ export function computeHeatmap(sessions: PomodoroSession[], days = 30): HeatmapC
9398
return cells;
9499
}
95100

96-
/** 每日学习时长趋势(含 7 天滑动均值 label) */
97-
export function computeTrend(sessions: PomodoroSession[], days = 30): TrendPoint[] {
101+
/** 每日学习时长趋势(含 7 天滑动均值 label)
102+
* 数据源:番茄钟 sessions(实际分钟)+ 可选折算(笔记/复习/费曼)
103+
*/
104+
export function computeTrend(
105+
sessions: PomodoroSession[], days = 30,
106+
extras?: { notes?: Note[]; reviews?: FlashcardReview[]; feynmanNotes?: FeynmanNote[] },
107+
): TrendPoint[] {
98108
const cutoff = Date.now() - days * DAY_MS;
99109
const dm: Record<string, number> = {};
100110
sessions.forEach((s) => {
101111
const d = new Date(s.completedAt);
102112
if (d.getTime() < cutoff) return;
103113
const k = toISO(d); dm[k] = (dm[k] ?? 0) + Math.round(s.actualDuration / 60);
104114
});
115+
116+
// 非番茄钟学习折算(笔记按 (id, 日期) 去重:自动保存会频繁更新 updatedAt)
117+
const seenNotes = new Set<string>();
118+
extras?.notes?.forEach((n) => {
119+
const d = new Date(n.updatedAt);
120+
if (d.getTime() < cutoff) return;
121+
const k = toISO(d);
122+
const key = `${n.id}-${k}`;
123+
if (seenNotes.has(key)) return;
124+
seenNotes.add(key);
125+
dm[k] = (dm[k] ?? 0) + NOTE_EDIT_MINUTES;
126+
});
127+
extras?.reviews?.forEach((r) => {
128+
const d = new Date(r.reviewedAt);
129+
if (d.getTime() < cutoff) return;
130+
const k = toISO(d); dm[k] = (dm[k] ?? 0) + REVIEW_MINUTES;
131+
});
132+
extras?.feynmanNotes?.forEach((f) => {
133+
const d = new Date(f.updatedAt);
134+
if (d.getTime() < cutoff) return;
135+
const k = toISO(d); dm[k] = (dm[k] ?? 0) + FEYNMAN_MINUTES;
136+
});
137+
105138
const dates: string[] = [];
106139
for (let i = days - 1; i >= 0; i--) {
107140
const k = toISO(new Date(Date.now() - i * DAY_MS));
@@ -154,7 +187,9 @@ export function aggregateAnalytics(data: AggregateInput, days = 30): AnalyticsAg
154187
return {
155188
radar: computeRadarData(data.sessions, data.notes, data.flashcards, data.feynmanNotes, data.reviews, days),
156189
heatmap,
157-
trend: computeTrend(data.sessions, days),
190+
trend: computeTrend(data.sessions, days, {
191+
notes: data.notes, reviews: data.reviews, feynmanNotes: data.feynmanNotes,
192+
}),
158193
recommendations: computeRecommendations(heatmap),
159194
period: { start: toISO(new Date(Date.now() - days * DAY_MS)), end: toISO(new Date()) },
160195
goals: computeGoalProgress(data.sessions, data.notes, data.reviews),

0 commit comments

Comments
 (0)