feat(复习):feed 智能模式——加权随机采样

- /review/feed?mode=smart:时间衰减 × 回忆掌握度加权采样
- 越久未见、回忆覆盖率越低的内容出现概率越高
- 保持随机性,符合复习模块偶遇式设计理念
- 概率性单元测试验证低掌握内容显著优先
This commit is contained in:
2026-07-03 23:44:40 +08:00
parent 7461b6b5ad
commit 3c4db2fead
2 changed files with 161 additions and 0 deletions
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
import com.guo.learningprogresstracker.dto.ReviewFeedItem;
import com.guo.learningprogresstracker.dto.request.UpsertReviewMindMapRequest;
import com.guo.learningprogresstracker.entity.ReviewMindMapEntity;
import com.guo.learningprogresstracker.entity.ReviewRecallRecordEntity;
import com.guo.learningprogresstracker.entity.StudyReportFragmentsEntity;
import com.guo.learningprogresstracker.entity.StudyReportsEntity;
import com.guo.learningprogresstracker.entity.StudySessionsEntity;
@@ -12,6 +13,7 @@ import com.guo.learningprogresstracker.entity.TaskEntity;
import com.guo.learningprogresstracker.enums.ReviewMindMapFileFormatEnum;
import com.guo.learningprogresstracker.enums.ReviewMindMapParseStatusEnum;
import com.guo.learningprogresstracker.mapper.ReviewMindMapMapper;
import com.guo.learningprogresstracker.mapper.ReviewRecallRecordMapper;
import com.guo.learningprogresstracker.mapper.StudyReportFragmentsMapper;
import com.guo.learningprogresstracker.mapper.StudyReportsMapper;
import com.guo.learningprogresstracker.mapper.StudySessionsMapper;
@@ -67,6 +69,66 @@ class ReviewServiceImplTest {
@Mock
private ReviewMindMapMapper reviewMindMapMapper;
@Mock
private ReviewRecallRecordMapper reviewRecallRecordMapper;
@Test
void getReviewFeed_smartMode_prefersLowRecallRatioContent() {
LocalDateTime now = LocalDateTime.now();
StudyReportsEntity oldLowMastery = new StudyReportsEntity();
oldLowMastery.setId(1);
oldLowMastery.setSessionNum("S_LOW");
oldLowMastery.setContent("很久没复习且掌握度低");
oldLowMastery.setCreatedTime(now.minusDays(60));
StudyReportsEntity freshHighMastery = new StudyReportsEntity();
freshHighMastery.setId(2);
freshHighMastery.setSessionNum("S_HIGH");
freshHighMastery.setContent("刚学完且掌握度高");
freshHighMastery.setCreatedTime(now);
StudySessionsEntity sessionLow = new StudySessionsEntity();
sessionLow.setSessionNum("S_LOW");
sessionLow.setTaskNum("T_LOW");
StudySessionsEntity sessionHigh = new StudySessionsEntity();
sessionHigh.setSessionNum("S_HIGH");
sessionHigh.setTaskNum("T_HIGH");
TaskEntity taskLow = new TaskEntity();
taskLow.setTaskNum("T_LOW");
taskLow.setTaskName("低掌握任务");
TaskEntity taskHigh = new TaskEntity();
taskHigh.setTaskNum("T_HIGH");
taskHigh.setTaskName("高掌握任务");
ReviewRecallRecordEntity highRatioRecord = new ReviewRecallRecordEntity();
highRatioRecord.setTaskNum("T_HIGH");
highRatioRecord.setRecallRatio(1.0);
highRatioRecord.setCreatedTime(now);
when(studyReportsMapper.selectList(any())).thenReturn(List.of(oldLowMastery, freshHighMastery));
when(studyReportFragmentsMapper.selectList(any())).thenReturn(List.of());
when(studySessionsMapper.selectList(any())).thenReturn(List.of(sessionLow, sessionHigh));
when(tasksMapper.selectList(any())).thenReturn(List.of(taskLow, taskHigh));
when(reviewRecallRecordMapper.selectList(any())).thenReturn(List.of(highRatioRecord));
List<ReviewFeedItem> items = reviewService.getReviewFeed(1, "smart");
// 采样是概率性的,多次运行统计低掌握内容被选中的频率应显著更高
int lowMasteryWins = 0;
for (int i = 0; i < 200; i++) {
List<ReviewFeedItem> sample = reviewService.getReviewFeed(1, "smart");
if (!sample.isEmpty() && "T_LOW".equals(sample.get(0).getTaskNum())) {
lowMasteryWins++;
}
}
assertEquals(1, items.size());
// 低掌握内容权重约为高掌握内容的 5 倍以上,200 次中至少应赢 120 次
org.junit.jupiter.api.Assertions.assertTrue(lowMasteryWins > 120,
"低掌握内容被选中次数应显著更多,实际: " + lowMasteryWins + "/200");
}
@Test
void getReviewFeed_shouldApplyFinalLimitAfterMerge() {
StudyReportsEntity report = new StudyReportsEntity();