using System; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using FinlyticCore.Dtos.TechnicalAnalysis; using FinlyticCore.Dtos.Trading; namespace FinlyticEngine.Database.Entities; /// /// Persists the full outcome of a single TradeLifecycleService.EvaluateAssetAsync run - one row per /// evaluated asset, whether or not it produced a trade proposal. This is the append-only audit trail the /// admin-only "why no proposals" Web UI tab (AdminEvaluationHistoryController) reads from via /// MqttTopics.Channels.EngineGetEvaluationHistory. /// [Table("engine_evaluation_snapshots")] public class EngineEvaluationSnapshotEntity { [Key] public Guid Id { get; set; } = Guid.NewGuid(); [Required] [MaxLength(20)] public string Isin { get; set; } = string.Empty; [MaxLength(30)] public string Symbol { get; set; } = string.Empty; [Column(TypeName = "decimal(6,2)")] public decimal TechnicalScore { get; set; } [Column(TypeName = "decimal(6,2)")] public decimal SentimentScore { get; set; } [Column(TypeName = "decimal(6,2)")] public decimal FundamentalScore { get; set; } [Column(TypeName = "decimal(6,2)")] public decimal CompositeOpportunityScore { get; set; } /// /// Bonus points CompositeOpportunityScorer added to the raw weighted score based on /// FinlyticSimulation's backtest-reliability matrix (see ScoringResult.ReliabilityBonus). Always /// 0 when no reliability data was available or no bonus applied - never fabricated (Rules.md §4). /// [Column(TypeName = "decimal(6,2)")] public decimal ReliabilityBonus { get; set; } public bool PassedEarningsLockout { get; set; } public int? DaysToNextEarnings { get; set; } /// Whether the ex-dividend gate (Engine.DividendGateDays) passed. See . public bool PassedDividendGate { get; set; } = true; public int? DaysToNextExDividend { get; set; } /// /// Which FinlyticTechnicals universe-selection mechanism was responsible for this ISIN being scanned in /// the first place (favorite/discovery/sentiment-spike), captured from /// StrategyResultDto.UniverseSource at evaluation time. when the evaluated /// setup did not originate from FinlyticTechnicals' continuously-scanned universe (e.g. a manual "Analyze /// now" call for an ISIN nobody favorited/discovered/spiked) - never a fabricated guess (Rules.md §4). /// public UniverseSource? UniverseSource { get; set; } /// When the ISIN above entered that scan universe, alongside . public DateTime? UniverseEnteredAtUtc { get; set; } /// /// Whether FinlyticSimulation's backtest-reliability matrix vetoed this strategy/asset combination (see /// ScoringResult.PassedSimulationVeto). Defaults to (matching /// ScoringResult's own default) so a row where this gate was never actually evaluated - e.g. the /// early-return case - never reads as "vetoed". /// public bool PassedSimulationVeto { get; set; } = true; public bool PassedAiValidation { get; set; } [MaxLength(2048)] public string AiThesisSummary { get; set; } = string.Empty; /// /// Whether this evaluation was fired by the autonomous OpportunityPollerBackgroundService scan loop /// or by an on-demand human request. See for why /// (not ) is the default/zero value. /// public TriggerSource TriggerSource { get; set; } = TriggerSource.Unknown; /// /// Identity of the human caller who triggered this evaluation, resolved server-side from the JWT in /// FinlyticBackend. Only ever set when is - /// the autonomous scanner never carries a user identity, so this stays for every /// row. /// public Guid? TriggeredByUserId { get; set; } /// /// Classifies why this evaluation did or did not produce a proposal. See /// TradeLifecycleService.DetermineOutcomeReason for the exact priority order used when multiple /// gates failed at once. /// public OutcomeReason OutcomeReason { get; set; } = OutcomeReason.Unknown; /// /// The EngineTradeProposalEntity.Id created by this evaluation, set if and only if /// is . for every /// rejected/no-setup evaluation - a proposal was never fabricated for those (Rules.md §4). /// public Guid? ProposalId { get; set; } [Required] public DateTime EvaluatedAtUtc { get; set; } = DateTime.UtcNow; }