feat(sentiment): dynamic settings, IFinlyticLogger, live log streaming, and EF migration
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using FinlyticCore.Dtos.News;
|
||||
using FinlyticCore.Dtos.Sentiment;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using FinlyticCore.Services;
|
||||
using FinlyticSentiment.Util;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace FinlyticSentiment.Services;
|
||||
|
||||
@@ -15,8 +19,6 @@ public interface IFinBertAnalyzerService
|
||||
/// <summary>
|
||||
/// Analyzes a news article using language-targeted FinBERT webhooks and returns structured metrics.
|
||||
/// </summary>
|
||||
/// <param name="article">The news article DTO to evaluate.</param>
|
||||
/// <returns>A task returning the FinBERT sentiment analysis result.</returns>
|
||||
Task<FinBertResultDto?> AnalyzeArticleAsync(NewsArticleDto article);
|
||||
}
|
||||
|
||||
@@ -27,22 +29,16 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly ILogger<FinBertAnalyzerService> _logger;
|
||||
private readonly IFinlyticLogger<FinBertAnalyzerService> _finlyticLogger;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="FinBertAnalyzerService"/> class.
|
||||
/// </summary>
|
||||
/// <param name="httpClient">The HTTP client instance.</param>
|
||||
/// <param name="scopeFactory">The service scope factory for DB access.</param>
|
||||
/// <param name="logger">The logging channel.</param>
|
||||
public FinBertAnalyzerService(
|
||||
HttpClient httpClient,
|
||||
IServiceScopeFactory scopeFactory,
|
||||
ILogger<FinBertAnalyzerService> logger)
|
||||
IFinlyticLogger<FinBertAnalyzerService> finlyticLogger)
|
||||
{
|
||||
_httpClient = httpClient;
|
||||
_scopeFactory = scopeFactory;
|
||||
_logger = logger;
|
||||
_finlyticLogger = finlyticLogger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,7 +60,7 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
minConfidence = settings.MinConfidenceScore;
|
||||
}
|
||||
|
||||
_logger.LogInformation("[{Channel}] Analyzing article (ID: {Id}, Lang: {Lang}) via webhook: {Url} (MinConf: {Conf})", "SentimentChannel", article.Id, article.Language ?? "de", targetUrl, minConfidence);
|
||||
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[FinBertAnalyzerService] Analyzing article (ID: {Id}, Lang: {Lang}) via webhook: {Url} (MinConf: {Conf})", article.Id, article.Language ?? "de", targetUrl, minConfidence);
|
||||
|
||||
var requestBody = new
|
||||
{
|
||||
@@ -88,13 +84,11 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
using var doc = JsonDocument.Parse(content);
|
||||
var root = doc.RootElement;
|
||||
|
||||
// Handle array response if n8n returns an array of items (e.g. [{ "json": { ... } }])
|
||||
if (root.ValueKind == JsonValueKind.Array && root.GetArrayLength() > 0)
|
||||
{
|
||||
root = root[0];
|
||||
}
|
||||
|
||||
// Unwrap n8n wrapper objects: "json", "output", "data", "result", "body"
|
||||
if (root.ValueKind == JsonValueKind.Object)
|
||||
{
|
||||
if (root.TryGetProperty("json", out var jsonChild) && jsonChild.ValueKind == JsonValueKind.Object)
|
||||
@@ -134,7 +128,6 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
neu = GetDoubleProp(probsElem, "neutral") ?? neu;
|
||||
}
|
||||
|
||||
// Normalize German vs English labels
|
||||
string label = rawLabel.Trim().ToUpperInvariant() switch
|
||||
{
|
||||
"POSITIV" or "POSITIVE" => "POSITIVE",
|
||||
@@ -142,7 +135,6 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
_ => "NEUTRAL"
|
||||
};
|
||||
|
||||
// If compoundScore is 0 but probabilities or label indicate sentiment, compute compoundScore
|
||||
if (Math.Abs(compoundScore) < 0.001)
|
||||
{
|
||||
if (pos > 0 || neg > 0)
|
||||
@@ -173,15 +165,14 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
|
||||
SummarySnippet = snippet
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogWarning("[{Channel}] n8n Webhook returned non-success status: {StatusCode}. Falling back to rule analyzer.", "SentimentChannel", response.StatusCode);
|
||||
await _finlyticLogger.LogWarningAsync(SettingKeys.SentimentChannel, "[FinBertAnalyzerService] n8n Webhook returned non-success status: {StatusCode}.", response.StatusCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "[{Channel}] Failed to call n8n sentiment webhook. Executing fallback sentiment analyzer.", "SentimentChannel");
|
||||
await _finlyticLogger.LogErrorAsync(SettingKeys.SentimentChannel, ex, "[FinBertAnalyzerService] Failed to call n8n sentiment webhook.");
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user