feat(recall): telemetryStats + EmitInput — recall 누적 + 열림율 + 평균 ageDays (#6 v0.2.3)
- DailyRow +4 cols (recall_shown/opened/dismissed/snoozed)
- accumulators + 4 branches + recallAgeDaysSum
- table 컬럼 +4
- summary lines: "- 회상 추천: shown N / opened O / dismissed D / snoozed S (열림율 X%)"
"- 회상 평균 ageDays: avg"
- TelemetryService.EmitInput union 15 → 19
- 단위 +2 cases
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -187,4 +187,27 @@ describe('aggregateStats — tag_vocab hit/miss', () => {
|
||||
expect(r.md).toContain('태그 vocab');
|
||||
expect(r.md).toContain('데이터 없음');
|
||||
});
|
||||
|
||||
it('aggregates recall events with open rate + average ageDays', () => {
|
||||
const events: TelemetryEvent[] = [
|
||||
e('2026-05-02T00:00:00Z', 'recall_shown', { noteId: 'n1', ageDays: 10 }),
|
||||
e('2026-05-02T00:00:01Z', 'recall_shown', { noteId: 'n2', ageDays: 20 }),
|
||||
e('2026-05-02T00:00:02Z', 'recall_shown', { noteId: 'n3', ageDays: 30 }),
|
||||
e('2026-05-02T00:00:03Z', 'recall_shown', { noteId: 'n4', ageDays: 40 }),
|
||||
e('2026-05-02T00:00:04Z', 'recall_opened', { noteId: 'n1' }),
|
||||
e('2026-05-02T00:00:05Z', 'recall_opened', { noteId: 'n2' }),
|
||||
e('2026-05-02T00:00:06Z', 'recall_dismissed', { noteId: 'n3' }),
|
||||
e('2026-05-02T00:00:07Z', 'recall_snoozed', { noteId: 'n4' })
|
||||
];
|
||||
const r = aggregateStats(events, new Date('2026-05-03T00:00:00Z'));
|
||||
expect(r.md).toContain('회상 추천: shown 4 / opened 2 / dismissed 1 / snoozed 1');
|
||||
expect(r.md).toContain('열림율 50.0%');
|
||||
expect(r.md).toContain('회상 평균 ageDays: 25'); // (10+20+30+40)/4
|
||||
});
|
||||
|
||||
it('회상 summary shows 데이터 없음 when no recall events', () => {
|
||||
const r = aggregateStats([], new Date('2026-05-03T00:00:00Z'));
|
||||
expect(r.md).toContain('회상 추천');
|
||||
expect(r.md).toContain('데이터 없음');
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user