feat(sentiment): add persistent sentiment entities, summaries, SentimentDbService and MQTT RPC refactoring

This commit is contained in:
2026-08-24 21:35:48 +02:00
parent 600ccf299e
commit 12e7b57b16
24 changed files with 1510 additions and 1300 deletions
@@ -0,0 +1,53 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FinlyticSentiment.Entities;
/// <summary>
/// Entity representing a single sentiment evaluation of an asset mentioned in a news article.
/// </summary>
[Table("article_sentiments")]
public class ArticleSentimentEntity
{
[Key]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public Guid ArticleId { get; set; }
[Required]
[MaxLength(12)]
public string Isin { get; set; } = string.Empty;
[Required]
[MaxLength(256)]
public string Name { get; set; } = string.Empty;
[MaxLength(128)]
public string? Sector { get; set; }
[Required]
[MaxLength(32)]
public string Label { get; set; } = "NEUTRAL";
public double CompoundScore { get; set; }
public double Confidence { get; set; }
[MaxLength(32)]
public string? Impact { get; set; }
public double PositiveProbability { get; set; }
public double NegativeProbability { get; set; }
public double NeutralProbability { get; set; }
[MaxLength(1024)]
public string? KeyHighlight { get; set; }
public DateTime PublishedAtUtc { get; set; }
public DateTime AnalyzedAtUtc { get; set; } = DateTime.UtcNow;
}
@@ -0,0 +1,52 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FinlyticSentiment.Entities;
/// <summary>
/// Pre-aggregated, real-time sentiment summary for a company/asset, enabling sub-millisecond lookups without recalculation.
/// </summary>
[Table("company_sentiment_summaries")]
public class CompanySentimentSummaryEntity
{
[Key]
[MaxLength(12)]
public string Isin { get; set; } = string.Empty;
[Required]
[MaxLength(256)]
public string Name { get; set; } = string.Empty;
[MaxLength(128)]
public string? Sector { get; set; }
[Required]
[MaxLength(32)]
public string CurrentLabel { get; set; } = "NEUTRAL";
public double AverageScore { get; set; }
public double WeightedScore { get; set; }
public double AverageConfidence { get; set; }
public int TotalAnalysesCount { get; set; }
public int PositiveCount { get; set; }
public int NegativeCount { get; set; }
public int NeutralCount { get; set; }
[MaxLength(1024)]
public string? LatestKeyHighlight { get; set; }
[MaxLength(32)]
public string? Trend { get; set; } = "STABLE";
public DateTime LastUpdatedUtc { get; set; } = DateTime.UtcNow;
[ConcurrencyCheck]
public uint Version { get; set; }
}
@@ -0,0 +1,28 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FinlyticSentiment.Entities;
/// <summary>
/// Pre-aggregated real-time sentiment summary for a market sector (e.g. Technology, Healthcare).
/// </summary>
[Table("sector_sentiment_summaries")]
public class SectorSentimentSummaryEntity
{
[Key]
[MaxLength(128)]
public string Sector { get; set; } = string.Empty;
[Required]
[MaxLength(32)]
public string CurrentLabel { get; set; } = "NEUTRAL";
public double AverageScore { get; set; }
public int TotalArticlesCount { get; set; }
public int TotalCompaniesCount { get; set; }
public DateTime LastUpdatedUtc { get; set; } = DateTime.UtcNow;
}
@@ -1,23 +0,0 @@
using System;
namespace FinlyticSentiment.Entities;
/// <summary>
/// Entity representing runtime operational settings for FinlyticSentiment stored in PostgreSQL.
/// </summary>
public class SentimentSettingsEntity
{
public Guid Id { get; set; } = Guid.NewGuid();
public double MinConfidenceScore { get; set; } = 0.70;
public int MaxBatchSize { get; set; } = 10;
public int SweepIntervalMinutes { get; set; } = 2;
public string GermanWebhookUrl { get; set; } = "https://n8n.kleidukos.me/webhook/sentiment/de";
public string EnglishWebhookUrl { get; set; } = "https://n8n.kleidukos.me/webhook/sentiment/en";
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}