Files
QuantEngineByItz/src/dotnet/QuantEngine.Application/Services/DecisionLearningService.cs
T
kjh2064 6e666844f0
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Failing after 17s
Validators (Pushes and Pull Requests) / validate-core (push) Has been cancelled
feat: add canonical decision learning service
2026-07-12 11:53:17 +09:00

102 lines
3.2 KiB
C#

using System.Text.Json;
using QuantEngine.Core.Interfaces;
namespace QuantEngine.Application.Services;
/// <summary>
/// Canonical application path for recording factor evidence, decisions, and
/// realized outcomes in the normalized PostgreSQL learning store.
/// </summary>
public sealed class DecisionLearningService
{
private readonly INormalizedLearningStore _store;
public DecisionLearningService(INormalizedLearningStore store) => _store = store;
public async Task<Guid> RecordDecisionAsync(
string decisionKey,
DateTimeOffset decidedAt,
string instrumentId,
string action,
string gate,
decimal? score,
string sourceVersion,
IEnumerable<FactorEvidenceInput> factors,
object? trace = null,
object? provenance = null)
{
var decisionId = await _store.AppendDecisionAsync(new DecisionEventRecord(
decisionKey,
decidedAt,
instrumentId,
action,
gate,
score,
sourceVersion,
JsonSerializer.Serialize(trace ?? new { }),
JsonSerializer.Serialize(provenance ?? new { })));
foreach (var factor in factors)
{
var observationId = await _store.AppendSourceObservationAsync(new SourceObservationRecord(
factor.ObservedAt,
instrumentId,
factor.SourceName,
sourceVersion,
factor.PayloadJson,
factor.ProvenanceJson));
var factorObservationId = await _store.AppendFactorObservationAsync(new FactorObservationRecord(
observationId,
factor.FactorObservationId,
factor.FactorId,
factor.FactorVersion,
factor.ObservedAt,
factor.NumericValue,
factor.TextValue,
factor.Gate,
factor.ProvenanceJson));
await _store.AppendDecisionFactorEvidenceAsync(decisionId, factorObservationId, factor.Role);
}
return decisionId;
}
public Task RecordOutcomeAsync(
Guid decisionId,
int horizonDays,
DateTimeOffset evaluatedAt,
decimal? realizedReturn,
decimal? benchmarkReturn,
string outcomeClass,
string evaluationGate,
object? provenance = null)
{
decimal? excessReturn = realizedReturn.HasValue && benchmarkReturn.HasValue
? realizedReturn.Value - benchmarkReturn.Value
: null;
return _store.AppendOutcomeAsync(new OutcomeEvaluationRecord(
decisionId,
horizonDays,
evaluatedAt,
realizedReturn,
benchmarkReturn,
excessReturn,
outcomeClass,
evaluationGate,
JsonSerializer.Serialize(provenance ?? new { })));
}
}
public sealed record FactorEvidenceInput(
Guid FactorObservationId,
string FactorId,
string FactorVersion,
DateTimeOffset ObservedAt,
decimal? NumericValue,
string? TextValue,
string Gate,
string Role,
string SourceName,
string PayloadJson,
string ProvenanceJson);