feat(settings): add dynamic N8N webhook URL configuration for News, Sentiment and Analyzer services

This commit is contained in:
2026-08-17 16:10:31 +02:00
parent 75a2510b39
commit 3972507cb0
6 changed files with 68 additions and 24 deletions
@@ -15,19 +15,20 @@ namespace FinlyticAnalyzer.Services;
public class N8nEvaluationService : IN8nEvaluationService public class N8nEvaluationService : IN8nEvaluationService
{ {
private readonly HttpClient _httpClient; private readonly HttpClient _httpClient;
private readonly ISettingsService _settingsService;
private readonly IConfiguration _configuration;
private readonly IFinlyticLogger<N8nEvaluationService> _finlyticLogger; private readonly IFinlyticLogger<N8nEvaluationService> _finlyticLogger;
private readonly string _webhookUrl;
public N8nEvaluationService(HttpClient httpClient, IConfiguration configuration, IFinlyticLogger<N8nEvaluationService> finlyticLogger) public N8nEvaluationService(
HttpClient httpClient,
ISettingsService settingsService,
IConfiguration configuration,
IFinlyticLogger<N8nEvaluationService> finlyticLogger)
{ {
_httpClient = httpClient; _httpClient = httpClient;
_settingsService = settingsService;
_configuration = configuration;
_finlyticLogger = finlyticLogger; _finlyticLogger = finlyticLogger;
_webhookUrl = configuration["N8N:WebhookUrl"] ?? configuration["N8N__WebhookUrl"] ?? string.Empty;
if (string.IsNullOrWhiteSpace(_webhookUrl))
{
_ = _finlyticLogger.LogWarningAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] N8N:WebhookUrl configuration is missing or empty.");
}
_httpClient.Timeout = TimeSpan.FromSeconds(45); _httpClient.Timeout = TimeSpan.FromSeconds(45);
} }
@@ -36,22 +37,28 @@ public class N8nEvaluationService : IN8nEvaluationService
/// </summary> /// </summary>
public async Task<N8nAnalysisResponseDto?> EvaluateAssetAsync(N8nAnalysisRequestDto request, CancellationToken cancellationToken = default) public async Task<N8nAnalysisResponseDto?> EvaluateAssetAsync(N8nAnalysisRequestDto request, CancellationToken cancellationToken = default)
{ {
if (string.IsNullOrWhiteSpace(_webhookUrl)) string webhookUrl = await _settingsService.GetSettingAsync(SettingKeys.N8nWebhookUrl, cancellationToken);
if (string.IsNullOrWhiteSpace(webhookUrl))
{ {
await _finlyticLogger.LogErrorAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] Cannot execute AI evaluation for {Symbol}: N8N:WebhookUrl is not configured.", request.TargetAsset.Symbol); webhookUrl = _configuration["N8N:WebhookUrl"] ?? _configuration["N8N__WebhookUrl"] ?? string.Empty;
}
if (string.IsNullOrWhiteSpace(webhookUrl))
{
await _finlyticLogger.LogErrorAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] Cannot execute AI evaluation for {Symbol}: N8N:WebhookUrl is not configured in dynamic settings or environment.", request.TargetAsset.Symbol);
return null; return null;
} }
try try
{ {
await _finlyticLogger.LogInfoAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] Sending n8n AI Evaluation request {RequestId} for Asset {Symbol} (ISIN: {Isin}) to {Url}...", await _finlyticLogger.LogInfoAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] Sending n8n AI Evaluation request {RequestId} for Asset {Symbol} (ISIN: {Isin}) to {Url}...",
request.RequestId, request.TargetAsset.Symbol, request.TargetAsset.Isin, _webhookUrl); request.RequestId, request.TargetAsset.Symbol, request.TargetAsset.Isin, webhookUrl);
using var content = JsonContent.Create( using var content = JsonContent.Create(
request, request,
FinlyticJsonSerializerContext.Default.N8nAnalysisRequestDto); FinlyticJsonSerializerContext.Default.N8nAnalysisRequestDto);
using var response = await _httpClient.PostAsync(_webhookUrl, content, cancellationToken); using var response = await _httpClient.PostAsync(webhookUrl, content, cancellationToken);
if (response.IsSuccessStatusCode) if (response.IsSuccessStatusCode)
{ {
+3
View File
@@ -27,4 +27,7 @@ public static class SettingKeys
public static readonly SettingKey<int> MaxAllowedLeverage = new("Trade.MaxAllowedLeverage", 10); public static readonly SettingKey<int> MaxAllowedLeverage = new("Trade.MaxAllowedLeverage", 10);
public static readonly SettingKey<double> MaxRiskPerTradePercent = new("Trade.MaxRiskPerTradePercent", 2.0); public static readonly SettingKey<double> MaxRiskPerTradePercent = new("Trade.MaxRiskPerTradePercent", 2.0);
public static readonly SettingKey<int> ProposalValidityHours = new("Trade.ProposalValidityHours", 24); public static readonly SettingKey<int> ProposalValidityHours = new("Trade.ProposalValidityHours", 24);
// --- N8N / Webhook-Konfiguration ---
public static readonly SettingKey<string> N8nWebhookUrl = new("N8N.WebhookUrl", "https://n8n.kleidukos.me/webhook/gemini/analysis/auto");
} }
+13
View File
@@ -50,6 +50,18 @@ public class N8nService : IN8nService
string? targetUrl = null; string? targetUrl = null;
using (var scope = _scopeFactory.CreateScope()) using (var scope = _scopeFactory.CreateScope())
{
var settingsService = scope.ServiceProvider.GetService<ISettingsService>();
if (settingsService != null)
{
var dynamicUrl = await settingsService.GetSettingAsync(SettingKeys.N8nArticleExtractionUrl, ct);
if (!string.IsNullOrWhiteSpace(dynamicUrl))
{
targetUrl = dynamicUrl.Trim();
}
}
if (string.IsNullOrWhiteSpace(targetUrl))
{ {
var settingsDb = scope.ServiceProvider.GetService<ISettingsDbService>(); var settingsDb = scope.ServiceProvider.GetService<ISettingsDbService>();
if (settingsDb != null) if (settingsDb != null)
@@ -58,6 +70,7 @@ public class N8nService : IN8nService
targetUrl = settings?.N8nWebhookUrl; targetUrl = settings?.N8nWebhookUrl;
} }
} }
}
if (string.IsNullOrWhiteSpace(targetUrl)) if (string.IsNullOrWhiteSpace(targetUrl))
{ {
+3
View File
@@ -21,4 +21,7 @@ public static class SettingKeys
// --- Daten-Retention & Cleanup --- // --- Daten-Retention & Cleanup ---
public static readonly SettingKey<int> ArticleRetentionDays = new("Data.ArticleRetentionDays", 90); public static readonly SettingKey<int> ArticleRetentionDays = new("Data.ArticleRetentionDays", 90);
// --- N8N / Webhook-Konfiguration ---
public static readonly SettingKey<string> N8nArticleExtractionUrl = new("N8N.ArticleExtractionUrl", "https://n8n.kleidukos.me/webhook/gemini/article/extraction");
} }
@@ -48,17 +48,31 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService
{ {
ArgumentNullException.ThrowIfNull(article); ArgumentNullException.ThrowIfNull(article);
string targetUrl; string targetUrl = string.Empty;
double minConfidence; double minConfidence = 0.60;
using (var scope = _scopeFactory.CreateScope()) using (var scope = _scopeFactory.CreateScope())
{
var settingsService = scope.ServiceProvider.GetService<ISettingsService>();
bool isEnglish = string.Equals(article.Language, "en", StringComparison.OrdinalIgnoreCase);
if (settingsService != null)
{
targetUrl = isEnglish
? await settingsService.GetSettingAsync(SettingKeys.EnglishWebhookUrl)
: await settingsService.GetSettingAsync(SettingKeys.GermanWebhookUrl);
minConfidence = await settingsService.GetSettingAsync(SettingKeys.MinimumConfidenceThreshold);
}
if (string.IsNullOrWhiteSpace(targetUrl))
{ {
var settingsDb = scope.ServiceProvider.GetRequiredService<ISettingsDbService>(); var settingsDb = scope.ServiceProvider.GetRequiredService<ISettingsDbService>();
var settings = await settingsDb.GetSettingsAsync(); var settings = await settingsDb.GetSettingsAsync();
targetUrl = string.Equals(article.Language, "en", StringComparison.OrdinalIgnoreCase) targetUrl = isEnglish
? settings.EnglishWebhookUrl ? settings.EnglishWebhookUrl
: settings.GermanWebhookUrl; : settings.GermanWebhookUrl;
minConfidence = settings.MinConfidenceScore; minConfidence = settings.MinConfidenceScore;
} }
}
await _finlyticLogger.LogInfoAsync(SettingKeys.SentimentChannel, "[FinBertAnalyzerService] Analyzing article (ID: {Id}, Lang: {Lang}) via webhook: {Url} (MinConf: {Conf})", 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);
+4
View File
@@ -16,4 +16,8 @@ public static class SettingKeys
public static readonly SettingKey<int> SentimentWindowDays = new("Sentiment.WindowDays", 14); public static readonly SettingKey<int> SentimentWindowDays = new("Sentiment.WindowDays", 14);
public static readonly SettingKey<double> DecayFactorPerDay = new("Sentiment.DecayFactorPerDay", 0.90); public static readonly SettingKey<double> DecayFactorPerDay = new("Sentiment.DecayFactorPerDay", 0.90);
public static readonly SettingKey<bool> EnableAutoSummarization = new("Feature.EnableAutoSummarization", true); public static readonly SettingKey<bool> EnableAutoSummarization = new("Feature.EnableAutoSummarization", true);
// --- N8N / Webhook-Konfiguration ---
public static readonly SettingKey<string> GermanWebhookUrl = new("N8N.GermanWebhookUrl", "https://n8n.kleidukos.me/webhook/sentiment/de");
public static readonly SettingKey<string> EnglishWebhookUrl = new("N8N.EnglishWebhookUrl", "https://n8n.kleidukos.me/webhook/sentiment/en");
} }