Add scoring message

This commit is contained in:
2025-07-17 15:57:51 +07:00
parent da48ee37ba
commit e27b4c4a76
9 changed files with 224 additions and 46 deletions

View File

@@ -16,4 +16,5 @@ public class LightBacktestResponse
public decimal Fees { get; set; } public decimal Fees { get; set; }
public double? SharpeRatio { get; set; } public double? SharpeRatio { get; set; }
public double Score { get; set; } public double Score { get; set; }
public string ScoreMessage { get; set; } = string.Empty;
} }

View File

@@ -317,7 +317,7 @@ namespace Managing.Application.Backtesting
timeframe: config.Timeframe timeframe: config.Timeframe
); );
var score = BacktestScorer.CalculateTotalScore(scoringParams); var scoringResult = BacktestScorer.CalculateDetailedScore(scoringParams);
// Create backtest result with conditional candles and indicators values // Create backtest result with conditional candles and indicators values
var result = new Backtest(config, bot.Positions, bot.Signals.ToList(), var result = new Backtest(config, bot.Positions, bot.Signals.ToList(),
@@ -334,7 +334,8 @@ namespace Managing.Application.Backtesting
IndicatorsValues = withCandles IndicatorsValues = withCandles
? AggregateValues(indicatorsValues, bot.IndicatorsValues) ? AggregateValues(indicatorsValues, bot.IndicatorsValues)
: new Dictionary<IndicatorType, IndicatorsResultBase>(), : new Dictionary<IndicatorType, IndicatorsResultBase>(),
Score = score, Score = scoringResult.Score,
ScoreMessage = scoringResult.SummaryMessage,
Id = Guid.NewGuid().ToString(), Id = Guid.NewGuid().ToString(),
RequestId = requestId, RequestId = requestId,
Metadata = metadata, Metadata = metadata,

View File

@@ -60,6 +60,7 @@ public class Backtest
[Required] public double Score { get; set; } [Required] public double Score { get; set; }
public string RequestId { get; set; } public string RequestId { get; set; }
public object? Metadata { get; set; } public object? Metadata { get; set; }
public string ScoreMessage { get; set; } = string.Empty;
/// <summary> /// <summary>
/// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot. /// Creates a new TradingBotConfig based on this backtest's configuration for starting a live bot.

View File

@@ -0,0 +1,114 @@
using System.Text;
namespace Managing.Domain.Backtests;
public class BacktestScoringResult
{
public double Score { get; set; }
public List<ScoringCheck> Checks { get; set; } = new();
public string SummaryMessage { get; set; } = string.Empty;
public BacktestScoringResult(double score)
{
Score = score;
}
public void AddCheck(string component, double score, double weight, string message, bool passed = true)
{
Checks.Add(new ScoringCheck
{
Component = component,
Score = score,
Weight = weight,
Message = message,
Passed = passed
});
}
public void AddEarlyExitCheck(string reason, string message)
{
Checks.Add(new ScoringCheck
{
Component = "Early Exit",
Score = 0,
Weight = 0,
Message = message,
Passed = false,
IsEarlyExit = true
});
}
public void AddPenaltyCheck(string component, double penaltyMultiplier, string message)
{
Checks.Add(new ScoringCheck
{
Component = component,
Score = 0,
Weight = 0,
Message = message,
Passed = penaltyMultiplier >= 1.0,
IsPenalty = true,
PenaltyMultiplier = penaltyMultiplier
});
}
public string GenerateSummaryMessage()
{
if (Score == 0)
{
var earlyExit = Checks.FirstOrDefault(c => c.IsEarlyExit);
if (earlyExit != null)
{
return $"Score: 0 - {earlyExit.Message}";
}
}
var passedChecks = Checks.Where(c => c.Passed && !c.IsEarlyExit && !c.IsPenalty).ToList();
var failedChecks = Checks.Where(c => !c.Passed && !c.IsEarlyExit && !c.IsPenalty).ToList();
var penalties = Checks.Where(c => c.IsPenalty).ToList();
var summary = new StringBuilder();
summary.AppendLine($"Final Score: {Score:F1}/100");
if (passedChecks.Any())
{
summary.AppendLine($"✅ Passed Checks ({passedChecks.Count}):");
foreach (var check in passedChecks.OrderByDescending(c => c.Score * c.Weight))
{
summary.AppendLine($" • {check.Component}: {check.Score:F1} points ({check.Message})");
}
}
if (failedChecks.Any())
{
summary.AppendLine($"❌ Failed Checks ({failedChecks.Count}):");
foreach (var check in failedChecks)
{
summary.AppendLine($" • {check.Component}: {check.Message}");
}
}
if (penalties.Any())
{
summary.AppendLine($"⚠️ Applied Penalties ({penalties.Count}):");
foreach (var penalty in penalties)
{
summary.AppendLine($" • {penalty.Component}: {penalty.Message}");
}
}
return summary.ToString().TrimEnd();
}
}
public class ScoringCheck
{
public string Component { get; set; } = string.Empty;
public double Score { get; set; }
public double Weight { get; set; }
public string Message { get; set; } = string.Empty;
public bool Passed { get; set; }
public bool IsEarlyExit { get; set; }
public bool IsPenalty { get; set; }
public double PenaltyMultiplier { get; set; }
}

View File

@@ -16,4 +16,5 @@ public class LightBacktest
public decimal Fees { get; set; } public decimal Fees { get; set; }
public double? SharpeRatio { get; set; } public double? SharpeRatio { get; set; }
public double Score { get; set; } public double Score { get; set; }
public string ScoreMessage { get; set; } = string.Empty;
} }

