167 lines
5.0 KiB
C#
167 lines
5.0 KiB
C#
namespace DramaLing.Api.Services.Domain.Learning;
|
|
|
|
/// <summary>
|
|
/// CEFR 等級服務介面
|
|
/// </summary>
|
|
public interface ICEFRLevelService
|
|
{
|
|
/// <summary>
|
|
/// 取得 CEFR 等級的數字索引
|
|
/// </summary>
|
|
int GetLevelIndex(string level);
|
|
|
|
/// <summary>
|
|
/// 判定詞彙對特定用戶是否為高價值
|
|
/// </summary>
|
|
bool IsHighValueForUser(string wordLevel, string userLevel);
|
|
|
|
/// <summary>
|
|
/// 取得用戶的目標學習等級範圍
|
|
/// </summary>
|
|
string GetTargetLevelRange(string userLevel);
|
|
|
|
/// <summary>
|
|
/// 取得下一個等級
|
|
/// </summary>
|
|
string GetNextLevel(string currentLevel);
|
|
|
|
/// <summary>
|
|
/// 計算等級進度百分比
|
|
/// </summary>
|
|
double CalculateLevelProgress(string currentLevel, int masteredWords, int totalWords);
|
|
|
|
/// <summary>
|
|
/// 根據掌握詞彙數推薦等級
|
|
/// </summary>
|
|
string RecommendLevel(Dictionary<string, int> masteredWordsByLevel);
|
|
|
|
/// <summary>
|
|
/// 驗證等級是否有效
|
|
/// </summary>
|
|
bool IsValidLevel(string level);
|
|
|
|
/// <summary>
|
|
/// 取得所有等級列表
|
|
/// </summary>
|
|
IEnumerable<string> GetAllLevels();
|
|
}
|
|
|
|
/// <summary>
|
|
/// CEFR 等級服務實作
|
|
/// </summary>
|
|
public class CEFRLevelService : ICEFRLevelService
|
|
{
|
|
private static readonly string[] Levels = { "A1", "A2", "B1", "B2", "C1", "C2" };
|
|
private readonly ILogger<CEFRLevelService> _logger;
|
|
|
|
public CEFRLevelService(ILogger<CEFRLevelService> logger)
|
|
{
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
public int GetLevelIndex(string level)
|
|
{
|
|
if (string.IsNullOrEmpty(level))
|
|
{
|
|
_logger.LogWarning("Invalid level provided: null or empty, defaulting to A2");
|
|
return 1; // 預設 A2
|
|
}
|
|
|
|
var index = Array.IndexOf(Levels, level.ToUpper());
|
|
if (index == -1)
|
|
{
|
|
_logger.LogWarning("Unknown CEFR level: {Level}, defaulting to A2", level);
|
|
return 1;
|
|
}
|
|
|
|
return index;
|
|
}
|
|
|
|
public bool IsHighValueForUser(string wordLevel, string userLevel)
|
|
{
|
|
var userIndex = GetLevelIndex(userLevel);
|
|
var wordIndex = GetLevelIndex(wordLevel);
|
|
|
|
// 無效等級處理
|
|
if (userIndex == -1 || wordIndex == -1)
|
|
{
|
|
_logger.LogWarning("Invalid levels for comparison: word={WordLevel}, user={UserLevel}", wordLevel, userLevel);
|
|
return false;
|
|
}
|
|
|
|
// 高價值 = 比用戶程度高 1-2 級
|
|
var isHighValue = wordIndex >= userIndex + 1 && wordIndex <= userIndex + 2;
|
|
|
|
_logger.LogDebug("High value check: word={WordLevel}({WordIndex}), user={UserLevel}({UserIndex}), result={IsHighValue}",
|
|
wordLevel, wordIndex, userLevel, userIndex, isHighValue);
|
|
|
|
return isHighValue;
|
|
}
|
|
|
|
public string GetTargetLevelRange(string userLevel)
|
|
{
|
|
var userIndex = GetLevelIndex(userLevel);
|
|
if (userIndex == -1) return "B1-B2";
|
|
|
|
var targetMin = Levels[Math.Min(userIndex + 1, Levels.Length - 1)];
|
|
var targetMax = Levels[Math.Min(userIndex + 2, Levels.Length - 1)];
|
|
|
|
return targetMin == targetMax ? targetMin : $"{targetMin}-{targetMax}";
|
|
}
|
|
|
|
public string GetNextLevel(string currentLevel)
|
|
{
|
|
var currentIndex = GetLevelIndex(currentLevel);
|
|
if (currentIndex == -1 || currentIndex >= Levels.Length - 1)
|
|
{
|
|
return Levels[^1]; // 返回最高等級
|
|
}
|
|
|
|
return Levels[currentIndex + 1];
|
|
}
|
|
|
|
public double CalculateLevelProgress(string currentLevel, int masteredWords, int totalWords)
|
|
{
|
|
if (totalWords == 0) return 0;
|
|
|
|
var progress = (double)masteredWords / totalWords;
|
|
_logger.LogDebug("Level progress for {Level}: {MasteredWords}/{TotalWords} = {Progress:P}",
|
|
currentLevel, masteredWords, totalWords, progress);
|
|
|
|
return Math.Min(progress, 1.0);
|
|
}
|
|
|
|
public string RecommendLevel(Dictionary<string, int> masteredWordsByLevel)
|
|
{
|
|
try
|
|
{
|
|
// 簡單的推薦邏輯:找到掌握詞彙最多的等級
|
|
var bestLevel = masteredWordsByLevel
|
|
.Where(kvp => kvp.Value > 0)
|
|
.OrderByDescending(kvp => kvp.Value)
|
|
.FirstOrDefault();
|
|
|
|
if (bestLevel.Key != null && IsValidLevel(bestLevel.Key))
|
|
{
|
|
return GetNextLevel(bestLevel.Key);
|
|
}
|
|
|
|
return "A2"; // 預設等級
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error recommending level");
|
|
return "A2";
|
|
}
|
|
}
|
|
|
|
public bool IsValidLevel(string level)
|
|
{
|
|
return !string.IsNullOrEmpty(level) && Levels.Contains(level.ToUpper());
|
|
}
|
|
|
|
public IEnumerable<string> GetAllLevels()
|
|
{
|
|
return Levels.AsEnumerable();
|
|
}
|
|
} |