feat(sentiment): add persistent sentiment entities, summaries, SentimentDbService and MQTT RPC refactoring
This commit is contained in:
@@ -13,13 +13,12 @@ namespace FinlyticSentiment.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Background hosted worker executing periodic sentiment analysis sweeps on pending news articles
|
||||
/// and processing real-time article broadcasts.
|
||||
/// and processing real-time article broadcasts from FinlyticNews.
|
||||
/// </summary>
|
||||
public class SentimentBackgroundService : BackgroundService
|
||||
{
|
||||
private readonly SentimentMqttClient _mqttClient;
|
||||
private readonly IFinBertAnalyzerService _analyzer;
|
||||
private readonly ISentimentStorageService _storage;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly IFinlyticLogger<SentimentBackgroundService> _finlyticLogger;
|
||||
|
||||
@@ -28,13 +27,11 @@ public class SentimentBackgroundService : BackgroundService
|
||||
public SentimentBackgroundService(
|
||||
SentimentMqttClient mqttClient,
|
||||
IFinBertAnalyzerService analyzer,
|
||||
ISentimentStorageService storage,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
IFinlyticLogger<SentimentBackgroundService> finlyticLogger)
|
||||
{
|
||||
_mqttClient = mqttClient;
|
||||
_analyzer = analyzer;
|
||||
_storage = storage;
|
||||
_scopeFactory = scopeFactory;
|
||||
_finlyticLogger = finlyticLogger;
|
||||
}
|
||||
@@ -56,11 +53,13 @@ public class SentimentBackgroundService : BackgroundService
|
||||
int maxBatchSize = 10;
|
||||
int sweepIntervalMinutes = 5;
|
||||
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
try
|
||||
{
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var settings = scope.ServiceProvider.GetRequiredService<ISettingsService>();
|
||||
maxBatchSize = await settings.GetSettingAsync(SettingKeys.MaxBatchSize, stoppingToken);
|
||||
}
|
||||
catch { }
|
||||
|
||||
var interval = TimeSpan.FromMinutes(Math.Max(1, sweepIntervalMinutes));
|
||||
|
||||
@@ -79,10 +78,9 @@ public class SentimentBackgroundService : BackgroundService
|
||||
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] Waiting {Minutes} minute(s) until next sentiment sweep...", interval.TotalMinutes);
|
||||
|
||||
using var timer = new PeriodicTimer(interval);
|
||||
try
|
||||
{
|
||||
await timer.WaitForNextTickAsync(stoppingToken);
|
||||
await Task.Delay(interval, stoppingToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
@@ -90,7 +88,7 @@ public class SentimentBackgroundService : BackgroundService
|
||||
}
|
||||
}
|
||||
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] FinlyticSentiment Background Service is shutting down gracefully.");
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] FinlyticSentiment Background Service shutting down.");
|
||||
}
|
||||
|
||||
private async Task PerformSentimentSweepAsync(int maxBatchSize, CancellationToken cancellationToken)
|
||||
@@ -137,45 +135,40 @@ public class SentimentBackgroundService : BackgroundService
|
||||
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
|
||||
await _storage.SaveArticleSentimentAsync(article, finbert);
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
var dbService = scope.ServiceProvider.GetRequiredService<ISentimentDbService>();
|
||||
|
||||
if (article.MatchedAssets != null && article.MatchedAssets.Count > 0)
|
||||
{
|
||||
foreach (var asset in article.MatchedAssets)
|
||||
{
|
||||
if (cancellationToken.IsCancellationRequested) break;
|
||||
if (string.IsNullOrWhiteSpace(asset.Isin)) continue;
|
||||
|
||||
await _storage.UpdateIsinSummaryAsync(
|
||||
asset.Isin,
|
||||
asset.Name,
|
||||
"General",
|
||||
article,
|
||||
finbert);
|
||||
await dbService.SaveArticleSentimentAsync(
|
||||
articleId: article.Id,
|
||||
isin: asset.Isin,
|
||||
companyName: asset.Name,
|
||||
sector: null,
|
||||
publishedAt: article.PublishedAt,
|
||||
finbert: finbert,
|
||||
ct: cancellationToken
|
||||
);
|
||||
|
||||
await _storage.UpdateSectorSummaryAsync(
|
||||
"General",
|
||||
asset.Isin,
|
||||
article.Id.ToString(),
|
||||
finbert);
|
||||
// Broadcast real-time updated summary for this asset
|
||||
var summaryDto = await dbService.GetIsinSummaryDtoAsync(asset.Isin, cancellationToken);
|
||||
if (summaryDto != null)
|
||||
{
|
||||
await _mqttClient.BroadcastSentimentResultAsync(asset.Isin, summaryDto);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cancellationToken.IsCancellationRequested) return;
|
||||
|
||||
bool updated = await _mqttClient.UpdateArticleStatusAsync(article.Id, "Analyzed");
|
||||
if (updated)
|
||||
{
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] Article sentiment processed and status set to 'Analyzed' in FinlyticNews: {Title} (ID: {Id}) -> {Label}", article.Title, article.Id, finbert.Label);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _finlyticLogger.LogWarningAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] Failed to confirm status update to 'Analyzed' in FinlyticNews for article: {Id}", article.Id);
|
||||
}
|
||||
await _mqttClient.UpdateArticleStatusAsync(article.Id, "Analyzed");
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[SentimentBackgroundService] Completed sentiment persistence and status transition for article {Id}.", article.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await _finlyticLogger.LogErrorAsync(SettingKeys.SentimentChannel, ex, "[SentimentBackgroundService] Error processing sentiment for article: {Id} ({Title})", article.Id, article.Title);
|
||||
await _finlyticLogger.LogErrorAsync(SettingKeys.SentimentChannel, ex, "[SentimentBackgroundService] Failed to process sentiment for article: {Id}", article.Id);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user