dramaling-vocab-learning/backend/DramaLing.Api/Services/Domain/Learning/ISpacedRepetitionService.cs

254 lines
8.1 KiB
C#

using DramaLing.Api.Services;
namespace DramaLing.Api.Services.Domain.Learning;
/// <summary>
/// 間隔重複學習服務介面
/// </summary>
public interface ISpacedRepetitionService
{
/// <summary>
/// 計算下次複習時間
/// </summary>
Task<ReviewSchedule> CalculateNextReviewAsync(ReviewInput input);
/// <summary>
/// 更新學習進度
/// </summary>
Task<StudyProgress> UpdateStudyProgressAsync(Guid flashcardId, int qualityRating, Guid userId);
/// <summary>
/// 取得今日應複習的詞卡
/// </summary>
Task<IEnumerable<ReviewCard>> GetDueCardsAsync(Guid userId, int limit = 20);
/// <summary>
/// 取得學習統計
/// </summary>
Task<LearningAnalytics> GetLearningAnalyticsAsync(Guid userId);
/// <summary>
/// 優化學習序列
/// </summary>
Task<OptimizedStudyPlan> GenerateStudyPlanAsync(Guid userId, int targetMinutes);
}
/// <summary>
/// 複習輸入參數
/// </summary>
public class ReviewInput
{
public Guid FlashcardId { get; set; }
public Guid UserId { get; set; }
public int QualityRating { get; set; } // 1-5 (SM2 標準)
public int CurrentRepetitions { get; set; }
public float CurrentEasinessFactor { get; set; }
public int CurrentIntervalDays { get; set; }
public DateTime LastReviewDate { get; set; }
}
/// <summary>
/// 複習排程結果
/// </summary>
public class ReviewSchedule
{
public DateTime NextReviewDate { get; set; }
public int NewIntervalDays { get; set; }
public float NewEasinessFactor { get; set; }
public int NewRepetitions { get; set; }
public int NewMasteryLevel { get; set; }
public string RecommendedAction { get; set; } = string.Empty;
}
/// <summary>
/// 學習進度
/// </summary>
public class StudyProgress
{
public Guid FlashcardId { get; set; }
public bool IsImproved { get; set; }
public int PreviousMasteryLevel { get; set; }
public int NewMasteryLevel { get; set; }
public DateTime NextReviewDate { get; set; }
public string ProgressMessage { get; set; } = string.Empty;
}
/// <summary>
/// 複習卡片
/// </summary>
public class ReviewCard
{
public Guid Id { get; set; }
public string Word { get; set; } = string.Empty;
public string Translation { get; set; } = string.Empty;
public string DifficultyLevel { get; set; } = string.Empty;
public int MasteryLevel { get; set; }
public DateTime NextReviewDate { get; set; }
public int DaysSinceLastReview { get; set; }
public int ReviewPriority { get; set; } // 1-5 (5 最高)
}
/// <summary>
/// 學習分析
/// </summary>
public class LearningAnalytics
{
public int TotalCards { get; set; }
public int DueCards { get; set; }
public int OverdueCards { get; set; }
public int MasteredCards { get; set; }
public double RetentionRate { get; set; }
public TimeSpan AverageStudyInterval { get; set; }
public Dictionary<string, int> DifficultyDistribution { get; set; } = new();
public List<DailyStudyStats> RecentPerformance { get; set; } = new();
}
/// <summary>
/// 每日學習統計
/// </summary>
public class DailyStudyStats
{
public DateOnly Date { get; set; }
public int CardsReviewed { get; set; }
public int CorrectAnswers { get; set; }
public double AccuracyRate => CardsReviewed > 0 ? (double)CorrectAnswers / CardsReviewed : 0;
public TimeSpan StudyDuration { get; set; }
}
/// <summary>
/// 優化學習計劃
/// </summary>
public class OptimizedStudyPlan
{
public IEnumerable<ReviewCard> RecommendedCards { get; set; } = new List<ReviewCard>();
public int EstimatedMinutes { get; set; }
public string StudyFocus { get; set; } = string.Empty; // "複習", "新學習", "加強練習"
public Dictionary<string, int> LevelBreakdown { get; set; } = new();
public string RecommendationReason { get; set; } = string.Empty;
}
/// <summary>
/// 間隔重複學習服務實作
/// </summary>
public class SpacedRepetitionService : ISpacedRepetitionService
{
private readonly ILogger<SpacedRepetitionService> _logger;
public SpacedRepetitionService(ILogger<SpacedRepetitionService> logger)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
public Task<ReviewSchedule> CalculateNextReviewAsync(ReviewInput input)
{
try
{
// 使用現有的 SM2Algorithm
var sm2Input = new SM2Input(
input.QualityRating,
input.CurrentEasinessFactor,
input.CurrentRepetitions,
input.CurrentIntervalDays
);
var sm2Result = SM2Algorithm.Calculate(sm2Input);
var schedule = new ReviewSchedule
{
NextReviewDate = sm2Result.NextReviewDate,
NewIntervalDays = sm2Result.IntervalDays,
NewEasinessFactor = sm2Result.EasinessFactor,
NewRepetitions = sm2Result.Repetitions,
NewMasteryLevel = CalculateMasteryLevel(sm2Result.EasinessFactor, sm2Result.Repetitions),
RecommendedAction = GetRecommendedAction(input.QualityRating)
};
return Task.FromResult(schedule);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error calculating next review for flashcard {FlashcardId}", input.FlashcardId);
throw;
}
}
public Task<StudyProgress> UpdateStudyProgressAsync(Guid flashcardId, int qualityRating, Guid userId)
{
// 這裡應該整合 Repository 來獲取和更新詞卡數據
// 暫時返回模擬結果
var progress = new StudyProgress
{
FlashcardId = flashcardId,
IsImproved = qualityRating >= 3,
ProgressMessage = GetProgressMessage(qualityRating)
};
return Task.FromResult(progress);
}
public Task<IEnumerable<ReviewCard>> GetDueCardsAsync(Guid userId, int limit = 20)
{
// 需要整合 Repository 來實作
var cards = new List<ReviewCard>();
return Task.FromResult<IEnumerable<ReviewCard>>(cards);
}
public Task<LearningAnalytics> GetLearningAnalyticsAsync(Guid userId)
{
// 需要整合 Repository 來實作
var analytics = new LearningAnalytics();
return Task.FromResult(analytics);
}
public Task<OptimizedStudyPlan> GenerateStudyPlanAsync(Guid userId, int targetMinutes)
{
// 需要整合 Repository 和 AI 服務來實作
var plan = new OptimizedStudyPlan
{
EstimatedMinutes = targetMinutes,
StudyFocus = "複習",
RecommendationReason = "基於間隔重複算法的個人化推薦"
};
return Task.FromResult(plan);
}
#region
private int CalculateMasteryLevel(float easinessFactor, int repetitions)
{
// 根據難度係數和重複次數計算掌握程度
if (repetitions >= 5 && easinessFactor >= 2.3f) return 5; // 完全掌握
if (repetitions >= 3 && easinessFactor >= 2.0f) return 4; // 熟練
if (repetitions >= 2 && easinessFactor >= 1.8f) return 3; // 理解
if (repetitions >= 1) return 2; // 認識
return 1; // 新學習
}
private string GetRecommendedAction(int qualityRating)
{
return qualityRating switch
{
1 => "建議重新學習此詞彙",
2 => "需要額外練習",
3 => "繼續複習",
4 => "掌握良好",
5 => "完全掌握",
_ => "繼續學習"
};
}
private string GetProgressMessage(int qualityRating)
{
return qualityRating switch
{
1 or 2 => "需要加強練習,別氣餒!",
3 => "不錯的進步!",
4 => "很好!掌握得不錯",
5 => "太棒了!完全掌握",
_ => "繼續努力學習!"
};
}
#endregion
}