From 3972507cb0002c55be53f7ff0624476b60326d3c Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Mon, 17 Aug 2026 16:10:31 +0200 Subject: [PATCH] feat(settings): add dynamic N8N webhook URL configuration for News, Sentiment and Analyzer services --- .../Services/N8nEvaluationService.cs | 31 ++++++++++++------- FinlyticAnalyzer/Util/SettingKeys.cs | 3 ++ FinlyticNews/Services/N8nService.cs | 21 ++++++++++--- FinlyticNews/Util/SettingKeys.cs | 3 ++ .../Services/FinBertAnalyzerService.cs | 30 +++++++++++++----- FinlyticSentiment/Util/SettingKeys.cs | 4 +++ 6 files changed, 68 insertions(+), 24 deletions(-) diff --git a/FinlyticAnalyzer/Services/N8nEvaluationService.cs b/FinlyticAnalyzer/Services/N8nEvaluationService.cs index aa90760..8aa57eb 100644 --- a/FinlyticAnalyzer/Services/N8nEvaluationService.cs +++ b/FinlyticAnalyzer/Services/N8nEvaluationService.cs @@ -15,19 +15,20 @@ namespace FinlyticAnalyzer.Services; public class N8nEvaluationService : IN8nEvaluationService { private readonly HttpClient _httpClient; + private readonly ISettingsService _settingsService; + private readonly IConfiguration _configuration; private readonly IFinlyticLogger _finlyticLogger; - private readonly string _webhookUrl; - public N8nEvaluationService(HttpClient httpClient, IConfiguration configuration, IFinlyticLogger finlyticLogger) + public N8nEvaluationService( + HttpClient httpClient, + ISettingsService settingsService, + IConfiguration configuration, + IFinlyticLogger finlyticLogger) { _httpClient = httpClient; + _settingsService = settingsService; + _configuration = configuration; _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); } @@ -36,22 +37,28 @@ public class N8nEvaluationService : IN8nEvaluationService /// public async Task 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; } try { 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( request, FinlyticJsonSerializerContext.Default.N8nAnalysisRequestDto); - using var response = await _httpClient.PostAsync(_webhookUrl, content, cancellationToken); + using var response = await _httpClient.PostAsync(webhookUrl, content, cancellationToken); if (response.IsSuccessStatusCode) { diff --git a/FinlyticAnalyzer/Util/SettingKeys.cs b/FinlyticAnalyzer/Util/SettingKeys.cs index a1f4eac..de70a65 100644 --- a/FinlyticAnalyzer/Util/SettingKeys.cs +++ b/FinlyticAnalyzer/Util/SettingKeys.cs @@ -27,4 +27,7 @@ public static class SettingKeys public static readonly SettingKey MaxAllowedLeverage = new("Trade.MaxAllowedLeverage", 10); public static readonly SettingKey MaxRiskPerTradePercent = new("Trade.MaxRiskPerTradePercent", 2.0); public static readonly SettingKey ProposalValidityHours = new("Trade.ProposalValidityHours", 24); + + // --- N8N / Webhook-Konfiguration --- + public static readonly SettingKey N8nWebhookUrl = new("N8N.WebhookUrl", "https://n8n.kleidukos.me/webhook/gemini/analysis/auto"); } diff --git a/FinlyticNews/Services/N8nService.cs b/FinlyticNews/Services/N8nService.cs index 011f40e..a31f3b2 100644 --- a/FinlyticNews/Services/N8nService.cs +++ b/FinlyticNews/Services/N8nService.cs @@ -51,11 +51,24 @@ public class N8nService : IN8nService using (var scope = _scopeFactory.CreateScope()) { - var settingsDb = scope.ServiceProvider.GetService(); - if (settingsDb != null) + var settingsService = scope.ServiceProvider.GetService(); + if (settingsService != null) { - var settings = await settingsDb.GetSettingsAsync(); - targetUrl = settings?.N8nWebhookUrl; + var dynamicUrl = await settingsService.GetSettingAsync(SettingKeys.N8nArticleExtractionUrl, ct); + if (!string.IsNullOrWhiteSpace(dynamicUrl)) + { + targetUrl = dynamicUrl.Trim(); + } + } + + if (string.IsNullOrWhiteSpace(targetUrl)) + { + var settingsDb = scope.ServiceProvider.GetService(); + if (settingsDb != null) + { + var settings = await settingsDb.GetSettingsAsync(); + targetUrl = settings?.N8nWebhookUrl; + } } } diff --git a/FinlyticNews/Util/SettingKeys.cs b/FinlyticNews/Util/SettingKeys.cs index e8cac49..9e651dd 100644 --- a/FinlyticNews/Util/SettingKeys.cs +++ b/FinlyticNews/Util/SettingKeys.cs @@ -21,4 +21,7 @@ public static class SettingKeys // --- Daten-Retention & Cleanup --- public static readonly SettingKey ArticleRetentionDays = new("Data.ArticleRetentionDays", 90); + + // --- N8N / Webhook-Konfiguration --- + public static readonly SettingKey N8nArticleExtractionUrl = new("N8N.ArticleExtractionUrl", "https://n8n.kleidukos.me/webhook/gemini/article/extraction"); } diff --git a/FinlyticSentiment/Services/FinBertAnalyzerService.cs b/FinlyticSentiment/Services/FinBertAnalyzerService.cs index ad83f38..5942829 100644 --- a/FinlyticSentiment/Services/FinBertAnalyzerService.cs +++ b/FinlyticSentiment/Services/FinBertAnalyzerService.cs @@ -48,16 +48,30 @@ public class FinBertAnalyzerService : IFinBertAnalyzerService { ArgumentNullException.ThrowIfNull(article); - string targetUrl; - double minConfidence; + string targetUrl = string.Empty; + double minConfidence = 0.60; using (var scope = _scopeFactory.CreateScope()) { - var settingsDb = scope.ServiceProvider.GetRequiredService(); - var settings = await settingsDb.GetSettingsAsync(); - targetUrl = string.Equals(article.Language, "en", StringComparison.OrdinalIgnoreCase) - ? settings.EnglishWebhookUrl - : settings.GermanWebhookUrl; - minConfidence = settings.MinConfidenceScore; + var settingsService = scope.ServiceProvider.GetService(); + 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(); + var settings = await settingsDb.GetSettingsAsync(); + targetUrl = isEnglish + ? settings.EnglishWebhookUrl + : settings.GermanWebhookUrl; + 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); diff --git a/FinlyticSentiment/Util/SettingKeys.cs b/FinlyticSentiment/Util/SettingKeys.cs index 0a35dc3..83e39fb 100644 --- a/FinlyticSentiment/Util/SettingKeys.cs +++ b/FinlyticSentiment/Util/SettingKeys.cs @@ -16,4 +16,8 @@ public static class SettingKeys public static readonly SettingKey SentimentWindowDays = new("Sentiment.WindowDays", 14); public static readonly SettingKey DecayFactorPerDay = new("Sentiment.DecayFactorPerDay", 0.90); public static readonly SettingKey EnableAutoSummarization = new("Feature.EnableAutoSummarization", true); + + // --- N8N / Webhook-Konfiguration --- + public static readonly SettingKey GermanWebhookUrl = new("N8N.GermanWebhookUrl", "https://n8n.kleidukos.me/webhook/sentiment/de"); + public static readonly SettingKey EnglishWebhookUrl = new("N8N.EnglishWebhookUrl", "https://n8n.kleidukos.me/webhook/sentiment/en"); }