using Backend.Interface; namespace Backend.ServiceHander { public sealed class NewsSentimentRefreshService : BackgroundService { private static readonly TimeSpan PollInterval = TimeSpan.FromMinutes(15); private static readonly TimeZoneInfo Pacific = TimeZoneInfo.FindSystemTimeZoneById("America/Los_Angeles"); private readonly IServiceProvider _services; private readonly ILogger _logger; public NewsSentimentRefreshService( IServiceProvider services, ILogger logger) { _services = services; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { if (IsWithinActiveWindow()) { await RefreshAsync(stoppingToken); } else { _logger.LogDebug("Outside active window (weekdays 07:00-20:00 PST), skipping refresh."); } await Task.Delay(PollInterval, stoppingToken); } } private static bool IsWithinActiveWindow() { var pacificNow = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, Pacific); if (pacificNow.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday) return false; var hour = pacificNow.Hour; return hour >= 7 && hour < 20; } private async Task RefreshAsync(CancellationToken cancellationToken) { try { var interests = _services.GetRequiredService(); var sentiment = _services.GetRequiredService(); var keywords = await interests.FetchAllDistinctValuesAsync(cancellationToken); _logger.LogInformation("Refreshing news for {Count} keywords.", keywords.Count); foreach (var keyword in keywords) { try { await sentiment.AnalyzeAsync(keyword, cancellationToken); } catch (Exception ex) { _logger.LogWarning(ex, "Failed to refresh news for keyword '{Keyword}'.", keyword); } } } catch (Exception ex) { _logger.LogError(ex, "News sentiment refresh cycle failed."); } } } }