126 lines
5.9 KiB
C#
126 lines
5.9 KiB
C#
using System;
|
|
using System.Net.Http;
|
|
using System.Net.Http.Json;
|
|
using System.Text.Json;
|
|
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using FinlyticAnalyzer.Util;
|
|
using FinlyticCore.Models.Analyzer;
|
|
using FinlyticCore.Services;
|
|
using FinlyticCore.Util;
|
|
using Microsoft.Extensions.Configuration;
|
|
|
|
namespace FinlyticAnalyzer.Services;
|
|
|
|
public class N8nEvaluationService : IN8nEvaluationService
|
|
{
|
|
private readonly HttpClient _httpClient;
|
|
private readonly ISettingsService _settingsService;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly IFinlyticLogger<N8nEvaluationService> _finlyticLogger;
|
|
|
|
public N8nEvaluationService(
|
|
HttpClient httpClient,
|
|
ISettingsService settingsService,
|
|
IConfiguration configuration,
|
|
IFinlyticLogger<N8nEvaluationService> finlyticLogger)
|
|
{
|
|
_httpClient = httpClient;
|
|
_settingsService = settingsService;
|
|
_configuration = configuration;
|
|
_finlyticLogger = finlyticLogger;
|
|
_httpClient.Timeout = TimeSpan.FromSeconds(45);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates an asset asynchronously using N8n / Gemini workflows.
|
|
/// </summary>
|
|
public async Task<N8nAnalysisResponseDto?> EvaluateAssetAsync(N8nAnalysisRequestDto request, CancellationToken cancellationToken = default)
|
|
{
|
|
string webhookUrl = await _settingsService.GetSettingAsync(SettingKeys.N8nWebhookUrl, cancellationToken);
|
|
if (string.IsNullOrWhiteSpace(webhookUrl))
|
|
{
|
|
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);
|
|
|
|
using var content = JsonContent.Create(
|
|
request,
|
|
FinlyticJsonSerializerContext.Default.N8nAnalysisRequestDto);
|
|
|
|
using var response = await _httpClient.PostAsync(webhookUrl, content, cancellationToken);
|
|
|
|
if (response.IsSuccessStatusCode)
|
|
{
|
|
var contentStr = await response.Content.ReadAsStringAsync(cancellationToken);
|
|
|
|
if (string.IsNullOrWhiteSpace(contentStr) || contentStr.Trim() == "{}" || contentStr.Trim() == "[]")
|
|
{
|
|
await _finlyticLogger.LogWarningAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] n8n Webhook returned an EMPTY response for Request {RequestId}. Flagging as AI Rejection (Too Risky).", request.RequestId);
|
|
return CreateRejectionFallback(request, "Die KI (n8n/Gemini) stuft den Trade als zu riskant ein und empfiehlt keine Positionierung.");
|
|
}
|
|
|
|
string jsonToDeserialize = contentStr.Trim();
|
|
if (jsonToDeserialize.StartsWith('[') && jsonToDeserialize.EndsWith(']'))
|
|
{
|
|
using var doc = JsonDocument.Parse(jsonToDeserialize);
|
|
if (doc.RootElement.ValueKind == JsonValueKind.Array && doc.RootElement.GetArrayLength() > 0)
|
|
{
|
|
jsonToDeserialize = doc.RootElement[0].GetRawText();
|
|
}
|
|
}
|
|
|
|
var responseDto = JsonSerializer.Deserialize(
|
|
jsonToDeserialize,
|
|
FinlyticJsonSerializerContext.Default.N8nAnalysisResponseDto);
|
|
|
|
if (responseDto != null && !string.IsNullOrWhiteSpace(responseDto.AiDecision))
|
|
{
|
|
await _finlyticLogger.LogInfoAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] Received n8n AI Response for Request {RequestId}: Decision={Decision}, Score={Score:F2}, Direction={Direction}, Timeframe={Timeframe}",
|
|
request.RequestId, responseDto.AiDecision, responseDto.EvalScore, responseDto.SuggestedDirection, responseDto.SuggestedTimeframe);
|
|
|
|
return responseDto;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
await _finlyticLogger.LogWarningAsync(SettingKeys.AnalyzerChannel, "[N8nEvaluationService] n8n Webhook returned HTTP {StatusCode} for Request {RequestId}",
|
|
response.StatusCode, request.RequestId);
|
|
}
|
|
}
|
|
catch (OperationCanceledException ex) when (!cancellationToken.IsCancellationRequested)
|
|
{
|
|
await _finlyticLogger.LogErrorAsync(SettingKeys.AnalyzerChannel, ex, "[N8nEvaluationService] Timeout waiting for n8n AI Evaluation Webhook (Request {RequestId}). Consider increasing timeout.", request.RequestId);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await _finlyticLogger.LogErrorAsync(SettingKeys.AnalyzerChannel, ex, "[N8nEvaluationService] Error calling n8n AI Evaluation Webhook for Request {RequestId}", request.RequestId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static N8nAnalysisResponseDto CreateRejectionFallback(N8nAnalysisRequestDto request, string reasoning)
|
|
{
|
|
return new N8nAnalysisResponseDto
|
|
{
|
|
RequestId = request.RequestId,
|
|
AiDecision = "Rejected",
|
|
EvalScore = 0.0,
|
|
SuggestedDirection = "NONE",
|
|
SuggestedRisk = request.UserPreferences?.RiskTolerance ?? "Moderate",
|
|
SuggestedTimeframe = request.UserPreferences?.TimeframeFormatted ?? "1D",
|
|
AiReasoning = reasoning
|
|
};
|
|
}
|
|
} |