namespace DramaLing.Api.Services; public record SM2Input( int Quality, // 1-5 評分 float EasinessFactor, // 難度係數 int Repetitions, // 重複次數 int IntervalDays // 當前間隔天數 ); public record SM2Result( float EasinessFactor, // 新的難度係數 int Repetitions, // 新的重複次數 int IntervalDays, // 新的間隔天數 DateTime NextReviewDate // 下次複習日期 ); public static class SM2Algorithm { // SM-2 算法常數 private const float MIN_EASINESS_FACTOR = 1.3f; private const float MAX_EASINESS_FACTOR = 2.5f; private const float INITIAL_EASINESS_FACTOR = 2.5f; private const int MIN_INTERVAL = 1; private const int MAX_INTERVAL = 365; /// /// 計算下次複習的間隔和參數 /// public static SM2Result Calculate(SM2Input input) { var (quality, easinessFactor, repetitions, intervalDays) = input; // 驗證輸入參數 if (quality < 1 || quality > 5) throw new ArgumentException("Quality must be between 1 and 5", nameof(input)); // 更新難度係數 var newEasinessFactor = UpdateEasinessFactor(easinessFactor, quality); int newRepetitions; int newIntervalDays; // 如果回答錯誤 (quality < 3),重置進度 if (quality < 3) { newRepetitions = 0; newIntervalDays = 1; } else { // 如果回答正確,增加重複次數並計算新間隔 newRepetitions = repetitions + 1; newIntervalDays = newRepetitions switch { 1 => 1, 2 => 6, _ => (int)Math.Round(intervalDays * newEasinessFactor) }; } // 限制間隔範圍 newIntervalDays = Math.Clamp(newIntervalDays, MIN_INTERVAL, MAX_INTERVAL); // 計算下次複習日期 var nextReviewDate = DateTime.Today.AddDays(newIntervalDays); return new SM2Result( newEasinessFactor, newRepetitions, newIntervalDays, nextReviewDate ); } /// /// 更新難度係數 /// private static float UpdateEasinessFactor(float currentEF, int quality) { // SM-2 公式:EF' = EF + (0.1 - (5-q) * (0.08 + (5-q) * 0.02)) var newEF = currentEF + (0.1f - (5 - quality) * (0.08f + (5 - quality) * 0.02f)); // 限制在有效範圍內 return Math.Clamp(newEF, MIN_EASINESS_FACTOR, MAX_EASINESS_FACTOR); } /// /// 獲取初始參數(新詞卡) /// public static SM2Input GetInitialParameters() { return new SM2Input( Quality: 3, EasinessFactor: INITIAL_EASINESS_FACTOR, Repetitions: 0, IntervalDays: 1 ); } /// /// 根據評分獲取描述 /// public static string GetQualityDescription(int quality) { return quality switch { 1 => "完全不記得", 2 => "有印象但錯誤", 3 => "困難但正確", 4 => "猶豫後正確", 5 => "輕鬆正確", _ => "無效評分" }; } /// /// 計算掌握度百分比 /// public static int CalculateMastery(int repetitions, float easinessFactor) { // 基於重複次數和難度係數計算掌握度 (0-100) var baseScore = Math.Min(repetitions * 20, 80); // 重複次數最多貢獻80分 var efficiencyBonus = Math.Min((easinessFactor - 1.3f) * 16.67f, 20f); // 難度係數最多貢獻20分 return Math.Min((int)Math.Round(baseScore + efficiencyBonus), 100); } } /// /// 複習優先級計算器 /// public static class ReviewPriorityCalculator { /// /// 計算複習優先級 (數字越大優先級越高) /// public static double CalculatePriority(DateTime nextReviewDate, float easinessFactor, int repetitions) { var now = DateTime.Today; var daysDiff = (now - nextReviewDate).Days; // 過期天數的權重 (越過期優先級越高) var overdueWeight = Math.Max(0, daysDiff) * 10; // 難度權重 (越難的優先級越高) var difficultyWeight = (3.8f - easinessFactor) * 5; // 新詞權重 (新詞優先級較高) var newWordWeight = repetitions == 0 ? 20 : 0; return overdueWeight + difficultyWeight + newWordWeight; } /// /// 獲取應該複習的詞卡 /// public static bool ShouldReview(DateTime nextReviewDate) { return DateTime.Today >= nextReviewDate; } }