using System.Collections.Generic; using System.Text.Json.Serialization; using FinlyticCore.Dtos.TechnicalAnalysis; namespace FinlyticCore.Dtos.Trading; /// /// Filter/pagination request for the admin-only evaluation-history RPC channel /// (MqttTopics.Channels.EngineGetEvaluationHistory), served by FinlyticEngine and exposed to the Web UI /// via FinlyticBackend/Controllers/AdminEvaluationHistoryController. All filters are optional and are /// combined with logical AND; means "do not filter on this field". /// /// Inclusive lower bound on EngineEvaluationSnapshotEntity.EvaluatedAtUtc. /// Inclusive upper bound on EngineEvaluationSnapshotEntity.EvaluatedAtUtc. /// Restricts results to a single . /// Restricts results to a single . /// /// Case-sensitive substring search against both Isin and Symbol (matches either). Trimmed /// server-side; blank/whitespace-only values are treated as "no search". /// /// 1-based page number. Values below 1 are treated as 1 server-side. /// /// Requested page size. Server-side clamped to at least 1 and at most 200 (see /// EvaluationHistoryService.MaxPageSize) so a caller cannot force FinlyticEngine to materialize/transmit /// an unbounded result set in a single response. /// public record GetEvaluationHistoryRequest( [property: JsonPropertyName("fromUtc")] System.DateTime? FromUtc = null, [property: JsonPropertyName("toUtc")] System.DateTime? ToUtc = null, [property: JsonPropertyName("outcomeFilter")] OutcomeReason? OutcomeFilter = null, [property: JsonPropertyName("triggerSourceFilter")] TriggerSource? TriggerSourceFilter = null, [property: JsonPropertyName("isinOrSymbolSearch")] string? IsinOrSymbolSearch = null, [property: JsonPropertyName("page")] int Page = 1, [property: JsonPropertyName("pageSize")] int PageSize = 50 ); /// /// One row of the evaluation history: the full, already-persisted record of a single /// TradeLifecycleService.EvaluateAssetAsync run, mapped 1:1 from EngineEvaluationSnapshotEntity. /// Every score field is the real, already-computed value - including the honest "0/default" values recorded /// for the case, never a fabricated placeholder (Rules.md §4). /// public record EvaluationHistoryEntryDto( [property: JsonPropertyName("id")] System.Guid Id, [property: JsonPropertyName("isin")] string Isin, [property: JsonPropertyName("symbol")] string Symbol, [property: JsonPropertyName("technicalScore")] decimal TechnicalScore, [property: JsonPropertyName("sentimentScore")] decimal SentimentScore, [property: JsonPropertyName("fundamentalScore")] decimal FundamentalScore, [property: JsonPropertyName("compositeOpportunityScore")] decimal CompositeOpportunityScore, [property: JsonPropertyName("reliabilityBonus")] decimal ReliabilityBonus, [property: JsonPropertyName("passedEarningsLockout")] bool PassedEarningsLockout, [property: JsonPropertyName("daysToNextEarnings")] int? DaysToNextEarnings, [property: JsonPropertyName("passedDividendGate")] bool PassedDividendGate, [property: JsonPropertyName("daysToNextExDividend")] int? DaysToNextExDividend, [property: JsonPropertyName("universeSource")] UniverseSource? UniverseSource, [property: JsonPropertyName("universeEnteredAtUtc")] System.DateTime? UniverseEnteredAtUtc, [property: JsonPropertyName("passedSimulationVeto")] bool PassedSimulationVeto, [property: JsonPropertyName("passedAiValidation")] bool PassedAiValidation, [property: JsonPropertyName("aiThesisSummary")] string AiThesisSummary, [property: JsonPropertyName("outcomeReason")] OutcomeReason OutcomeReason, [property: JsonPropertyName("triggerSource")] TriggerSource TriggerSource, [property: JsonPropertyName("triggeredByUserId")] System.Guid? TriggeredByUserId, [property: JsonPropertyName("proposalId")] System.Guid? ProposalId, [property: JsonPropertyName("evaluatedAtUtc")] System.DateTime EvaluatedAtUtc ); /// /// Number of evaluation-history rows matching a given filter set that carry a specific . /// A typed list of these (rather than a Dictionary<OutcomeReason,int>) is used on /// purely so this DTO round-trips through /// System.Text.Json (including the AOT source-generated FinlyticJsonSerializerContext) without needing a /// custom enum-keyed dictionary converter. /// public record OutcomeReasonCountDto( [property: JsonPropertyName("outcomeReason")] OutcomeReason OutcomeReason, [property: JsonPropertyName("count")] int Count ); /// /// Pre-aggregated headline numbers for the admin evaluation-history tab (e.g. "1.847 Analysen letzte 24h · /// 0 Vorschläge seit 14h · Ø-Score 66,7"), computed server-side so the Web UI never has to aggregate the full, /// unpaginated result set itself. Every field except is scoped to /// exactly the same filters as the paginated list it accompanies (see /// ) - only pagination (Page/PageSize) does not apply, /// since these are totals over the whole filtered set, not just the current page. /// /// Total number of snapshot rows matching the request's filters (unpaginated). /// Breakdown of by . /// /// Average CompositeOpportunityScore across the filtered set; 0 when is 0. /// /// /// Number of filtered rows whose is /// - i.e. the same value as the entry /// in , exposed directly so the UI does not need to search that list. /// /// /// Timestamp of the most recently created EngineTradeProposalEntity across the ENTIRE proposals table - /// deliberately NOT scoped to this request's FromUtc/ToUtc filters, because "how long since the /// last real proposal" is a single wall-clock fact the admin wants regardless of which historical window they /// are currently browsing. only if no proposal has ever been created. /// public record EvaluationHistorySummaryDto( [property: JsonPropertyName("totalEvaluations")] int TotalEvaluations, [property: JsonPropertyName("countsByOutcome")] List CountsByOutcome, [property: JsonPropertyName("averageCompositeScore")] decimal AverageCompositeScore, [property: JsonPropertyName("proposalsCreated")] int ProposalsCreated, [property: JsonPropertyName("lastProposalCreatedAtUtc")] System.DateTime? LastProposalCreatedAtUtc ); /// /// Full response for the evaluation-history RPC channel: a page of matching rows, the total match count (for /// pagination), and a pre-aggregated so the Web UI never needs a second round trip (and a /// second, potentially-inconsistent set of filters) just to render a header line above the table. /// public record GetEvaluationHistoryResponse( [property: JsonPropertyName("totalCount")] int TotalCount, [property: JsonPropertyName("entries")] List Entries, [property: JsonPropertyName("summary")] EvaluationHistorySummaryDto Summary );