191 lines
6.8 KiB
C#
191 lines
6.8 KiB
C#
using DramaLing.Api.Data;
|
|
using DramaLing.Api.Models.Configuration;
|
|
using DramaLing.Api.Models.Entities;
|
|
using DramaLing.Api.Services.Monitoring;
|
|
using Microsoft.EntityFrameworkCore;
|
|
using Microsoft.Extensions.Caching.Memory;
|
|
using Microsoft.Extensions.Options;
|
|
using System.Diagnostics;
|
|
|
|
namespace DramaLing.Api.Services;
|
|
|
|
/// <summary>
|
|
/// 選項詞彙庫服務實作
|
|
/// 提供基於 CEFR 等級、詞性和字數的智能選項生成
|
|
/// </summary>
|
|
public class OptionsVocabularyService : IOptionsVocabularyService
|
|
{
|
|
private readonly DramaLingDbContext _context;
|
|
private readonly IMemoryCache _cache;
|
|
private readonly ILogger<OptionsVocabularyService> _logger;
|
|
private readonly OptionsVocabularyOptions _options;
|
|
private readonly OptionsVocabularyMetrics _metrics;
|
|
|
|
public OptionsVocabularyService(
|
|
DramaLingDbContext context,
|
|
IMemoryCache cache,
|
|
ILogger<OptionsVocabularyService> logger,
|
|
IOptions<OptionsVocabularyOptions> options,
|
|
OptionsVocabularyMetrics metrics)
|
|
{
|
|
_context = context;
|
|
_cache = cache;
|
|
_logger = logger;
|
|
_options = options.Value;
|
|
_metrics = metrics;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成智能干擾選項
|
|
/// </summary>
|
|
public async Task<List<string>> GenerateDistractorsAsync(
|
|
string targetWord,
|
|
string cefrLevel,
|
|
string partOfSpeech,
|
|
int count = 3)
|
|
{
|
|
var distractorsWithDetails = await GenerateDistractorsWithDetailsAsync(
|
|
targetWord, cefrLevel, partOfSpeech, count);
|
|
|
|
return distractorsWithDetails.Select(v => v.Word).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 生成智能干擾選項(含詳細資訊)
|
|
/// </summary>
|
|
public async Task<List<OptionsVocabulary>> GenerateDistractorsWithDetailsAsync(
|
|
string targetWord,
|
|
string cefrLevel,
|
|
string partOfSpeech,
|
|
int count = 3)
|
|
{
|
|
var stopwatch = Stopwatch.StartNew();
|
|
|
|
try
|
|
{
|
|
// 記錄請求指標
|
|
_metrics.RecordGenerationRequest(cefrLevel, partOfSpeech, count);
|
|
|
|
_logger.LogInformation("Generating {Count} distractors for word '{Word}' (CEFR: {CEFR}, PartOfSpeech: {PartOfSpeech}) - Using fixed options",
|
|
count, targetWord, cefrLevel, partOfSpeech);
|
|
|
|
// 暫時使用固定選項,跳過複雜的詞彙篩選機制
|
|
var fixedDistractors = new List<OptionsVocabulary>
|
|
{
|
|
new OptionsVocabulary
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Word = "apple",
|
|
CEFRLevel = cefrLevel,
|
|
PartOfSpeech = partOfSpeech,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
},
|
|
new OptionsVocabulary
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Word = "orange",
|
|
CEFRLevel = cefrLevel,
|
|
PartOfSpeech = partOfSpeech,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
},
|
|
new OptionsVocabulary
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
Word = "banana",
|
|
CEFRLevel = cefrLevel,
|
|
PartOfSpeech = partOfSpeech,
|
|
IsActive = true,
|
|
CreatedAt = DateTime.UtcNow,
|
|
UpdatedAt = DateTime.UtcNow
|
|
}
|
|
};
|
|
|
|
// 計算字數長度
|
|
foreach (var distractor in fixedDistractors)
|
|
{
|
|
distractor.CalculateWordLength();
|
|
}
|
|
|
|
// 排除目標詞彙本身(如果匹配)
|
|
var selectedDistractors = fixedDistractors
|
|
.Where(v => !string.Equals(v.Word, targetWord, StringComparison.OrdinalIgnoreCase))
|
|
.Take(count)
|
|
.ToList();
|
|
|
|
_logger.LogInformation("Successfully generated {Count} fixed distractors for '{Word}': {Distractors}",
|
|
selectedDistractors.Count, targetWord,
|
|
string.Join(", ", selectedDistractors.Select(d => d.Word)));
|
|
|
|
// 記錄生成完成指標
|
|
stopwatch.Stop();
|
|
_metrics.RecordGenerationDuration(stopwatch.Elapsed, selectedDistractors.Count);
|
|
|
|
return selectedDistractors;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error generating distractors for word '{Word}'", targetWord);
|
|
_metrics.RecordError("generation_failed", "GenerateDistractorsWithDetailsAsync");
|
|
return new List<OptionsVocabulary>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 檢查詞彙庫是否有足夠的詞彙支援選項生成
|
|
/// </summary>
|
|
public async Task<bool> HasSufficientVocabularyAsync(string cefrLevel, string partOfSpeech)
|
|
{
|
|
try
|
|
{
|
|
var allowedLevels = GetAllowedCEFRLevels(cefrLevel);
|
|
|
|
var count = await _context.OptionsVocabularies
|
|
.Where(v => v.IsActive &&
|
|
allowedLevels.Contains(v.CEFRLevel) &&
|
|
v.PartOfSpeech == partOfSpeech)
|
|
.CountAsync();
|
|
|
|
var hasSufficient = count >= _options.MinimumVocabularyThreshold;
|
|
|
|
_logger.LogDebug("Vocabulary count for CEFR: {CEFR}, PartOfSpeech: {PartOfSpeech}: {Count} (sufficient: {HasSufficient})",
|
|
cefrLevel, partOfSpeech, count, hasSufficient);
|
|
|
|
return hasSufficient;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error checking vocabulary sufficiency for CEFR: {CEFR}, PartOfSpeech: {PartOfSpeech}",
|
|
cefrLevel, partOfSpeech);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 獲取允許的 CEFR 等級(包含相鄰等級)
|
|
/// </summary>
|
|
private static List<string> GetAllowedCEFRLevels(string targetLevel)
|
|
{
|
|
var levels = new[] { "A1", "A2", "B1", "B2", "C1", "C2" };
|
|
var targetIndex = Array.IndexOf(levels, targetLevel);
|
|
|
|
if (targetIndex == -1)
|
|
{
|
|
// 如果不是標準 CEFR 等級,只返回原等級
|
|
return new List<string> { targetLevel };
|
|
}
|
|
|
|
var allowed = new List<string> { targetLevel };
|
|
|
|
// 加入相鄰等級(允許難度稍有差異)
|
|
if (targetIndex > 0)
|
|
allowed.Add(levels[targetIndex - 1]);
|
|
if (targetIndex < levels.Length - 1)
|
|
allowed.Add(levels[targetIndex + 1]);
|
|
|
|
return allowed;
|
|
}
|
|
} |