View File

@@ -21,58 +21,101 @@ public class BacktestScorer
public static double CalculateTotalScore(BacktestScoringParams p) public static double CalculateTotalScore(BacktestScoringParams p)
{ {
var result = CalculateDetailedScore(p);
return result.Score;
}
public static BacktestScoringResult CalculateDetailedScore(BacktestScoringParams p)
{
var result = new BacktestScoringResult(0);
try try
{ {
// Early exit for no positions // Early exit for no positions
if (p.TradeCount == 0) if (p.TradeCount == 0)
{ {
return 0; result.AddEarlyExitCheck("No Trades", "No trading positions were taken during the backtest period. A strategy must execute trades to be evaluated.");
return result;
} }
// Early exit for negative PnL - should be 0 score // Early exit for negative PnL - should be 0 score
if (p.TotalPnL <= 0) if (p.TotalPnL <= 0)
{ {
return 0; result.AddEarlyExitCheck("Negative PnL", $"Total profit/loss is negative (${p.TotalPnL:F2}). Only profitable strategies receive scores.");
return result;
} }
// Early exit if strategy significantly underperforms HODL (more than 2% worse) // Early exit if strategy significantly underperforms HODL (more than 2% worse)
if (p.GrowthPercentage < p.HodlPercentage - 2) if (p.GrowthPercentage < p.HodlPercentage - 2)
{ {
return 0; result.AddEarlyExitCheck("HODL Underperformance", $"Strategy growth ({p.GrowthPercentage:F2}%) significantly underperforms HODL ({p.HodlPercentage:F2}%) by more than 2%. Buy-and-hold would have been more profitable.");
return result;
} }
var baseScore = CalculateBaseScore(p); var baseScore = CalculateBaseScore(p, result);
var finalScore = ApplyProfitabilityRules(baseScore, p); var finalScore = ApplyProfitabilityRules(baseScore, p, result);
return double.Clamp(finalScore, 0, 100); result.Score = double.Clamp(finalScore, 0, 100);
result.SummaryMessage = result.GenerateSummaryMessage();
return result;
} }
catch catch (Exception ex)
{ {
return 0; result.AddEarlyExitCheck("Calculation Error", $"An error occurred during score calculation: {ex.Message}");
return result;
} }
} }
private static double CalculateBaseScore(BacktestScoringParams p) private static double CalculateBaseScore(BacktestScoringParams p, BacktestScoringResult result)
{ {
var componentScores = new Dictionary<string, double> var componentScores = new Dictionary<string, double>
{ {
{ "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage) }, { "GrowthPercentage", CalculateGrowthScore(p.GrowthPercentage, result) },
{ "SharpeRatio", CalculateSharpeScore(p.SharpeRatio) }, { "SharpeRatio", CalculateSharpeScore(p.SharpeRatio, result) },
{ "MaxDrawdownUsd", CalculateDrawdownUsdScore(p.MaxDrawdown, p.InitialBalance) }, { "MaxDrawdownUsd", CalculateDrawdownUsdScore(p.MaxDrawdown, p.InitialBalance, result) },
{ "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage) }, { "HodlComparison", CalculateHodlComparisonScore(p.GrowthPercentage, p.HodlPercentage, result) },
{ "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount) }, { "WinRate", CalculateWinRateScore(p.WinRate, p.TradeCount, result) },
{ "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage) }, { "ProfitabilityBonus", CalculateProfitabilityBonus(p.GrowthPercentage, result) },
{ "TradeCount", CalculateTradeCountScore(p.TradeCount) }, { "TradeCount", CalculateTradeCountScore(p.TradeCount, result) },
{ "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime, p.Timeframe) }, { "RecoveryTime", CalculateRecoveryScore(p.MaxDrawdownRecoveryTime, p.Timeframe, result) },
{ "TestDuration", CalculateTestDurationScore(p.StartDate, p.EndDate, p.Timeframe) }, { "TestDuration", CalculateTestDurationScore(p.StartDate, p.EndDate, p.Timeframe, result) },
{ "FeesImpact", CalculateFeesImpactScore(p.FeesPaid, p.InitialBalance, (decimal)p.TotalPnL) }, { "FeesImpact", CalculateFeesImpactScore(p.FeesPaid, p.InitialBalance, (decimal)p.TotalPnL, result) },
{ "RiskAdjustedReturn", CalculateRiskAdjustedReturnScore(p.TotalPnL, p.MaxDrawdown, p.InitialBalance) } { "RiskAdjustedReturn", CalculateRiskAdjustedReturnScore(p.TotalPnL, p.MaxDrawdown, p.InitialBalance, result) }
}; };
return componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]); var totalScore = componentScores.Sum(kvp => kvp.Value * Weights[kvp.Key]);
// Add component scores to result
foreach (var kvp in componentScores)
{
var passed = kvp.Value > 0;
result.AddCheck(kvp.Key, kvp.Value, Weights[kvp.Key], GetComponentMessage(kvp.Key, kvp.Value, p), passed);
} }
private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p) return totalScore;
}
private static string GetComponentMessage(string component, double score, BacktestScoringParams p)
{
return component switch
{
"GrowthPercentage" => $"Growth of {p.GrowthPercentage:F2}% (target: 10% for full score)",
"SharpeRatio" => $"Sharpe ratio of {p.SharpeRatio:F2} (target: 4.0 for full score)",
"MaxDrawdownUsd" => $"Max drawdown of ${p.MaxDrawdown:F2} ({p.MaxDrawdownPc:F1}% of balance)",
"HodlComparison" => $"Strategy vs HODL: {p.GrowthPercentage:F2}% vs {p.HodlPercentage:F2}% (difference: {p.GrowthPercentage - p.HodlPercentage:F2}%)",
"WinRate" => $"Win rate of {p.WinRate * 100:F1}% with {p.TradeCount} trades",
"ProfitabilityBonus" => $"Bonus for positive growth of {p.GrowthPercentage:F2}%",
"TradeCount" => $"{p.TradeCount} trades executed (minimum 5, optimal 50+)",
"RecoveryTime" => $"Recovery time: {p.MaxDrawdownRecoveryTime.TotalDays:F1} days",
"TestDuration" => $"Test duration: {(p.EndDate - p.StartDate).TotalDays:F1} days",
"FeesImpact" => $"Fees: ${p.FeesPaid:F2} ({p.FeesPaid / p.InitialBalance * 100:F2}% of balance)",
"RiskAdjustedReturn" => $"PnL/Drawdown ratio: {p.TotalPnL / (double)p.MaxDrawdown:F2}:1",
_ => $"Component score: {score:F1}"
};
}
private static double ApplyProfitabilityRules(double baseScore, BacktestScoringParams p, BacktestScoringResult result)
{ {
var penaltyMultiplier = 1.0; var penaltyMultiplier = 1.0;
@@ -80,30 +123,36 @@ public class BacktestScorer
if (p.GrowthPercentage < 0) if (p.GrowthPercentage < 0)
{ {
var negativePenalty = Math.Abs(p.GrowthPercentage) * 0.1; // 10% penalty per 1% loss var negativePenalty = Math.Abs(p.GrowthPercentage) * 0.1; // 10% penalty per 1% loss
penaltyMultiplier *= Math.Max(0.1, 1 - negativePenalty); var newMultiplier = Math.Max(0.1, 1 - negativePenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("Negative Growth", newMultiplier, $"Negative growth of {p.GrowthPercentage:F2}% applied {negativePenalty:F1}% penalty");
} }
// Note: Negative PnL is now handled by early exit in CalculateTotalScore
// 3. Win Rate Validation (Dynamic) // 3. Win Rate Validation (Dynamic)
if (p.WinRate < 0.3 && p.TradeCount > 10) if (p.WinRate < 0.3 && p.TradeCount > 10)
{ {
var winRatePenalty = (0.3 - p.WinRate) * 0.5; // 50% penalty per 10% below 30% var winRatePenalty = (0.3 - p.WinRate) * 0.5; // 50% penalty per 10% below 30%
penaltyMultiplier *= Math.Max(0.2, 1 - winRatePenalty); var newMultiplier = Math.Max(0.2, 1 - winRatePenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("Low Win Rate", newMultiplier, $"Win rate of {p.WinRate * 100:F1}% below 30% threshold applied {winRatePenalty:F1}% penalty");
} }
// 4. Minimum Profit Threshold (Dynamic) // 4. Minimum Profit Threshold (Dynamic)
if (p.GrowthPercentage < 2 && p.TradeCount > 5) if (p.GrowthPercentage < 2 && p.TradeCount > 5)
{ {
var profitPenalty = (2 - p.GrowthPercentage) * 0.1; // 10% penalty per 1% below 2% var profitPenalty = (2 - p.GrowthPercentage) * 0.1; // 10% penalty per 1% below 2%
penaltyMultiplier *= Math.Max(0.5, 1 - profitPenalty); var newMultiplier = Math.Max(0.5, 1 - profitPenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("Low Profit", newMultiplier, $"Growth of {p.GrowthPercentage:F2}% below 2% minimum applied {profitPenalty:F1}% penalty");
} }
// 5. Drawdown Penalty (Dynamic) - Enhanced to consider PnL ratio // 5. Drawdown Penalty (Dynamic) - Enhanced to consider PnL ratio
if (p.MaxDrawdownPc > 20) if (p.MaxDrawdownPc > 20)
{ {
var drawdownPenalty = (p.MaxDrawdownPc - 20) * 0.02; // 2% penalty per 1% above 20% var drawdownPenalty = (p.MaxDrawdownPc - 20) * 0.02; // 2% penalty per 1% above 20%
penaltyMultiplier *= Math.Max(0.3, 1 - drawdownPenalty); var newMultiplier = Math.Max(0.3, 1 - drawdownPenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("High Drawdown", newMultiplier, $"Drawdown of {p.MaxDrawdownPc:F1}% above 20% threshold applied {drawdownPenalty:F1}% penalty");
} }
// 6. Enhanced Drawdown vs PnL Penalty // 6. Enhanced Drawdown vs PnL Penalty
@@ -113,7 +162,9 @@ public class BacktestScorer
if (drawdownToPnLRatio > 1.5) // If drawdown is more than 150% of PnL if (drawdownToPnLRatio > 1.5) // If drawdown is more than 150% of PnL
{ {
var ratioPenalty = (drawdownToPnLRatio - 1.5) * 0.2; // 20% penalty per 0.5 ratio above 1.5 var ratioPenalty = (drawdownToPnLRatio - 1.5) * 0.2; // 20% penalty per 0.5 ratio above 1.5
penaltyMultiplier *= Math.Max(0.2, 1 - ratioPenalty); var newMultiplier = Math.Max(0.2, 1 - ratioPenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("Poor Risk/Reward", newMultiplier, $"Drawdown/PnL ratio of {drawdownToPnLRatio:F2}:1 above 1.5:1 threshold applied {ratioPenalty:F1}% penalty");
} }
} }
@@ -122,7 +173,9 @@ public class BacktestScorer
if (testDurationDays < 30) if (testDurationDays < 30)
{ {
var durationPenalty = (30 - testDurationDays) * 0.02; // 2% penalty per day below 30 var durationPenalty = (30 - testDurationDays) * 0.02; // 2% penalty per day below 30
penaltyMultiplier *= Math.Max(0.5, 1 - durationPenalty); var newMultiplier = Math.Max(0.5, 1 - durationPenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("Short Test Duration", newMultiplier, $"Test duration of {testDurationDays:F1} days below 30-day minimum applied {durationPenalty:F1}% penalty");
} }
// 8. HODL Underperformance Penalty (Dynamic) // 8. HODL Underperformance Penalty (Dynamic)
@@ -130,13 +183,15 @@ public class BacktestScorer
{ {
var hodlUnderperformance = p.HodlPercentage - p.GrowthPercentage; var hodlUnderperformance = p.HodlPercentage - p.GrowthPercentage;
var hodlPenalty = hodlUnderperformance * 0.3; // 30% penalty per 1% underperformance var hodlPenalty = hodlUnderperformance * 0.3; // 30% penalty per 1% underperformance
penaltyMultiplier *= Math.Max(0.1, 1 - hodlPenalty); var newMultiplier = Math.Max(0.1, 1 - hodlPenalty);
penaltyMultiplier *= newMultiplier;
result.AddPenaltyCheck("HODL Underperformance", newMultiplier, $"Underperforming HODL by {hodlUnderperformance:F2}% applied {hodlPenalty:F1}% penalty");
} }
return baseScore * penaltyMultiplier; return baseScore * penaltyMultiplier;
} }
private static double CalculateGrowthScore(double growthPercentage) private static double CalculateGrowthScore(double growthPercentage, BacktestScoringResult result)
{ {
// More aggressive scoring - harder to reach 100 // More aggressive scoring - harder to reach 100
if (growthPercentage < 0) if (growthPercentage < 0)
@@ -164,7 +219,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateProfitabilityBonus(double growthPercentage) private static double CalculateProfitabilityBonus(double growthPercentage, BacktestScoringResult result)
{ {
return growthPercentage switch return growthPercentage switch
{ {
@@ -173,7 +228,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateSharpeScore(double sharpeRatio) private static double CalculateSharpeScore(double sharpeRatio, BacktestScoringResult result)
{ {
return sharpeRatio switch return sharpeRatio switch
{ {
@@ -183,7 +238,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateDrawdownUsdScore(decimal maxDrawdown, decimal initialBalance) private static double CalculateDrawdownUsdScore(decimal maxDrawdown, decimal initialBalance, BacktestScoringResult result)
{ {
if (initialBalance <= 0) return 0; if (initialBalance <= 0) return 0;
@@ -195,7 +250,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateWinRateScore(double winRate, int tradeCount) private static double CalculateWinRateScore(double winRate, int tradeCount, BacktestScoringResult result)
{ {
// Base win rate score // Base win rate score
var baseScore = winRate * 100; var baseScore = winRate * 100;
@@ -212,7 +267,7 @@ public class BacktestScorer
return baseScore * significanceFactor; return baseScore * significanceFactor;
} }
private static double CalculateHodlComparisonScore(double strategyGrowth, double hodlGrowth) private static double CalculateHodlComparisonScore(double strategyGrowth, double hodlGrowth, BacktestScoringResult result)
{ {
var difference = strategyGrowth - hodlGrowth; var difference = strategyGrowth - hodlGrowth;
@@ -228,7 +283,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateTradeCountScore(int tradeCount) private static double CalculateTradeCountScore(int tradeCount, BacktestScoringResult result)
{ {
return tradeCount switch return tradeCount switch
{ {
@@ -239,7 +294,7 @@ public class BacktestScorer
}; };
} }
private static double CalculateRecoveryScore(TimeSpan recoveryTime, Timeframe timeframe) private static double CalculateRecoveryScore(TimeSpan recoveryTime, Timeframe timeframe, BacktestScoringResult result)
{ {
var days = recoveryTime.TotalDays; var days = recoveryTime.TotalDays;
@@ -260,7 +315,7 @@ public class BacktestScorer
return 100 - (days / maxRecoveryDays * 100); return 100 - (days / maxRecoveryDays * 100);
} }
private static double CalculateTestDurationScore(DateTime startDate, DateTime endDate, Timeframe timeframe) private static double CalculateTestDurationScore(DateTime startDate, DateTime endDate, Timeframe timeframe, BacktestScoringResult result)
{ {
var durationDays = (endDate - startDate).TotalDays; var durationDays = (endDate - startDate).TotalDays;
@@ -283,7 +338,7 @@ public class BacktestScorer
return 100; return 100;
} }
private static double CalculateFeesImpactScore(decimal feesPaid, decimal initialBalance, decimal totalPnL) private static double CalculateFeesImpactScore(decimal feesPaid, decimal initialBalance, decimal totalPnL, BacktestScoringResult result)
{ {
if (initialBalance <= 0) return 0; if (initialBalance <= 0) return 0;
@@ -307,7 +362,7 @@ public class BacktestScorer
return feeEfficiency; return feeEfficiency;
} }
private static double CalculateRiskAdjustedReturnScore(double totalPnL, decimal maxDrawdown, decimal initialBalance) private static double CalculateRiskAdjustedReturnScore(double totalPnL, decimal maxDrawdown, decimal initialBalance, BacktestScoringResult result)
{ {
if (initialBalance <= 0 || maxDrawdown <= 0) return 0; if (initialBalance <= 0 || maxDrawdown <= 0) return 0;

View File

@@ -157,7 +157,8 @@ public class BacktestRepository : IBacktestRepository
MaxDrawdown = b.Statistics?.MaxDrawdown, MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees, Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null, SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score Score = b.Score,
ScoreMessage = b.ScoreMessage ?? string.Empty
}); });
return (mappedBacktests, (int)totalCount); return (mappedBacktests, (int)totalCount);
@@ -284,7 +285,8 @@ public class BacktestRepository : IBacktestRepository
MaxDrawdown = b.Statistics?.MaxDrawdown, MaxDrawdown = b.Statistics?.MaxDrawdown,
Fees = b.Fees, Fees = b.Fees,
SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null, SharpeRatio = b.Statistics?.SharpeRatio != null ? (double)b.Statistics.SharpeRatio : null,
Score = b.Score Score = b.Score,
ScoreMessage = b.ScoreMessage ?? string.Empty
}); });
return (mappedBacktests, (int)totalCount); return (mappedBacktests, (int)totalCount);

View File

@@ -29,6 +29,7 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
[BsonRepresentation(BsonType.Decimal128)] [BsonRepresentation(BsonType.Decimal128)]
public decimal Fees { get; set; } public decimal Fees { get; set; }
public double Score { get; set; } public double Score { get; set; }
public string ScoreMessage { get; set; } = string.Empty;
public string Identifier { get; set; } public string Identifier { get; set; }
public string RequestId { get; set; } public string RequestId { get; set; }
public string? Metadata { get; set; } public string? Metadata { get; set; }

View File

@@ -150,6 +150,7 @@ public static class MongoMappers
StartDate = b.StartDate, StartDate = b.StartDate,
EndDate = b.EndDate, EndDate = b.EndDate,
Score = b.Score, Score = b.Score,
ScoreMessage = b.ScoreMessage ?? string.Empty,
RequestId = b.RequestId, RequestId = b.RequestId,
Metadata = string.IsNullOrEmpty(b.Metadata) ? null : JsonSerializer.Deserialize<object>(b.Metadata) Metadata = string.IsNullOrEmpty(b.Metadata) ? null : JsonSerializer.Deserialize<object>(b.Metadata)
}; };
@@ -179,6 +180,7 @@ public static class MongoMappers
StartDate = result.StartDate, StartDate = result.StartDate,
EndDate = result.EndDate, EndDate = result.EndDate,
Score = result.Score, Score = result.Score,
ScoreMessage = result.ScoreMessage ?? string.Empty,
RequestId = result.RequestId, RequestId = result.RequestId,
Metadata = result.Metadata == null ? null : JsonSerializer.Serialize(result.Metadata) Metadata = result.Metadata == null ? null : JsonSerializer.Serialize(result.Metadata)
}; };