refactor: save current workspace state including FinlyticAnalyzer fixes, FinlyticApp trade route alignment, and DTO audit documentation

This commit is contained in:
2026-08-12 18:30:42 +02:00
parent a9553e9fbf
commit 3d8af3940b
163 changed files with 3421 additions and 1751 deletions
@@ -0,0 +1,21 @@
using System;
using System.Text.Json.Serialization;
namespace FinlyticCore.Dtos;
/// <summary>
/// Response DTO for corporate calendar events (AOT-compliant).
/// </summary>
public record CalendarEventResponseDto(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("symbol")] string Symbol,
[property: JsonPropertyName("companyName")] string CompanyName,
[property: JsonPropertyName("eventType")] string EventType,
[property: JsonPropertyName("eventDate")] DateTime EventDate,
[property: JsonPropertyName("date")] string Date,
[property: JsonPropertyName("isin")] string Isin,
[property: JsonPropertyName("ticker")] string Ticker,
[property: JsonPropertyName("description")] string Description,
[property: JsonPropertyName("details")] string Details,
[property: JsonPropertyName("image")] string Image
);
+67 -39
View File
@@ -13,39 +13,40 @@ public record LimitRequest(
/// Generic request payload for paginated queries with optional ISIN filter.
/// </summary>
public record PaginatedRequest(
[property: JsonPropertyName("limit")] int Limit,
[property: JsonPropertyName("offset")] int Offset,
[property: JsonPropertyName("isin")] string? Isin = null
[property: JsonPropertyName("limit")] int Limit,
[property: JsonPropertyName("offset")] int Offset,
[property: JsonPropertyName("isin")] string? Isin = null
);
/// <summary>
/// Request payload for daily-news queries with optional filters.
/// </summary>
public record DailyNewsRequest(
[property: JsonPropertyName("limit")] int Limit,
[property: JsonPropertyName("offset")] int Offset,
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("date")] string? Date = null,
[property: JsonPropertyName("status")] string? Status = null,
[property: JsonPropertyName("query")] string? Query = null,
[property: JsonPropertyName("hasSentiment")] bool? HasSentiment = null
[property: JsonPropertyName("limit")] int Limit,
[property: JsonPropertyName("offset")] int Offset,
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("date")] DateTime? Date = null,
[property: JsonPropertyName("status")] string? Status = null,
[property: JsonPropertyName("query")] string? Query = null,
[property: JsonPropertyName("hasSentiment")]
bool? HasSentiment = null
);
/// <summary>
/// Request payload for fetching fundamentals or technical-analysis data by ISIN.
/// </summary>
public record IsinRequest(
[property: JsonPropertyName("isin")] string Isin,
[property: JsonPropertyName("ticker")] string? Ticker = "",
[property: JsonPropertyName("forceRefresh")] bool ForceRefresh = false
[property: JsonPropertyName("isin")] string Isin,
[property: JsonPropertyName("ticker")] string? Ticker = "",
[property: JsonPropertyName("forceRefresh")]
bool ForceRefresh = false
);
/// <summary>
/// Request payload for fetching trades filtered by ISIN and/or status.
/// </summary>
public record GetTradesRequest(
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("status")] string? Status = null,
[property: JsonPropertyName("userId")] string? UserId = null
);
@@ -54,17 +55,20 @@ public record GetTradesRequest(
/// Request payload for fetching sentiment by article ID.
/// </summary>
public record ArticleRequest(
[property: JsonPropertyName("articleId")] string ArticleId,
[property: JsonPropertyName("id")] string? Id = null
[property: JsonPropertyName("articleId")]
string ArticleId,
[property: JsonPropertyName("id")] string? Id = null
);
/// <summary>
/// Request payload for triggering a manual sentiment analysis for an article or ISIN.
/// </summary>
public record AnalyzeSentimentRequest(
[property: JsonPropertyName("articleId")] string? ArticleId = null,
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("forceReload")] bool ForceReload = false
[property: JsonPropertyName("articleId")]
string? ArticleId = null,
[property: JsonPropertyName("isin")] string? Isin = null,
[property: JsonPropertyName("forceReload")]
bool ForceReload = false
);
/// <summary>
@@ -72,6 +76,14 @@ public record AnalyzeSentimentRequest(
/// </summary>
public record EmptyRequest;
/// <summary>
/// Request payload for paginated/monthly calendar queries.
/// </summary>
public record GetEventsByMonthRequest(
[property: JsonPropertyName("year")] int Year,
[property: JsonPropertyName("month")] int Month
);
/// <summary>
/// Request payload for triggering a manual AI analysis.
/// </summary>
@@ -79,28 +91,40 @@ public record ManualAnalysisRpcRequest(
[property: JsonPropertyName("isin")] string Isin,
[property: JsonPropertyName("symbol")] string Symbol,
[property: JsonPropertyName("sector")] string Sector,
[property: JsonPropertyName("headline")] string Headline,
[property: JsonPropertyName("currentPrice")] decimal CurrentPrice,
[property: JsonPropertyName("riskScore")] int RiskScore,
[property: JsonPropertyName("minTimeframeValue")] int MinTimeframeValue,
[property: JsonPropertyName("maxTimeframeValue")] int MaxTimeframeValue,
[property: JsonPropertyName("timeframeUnit")] string TimeframeUnit,
[property: JsonPropertyName("instrumentType")] string InstrumentType,
[property: JsonPropertyName("userNotes")] string UserNotes,
[property: JsonPropertyName("headline")]
string Headline,
[property: JsonPropertyName("currentPrice")]
decimal CurrentPrice,
[property: JsonPropertyName("riskScore")]
int RiskScore,
[property: JsonPropertyName("minTimeframeValue")]
int MinTimeframeValue,
[property: JsonPropertyName("maxTimeframeValue")]
int MaxTimeframeValue,
[property: JsonPropertyName("timeframeUnit")]
string TimeframeUnit,
[property: JsonPropertyName("instrumentType")]
string InstrumentType,
[property: JsonPropertyName("userNotes")]
string UserNotes,
[property: JsonPropertyName("taData")] FinlyticCore.Dtos.TechnicalAnalysis.TechnicalAnalysisDto? TaData,
[property: JsonPropertyName("fundamentalsData")] FinlyticCore.Dtos.Fundamentals.AssetFundamentalsDto? FundamentalsData,
[property: JsonPropertyName("sentimentData")] FinlyticCore.Dtos.Sentiment.IsinSentimentSummaryDto? SentimentData
[property: JsonPropertyName("fundamentalsData")]
FinlyticCore.Dtos.Fundamentals.AssetFundamentalsDto? FundamentalsData,
[property: JsonPropertyName("sentimentData")]
FinlyticCore.Dtos.Sentiment.IsinSentimentSummaryDto? SentimentData
);
/// <summary>
/// Response payload returned by microservice health pings over MQTT.
/// </summary>
public record ServiceHealthResponse(
[property: JsonPropertyName("serviceName")] string ServiceName,
[property: JsonPropertyName("serviceName")]
string ServiceName,
[property: JsonPropertyName("status")] string Status,
[property: JsonPropertyName("timestamp")] DateTime Timestamp,
[property: JsonPropertyName("dbStatus")] string DbStatus
[property: JsonPropertyName("timestamp")]
DateTime Timestamp,
[property: JsonPropertyName("dbStatus")]
string DbStatus
);
/// <summary>
@@ -109,7 +133,8 @@ public record ServiceHealthResponse(
public record FetchLogoResponse(
[property: JsonPropertyName("isin")] string? Isin,
[property: JsonPropertyName("path")] string? Path,
[property: JsonPropertyName("success")] bool Success
[property: JsonPropertyName("success")]
bool Success
);
/// <summary>
@@ -117,9 +142,12 @@ public record FetchLogoResponse(
/// Replaces the anonymous type to be compatible with AOT/source-gen JSON serialization.
/// </summary>
public record ServiceConfigUpdatePayload(
[property: JsonPropertyName("serviceName")] string ServiceName,
[property: JsonPropertyName("timestamp")] DateTime Timestamp,
[property: JsonPropertyName("settings")] Dictionary<string, string> Settings
[property: JsonPropertyName("serviceName")]
string ServiceName,
[property: JsonPropertyName("timestamp")]
DateTime Timestamp,
[property: JsonPropertyName("settings")]
Dictionary<string, string> Settings
);
/// <summary>
@@ -127,4 +155,4 @@ public record ServiceConfigUpdatePayload(
/// </summary>
public record TickMessageDto(
[property: JsonPropertyName("price")] decimal Price
);
);
@@ -10,6 +10,7 @@ public class AuthResponseDto
public string Email { get; set; } = string.Empty;
public string FullName { get; set; } = string.Empty;
public string Role { get; set; } = "User";
public string ThemePreference { get; set; } = "dark_classic";
public List<string> FcmTokens { get; set; } = new();
public DateTime ExpiresAt { get; set; }
public bool RequiresPasswordChange { get; set; }
+1
View File
@@ -10,6 +10,7 @@ public class UserDto
public string FullName { get; set; } = string.Empty;
public string Role { get; set; } = "User";
public bool IsActive { get; set; } = true;
public string ThemePreference { get; set; } = "dark_classic";
public List<string> FcmTokens { get; set; } = new();
public DateTime CreatedAt { get; set; }
public DateTime? LastLoginAt { get; set; }
@@ -43,6 +43,8 @@ namespace FinlyticCore.Util;
[JsonSerializable(typeof(List<AssetFundamentalsDto>))]
[JsonSerializable(typeof(CorporateEventDto))]
[JsonSerializable(typeof(List<CorporateEventDto>))]
[JsonSerializable(typeof(CalendarEventResponseDto))]
[JsonSerializable(typeof(List<CalendarEventResponseDto>))]
[JsonSerializable(typeof(IsinSentimentSummaryDto))]
[JsonSerializable(typeof(IsinAnalysisEntry))]
[JsonSerializable(typeof(SectorSentimentSummaryDto))]
@@ -72,6 +74,7 @@ namespace FinlyticCore.Util;
[JsonSerializable(typeof(ArticleRequest))]
[JsonSerializable(typeof(AnalyzeSentimentRequest))]
[JsonSerializable(typeof(EmptyRequest))]
[JsonSerializable(typeof(GetEventsByMonthRequest))]
[JsonSerializable(typeof(ManualAnalysisRpcRequest))]
[JsonSerializable(typeof(ServiceHealthResponse))]
+23 -14
View File
@@ -281,24 +281,33 @@ public abstract class ManagedMqttClient : IDisposable
if (_cts == null || _cts.IsCancellationRequested)
return;
_logger.LogWarning("Lost connection to MQTT broker (Reason: {Reason}). Initiating auto-reconnect loop in 5 seconds...", e.Reason);
_logger.LogWarning("Lost connection to MQTT broker (Reason: {Reason}). Initiating auto-reconnect loop...", e.Reason);
try
int attempt = 0;
while (_cts != null && !_cts.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromSeconds(5), _cts.Token);
await _mqttClient.ReconnectAsync(_cts.Token);
if (_mqttClient.IsConnected)
attempt++;
var delaySeconds = Math.Min(5 * Math.Pow(2, attempt - 1), 60); // 5s, 10s, 20s, 40s, 60s max
try
{
_logger.LogInformation("MQTT client reconnected successfully.");
await OnConnectedAsync();
_logger.LogInformation("Reconnect attempt {Attempt} in {Delay}s...", attempt, delaySeconds);
await Task.Delay(TimeSpan.FromSeconds(delaySeconds), _cts.Token);
await _mqttClient.ReconnectAsync(_cts.Token);
if (_mqttClient.IsConnected)
{
_logger.LogInformation("MQTT client reconnected successfully after {Attempt} attempt(s).", attempt);
await OnConnectedAsync();
return;
}
}
catch (OperationCanceledException) { return; /* Expected on application shutdown */ }
catch (Exception ex)
{
_logger.LogWarning(ex, "Reconnect attempt {Attempt} to the MQTT broker failed.", attempt);
}
}
catch (OperationCanceledException) { /* Expected swallow on application shutdown */ }
catch (Exception ex)
{
_logger.LogError(ex, "Reconnection attempt to the MQTT broker failed.");
}
}