diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6f3c602 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +.PHONY: help ops:prepare ops:validate ops:build ops:data-collect ops:render ops:release ops:package full-gate + +help: + @echo "QuantEngine v0.1 — Operations CLI" + @echo "" + @echo "Core operations:" + @echo " make ops:render — Render operational report from packet" + @echo " make ops:validate — Validate release pipeline" + @echo " make ops:release — Full release DAG" + @echo " make ops:package — Package for deployment" + @echo " make full-gate — Strict validation (all gates must PASS)" + @echo "" + @echo "Data operations:" + @echo " make ops:prepare — Convert XLSX → JSON" + @echo " make ops:data-collect — KIS data collection" + @echo "" + @echo "Development:" + @echo " make dotnet:build — Build .NET projects" + @echo " make dotnet:run — Run Web API (port 8788)" + @echo " make dotnet:watch — Hot-reload API server" + +ops:prepare: + python tools/convert_xlsx_to_json.py + +ops:validate: + python tools/run_release_dag_v3.py --mode release + +ops:build: + python tools/build_bundle.py + +ops:data-collect: + python tools/run_kis_data_collection_v1.py --input-json GatherTradingData.json --sqlite-db src/quant_engine/kis_data_collection.db --output-json Temp/kis_data_collection_v1.json --kis-account real + +ops:render: + dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json + +ops:release: + python tools/run_release_dag_v3.py --mode full + +ops:package: + python tools/refresh_trading_calendar.py && python tools/prepare_upload_zip.py --validation-mode release + +full-gate: + python tools/run_release_dag_v3.py --mode release --strict + +dotnet:build: + cd src/dotnet && dotnet build + +dotnet:run: + cd src/dotnet && dotnet run --project src/DataFeed.Api/QuantEngine.Web/QuantEngine.Web.csproj + +dotnet:watch: + cd src/dotnet && dotnet watch run --project src/QuantEngine.Web/QuantEngine.Web.csproj + +dotnet:test: + cd src/dotnet && dotnet test diff --git a/src/dotnet/QuantEngine.Core/Interfaces/ICollectionRepository.cs b/src/dotnet/QuantEngine.Core/Interfaces/ICollectionRepository.cs new file mode 100644 index 0000000..bbfe926 --- /dev/null +++ b/src/dotnet/QuantEngine.Core/Interfaces/ICollectionRepository.cs @@ -0,0 +1,56 @@ +namespace QuantEngine.Core.Interfaces; + +/// +/// Data collection repository (Dapper + PostgreSQL). +/// Higher-level abstraction over IDataCollectionStore for Web API consumers. +/// +public interface ICollectionRepository +{ + /// + /// Save new collection run. + /// + Task SaveRunAsync(CollectionRunRecord run); + + /// + /// Update run with completion status. + /// + Task UpdateRunStatusAsync(string runId, string status, string? finishedAt = null, int? totalSnapshots = null, int? totalErrors = null); + + /// + /// Save collection snapshot. + /// + Task SaveSnapshotAsync(CollectionSnapshotRecord snapshot); + + /// + /// Save collection error. + /// + Task SaveErrorAsync(CollectionErrorRecord error); + + /// + /// Fetch recent collection runs for UI dashboard. + /// + /// Number of runs to return (default: 20) + Task> GetRecentRunsAsync(int limit = 20); + + /// + /// Fetch snapshots for a specific run. + /// + Task> GetRunSnapshotsAsync(string runId); + + /// + /// Fetch errors for a specific run. + /// + /// Run ID + /// Max errors to return (default: 50) + Task> GetRunErrorsAsync(string runId, int limit = 50); + + /// + /// Get collection pipeline dashboard state for Web UI. + /// + Task GetDashboardStateAsync(); + + /// + /// Fetch latest snapshots for a ticker across all datasets. + /// + Task> GetLatestSnapshotsForTickerAsync(string ticker, int limit = 10); +} diff --git a/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs b/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs new file mode 100644 index 0000000..e12db28 --- /dev/null +++ b/src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs @@ -0,0 +1,95 @@ +namespace QuantEngine.Core.Interfaces; + +/// +/// Data collection storage abstraction layer. +/// Maps Python data_collection_store_v1.py contracts to .NET. +/// Supports PostgreSQL backend for production. +/// +public interface IDataCollectionStore +{ + /// + /// Initialize storage tables and schema (idempotent). + /// + Task InitializeAsync(); + + /// + /// Insert or update collection run record. + /// + Task UpsertRunAsync(CollectionRunRecord run); + + /// + /// Insert collection snapshot record. + /// + Task UpsertSnapshotAsync(CollectionSnapshotRecord snapshot); + + /// + /// Append error record from collection attempt. + /// + Task AppendErrorAsync(CollectionErrorRecord error); + + /// + /// Fetch recent collection runs for dashboard. + /// + /// Max number of runs to return + Task> FetchRecentRunsAsync(int limit = 20); + + /// + /// Fetch latest snapshots for a ticker/dataset combination. + /// + Task> FetchLatestSnapshotsAsync(string ticker, string? datasetName = null, int limit = 10); + + /// + /// Get collection pipeline dashboard state. + /// + Task GetDashboardStateAsync(); +} + +/// +/// Collection run record (maps Python CollectionRun). +/// +public record CollectionRunRecord( + string RunId, + string Status, + string StartedAt, + string? FinishedAt = null, + int? TotalSnapshots = null, + int? TotalErrors = null, + string UpdatedAt = "" +); + +/// +/// Collection snapshot record (maps Python CollectionSnapshot). +/// +public record CollectionSnapshotRecord( + string RunId, + string DatasetName, + string Ticker, + string SourceName, + string PayloadJson, + string CapturedAt, + string CreatedAt = "" +); + +/// +/// Collection error record (maps Python CollectionSourceError). +/// +public record CollectionErrorRecord( + string RunId, + string SourceName, + string ErrorKind, + string ErrorMessage, + string Ticker = "", + string CreatedAt = "" +); + +/// +/// Dashboard state summary. +/// +public record CollectionDashboardStateRecord( + string? LastRunId, + string? LastRunStatus, + string? LastFinishedAt, + int TotalSnapshots, + int TotalErrors, + List RecentErrors +); diff --git a/src/dotnet/QuantEngine.Core/Interfaces/ITokenCache.cs b/src/dotnet/QuantEngine.Core/Interfaces/ITokenCache.cs new file mode 100644 index 0000000..646e7b2 --- /dev/null +++ b/src/dotnet/QuantEngine.Core/Interfaces/ITokenCache.cs @@ -0,0 +1,28 @@ +namespace QuantEngine.Core.Interfaces; + +/// +/// Token caching for KIS API authentication. +/// Replaces Python's sqlite3-based token storage with PostgreSQL. +/// +public interface ITokenCache +{ + /// + /// Retrieve cached access token if valid and not near expiration. + /// + /// Account type: "real" or "mock" + /// Access token if cached and valid; null if expired or missing + Task GetCachedTokenAsync(string account); + + /// + /// Store access token with expiration time. + /// + /// Account type: "real" or "mock" + /// Access token + /// Token expiration time (UTC) + Task SaveTokenAsync(string account, string token, DateTime expiresAt); + + /// + /// Clear expired tokens and stale entries. + /// + Task ClearExpiredTokensAsync(); +}