using System.Collections.Generic; using System.Threading.Tasks; using QuantEngine.Core.Domain; using QuantEngine.Core.Interfaces; namespace QuantEngine.Application.Services { public class FormulaService { private readonly IPostgresqlHistoryStore _historyStore; private readonly DecisionLearningService _learningService; public FormulaService(IPostgresqlHistoryStore historyStore, DecisionLearningService learningService) { _historyStore = historyStore; _learningService = learningService; } public TimingDecisionResult ComputeTimingDecision(Dictionary ctx) => FormulaEngine.ComputeTimingDecision(RequireContext(ctx)); public SellDecisionResult ComputeSellDecision(Dictionary ctx) => FormulaEngine.ComputeSellDecision(RequireContext(ctx)); public FinalDecisionResult ComputeFinalDecision(Dictionary ctx) => FormulaEngine.ComputeFinalDecision(RequireContext(ctx)); public async Task ComputeAndRecordFinalDecisionAsync( Dictionary ctx, string decisionKey, string instrumentId, string sourceVersion, IEnumerable factorEvidence) { var normalizedContext = RequireContext(ctx); var decision = ComputeFinalDecision(normalizedContext); return await _learningService.RecordDecisionAsync( RequireValue(decisionKey, nameof(decisionKey)), DateTimeOffset.UtcNow, RequireValue(instrumentId, nameof(instrumentId)), decision.FinalAction, "PASS", Convert.ToDecimal(decision.PriorityScore), RequireValue(sourceVersion, nameof(sourceVersion)), factorEvidence, new { context_keys = normalizedContext.Keys.OrderBy(key => key).ToArray() }, new { formula = "FormulaEngine.ComputeFinalDecision", source_version = sourceVersion }); } public CashShortfallResult ComputeCashShortfallHarness( Dictionary asResult, double totalAsset, Dictionary cashFloorInfo, double mrsScore) => FormulaEngine.ComputeCashShortfallHarness(asResult, totalAsset, cashFloorInfo, mrsScore); public CashRecoveryPlanResult ComputeCashRecoveryOptimizer( List> sellCandidates, double cashShortfallMinKrw) => FormulaEngine.ComputeCashRecoveryOptimizer(sellCandidates, cashShortfallMinKrw); public Task AppendFormulaRunAsync(string formulaName, Dictionary payload) => _historyStore.AppendAsync($"formula_{RequireValue(formulaName, nameof(formulaName))}_history", payload); private static Dictionary RequireContext(Dictionary ctx) { ArgumentNullException.ThrowIfNull(ctx); return ctx; } private static string RequireValue(string value, string parameterName) { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("Value is required.", parameterName); } return value.Trim(); } } }