# KIS Data Collection Python→.NET Migration WBS **프로젝트**: Python `kis_data_collection_v1.py` → C# `QuantEngine.Application` 포팅 + 코드 품질 개선 **시작**: 2026-07-05 **목표**: 완전한 기능 호환성 + SOLID + 정규화 + 테스트 커버리지 **성공 기준**: Python 테스트와 동등 검증 + 코드 리뷰 승인 --- ## 📋 전체 작업 분해 (WBS) ### **Phase 0: 기초 설계 & 분석** ✅ (현재 진행 중) - [x] 0.1: Python 코드 분석 (`kis_data_collection_v1.py` 436줄 읽음) - [x] 0.2: .NET 현황 분석 (`DataCollectionService.cs` 부분 구현) - [x] 0.3: DB 스키마 분석 (`DbMigrator.cs` 11개 테이블) - [x] 0.4: Python 테스트 분석 (`test_kis_data_collection_v1.py` 데이터 규칙) - [x] 0.5: 마이그레이션 전략 수립 (과유불급 SOLID) - [ ] 0.6: **이 WBS 문서 작성 및 검증** ← 현재 --- ### **Phase 1: 데이터 모델 정의** (4 tasks) #### 1.1: Core Entity Models 작성 **책임**: `QuantEngine.Core/Models/` 에 도메인 모델 정의 **입출력**: - **입력**: Python `kis_data_collection_v1.py` 라인 330-359 (`_collect_one` 반환값) - **출력**: C# 타입 정의 완료 - **파일**: - `CollectionSnapshot.cs` (정규화된 스냅샷) - `PriceCollectionResult.cs` (수집 결과) - `CollectionStatusEnum.cs` (OK, PARTIAL, ERROR) **성공 규칙 (데이터 증빙)**: ``` ✅ 체크리스트: 1. CollectionSnapshot에 Python _collect_one() 반환값의 모든 필드 포함 - ticker, name, sector, current_price, open, high, low, volume - price_status, orderbook_status, short_sale_status - collection_as_of (ISO 8601 KST) 2. 타입 안전성 - nullable fields는 `?` 명시 (price: double?, status: string) 3. Serialization 지원 - [JsonPropertyName] attribute로 Python 필드명 맵핑 4. 테스트 가능성 - 기본 생성자, 공개 속성 ``` **완료 기준**: ```csharp // 컴파일 성공, 타입 일관성, 스키마와 1:1 매핑 [Theory] [InlineData("005930", "삼성전자", "반도체")] public void CollectionSnapshot_SerializeDeserialize_RoundTrips(string ticker, string name, string sector) { var snapshot = new CollectionSnapshot { Ticker = ticker, Name = name, Sector = sector, CurrentPrice = 70000.5, PriceStatus = "OK" }; var json = JsonSerializer.Serialize(snapshot); var deserialized = JsonSerializer.Deserialize(json); Assert.Equal(ticker, deserialized.Ticker); Assert.Equal(70000.5, deserialized.CurrentPrice); } ``` --- #### 1.2: Price Source Result Model **책임**: 모든 price source의 통일된 응답 표현 **입출력**: - **입력**: Python 라인 128-179 (`_normalize_kis_fields` 반환값) - **출력**: C# PriceSourceResult 클래스 **성공 규칙**: ``` ✅ 체크리스트: 1. KIS API 응답 필드 포함 - current_price, open, high, low, volume - ask_1, bid_1, microstructure_pressure - short_turnover_share 2. Status 추적 - PriceStatus (OK, ERROR) - OrderbookStatus (OK, ERROR) - ShortSaleStatus (OK, ERROR) 3. Raw 데이터 보존 - current_price_raw, orderbook_raw, short_sale_raw (Dictionary) 4. 소스 식별 - source: enum (KIS, Naver, JSON) ``` **완료 기준**: ```csharp // Python _normalize_kis_fields() 결과와 동등한 C# 객체 var pythonResult = { "status": "OK", "current_price": 70000, "ask_1": 70100, "bid_1": 69900 }; var csharpResult = new PriceSourceResult { Status = "OK", CurrentPrice = 70000, Ask1 = 70100, Bid1 = 69900 }; // JSON 직렬화 동일 ``` --- #### 1.3: Collection Error Model **책임**: 에러 추적 구조화 **파일**: `CollectionErrorRecord.cs` (이미 Infrastructure에 있음 — 검증만) **성공 규칙**: ``` ✅ 체크리스트: 1. Python test_kis_data_collection_v1.py 라인 75-83 검증 - ticker, error 필드 2. 데이터베이스 스키마 (DbMigrator.cs 라인 94-106) 매핑 - run_id, ticker, source_name, error_kind, error_message ``` --- #### 1.4: Collection Run Summary Model **책임**: 수집 실행 종합 결과 **파일**: `CollectionRunResult.cs` (DataCollectionService.cs 라인 24-101 기존 코드) **성공 규칙**: ``` ✅ 체크리스트: 1. Python kis_data_collection_v1.py 라인 387-396 summary 구조 맵핑 2. JSON 직렬화 (Temp/kis_data_collection_v1.json 출력) - formula_id, run_id, started_at, finished_at - row_count, source_counts, errors, rows 3. 타입 안전성 - source_counts: Dictionary 또는 SortedDictionary ``` **완료 기준**: ```json { "formula_id": "KIS_DATA_COLLECTION_V1", "run_id": "abc123def456", "started_at": "2026-07-05T14:18:00+09:00", "finished_at": "2026-07-05T14:19:00+09:00", "row_count": 100, "source_counts": { "kis_open_api": 95, "gathertradingdata_json": 5 }, "errors": [], "rows": [ { "ticker": "005930", "name": "삼성전자", "sector": "반도체", "source_priority": "kis_open_api", "current_price": 70000 } ] } ``` --- ### **Phase 2: Price Source 추상화 (SOLID I, S)** (3 tasks) #### 2.1: IPriceSource 인터페이스 정의 **책임**: 모든 price source의 계약 정의 **파일**: `QuantEngine.Core/Interfaces/IPriceSource.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. 메서드 서명 Task GetPriceDataAsync(string ticker, string account); - ticker: 6자리 숫자 - account: "real" | "mock" - 반환: PriceSourceResult (status OK/ERROR 포함) 2. Liskov Substitution - 모든 구현이 같은 계약 준수 3. 에러 처리 - 네트워크 에러, 타임아웃, 데이터 파싱 에러를 처리하고 status="ERROR" 반환 ``` **완료 기준**: ```csharp public interface IPriceSource { string SourceName { get; } Task GetPriceDataAsync(string ticker, string account); } // 모든 구현이 이 계약을 따름 public class KisApiPriceSource : IPriceSource { public string SourceName => "kis_open_api"; public async Task GetPriceDataAsync(string ticker, string account) { try { /* ... */ } catch (Exception ex) { return new PriceSourceResult { Status = "ERROR", Error = ex.Message }; } } } ``` --- #### 2.2: KisApiPriceSource 구현 **책임**: Python `_normalize_kis_fields()` (라인 128-179) 포팅 **파일**: `QuantEngine.Application/Services/KisApiPriceSource.cs` **입출력**: - **입력**: - Python `_normalize_kis_fields(code, account)` 함수 - IKisApiClient (이미 있음) - **출력**: - C# KisApiPriceSource 클래스 (≈120줄) **성공 규칙 (데이터 증빙)**: ``` ✅ 체크리스트: 1. 기능 동등성 - Python 라인 137-147: 가격 조회 → C# GetCurrentPriceAsync() - Python 라인 151-163: 호가 조회 → C# GetAskingPrice10LevelAsync() - Python 라인 165-177: 공매도 조회 → C# GetDailyShortSaleAsync() 2. 데이터 정규화 - CoerceFloat() 유틸로 문자열→float 변환 - FindFirstValue() 유틸로 필드 탐색 (다중 경로 fallback) 3. 에러 처리 - 각 API 호출 별도 try-catch - status: "OK", "ERROR" 반환 4. 타입 안전성 - Dictionary 대신 PriceSourceResult 반환 5. 테스트 동등성 - Python test_kis_data_collection_v1.py 라인 44-62 테스트와 동등 ``` **완료 기준**: ```csharp [Fact] public async Task GetPriceDataAsync_WithValidKisCredentials_ReturnsPriceSourceResult() { // Python 테스트와 동등: _normalize_kis_fields() 반환값 검증 var result = await _kisSource.GetPriceDataAsync("005930", "mock"); Assert.Equal("OK", result.Status); Assert.NotNull(result.CurrentPrice); Assert.NotNull(result.Ask1); Assert.NotNull(result.Bid1); // JSON 직렬화 가능 (역정규화) var json = JsonSerializer.Serialize(result); Assert.NotEmpty(json); } ``` --- #### 2.3: NaverApiPriceSource 구현 (선택사항) **책임**: Python `_normalize_naver_price_history()` (라인 102-125) 포팅 (선택) **우선순위**: 낮음 (KIS만으로 충분 → 필요시 추가) **체크**: 일단 스킵, 필요시 Phase 4에 추가 --- ### **Phase 3: 데이터 정규화 레이어** (3 tasks) #### 3.1: DataNormalizationHelper 추출 **책임**: Python 유틸 함수 (라인 76-99) → C# 정적 메서드로 추출 **파일**: `QuantEngine.Application/Services/DataNormalizationHelper.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. CoerceFloat() — Python 라인 76-84 - null, "" → null 반환 - "1,234.56%" → 1234.56 변환 - 예외 → null 반환 2. FindFirstValue() — Python 라인 87-99 - 재귀적 탐색 (dict/list 모두 지원) - 첫 non-null 값 반환 3. 테스트 데이터 - Python test 라인 111 (CoerceFloat("1,234.5") == 1234.5) ``` **완료 기준**: ```csharp [Theory] [InlineData("1,234.56", 1234.56)] [InlineData("1,234.56%", 1234.56)] [InlineData(null, null)] [InlineData("", null)] public void CoerceFloat_WithVariousFormats_ParsesCorrectly(string? input, double? expected) { var result = DataNormalizationHelper.CoerceFloat(input); Assert.Equal(expected, result); } ``` --- #### 3.2: PriceDataNormalizer 구현 **책임**: Python `_collect_one()` (라인 330-359) 로직 → C# 메서드 **파일**: `QuantEngine.Application/Services/PriceDataNormalizer.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. 입력 (Python 라인 331-340) - row: 시드 데이터 한 행 (Ticker, Name, Sector) - kis: KIS API 결과 (또는 null) - naver: Naver API 결과 (또는 null) 2. 출력 - normalized: 정규화된 Dictionary - provenance: 소스 추적 정보 3. 소스 우선순위 (Python 라인 342-354) - KIS status=="OK" 있으면 kis_open_api 1순위 - Naver 있으면 naver_finance 추가 - 기본은 gathertradingdata_json 4. 데이터 폴백 (Python 라인 355) - 소스에서 누락된 필드는 row 데이터로 폴백 ``` **완료 기준**: ```csharp [Fact] public async Task NormalizeCollectionRow_WithKisAndNaver_ReturnsNormalizedData() { // Python test 라인 44-62 동등 var row = new { Ticker = "005930", Name = "삼성전자", Sector = "반도체" }; var kis = new PriceSourceResult { Status = "OK", CurrentPrice = 70000 }; var naver = new PriceSourceResult { Status = "OK", CurrentPrice = 65000 }; var (normalized, provenance) = _normalizer.NormalizeCollectionRow(row, kis, naver); Assert.Equal(70000, normalized["current_price"]); // KIS 우선 Assert.Equal(new[] { "kis_open_api", "naver_finance" }, provenance["source_priority"]); } ``` --- #### 3.3: SourcePriorityResolver 구현 **책임**: 소스별 우선순위 결정 (Python 라인 208-229 `_resolve_price_source`) **파일**: `QuantEngine.Application/Services/SourcePriorityResolver.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. 입력 - ticker: 식별자 - kis, naver: 각 소스 결과 - includeLiveKis, includeNaver: 플래그 2. 출력 - source_priority: List (정렬된) 3. 로직 (Python 라인 219-227) - KIS status=="OK" → kis_open_api 1순위 - Naver status=="OK" or "DATA_MISSING" → naver_finance 추가 4. 테스트 동등성 - Python test 라인 44-62 ``` --- ### **Phase 4: 컬렉션 오케스트레이터 (SOLID O, D)** (2 tasks) #### 4.1: ICollectionOrchestrator 인터페이스 **책임**: 메인 파이프라인의 계약 **파일**: `QuantEngine.Core/Interfaces/ICollectionOrchestrator.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. 메서드 Task RunCollectionAsync( string runId, string account, List tickers) 2. 의존성 주입 가능 (테스트 목 용이) 3. 에러 처리 - 개별 종목 에러 → 계속 진행 (robust) - 치명적 에러 → 실패 상태로 마무리 ``` --- #### 4.2: KisDataCollectionOrchestrator 구현 **책임**: Python `collect_to_sqlite()` (라인 361-436) 포팅 **파일**: `QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs` **입출력**: - **입력**: - runId, account, tickers - GatherTradingData.json (시드 데이터) - **출력**: - CollectionRunResult - Temp/kis_data_collection_v1.json (JSON 파일) - DB 저장 (kis_collection_runs, kis_collection_snapshots, kis_collection_errors) **성공 규칙 (데이터 증빙)**: ``` ✅ 체크리스트: 1. 시드 데이터 로드 (Python 라인 182-199) - GatherTradingData.json 파싱 - data.data_feed[] 배열 - core_satellite merge 2. 종목별 수집 루프 (Python 라인 399-435) - 각 종목마다 PriceSourceResult 수집 - 정규화 및 저장 - 에러 추적 3. 결과 요약 (Python 라인 303-327) - started_at, finished_at (KST) - source_counts 집계 - 상태: PASS / PASS_WITH_WARNINGS / FAIL 4. JSON 출력 (Python 라인 309-312) - Temp/kis_data_collection_v1.json 생성 - UTF-8, indent=2 5. DB 저장 (Python 라인 313-326) - collection_runs 테이블 - collection_snapshots 테이블 - collection_source_errors 테이블 6. 테스트 동등성 - Python test_kis_data_collection_v1.py 라인 39-83 (모든 케이스) ``` **완료 기준**: ```csharp [Fact] public async Task RunCollectionAsync_WithValidSeedAndKisAccount_ReturnsSuccessAndCreatesJson() { // Python test 라인 39-83 동등 var result = await _orchestrator.RunCollectionAsync( runId: "test-run-123", account: "mock", tickers: new[] { "005930", "000660" }.ToList() ); // 1. 결과 검증 Assert.Equal("COMPLETED", result.Status); Assert.True(result.SuccessCount > 0); // 2. JSON 파일 생성 확인 var jsonPath = Path.Combine(Path.GetTempPath(), "kis_data_collection_v1.json"); Assert.True(File.Exists(jsonPath)); var json = JsonDocument.Parse(File.ReadAllText(jsonPath)); Assert.Equal("KIS_DATA_COLLECTION_V1", json.RootElement.GetProperty("formula_id").GetString()); // 3. DB 저장 확인 var runs = await _repository.GetRunsByIdAsync("test-run-123"); Assert.Single(runs); } ``` --- ### **Phase 5: 시드 데이터 파서** (1 task) #### 5.1: GatherTradingDataParser 구현 **책임**: Python `_build_seed_rows()` (라인 182-199) 포팅 **파일**: `QuantEngine.Application/Services/GatherTradingDataParser.cs` **성공 규칙**: ``` ✅ 체크리스트: 1. 입력 형식 { "data": { "data_feed": [ { "Ticker": "005930", "Name": "삼성전자", ... } ], "core_satellite": [ { "Ticker": "005930", "Sector": "반도체" } ] } } 2. 병합 로직 (Python 라인 185-197) - data_feed와 core_satellite를 Ticker로 병합 - core_satellite 필드를 data_feed 행에 추가 3. 검증 - Ticker 필수 (비어있으면 스킵) - Name, Sector는 선택 4. 테스트 동등성 - Python test 라인 39-42 (_build_seed_rows) ``` **완료 기준**: ```csharp [Fact] public void ParseGatherTradingData_WithCoreAndSatellite_MergesCorrectly() { // Python test 라인 39-42 동등 var json = JsonDocument.Parse(@" { ""data"": { ""data_feed"": [{ ""Ticker"": ""005930"", ""Name"": ""삼성전자"" }], ""core_satellite"": [{ ""Ticker"": ""005930"", ""Sector"": ""반도체"" }] } }"); var rows = _parser.ParseGatherTradingData(json); Assert.Single(rows); Assert.Equal("005930", rows[0]["Ticker"]); Assert.Equal("삼성전자", rows[0]["Name"]); Assert.Equal("반도체", rows[0]["Sector"]); } ``` --- ### **Phase 6: 통합 & 엔드포인트** (2 tasks) #### 6.1: DataCollectionService 통합 리팩토링 **책임**: 기존 DataCollectionService.cs 개선 (라인 1-230) **파일**: `QuantEngine.Application/Services/DataCollectionService.cs` **개선 사항**: ``` ✅ 체크리스트: 1. 의존성 주입 - ICollectionOrchestrator 추가 - IPriceSource[] 제거 (Orchestrator가 관리) 2. 메서드 분리 - RunCollectionAsync() → 직접 구현 X, Orchestrator 위임 - CollectOneAsync() → 유틸만 (테스트용) 3. 에러 처리 구조화 - Generic Exception → PriceCollectionException, DataValidationException 4. 로깅 - ILogger 주입 ``` **완료 기준**: ```csharp public class DataCollectionService { private readonly ICollectionOrchestrator _orchestrator; private readonly ILogger _logger; public async Task RunCollectionAsync( string runId, string account, List tickers) { _logger.LogInformation("Starting collection run {RunId}", runId); try { return await _orchestrator.RunCollectionAsync(runId, account, tickers); } catch (Exception ex) { _logger.LogError(ex, "Collection run {RunId} failed", runId); throw; } } } ``` --- #### 6.2: API 엔드포인트 추가 (선택) **책임**: HTTP 엔드포인트 (POST /api/collection/run) **파일**: `QuantEngine.Web/Endpoints/CollectionEndpoints.cs` (이미 있음 — 확장) **성공 규칙**: ``` ✅ 체크리스트: 1. 요청 POST /api/collection/run { "account": "mock", "tickers": ["005930", "000660"] } 2. 응답 { "runId": "...", "status": "COMPLETED", "successCount": 2, "errorCount": 0, "startedAt": "2026-07-05T14:18:00+09:00" } 3. 에러 처리 - 400: 잘못된 account - 500: 내부 에러 ``` --- ### **Phase 7: 테스트 & 검증** (3 tasks) #### 7.1: Unit Tests (DataNormalizationHelper, Parsers) **파일**: `QuantEngine.Application.Tests/Services/DataNormalizationHelperTests.cs` **범위**: 300-400줄 (Python test 동등성) **성공 규칙**: ``` ✅ 체크리스트: 1. DataNormalizationHelper - CoerceFloat (10 test cases) - FindFirstValue (8 test cases) 2. GatherTradingDataParser - Basic parsing (3 cases) - Core-satellite merge (2 cases) - Invalid input (2 cases) 3. SourcePriorityResolver - KIS only (1 case) - KIS + Naver (1 case) - Naver only (1 case) 4. PriceDataNormalizer - With KIS (1 case) - With Naver (1 case) - Fallback to JSON (1 case) 5. 커버리지 - 목표: ≥85% 라인 커버리지 - 신규 클래스: 100% 커버리지 ``` **완료 기준**: ```bash dotnet test QuantEngine.Application.Tests --collect:"XPlat Code Coverage" # 결과: Lines: 85%+ ✅ ``` --- #### 7.2: Integration Tests (KisDataCollectionOrchestrator) **파일**: `QuantEngine.Application.Tests/Integration/KisDataCollectionOrchestratorTests.cs` **범위**: 200-300줄 **성공 규칙 (데이터 증빙)**: ``` ✅ 체크리스트: 1. Happy Path - Mock KIS API + valid GatherTradingData.json - status = "COMPLETED", successCount > 0 2. Partial Failure - 1개 종목 에러, 나머지 성공 - status = "COMPLETED_WITH_ERRORS" 3. JSON Output - Temp/kis_data_collection_v1.json 생성 - 구조 검증 (formula_id, run_id, rows 배열) 4. DB Persistence - kis_collection_runs 행 생성 - kis_collection_snapshots 행 수 = successCount - kis_collection_source_errors 행 수 = errorCount 5. Python 동등성 - kis_data_collection_v1.py test와 동일 시나리오 재현 ``` **완료 기준**: ```csharp [Fact] public async Task KisDataCollectionOrchestrator_RunCollection_ProducesIdenticalOutputToPython() { // Python test test_kis_data_collection_v1.py::test_persist_collection_row_and_failure_helpers // C# 동등 재현 var result = await _orchestrator.RunCollectionAsync("run-1", "mock", new { "005930" }.ToList()); // 1. 상태 확인 Assert.NotNull(result.Status); Assert.True(result.SuccessCount >= 0); // 2. JSON 파일 확인 var json = JsonDocument.Parse(File.ReadAllText(...)); Assert.NotNull(json.RootElement.GetProperty("run_id")); // 3. DB 확인 var run = await _repo.GetRunByIdAsync(result.RunId); Assert.NotNull(run); Assert.Equal("COMPLETED", run.Status); } ``` --- #### 7.3: E2E Test (API → DB → UI) **파일**: `QuantEngine.Web.Tests/E2E/CollectionEndpointTests.cs` **범위**: 100-150줄 **성공 규칙**: ``` ✅ 체크리스트: 1. HTTP 요청 POST /api/collection/run { "account": "mock", "tickers": ["005930"] } 2. HTTP 응답 status 200, body.status == "COMPLETED" 3. 부수 효과 - Temp/kis_data_collection_v1.json 파일 생성 - kis_collection_runs DB 행 생성 - kis_collection_snapshots DB 행 생성 4. 타이밍 - 응답 시간 < 30초 (3개 API 호출) ``` --- ### **Phase 8: 코드 리뷰 & 최종화** (2 tasks) #### 8.1: Code Review & Refactoring **책임**: 스스로 코드 검토, SOLID 원칙 재확인 **체크리스트**: ``` ✅ 코드 품질 검사: 1. SOLID 원칙 - S: DataCollectionService 단일 책임 ✓ - O: IPriceSource로 확장 가능 ✓ - L: 모든 구현이 계약 준수 ✓ - I: 필요한 메서드만 expose ✓ - D: 인터페이스에 의존 ✓ 2. 중복 제거 - 유틸 함수 (CoerceFloat, FindFirstValue) 1곳만 - 에러 처리 패턴 일관성 3. 타입 안전성 - Dictionary → Model classes로 변환 - Nullable 필드 명시 (?) 4. 성능 - 불필요한 배열 copy 제거 - 큰 JSON 파일 스트리밍 (필요시) 5. 테스트 가능성 - 모든 의존성 주입 가능 - Mock 가능 6. 문서화 - XML doc comments 추가 (public API) ``` **완료 기준**: ```bash # 정적 분석 dotnet build /p:TreatWarningsAsErrors=true # 0 errors, 0 warnings # 테스트 커버리지 dotnet test --collect:"XPlat Code Coverage" # Lines: ≥85% # 코드 리뷰 체크리스트 통과 # - 변수명 명확성 ✓ # - 함수/메서드 크기 ≤50줄 ✓ # - 복잡도 <= 10 ✓ ``` --- #### 8.2: 최종 검증 & 문서화 **책임**: 모든 성공 기준 재확인, 문서 작성 **체크리스트**: ``` ✅ 최종 검증: 1. 기능 완성도 - Python 336줄 → C# ≈450-550줄 (타입 추가로 인한 증가) - 모든 Python 기능 포팅 ✓ 2. 성능 - 단일 종목 수집: < 2초 - 100개 종목 수집: < 120초 3. 호환성 - GatherTradingData.json 읽음 ✓ - kis_collection_runs/snapshots/errors 저장 ✓ - Temp/kis_data_collection_v1.json 생성 ✓ 4. 안정성 - 네트워크 에러 처리 ✓ - NULL 값 처리 ✓ - 부분 실패 시에도 진행 ✓ 5. 문서 - README 작성 (아키텍처, 사용법, 확장 방법) - API 문서 (Swagger/OpenAPI) ``` **출력물**: ``` - ✅ docs/KIS_DATA_COLLECTION_ARCHITECTURE.md - ✅ docs/KIS_DATA_COLLECTION_API.md - ✅ CODE_REVIEW_CHECKLIST.md ``` --- ## 📊 진행 상황 추적 | Phase | Task | 상태 | 완료 기한 | 담당 | |-------|------|------|---------|------| | 0 | 기초 설계 분석 | ✅ | 2026-07-05 | Claude | | 1.1 | Core Entity Models | ⬜ | 2026-07-05 | → | | 1.2 | PriceSourceResult | ⬜ | 2026-07-05 | → | | 1.3 | CollectionErrorRecord | ✅ | 2026-07-05 | ✓ | | 1.4 | CollectionRunResult | 🔄 | 2026-07-05 | Claude | | 2.1 | IPriceSource 인터페이스 | ⬜ | 2026-07-05 | → | | 2.2 | KisApiPriceSource | ⬜ | 2026-07-06 | → | | 2.3 | NaverApiPriceSource | ⏸️ | 2026-07-07 | (선택) | | 3.1 | DataNormalizationHelper | ⬜ | 2026-07-05 | → | | 3.2 | PriceDataNormalizer | ⬜ | 2026-07-06 | → | | 3.3 | SourcePriorityResolver | ⬜ | 2026-07-06 | → | | 4.1 | ICollectionOrchestrator | ⬜ | 2026-07-06 | → | | 4.2 | KisDataCollectionOrchestrator | ⬜ | 2026-07-07 | → | | 5.1 | GatherTradingDataParser | ⬜ | 2026-07-06 | → | | 6.1 | DataCollectionService 통합 | ⬜ | 2026-07-07 | → | | 6.2 | API 엔드포인트 (선택) | ⏸️ | 2026-07-08 | (선택) | | 7.1 | Unit Tests | ⬜ | 2026-07-07 | → | | 7.2 | Integration Tests | ⬜ | 2026-07-08 | → | | 7.3 | E2E Tests | ⬜ | 2026-07-08 | → | | 8.1 | Code Review & Refactoring | ⬜ | 2026-07-08 | → | | 8.2 | 최종 검증 & 문서화 | ⬜ | 2026-07-09 | → | **범례**: ✅=완료, 🔄=진행중, ⬜=대기, ⏸️=선택사항 --- ## 🎯 성공 기준 (데이터 증빙) ### 기능 동등성 ``` ✅ Python vs C# 동등 검증: 1. 입출력 시그니처 collect_to_sqlite(...) → RunCollectionAsync(...) 같은 파라미터, 같은 반환값 구조 2. 데이터 흐름 GatherTradingData.json (입력) → 시드 데이터 파싱 → KIS API 호출 (3개 endpoint) → 데이터 정규화 → DB 저장 (3개 테이블) → JSON 출력 (Temp/kis_data_collection_v1.json) 3. 에러 처리 Python test_kis_data_collection_v1.py 모든 케이스 통과 ``` ### 코드 품질 ``` ✅ SOLID 원칙: 1. Single Responsibility ✓ - DataCollectionService: 오케스트레이션만 - PriceDataNormalizer: 정규화만 - GatherTradingDataParser: 파싱만 2. Open/Closed ✓ - IPriceSource 추가 시 기존 코드 수정 X - NaverApiPriceSource 추가 가능 3. Liskov Substitution ✓ - KisApiPriceSource, NaverApiPriceSource 모두 IPriceSource 준수 4. Interface Segregation ✓ - IPriceSource: 3 메서드만 (GetPriceDataAsync) - ICollectionOrchestrator: 2 메서드 (RunCollectionAsync, ...) 5. Dependency Inversion ✓ - 구체적 클래스 X, 인터페이스에 의존 ``` ### 테스트 커버리지 ``` ✅ 목표: ≥85% 라인 커버리지 1. Unit Tests: 20+ test cases - CoerceFloat (10) - FindFirstValue (8) - GatherTradingDataParser (5) - SourcePriorityResolver (3) - PriceDataNormalizer (3) 2. Integration Tests: 5+ scenarios - Happy path - Partial failure - All errors - JSON output - DB persistence 3. E2E Tests: 3+ flows - POST /api/collection/run - File creation - DB verification ``` ### 성능 기준 ``` ✅ 성능 목표: 1. 단일 종목 수집 - 목표: < 2초 - KIS API 3개 호출 포함 2. 배치 수집 (100개 종목) - 목표: < 120초 - 평균 1.2초/종목 3. JSON 파일 크기 - 목표: < 10MB (100개 종목) ``` ### 호환성 검증 ``` ✅ Python 동등성: 1. 입력 형식 GatherTradingData.json 구조 100% 호환 2. 출력 형식 Temp/kis_data_collection_v1.json 구조 100% 동일 - JSON 필드명, 타입, 순서 3. DB 스키마 kis_collection_runs, snapshots, errors 모두 호환 4. 에러 처리 Python과 동일한 에러 메시지, status 코드 ``` --- ## 📝 진행 방식 ### 매 Phase마다 1. **Task 시작 전**: 성공 기준 재확인 2. **Task 진행 중**: WBS의 체크리스트 항목 하나씩 수행 3. **Task 완료 후**: - 코드 자가 검토 - 관련 테스트 작성 및 통과 - WBS 문서에 완료 체크 표시 4. **최종 검증**: 이 파일의 진행 상황 표 업데이트 ### 커밋 규칙 ``` Format: .: <변경사항> — <성공기준 1개> 예시: 1.1: Add CollectionSnapshot model — JSON serialization works ✅ 2.2: Implement KisApiPriceSource — Test passes vs Python ✅ 7.1: Add unit tests for DataNormalizationHelper — 85% coverage ✅ ``` ### 블록 상황 처리 ``` 1. 구현 중 막히면? - WBS 해당 Task의 "성공 규칙" 다시 읽기 - Python 원본 코드 라인 번호 재확인 - 테스트 케이스로 구현하기 (TDD) 2. 테스트 실패? - Python test 다시 실행 (비교) - 데이터 타입/값 불일치 확인 - 로깅 추가해서 디버그 ``` --- ## 📎 참고 - **Python 원본**: `src/quant_engine/kis_data_collection_v1.py` (436줄) - **Python 테스트**: `tests/unit/test_kis_data_collection_v1.py` (87줄) - **DB 스키마**: `src/dotnet/QuantEngine.Infrastructure/Data/DbMigrator.cs` (라인 59-106) - **기존 .NET**: `src/dotnet/QuantEngine.Application/Services/DataCollectionService.cs`