diff --git a/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs b/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs index 7d3888b3..ec5304fe 100644 --- a/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs +++ b/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs @@ -63,6 +63,11 @@ public interface ICollectionReadRepository Task> GetPriceHistorySummaryAsync(); } +public interface ICollectionSchemaInitializer +{ + Task InitializeAsync(); +} + /// /// Collection run record (maps Python CollectionRun). /// diff --git a/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionRepository.cs b/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionRepository.cs index e23cc7cf..84746b42 100644 --- a/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionRepository.cs +++ b/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionRepository.cs @@ -37,7 +37,6 @@ namespace QuantEngine.Infrastructure.Repositories public async Task SaveRunAsync(CollectionRunRecord run) { - await EnsureTablesAsync(); await ExecuteAsync(@" INSERT INTO quantengine.kis_collection_runs (run_id, status, started_at, finished_at, total_snapshots, total_errors, updated_at) VALUES (@RunId, @Status, @StartedAt, @FinishedAt, @TotalSnapshots, @TotalErrors, @UpdatedAt) @@ -198,45 +197,5 @@ namespace QuantEngine.Infrastructure.Repositories ); } - private async Task EnsureTablesAsync() - { - await ExecuteAsync(@" - CREATE TABLE IF NOT EXISTS quantengine.kis_collection_runs ( - run_id TEXT PRIMARY KEY, - status TEXT NOT NULL, - started_at TEXT NOT NULL, - finished_at TEXT, - total_snapshots INTEGER, - total_errors INTEGER, - updated_at TEXT NOT NULL - ); - - CREATE TABLE IF NOT EXISTS quantengine.kis_collection_snapshots ( - run_id TEXT NOT NULL, - dataset_name TEXT, - ticker TEXT NOT NULL, - source_name TEXT NOT NULL, - payload_json TEXT NOT NULL, - captured_at TEXT NOT NULL, - created_at TEXT NOT NULL, - PRIMARY KEY (run_id, ticker, source_name) - ); - - CREATE TABLE IF NOT EXISTS quantengine.kis_collection_errors ( - id SERIAL PRIMARY KEY, - run_id TEXT NOT NULL, - source_name TEXT NOT NULL, - error_kind TEXT NOT NULL, - error_message TEXT, - ticker TEXT, - created_at TEXT NOT NULL - ); - - CREATE INDEX IF NOT EXISTS idx_kis_runs_started_at ON quantengine.kis_collection_runs(started_at DESC); - CREATE INDEX IF NOT EXISTS idx_kis_snapshots_ticker ON quantengine.kis_collection_snapshots(ticker); - CREATE INDEX IF NOT EXISTS idx_kis_snapshots_captured_at ON quantengine.kis_collection_snapshots(captured_at DESC); - CREATE INDEX IF NOT EXISTS idx_kis_errors_run_id ON quantengine.kis_collection_errors(run_id); - "); - } } } diff --git a/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionSchemaInitializer.cs b/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionSchemaInitializer.cs new file mode 100644 index 00000000..c4f78db5 --- /dev/null +++ b/src/dotnet/QuantEngine.Infrastructure/Repositories/CollectionSchemaInitializer.cs @@ -0,0 +1,57 @@ +using Dapper; +using QuantEngine.Core.Interfaces; +using QuantEngine.Infrastructure.Data; + +namespace QuantEngine.Infrastructure.Repositories; + +public sealed class CollectionSchemaInitializer : ICollectionSchemaInitializer +{ + private readonly IDbConnectionFactory _connectionFactory; + + public CollectionSchemaInitializer(IDbConnectionFactory connectionFactory) + { + _connectionFactory = connectionFactory; + } + + public async Task InitializeAsync() + { + using var conn = _connectionFactory.CreateConnection(); + await conn.ExecuteAsync(@" + CREATE TABLE IF NOT EXISTS quantengine.kis_collection_runs ( + run_id TEXT PRIMARY KEY, + status TEXT NOT NULL, + started_at TEXT NOT NULL, + finished_at TEXT, + total_snapshots INTEGER, + total_errors INTEGER, + updated_at TEXT NOT NULL + ); + + CREATE TABLE IF NOT EXISTS quantengine.kis_collection_snapshots ( + run_id TEXT NOT NULL, + dataset_name TEXT, + ticker TEXT NOT NULL, + source_name TEXT NOT NULL, + payload_json TEXT NOT NULL, + captured_at TEXT NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (run_id, ticker, source_name) + ); + + CREATE TABLE IF NOT EXISTS quantengine.kis_collection_errors ( + id SERIAL PRIMARY KEY, + run_id TEXT NOT NULL, + source_name TEXT NOT NULL, + error_kind TEXT NOT NULL, + error_message TEXT, + ticker TEXT, + created_at TEXT NOT NULL + ); + + CREATE INDEX IF NOT EXISTS idx_kis_runs_started_at ON quantengine.kis_collection_runs(started_at DESC); + CREATE INDEX IF NOT EXISTS idx_kis_snapshots_ticker ON quantengine.kis_collection_snapshots(ticker); + CREATE INDEX IF NOT EXISTS idx_kis_snapshots_captured_at ON quantengine.kis_collection_snapshots(captured_at DESC); + CREATE INDEX IF NOT EXISTS idx_kis_errors_run_id ON quantengine.kis_collection_errors(run_id); + "); + } +} diff --git a/src/dotnet/QuantEngine.Web/Program.cs b/src/dotnet/QuantEngine.Web/Program.cs index d475b413..3ab7b6a1 100644 --- a/src/dotnet/QuantEngine.Web/Program.cs +++ b/src/dotnet/QuantEngine.Web/Program.cs @@ -111,6 +111,7 @@ try builder.Services.AddScoped(sp => sp.GetRequiredService()); builder.Services.AddScoped(sp => sp.GetRequiredService()); builder.Services.AddScoped(sp => sp.GetRequiredService()); + builder.Services.AddSingleton(); builder.Services.AddScoped(); builder.Services.AddSingleton(); builder.Services.AddScoped(); @@ -151,10 +152,12 @@ try var migrator = scope.ServiceProvider.GetRequiredService(); var workspaceRepo = scope.ServiceProvider.GetRequiredService(); var collectionRepo = scope.ServiceProvider.GetRequiredService(); + var collectionSchemaInitializer = scope.ServiceProvider.GetRequiredService(); var tokenCache = scope.ServiceProvider.GetRequiredService(); try { + await collectionSchemaInitializer.InitializeAsync(); migrator.Migrate(); await workspaceRepo.GetAccountsAsync(); await collectionRepo.GetDashboardStateAsync();