From d7c106f2921e154c025de0a6d329ecf600f47e6a Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 13 Jul 2026 00:08:28 +0900 Subject: [PATCH] refactor(dotnet): standardize scheduler state and definitions --- .../SchedulerServiceTests.cs | 14 +++++++++ .../Services/SchedulerModels.cs | 26 +++++++++++++++++ .../Services/SchedulerService.cs | 29 ++++++++++--------- 3 files changed, 56 insertions(+), 13 deletions(-) create mode 100644 src/dotnet/QuantEngine.Web/Services/SchedulerModels.cs diff --git a/src/dotnet/QuantEngine.Core.Tests/SchedulerServiceTests.cs b/src/dotnet/QuantEngine.Core.Tests/SchedulerServiceTests.cs index 6a4d235b..979bfa28 100644 --- a/src/dotnet/QuantEngine.Core.Tests/SchedulerServiceTests.cs +++ b/src/dotnet/QuantEngine.Core.Tests/SchedulerServiceTests.cs @@ -73,6 +73,20 @@ public class SchedulerServiceTests ), Times.Once); } + [Fact] + public void GetRecurringJobDefinitions_ReturnsCanonicalDefinitions() + { + var service = CreateService(); + + var defs = service.GetRecurringJobDefinitions(); + + Assert.Equal(4, defs.Count); + Assert.Contains(defs, d => d.JobId == "daily-collection" && d.IsRecurring); + Assert.Contains(defs, d => d.JobId == "hourly-price-update" && d.IsRecurring); + Assert.Contains(defs, d => d.JobId == "weekly-report" && d.IsRecurring); + Assert.Contains(defs, d => d.JobId == "monthly-optimization" && d.IsRecurring); + } + [Fact] public void LoadTickersFromJson_WhenFileMissing_FallsBackToDefaultUniverse() { diff --git a/src/dotnet/QuantEngine.Web/Services/SchedulerModels.cs b/src/dotnet/QuantEngine.Web/Services/SchedulerModels.cs new file mode 100644 index 00000000..b85371e7 --- /dev/null +++ b/src/dotnet/QuantEngine.Web/Services/SchedulerModels.cs @@ -0,0 +1,26 @@ +namespace QuantEngine.Web.Services; + +public sealed record SchedulerJobDefinition( + string JobId, + string Cron, + string Description, + bool IsRecurring); + +public sealed record SchedulerJobExecutionAudit( + string JobId, + string RunId, + string State, + string? Reason, + DateTimeOffset StartedAt, + DateTimeOffset? FinishedAt, + string? ResourceKey); + +public static class SchedulerStates +{ + public const string Pending = "PENDING"; + public const string Running = "RUNNING"; + public const string Succeeded = "SUCCEEDED"; + public const string Failed = "FAILED"; + public const string Retrying = "RETRYING"; + public const string Blocked = "BLOCKED"; +} diff --git a/src/dotnet/QuantEngine.Web/Services/SchedulerService.cs b/src/dotnet/QuantEngine.Web/Services/SchedulerService.cs index 15d648ba..4a03047c 100644 --- a/src/dotnet/QuantEngine.Web/Services/SchedulerService.cs +++ b/src/dotnet/QuantEngine.Web/Services/SchedulerService.cs @@ -16,12 +16,6 @@ namespace QuantEngine.Web.Services; /// public class SchedulerService { - private sealed record RecurringJobDefinition( - string JobId, - Expression> Job, - string Cron, - string Description); - private readonly ILogger _logger; private readonly IBackgroundJobClient _jobClient; private readonly IRecurringJobManager _recurringJobManager; @@ -82,12 +76,12 @@ public class SchedulerService } } - private IReadOnlyList BuildRecurringJobs() => new[] + public IReadOnlyList GetRecurringJobDefinitions() => new[] { - new RecurringJobDefinition("daily-collection", () => RunDailyCollectionAsync(), "0 9 * * *", "Daily data collection"), - new RecurringJobDefinition("hourly-price-update", () => UpdatePricesAsync(), "0 9,11,13,15 * * 1-5", "Hourly price update"), - new RecurringJobDefinition("weekly-report", () => GenerateWeeklyReportAsync(), "0 17 * * 5", "Weekly report generation"), - new RecurringJobDefinition("monthly-optimization", () => RunMonthlyOptimizationAsync(), "0 2 1 * *", "Monthly optimization"), + new SchedulerJobDefinition("daily-collection", "0 9 * * *", "Daily data collection", true), + new SchedulerJobDefinition("hourly-price-update", "0 9,11,13,15 * * 1-5", "Hourly price update", true), + new SchedulerJobDefinition("weekly-report", "0 17 * * 5", "Weekly report generation", true), + new SchedulerJobDefinition("monthly-optimization", "0 2 1 * *", "Monthly optimization", true), }; private static string? FindGatherTradingDataJson() @@ -118,11 +112,11 @@ public class SchedulerService { _logger.LogInformation("Initializing Hangfire schedules..."); - foreach (var job in BuildRecurringJobs()) + foreach (var job in GetRecurringJobDefinitions()) { _recurringJobManager.AddOrUpdate( job.JobId, - job.Job, + ResolveRecurringJob(job.JobId), job.Cron, new RecurringJobOptions { TimeZone = TimeZoneInfo.Local } ); @@ -279,6 +273,15 @@ public class SchedulerService return JobStorage.Current.GetConnection().GetJobData(jobId)?.State; } + private Expression> ResolveRecurringJob(string jobId) => jobId switch + { + "daily-collection" => () => RunDailyCollectionAsync(), + "hourly-price-update" => () => UpdatePricesAsync(), + "weekly-report" => () => GenerateWeeklyReportAsync(), + "monthly-optimization" => () => RunMonthlyOptimizationAsync(), + _ => throw new InvalidOperationException($"Unknown recurring job id: {jobId}") + }; + /// /// Cancel scheduled job ///