feat(news,calendar): improve corporate calendar filters and news sentiment dialogs

This commit is contained in:
2026-08-15 19:30:00 +02:00
parent 067f1bfdd5
commit 960a4bdbd6
11 changed files with 556 additions and 87 deletions
@@ -105,8 +105,28 @@ public class CalendarController : ControllerBase
!string.Equals(category, "Alle", StringComparison.OrdinalIgnoreCase) &&
!string.Equals(category, "all", StringComparison.OrdinalIgnoreCase))
{
string catKey = category.ToLowerInvariant();
filtered = filtered.Where(e =>
string.Equals(e.EventType, category, StringComparison.OrdinalIgnoreCase));
{
string t = (e.EventType ?? string.Empty).ToLowerInvariant();
if (catKey.Contains("earn") || catKey.Contains("quartal") || catKey.Contains("ergebnis"))
{
return t.Contains("earn") || t.Contains("quart") || t.Contains("ergebnis") || t.Contains("finan") || t.Contains("report") || t.Contains("bilanz") || string.Equals(t, "event", StringComparison.OrdinalIgnoreCase);
}
if (catKey.Contains("ex") || catKey.Contains("div"))
{
return (t.Contains("ex") || t.Contains("div") || t.Contains("ausschütt")) && !t.Contains("pay") && !t.Contains("zahl");
}
if (catKey.Contains("pay") || catKey.Contains("zahl"))
{
return t.Contains("pay") || t.Contains("zahl") || t.Contains("auszahl");
}
if (catKey.Contains("split"))
{
return t.Contains("split");
}
return t.Contains(catKey) || string.Equals(e.EventType, category, StringComparison.OrdinalIgnoreCase);
});
}
if (date.HasValue)
@@ -240,4 +240,87 @@ public class NewsController : ControllerBase
return NotFound(new { message = $"No sentiment analysis found for article {articleId}." });
}
/// <summary>
/// Triggers or renews sentiment analysis for a specific news article via FinlyticSentiment.
/// </summary>
[HttpPost("sentiment/article/{articleId}/analyze")]
[HttpPost("{articleId}/reanalyze")]
public async Task<IActionResult> ReanalyzeArticleSentiment(string articleId)
{
if (string.IsNullOrWhiteSpace(articleId)) return BadRequest(new { message = "ArticleId ist erforderlich." });
var targetId = articleId.Trim();
try
{
if (_mqttClient.IsConnected)
{
var rpcResult = await _mqttClient.SendRpcRequestAsync<IsinAnalysisEntry, AnalyzeSentimentRequest>(
"sentiment_Analyze",
new AnalyzeSentimentRequest(ArticleId: targetId, ForceReload: true),
TimeSpan.FromSeconds(20)
);
if (rpcResult != null)
{
// Fetch full article from FinlyticNews to return complete updated DTO
var article = await _mqttClient.SendRpcRequestAsync<NewsArticleDto, ArticleRequest>(
"news_GetById",
new ArticleRequest(targetId, targetId),
TimeSpan.FromSeconds(5)
);
if (article != null && rpcResult.FinbertResult != null)
{
var res = rpcResult.FinbertResult;
var enriched = article with
{
Sentiment = res.Label,
SentimentScore = res.CompoundScore,
Confidence = res.Confidence,
FinbertResult = res,
Status = "Analyzed"
};
return Ok(enriched);
}
if (article != null)
{
return Ok(article);
}
// Fallback to synthetic DTO from entry
var synthetic = new NewsArticleDto
{
Id = Guid.TryParse(targetId, out var g) ? g : Guid.NewGuid(),
Title = rpcResult.Article?.Title ?? "Artikel",
Author = rpcResult.Article?.Source ?? "FinlyticNews",
Summary = rpcResult.SummarySnippet,
ContentRaw = "",
SourceUrl = "",
ScrapedAt = DateTime.UtcNow,
PublishedAt = DateTime.TryParse(rpcResult.Article?.PublishedAt, out var pDate) ? pDate : DateTime.UtcNow,
Status = "Analyzed",
Sentiment = rpcResult.FinbertResult?.Label ?? "NEUTRAL",
SentimentScore = rpcResult.FinbertResult?.CompoundScore ?? 0.0,
Confidence = rpcResult.FinbertResult?.Confidence ?? 0.0,
FinbertResult = rpcResult.FinbertResult
};
return Ok(synthetic);
}
}
else
{
return StatusCode(503, new { message = "MQTT Broker nicht verbunden." });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Fehler beim erneuten Analysieren des Artikels {ArticleId}", targetId);
return StatusCode(500, new { message = $"Analyse fehlgeschlagen: {ex.Message}" });
}
return NotFound(new { message = $"Artikel {targetId} konnte nicht analysiert werden." });
}
}