feat: add quant engine WBS verification harness
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
# QuantEngine 수집 파이프라인 (KIS API)
|
||||
|
||||
## 1. 수집 실행 상태 전이도 (State Diagram)
|
||||
|
||||
KIS 데이터 수집 실행(kis_collection_runs)의 상태 흐름. 상태값은 KisDataCollectionOrchestrator 에서 정의:
|
||||
- `RUNNING`: 수집 진행 중
|
||||
- `COMPLETED`: 모든 스냅샷 수집 완료 (에러 없음, `total_errors == 0`)
|
||||
- `COMPLETED_WITH_ERRORS`: 부분 수집 완료 (에러 발생, `total_errors > 0`이지만 일부 성공)
|
||||
- `FAILED`: 전체 실패 (예외 발생, 데이터 미적재)
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> RUNNING: 수집 시작<br/>(RunCollectionAsync)
|
||||
RUNNING --> COMPLETED: 완료 & error_count==0
|
||||
RUNNING --> COMPLETED_WITH_ERRORS: 완료 & error_count>0
|
||||
RUNNING --> FAILED: 예외 발생
|
||||
COMPLETED --> [*]
|
||||
COMPLETED_WITH_ERRORS --> [*]
|
||||
FAILED --> [*]
|
||||
```
|
||||
|
||||
**상태 전이 조건** (KisDataCollectionOrchestrator.cs 라인 104-105):
|
||||
- `error_count == 0` → `COMPLETED`
|
||||
- `error_count > 0` → `COMPLETED_WITH_ERRORS`
|
||||
- 예외(Exception) → `FAILED`
|
||||
|
||||
**성공 기준** (CLAUDE.md "Collection Run Success Criteria"):
|
||||
- Success: `status == "COMPLETED"` (NOT failed)
|
||||
- Partial Success: `status == "COMPLETED"` + `total_snapshots > 0` + `total_errors > 0`
|
||||
- Failure: `status == "FAILED"` OR `total_snapshots == 0`
|
||||
|
||||
---
|
||||
|
||||
## 2. 수집 파이프라인 흐름도 (Flowchart)
|
||||
|
||||
KIS API 데이터 수집의 전체 흐름. 두 개의 트리거:
|
||||
1. **Hangfire 정기 작업**: 매일 09:00 에 자동 실행
|
||||
2. **API 수동 트리거**: POST /api/collection/run (쿠키 기반 인증)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["Hangfire daily-collection<br/>(09:00 KST)"]
|
||||
B["POST /api/collection/run<br/>(Cookie Auth)"]
|
||||
|
||||
A --> C["IServiceScopeFactory.CreateScope<br/>(resolve ICollectionOrchestrator)"]
|
||||
B --> C
|
||||
|
||||
C --> D["KisDataCollectionOrchestrator.RunCollectionAsync<br/>(tickers: [005930, 000660, ...])"]
|
||||
|
||||
D --> E["Per-ticker 루프"]
|
||||
E --> F["KisApiPriceSource.GetPriceDataAsync<br/>(ticker, account)"]
|
||||
F --> G["PriceDataNormalizer.NormalizeCollectionRow<br/>(seedRow, kisResult)"]
|
||||
G --> H["CollectionRepository.SaveSnapshot<br/>(kis_collection_snapshots)"]
|
||||
G --> I["CollectionRepository.SaveError<br/>(kis_collection_errors, on exception)"]
|
||||
|
||||
H --> J{루프 끝?}
|
||||
I --> J
|
||||
J -->|Yes| K["CollectionRepository.SaveRun<br/>(kis_collection_runs)"]
|
||||
J -->|No| E
|
||||
|
||||
K --> L["파일 출력:<br/>Temp/kis_dotnet_collection_v1.json"]
|
||||
L --> M["Serilog 로그:<br/>src/dotnet/.../logs/"]
|
||||
|
||||
M --> N["Admin UI: /Admin/Collection<br/>(CollectionRepository 읽기)"]
|
||||
N --> O["대시보드 표시:<br/>상태, 스냅샷 수, 에러"]
|
||||
```
|
||||
|
||||
**데이터 흐름**:
|
||||
1. **입력**: Hangfire 스케줄 or API 수동 요청
|
||||
2. **오케스트레이션**: ICollectionOrchestrator 스코프 생성
|
||||
3. **수집**: KIS Open API 호출 → PriceDataNormalizer → DB 저장
|
||||
4. **출력**:
|
||||
- kis_collection_runs: 실행 메타데이터 (run_id, status, total_snapshots, total_errors)
|
||||
- kis_collection_snapshots: 종목별 가격 데이터 (JSON payload)
|
||||
- kis_collection_errors: 에러 기록
|
||||
- Temp/kis_dotnet_collection_v1.json: 수집 결과 요약 (formula_id, gate, run_id, summary)
|
||||
- Serilog 로그: 런타임 로그 (src/dotnet/QuantEngine.Web/logs/)
|
||||
5. **표시**: Admin UI에서 CollectionRepository API 호출 → kis_collection_* 읽기 → Dashboard 렌더링
|
||||
|
||||
---
|
||||
|
||||
## 3. WBS 증거 검증 시퀀스도 (Sequence Diagram)
|
||||
|
||||
작업 완료 증거를 자동 검증하는 파이프라인. 도구: `verify_wbs_task_v1.py` (증거 수집) + `validate_quant_engine_wbs_v1.py` (CI에서 재검증).
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Developer->>verify_wbs_task_v1.py: python verify_wbs_task_v1.py --task QE-M1-01<br/>(또는 --run-commands)
|
||||
verify_wbs_task_v1.py->>+spec/60_quant_engine_wbs.yaml: load spec
|
||||
spec/60_quant_engine_wbs.yaml-->>-verify_wbs_task_v1.py: meta + tasks[QE-M1-01]
|
||||
|
||||
Note over verify_wbs_task_v1.py: evidence_checks 선언형 해석
|
||||
|
||||
alt pg_query 체크
|
||||
verify_wbs_task_v1.py->>+PostgreSQL: SELECT ... (WHERE 절)
|
||||
PostgreSQL-->>-verify_wbs_task_v1.py: 스칼라 결과 또는 행
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: expect{min,max,equals} 비교
|
||||
end
|
||||
|
||||
alt log_pattern 체크
|
||||
verify_wbs_task_v1.py->>+src/dotnet/.../logs/: file_glob 매칭
|
||||
src/dotnet/.../logs/-->>-verify_wbs_task_v1.py: 로그 라인
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: 정규식 패턴 검사<br/>(min_matches, max_age_hours)
|
||||
end
|
||||
|
||||
alt json_gate 체크
|
||||
verify_wbs_task_v1.py->>+Temp/kis_dotnet_collection_v1.json: read JSON
|
||||
Temp/kis_dotnet_collection_v1.json-->>-verify_wbs_task_v1.py: payload
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: 점 표기 경로(dot notation)<br/>+ 값 비교 (>=N 지원)
|
||||
end
|
||||
|
||||
alt file_exists 체크
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: paths[] 존재 확인<br/>(min_bytes 검증)
|
||||
end
|
||||
|
||||
alt playwright_report 체크
|
||||
verify_wbs_task_v1.py->>+tests/e2e/playwright-report.json: read report
|
||||
tests/e2e/playwright-report.json-->>-verify_wbs_task_v1.py: suites[].specs[]
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: spec_file 매칭<br/>(passed_min, failed)
|
||||
end
|
||||
|
||||
verify_wbs_task_v1.py->>verify_wbs_task_v1.py: 모든 체크 결과 종합<br/>(gate = ALL PASS? → PASS : FAIL)
|
||||
|
||||
verify_wbs_task_v1.py->>+Temp/evidence/QE-M1-01/: mkdir
|
||||
verify_wbs_task_v1.py->>Temp/evidence/QE-M1-01/verdict.json: write verdict<br/>(task_id, gate, checks[])
|
||||
verify_wbs_task_v1.py->>Temp/evidence/QE-M1-01/: save raw evidence<br/>(pg_query_n.json, log_excerpt.txt, ...)
|
||||
|
||||
verify_wbs_task_v1.py->>+runtime/lineage_events.jsonl: append event<br/>(node_id, gate, timestamp)
|
||||
|
||||
Developer<<--verify_wbs_task_v1.py: exit 0 (gate=PASS)<br/>or exit 1 (gate=FAIL)
|
||||
|
||||
Note over Developer: 선택: --run-commands 플래그<br/>verification_commands[] 실행
|
||||
|
||||
Developer->>+validate_quant_engine_wbs_v1.py: (CI) python validate_quant_engine_wbs_v1.py
|
||||
validate_quant_engine_wbs_v1.py->>validate_quant_engine_wbs_v1.py: spec load
|
||||
validate_quant_engine_wbs_v1.py->>validate_quant_engine_wbs_v1.py: tasks[status==DONE] 필터
|
||||
validate_quant_engine_wbs_v1.py->>+Temp/evidence/*/verdict.json: load all verdicts
|
||||
Temp/evidence/*/verdict.json-->>-validate_quant_engine_wbs_v1.py: gate 값
|
||||
validate_quant_engine_wbs_v1.py->>validate_quant_engine_wbs_v1.py: gate=FAIL? → CI FAIL
|
||||
validate_quant_engine_wbs_v1.py->>+Temp/quant_engine_wbs_v1.json: write summary
|
||||
Developer<<--validate_quant_engine_wbs_v1.py: exit 0 (모두 PASS)<br/>or exit 1 (일부 FAIL)
|
||||
```
|
||||
|
||||
**검증 프로세스 상세**:
|
||||
|
||||
| 단계 | 역할 | 산출물 |
|
||||
|------|------|--------|
|
||||
| **1. 스펙 로드** | verify_wbs_task_v1.py | spec/60_quant_engine_wbs.yaml |
|
||||
| **2. 증거 체크 실행** | 선언형 evidence_checks[] | pg_query / log_pattern / json_gate / file_exists / playwright_report |
|
||||
| **3. 게이트 결정** | 모든 체크 PASS? | gate = PASS or FAIL |
|
||||
| **4. 증거 저장** | Temp/evidence/<TASK_ID>/ | verdict.json + 원시 증거 |
|
||||
| **5. 계보 로깅** | runtime/lineage_events.jsonl | node_id, gate, timestamp |
|
||||
| **6. CI 재검증** | validate_quant_engine_wbs_v1.py | status=DONE 작업만 재검증 |
|
||||
|
||||
**주요 특징**:
|
||||
- **선언형 검증**: 체크 로직을 YAML에 기술 (하드코딩 최소화)
|
||||
- **원시 증거 보존**: 각 체크의 상세 결과를 JSON/텍스트로 저장
|
||||
- **완료 주장 차단**: "완료했다"는 수동 선언 불가 → verdict.json gate=PASS만 인정
|
||||
- **CI 편입**: validate_quant_engine_wbs_v1.py가 release DAG의 노드로 동작
|
||||
- **멀티 트리거**: 단일 작업 검증 (--task) 또는 전체 검증 (CI)
|
||||
|
||||
**검증 체크 타입 참고** (spec/60_quant_engine_wbs.yaml "evidence_check_types"):
|
||||
- **pg_query**: PostgreSQL 스칼라 결과 비교 (min/max/equals)
|
||||
- **log_pattern**: 로그 파일 정규식 매칭 (min_matches, max_age_hours)
|
||||
- **json_gate**: JSON 아티팩트 키-값 검사 (점 표기 경로, >=N 비교)
|
||||
- **file_exists**: 파일 존재 + 크기 검증 (min_bytes)
|
||||
- **playwright_report**: Playwright 리포트 테스트 결과 (passed_min, failed)
|
||||
Reference in New Issue
Block a user