From e745cfb0ae8e5e9550639e5bea89b48bea98eae1 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 13 Jul 2026 01:26:19 +0900 Subject: [PATCH] refactor(dotnet): normalize factor computation outputs --- .../Services/FactorComputationService.cs | 14 ++++++++++++++ .../FactorComputationServiceTests.cs | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs b/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs index b93fac1d..9e783669 100644 --- a/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs +++ b/src/dotnet/QuantEngine.Application/Services/FactorComputationService.cs @@ -42,6 +42,10 @@ public sealed class FactorComputationService FactorOutputs outputs, DateTimeOffset? observedAt = null) { + ticker = RequireValue(ticker, nameof(ticker)); + sourceVersion = RequireValue(sourceVersion, nameof(sourceVersion)); + ArgumentNullException.ThrowIfNull(outputs); + var when = observedAt ?? DateTimeOffset.UtcNow; await _history.AppendFactorOutputAsync("momentum_20d", sourceVersion, outputs.Momentum20D, "PASS", sourceVersion, when); await _history.AppendFactorOutputAsync("momentum_60d", sourceVersion, outputs.Momentum60D, "PASS", sourceVersion, when); @@ -52,4 +56,14 @@ public sealed class FactorComputationService await _history.AppendFactorOutputAsync("rs_20d", sourceVersion, outputs.Rs20D, "PASS", sourceVersion, when); _auditTrail.Append("factor_audit", ticker, new FactorComputationAudit(ticker, 0, 0, "PERSISTED", when, sourceVersion)); } + + private static string RequireValue(string value, string parameterName) + { + if (string.IsNullOrWhiteSpace(value)) + { + throw new ArgumentException("Value is required.", parameterName); + } + + return value.Trim(); + } } diff --git a/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs b/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs index d536a086..42311b66 100644 --- a/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs +++ b/src/dotnet/QuantEngine.Core.Tests/FactorComputationServiceTests.cs @@ -29,4 +29,16 @@ public class FactorComputationServiceTests storeMock.Verify(s => s.AppendAsync("factor_output_history", It.IsAny>()), Times.Exactly(7)); auditTrailMock.Verify(a => a.Append("factor_audit", "005930", It.IsAny()), Times.Once); } + + [Fact] + public async Task AppendFactorOutputsAsync_RejectsBlankTicker() + { + var storeMock = new Mock(); + var auditTrailMock = new Mock(); + var history = new HistoryIngestionService(storeMock.Object); + var service = new FactorComputationService(history, auditTrailMock.Object); + + await Assert.ThrowsAsync(() => + service.AppendFactorOutputsAsync(" ", "v1", new FactorOutputs(1, 2, 3, 4, 5, 6, 7))); + } }