From 21da79cb465129af066ae6b47a42861a937df101 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Sun, 12 Jul 2026 11:57:35 +0900 Subject: [PATCH] feat: ingest canonical JSON seeds into PostgreSQL --- .../Services/JsonSeedIngestionService.cs | 69 +++++++++++++++++++ src/dotnet/QuantEngine.Web/Program.cs | 2 + 2 files changed, 71 insertions(+) create mode 100644 src/dotnet/QuantEngine.Application/Services/JsonSeedIngestionService.cs diff --git a/src/dotnet/QuantEngine.Application/Services/JsonSeedIngestionService.cs b/src/dotnet/QuantEngine.Application/Services/JsonSeedIngestionService.cs new file mode 100644 index 00000000..f3c65e94 --- /dev/null +++ b/src/dotnet/QuantEngine.Application/Services/JsonSeedIngestionService.cs @@ -0,0 +1,69 @@ +using System.Text.Json; +using QuantEngine.Core.Interfaces; + +namespace QuantEngine.Application.Services; + +/// +/// JSON-first seed ingestion path. XLSX conversion remains an external +/// preparation step; the runtime application only reads canonical JSON and +/// persists normalized snapshots to PostgreSQL. +/// +public sealed class JsonSeedIngestionService +{ + private readonly GatherTradingDataParser _parser; + private readonly ICollectionRepository _repository; + private readonly ILogger _logger; + + public JsonSeedIngestionService( + GatherTradingDataParser parser, + ICollectionRepository repository, + ILogger logger) + { + _parser = parser; + _repository = repository; + _logger = logger; + } + + public async Task IngestAsync(string jsonPath, string runId) + { + var startedAt = DateTimeOffset.UtcNow; + var rows = _parser.ParseGatherTradingData(jsonPath); + await _repository.SaveRunAsync(new CollectionRunRecord( + runId, "RUNNING", startedAt.ToString("O"), null, rows.Count, 0)); + + var errors = 0; + foreach (var row in rows) + { + if (!row.TryGetValue("Ticker", out var tickerValue) || string.IsNullOrWhiteSpace(tickerValue?.ToString())) + { + errors++; + continue; + } + + var ticker = tickerValue.ToString()!; + await _repository.SaveSnapshotAsync(new CollectionSnapshotRecord( + runId, + "data_feed", + ticker, + "json_seed", + JsonSerializer.Serialize(row), + startedAt.ToString("O"))); + } + + var finishedAt = DateTimeOffset.UtcNow; + var status = rows.Count > 0 && errors == 0 ? "COMPLETED" : "COMPLETED_WITH_ERRORS"; + await _repository.UpdateRunStatusAsync(runId, status, finishedAt.ToString("O"), rows.Count - errors, errors); + _logger.LogInformation("JSON seed ingestion {RunId} completed: {Snapshots} snapshots, {Errors} errors", runId, rows.Count - errors, errors); + + return new CollectionRunResult + { + RunId = runId, + Status = status, + StartedAt = startedAt.ToString("O"), + FinishedAt = finishedAt.ToString("O"), + SuccessCount = rows.Count - errors, + ErrorCount = errors, + Rows = rows + }; + } +} diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index ec2b9ce7..77b95202 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -100,6 +100,8 @@ try builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); + builder.Services.AddSingleton(); + builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped();