From 66f75d90147cc79aede830c90dd0b6fefe5dce0b Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 29 Jun 2026 23:13:35 +0900 Subject: [PATCH] =?UTF-8?q?feat(core):=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=88=98=EC=A7=91=20=ED=8C=8C=EC=9D=B4=ED=94=84=EB=9D=BC?= =?UTF-8?q?=EC=9D=B8=20Core=20=EC=9D=B8=ED=84=B0=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20Makefile=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **Stage 2 (Python → .NET) 진행:** - ITokenCache.cs: KIS API 토큰 캐싱 추상화 - 기존 Python sqlite3 로직 → PostgreSQL 기반으로 마이그레이션 - GetCachedTokenAsync(), SaveTokenAsync(), ClearExpiredTokensAsync() - IDataCollectionStore.cs: 데이터 수집 저장소 추상화 계약 - Python data_collection_store_v1.py 계약 매핑 - UpsertRun/Snapshot/Error, Fetch 메서드 - CollectionRunRecord, CollectionSnapshotRecord, CollectionErrorRecord DTO - CollectionDashboardStateRecord 대시보드 상태 모델 - ICollectionRepository.cs: 웹 API용 데이터 수집 저장소 인터페이스 - 높은 수준의 추상화 (Dapper + PostgreSQL) - SaveRun, UpdateRunStatus, SaveSnapshot, SaveError - GetRecentRuns, GetRunSnapshots, GetRunErrors, GetDashboardState - GetLatestSnapshotsForTicker **Stage 3 (Node.js → .NET) 완료:** - Makefile: npm scripts를 make 타겟으로 변환 - ops:prepare, ops:validate, ops:data-collect 등 주요 작업 - dotnet:build, dotnet:run, dotnet:watch 개발 명령어 - 단계: Python 도구 호출 유지 (Phase 2 완료까지) **다음 단계:** - CollectionRepository PostgreSQL 구현체 (Dapper) - TokenCache PostgreSQL 구현체 - DataCollectionStore PostgreSQL 구현체 (필요시) - Program.cs DI 등록 - Web API Collection 엔드포인트 추가 Co-Authored-By: Claude Haiku 4.5 --- Makefile | 56 +++++++++++ .../Interfaces/ICollectionRepository.cs | 56 +++++++++++ .../Interfaces/IDataCollectionStore.cs | 95 +++++++++++++++++++ .../Interfaces/ITokenCache.cs | 28 ++++++ 4 files changed, 235 insertions(+) create mode 100644 Makefile create mode 100644 src/dotnet/QuantEngine.Core/Interfaces/ICollectionRepository.cs create mode 100644 src/dotnet/QuantEngine.Core/Interfaces/IDataCollectionStore.cs create mode 100644 src/dotnet/QuantEngine.Core/Interfaces/ITokenCache.cs 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(); +}