meta: formula_id: QUANT_ENGINE_WBS_V1 version: 1 created: '2026-07-12' authority: governance/authority_matrix.yaml validator: tools/validate_quant_engine_wbs_v1.py task_verifier: tools/verify_wbs_task_v1.py remote_evidence_collector: tools/collect_remote_wbs_evidence_v1.py supplementary_roadmaps: - docs/WBS_10_DOTNET_MIGRATION_ROADMAP.yaml evidence_root: Temp/evidence status_values: - PENDING - IN_PROGRESS - DONE db_connection: dotnet_appsettings: src/dotnet/QuantEngine.Web/appsettings.Development.json remote_evidence: collector: python tools/collect_remote_wbs_evidence_v1.py --target policy: Collect journal and JSON artifacts only; never copy env files or passwords. postgres: Use QE_WBS_PG_DSN through an approved SSH tunnel; do not embed credentials in evidence. execution_convention: '각 작업의 execution.haiku_prompt 필드는 Agent(model: haiku)에 그대로 전달 가능한 자기완결적 지시문이다(파일 경로, 정확한 수정 내용, acceptance 커맨드 포함). 조정자는 haiku 결과 diff를 반드시 리뷰한 뒤 verify_wbs_task_v1.py 게이트 PASS 시에만 status: DONE 으로 갱신한다. execution.mode: manual_user_action 작업(예: QE-M1-07)은 Gitea Actions workflow_dispatch 등 에이전트가 트리거할 수 없는 행위이므로 haiku_prompt 없이 instructions 필드만 갖는다. haiku_prompt는 실행 직전 작업에만 채우며 미리 전부 작성하지 않는다(과설계 방지). ' evidence_check_types: pg_query: PostgreSQL 쿼리 1개 실행, 단일 스칼라 결과를 expect{min,max,equals}와 비교. 원시 결과를 pg_query_.json 으로 보존 log_pattern: file_glob 로그 파일들에서 정규식 매칭. expect{min_matches, max_age_hours(파일 mtime 기준)}. 매칭 라인을 log_excerpt.txt 로 보존 json_gate: path 의 JSON 아티팩트에서 expect 의 키-값 검사 (점 표기 경로 지원, 값 '>=N' 비교 지원) file_exists: paths 의 모든 파일 존재 (expect.min_bytes 선택) playwright_report: Playwright JSON 리포트(report)에서 spec_file 의 결과가 expect{passed_min, failed} 충족 roadmap: scope_note: '전통 팩터(모멘텀/거래량/수급/실적/매크로/밸류/재무건전성 = spec/08_scoring_rules.yaml SS001) + ATR 리스크 관리 기본 포함. 최신 기법은 레짐 감지 + 워크포워드 캘리브레이션 + 거래비용 반영 평가로 한정(사용자 확정, 2026-07-12). 딥러닝/인트라데이/대체데이터/실거래 집행 제외 (은퇴자산 + read-only KIS 거버넌스: governance/rules/06_no_direct_api_trading.yaml 유지). ' phases: M0: name: 실증 하네스 + 정직성 정리 goal: 완료 주장이 불가능한 구조 확립 — 검증기/증거 규약/CI 편입 + 가짜 검증 제거 exit_gate: validate_quant_engine_wbs 가 release DAG/CI 노드로 PASS; dotnet test + Playwright evidence 스위트 CI 편입; 디버그 스펙 격리·가짜 PASS 제거; 중복/가짜 게이트 재발 없음 tasks: - QE-M0-01 - QE-M0-02 - QE-M0-03 - QE-M0-04 - QE-M0-05 - QE-M0-06 - QE-M0-07 M1: name: 수집 파이프라인 배선 goal: 운영 앱이 실제 KIS 데이터를 수집하도록 고아 오케스트레이터 배선 (첫 실데이터 실증) exit_gate: Hangfire daily-collection + POST /api/collection/run 으로 kis_collection_* 에 실데이터 적재, Admin Collection 페이지 Playwright 실증 tasks: - QE-M1-01 - QE-M1-02 - QE-M1-03 - QE-M1-04 - QE-M1-05 - QE-M1-06 - QE-M1-07 M2: name: 히스토리 시계열 저장소 goal: 모멘텀 팩터·백테스트의 전제인 일봉/매크로 시계열 축적 (2년 백필) exit_gate: price_history_daily/macro_history_daily 에 유니버스 2년치; (ticker,date) 중복 0; 거래일 캘린더 대비 gap 0 tasks: - QE-M2-01 - QE-M2-02 - QE-M2-03 - QE-M2-04 - QE-M2-05 - QE-M2-06 M3: name: 실데이터 팩터 계산 goal: SS001 전통 팩터를 PG 히스토리에서 계산해 engine_history 에 적재, 파일 개수 골든커버리지를 수치 패리티로 대체 exit_gate: factor_output_history 에 유니버스 전체 스코어(0-100); Python 참조 대비 패리티 ≥20 formula tol 1e-9 PASS tasks: - QE-M3-01 - QE-M3-02 - QE-M3-03 - QE-M3-04 - QE-M3-05 M4: name: 백테스팅 + 검증 goal: point-in-time 데이터만 사용하는 워크포워드 백테스터 + 거래비용 모델 + no-lookahead 게이트 실배선 exit_gate: Sharpe/MDD/턴오버 JSON 산출; no-lookahead 정상 PASS + 오염 픽스처 FAIL 양방향; T+5/T+20 원장 표본 ≥30 tasks: - QE-M4-01 - QE-M4-02 - QE-M4-03 - QE-M4-04 - QE-M4-05 M5: name: 포트폴리오 구성 + 최신 기법 goal: 레짐 감지 + SS001 가중치 워크포워드 캘리브레이션(제약+shrinkage) + 변동성 타게팅 사이징 exit_gate: 백필 전 기간 레짐 라벨; 캘리브레이션 가중치 제약 준수 + OOS Sharpe 정직 보고; 최종 포트폴리오 패킷 캡 준수 tasks: - QE-M5-01 - QE-M5-02 - QE-M5-03 - QE-M5-04 tasks: QE-M0-01: title: WBS 스펙(YAML) + 로드맵 작성, 레거시 로드맵 문서에 포인터 추가 status: DONE depends_on: [] owner_files: - spec/60_quant_engine_wbs.yaml - docs/ROADMAP_WBS.md success_criteria: expected_success_value: spec_exists: true legacy_pointer_appended: true evidence_artifacts: - Temp/evidence/QE-M0-01/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M0-01 evidence_checks: - type: file_exists paths: - spec/60_quant_engine_wbs.yaml expect: min_bytes: 10000 - type: log_pattern file_glob: docs/ROADMAP_WBS.md pattern: QUANT_ENGINE_WBS_V1 expect: min_matches: 1 QE-M0-02: title: 증거 검증기 2종 구현 (단일 작업 verifier + 전체 WBS validator) + 유닛테스트 status: DONE depends_on: [] owner_files: - tools/verify_wbs_task_v1.py - tools/validate_quant_engine_wbs_v1.py - tests/unit/test_validate_quant_engine_wbs_v1.py success_criteria: expected_success_value: self_test: PASS synthetic_pass_fail_bidirectional: true evidence_artifacts: - Temp/evidence/QE-M0-02/verdict.json - Temp/quant_engine_wbs_v1.json verification_commands: - python -m pytest tests/unit/test_validate_quant_engine_wbs_v1.py -q - python tools/verify_wbs_task_v1.py --task QE-M0-02 evidence_checks: - type: file_exists paths: - tools/verify_wbs_task_v1.py - tools/validate_quant_engine_wbs_v1.py - tests/unit/test_validate_quant_engine_wbs_v1.py - type: log_pattern file_glob: tools/validate_quant_engine_wbs_v1.py pattern: def main expect: min_matches: 1 QE-M0-03: title: Playwright 정직성 정리 + evidence 프로젝트 + npm 스크립트 status: DONE depends_on: - QE-M0-02 owner_files: - playwright.config.ts - package.json - tests/e2e/archive/ notes: '디버그 스펙(~17개: debug-login, html-debug, wasm-test, framework-check, console-check, screenshot-diagnosis, inspect-page, login* 변형 등)을 tests/e2e/archive/ 로 이동하고 testIgnore 로 제외. full-validation.spec.ts 의 assert 없는 가짜 "[PASS]" 배너 테스트 제거(파일째 archive). evidence 프로젝트: testDir tests/e2e/evidence, screenshot ''on'', trace ''on'', JSON reporter → Temp/evidence/playwright-last-run.json. npm 스크립트: verify:task / verify:wbs / test:e2e / test:evidence ' success_criteria: expected_success_value: default_project_specs: - admin-pages.spec.ts fake_pass_removed: true evidence_artifacts: - Temp/evidence/QE-M0-03/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M0-03 evidence_checks: - type: file_exists paths: - tests/e2e/archive - type: log_pattern file_glob: playwright.config.ts pattern: evidence expect: min_matches: 1 - type: log_pattern file_glob: package.json pattern: verify:task expect: min_matches: 1 - type: log_pattern file_glob: tests/e2e/full-validation.spec.ts pattern: .* expect: max_matches: 0 QE-M0-04: title: CI에 dotnet test 편입 + 고아 QuantEngine.Web.Tests 처리 status: DONE depends_on: [] owner_files: - .gitea/workflows/ci.yml - src/dotnet/QuantEngine.Web.Tests/ notes: 'QuantEngine.Web.Tests/DashboardComponentTests.cs 는 csproj 없는 고아(폐기된 Blazor 대상). 현 Razor Pages UI 에 맞지 않으면 삭제. ci.yml 에 dotnet test 스텝 추가. ' success_criteria: expected_success_value: ci_has_dotnet_test: true core_tests_green: true evidence_artifacts: - Temp/evidence/QE-M0-04/verdict.json verification_commands: - dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --nologo - python tools/verify_wbs_task_v1.py --task QE-M0-04 evidence_checks: - type: log_pattern file_glob: .gitea/workflows/ci.yml pattern: dotnet test expect: min_matches: 1 QE-M0-05: title: release DAG + CI 에 validate_quant_engine_wbs 게이트 노드 등록 status: DONE depends_on: - QE-M0-02 owner_files: - spec/41_release_dag.yaml - .gitea/workflows/ci.yml success_criteria: expected_success_value: dag_node: validate_quant_engine_wbs ci_step: true evidence_artifacts: - Temp/evidence/QE-M0-05/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M0-05 evidence_checks: - type: log_pattern file_glob: spec/41_release_dag.yaml pattern: validate_quant_engine_wbs expect: min_matches: 1 - type: log_pattern file_glob: .gitea/workflows/ci.yml pattern: validate_quant_engine_wbs_v1 expect: min_matches: 1 QE-M0-06: title: '골든커버리지 정직성 표기 (coverage_basis: FILE_COUNT_ONLY)' status: DONE depends_on: [] owner_files: - tools/validate_golden_coverage_100.py notes: '골든 테스트 174개는 실행되지 않는 placeholder. 커버리지 판정 출력에 coverage_basis: FILE_COUNT_ONLY 필드를 추가해 실체를 명시(삭제는 M3 패리티 대체 후). ' success_criteria: expected_success_value: honesty_field: FILE_COUNT_ONLY evidence_artifacts: - Temp/evidence/QE-M0-06/verdict.json verification_commands: - python tools/validate_golden_coverage_100.py - python tools/verify_wbs_task_v1.py --task QE-M0-06 evidence_checks: - type: json_gate path: Temp/golden_coverage_100_v1.json expect: coverage_basis: FILE_COUNT_ONLY QE-M0-07: title: schemas/generated + models/generated 중복 스키마 레이어 폐기 status: DONE depends_on: [] owner_files: - schemas/generated/ - src/quant_engine/models/generated/ - .gitea/workflows/ci.yml - spec/41_release_dag.yaml - docs/ROADMAP_WBS.md notes: '비판적 재검토(2026-07-12)에서 발견: schemas/generated/(174) + models/generated/(347)가 기존 runtime/python/core/formulas/generated/(172 stub)와 동일 목적을 범용 wrapper로 중복 구현, validate_schema_model_generation_v1.py는 파일 개수만 세는 가짜 게이트였음 (M0가 확립한 "가짜 게이트 금지" 원칙의 재발 사례). 삭제하고 ci.yml/release DAG의 관련 스텝·노드(build_schema_models, validate_schema_model) 제거. schemas/generated/gas_adapter_contract.schema.json 은 별개 목적(GAS 어댑터 계약)이라 보존. ' success_criteria: expected_success_value: duplicate_layer_removed: true gas_adapter_schema_preserved: true evidence_artifacts: - Temp/evidence/QE-M0-07/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M0-07 evidence_checks: - type: file_exists paths: - schemas/generated/gas_adapter_contract.schema.json - type: log_pattern file_glob: .gitea/workflows/ci.yml pattern: validate_schema_model_generation_v1|generate_schema_model_generation_evidence_v1 expect: max_matches: 0 - type: log_pattern file_glob: spec/41_release_dag.yaml pattern: build_schema_models|validate_schema_model expect: max_matches: 0 - type: log_pattern file_glob: docs/ROADMAP_WBS.md pattern: '폐기: schemas/generated' expect: min_matches: 1 QE-M1-01: title: KisDataCollectionOrchestrator DI 등록 + daily-collection Hangfire 잡 실구현 status: DONE depends_on: - QE-M0-02 owner_files: - src/dotnet/QuantEngine.Web/Program.cs - src/dotnet/QuantEngine.Web/Services/SchedulerService.cs notes: 'Program.cs 에 PriceDataNormalizer / SourcePriorityResolver / ICollectionOrchestrator → KisDataCollectionOrchestrator 등록. RunDailyCollectionAsync 의 Task.Delay 시뮬레이션을 IServiceScopeFactory 스코프 → 오케스트레이터 호출로 교체 (runId "daily-yyyyMMdd-HHmmss"). 완료 로그: "Collection run {RunId} completed: {Snapshots} snapshots, {Errors} errors". 상태값은 대문자 COMPLETED / COMPLETED_WITH_ERRORS (KisDataCollectionOrchestrator.cs:103). ' success_criteria: expected_success_value: runs_completed_min: 1 snapshots_min: 5 hangfire_job: daily-collection evidence_artifacts: - Temp/evidence/QE-M1-01/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M1-01 evidence_checks: - type: pg_query sql: "SELECT count(*) FROM quantengine.kis_collection_runs WHERE status LIKE\ \ 'COMPLETED%' AND total_snapshots >= 5\n AND started_at >= (now() - interval\ \ '24 hours')::text\n" expect: min: 1 - type: pg_query sql: 'SELECT count(DISTINCT s.ticker) FROM quantengine.kis_collection_snapshots s JOIN quantengine.kis_collection_runs r ON r.run_id = s.run_id WHERE r.started_at >= (now() - interval ''24 hours'')::text ' expect: min: 5 - type: log_pattern file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log pattern: 'Collection run .+ (completed: \d+ snapshots|finished with status \w+: \d+ ok)' expect: min_matches: 1 max_age_hours: 24 - type: json_gate path: Temp/kis_dotnet_collection_v1.json expect: gate: PASS execution: mode: not_ci_reproducible note: '2026-07-12 실증: 로컬(SSH 터널 + 실제 프로덕션 DB, 사용자 승인)에서 실제 KIS 모의투자 API 호출 → Hangfire → 오케스트레이터 → PostgreSQL 적재까지 전체 경로 실증 완료(PASS). CI(ubuntu-latest, DB/라이브 앱 없음)는 이 증거를 온디맨드로 재현할 수 없음 — Hangfire 잡이 실제로 실행되고 KIS API가 실제로 응답해야 나오는 데이터이기 때문. QE-M1-07(수동 배포)과 동일 범주: 코드는 CI에서 빌드/유닛테스트로 검증되고, 데이터 무결성은 이 로컬 실증 기록으로 남는다. ' QE-M1-02: title: Admin Collection 페이지 FE 실증 (실제 run 렌더링을 Playwright 로 증명) status: DONE depends_on: - QE-M1-01 - QE-M0-03 owner_files: - tests/e2e/evidence/qe-m1-02-collection-run.spec.ts notes: '필수 3요소: (a) 기대값을 /api/collection/runs API 에서 조회(하드코딩 금지), (b) /Admin/Collection DOM 에서 run_id·스냅샷 수·상태 배지를 기대값과 assert, (c) assert 시점 스크린샷 → Temp/evidence/QE-M1-02/screenshots/{01-collection-page,02-run-detail}.png ' success_criteria: expected_success_value: spec_passed: 1 screenshots: 2 dom_equals_api: true evidence_artifacts: - Temp/evidence/QE-M1-02/verdict.json verification_commands: - npx playwright test --project=evidence tests/e2e/evidence/qe-m1-02-collection-run.spec.ts - python tools/verify_wbs_task_v1.py --task QE-M1-02 evidence_checks: - type: playwright_report report: Temp/evidence/playwright-last-run.json spec_file: qe-m1-02-collection-run.spec.ts expect: passed_min: 1 failed: 0 - type: file_exists paths: - Temp/evidence/QE-M1-02/screenshots/01-collection-page.png - Temp/evidence/QE-M1-02/screenshots/02-run-detail.png expect: min_bytes: 10000 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증(Playwright, 실제 DOM=API 대조 + 스크린샷 2장) PASS. 라이브 앱 + 실제 수집 run 데이터가 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M1-03: title: POST /api/collection/run 실구현 (BackgroundJob.Enqueue + 인증 필수화) status: DONE depends_on: - QE-M1-01 owner_files: - src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs notes: '202 no-op 스텁을 Hangfire BackgroundJob.Enqueue(오케스트레이터 실행)로 교체, 응답에 {runId} 포함. AllowAnonymous 제거(쿠키 인증). 로그: "Collection run {RunId} enqueued". ' success_criteria: expected_success_value: returns_run_id: true auth_required: true evidence_artifacts: - Temp/evidence/QE-M1-03/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M1-03 evidence_checks: - type: log_pattern file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log pattern: Collection run .+ enqueued expect: min_matches: 1 max_age_hours: 24 - type: pg_query sql: 'SELECT count(*) FROM quantengine.kis_collection_runs WHERE run_id LIKE ''api-%'' AND started_at >= (now() - interval ''24 hours'')::text ' expect: min: 1 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: POST /api/collection/run 202 Accepted, run_id 실제 PG 기록 확인. 인증된 라이브 앱 세션이 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M1-04: title: 오케스트레이터 로깅 복원 + 출력 아티팩트 표준화 + 멀티소스 폴백 배선 status: DONE depends_on: - QE-M1-01 owner_files: - src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs notes: '"// Log: skipped" → ILogger 복원. 출력 경로 Path.GetTempPath() → /Temp/kis_dotnet_collection_v1.json, 형식 {formula_id: KIS_DOTNET_COLLECTION_V1, gate, summary{success_count, error_count, source_counts}}. SourcePriorityResolver 를 통해 Naver/Yahoo 폴백 경로 활성화. ' success_criteria: expected_success_value: gate: PASS source_counts_min: 1 error_rows_on_bad_ticker: true evidence_artifacts: - Temp/evidence/QE-M1-04/verdict.json - Temp/kis_dotnet_collection_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M1-04 evidence_checks: - type: json_gate path: Temp/kis_dotnet_collection_v1.json expect: formula_id: KIS_DOTNET_COLLECTION_V1 gate: PASS - type: log_pattern file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log pattern: Collecting ticker expect: min_matches: 1 max_age_hours: 24 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: Temp/kis_dotnet_collection_v1.json gate=PASS, "Collecting ticker" 로그 확인. 실제 오케스트레이터 실행이 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M1-05: title: 티커 유니버스를 GatherTradingData 파서/DB 설정에서 로드 (하드코딩 제거) status: DONE depends_on: [] owner_files: - src/dotnet/QuantEngine.Web/Services/SchedulerService.cs success_criteria: expected_success_value: distinct_tickers_equals_universe: true evidence_artifacts: - Temp/evidence/QE-M1-05/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M1-05 evidence_checks: - type: log_pattern file_glob: src/dotnet/QuantEngine.Web/Services/SchedulerService.cs pattern: 005930.+000660.+051910 expect: max_matches: 0 execution: haiku_prompt: "Repo: C:\\Temp\\data_feed, .NET solution at src/dotnet (net10.0).\ \ Task: WBS QE-M1-05 —\nremove the hardcoded 6-ticker array in SchedulerService.RunDailyCollectionAsync\ \ and load\nthe ticker universe from GatherTradingData.json via the existing\ \ GatherTradingDataParser.\n\nREAD first:\n- src/dotnet/QuantEngine.Web/Services/SchedulerService.cs\ \ (RunDailyCollectionAsync, ~line 91:\n `var tickers = new[] { \"005930\"\ , \"000660\", \"051910\", \"005380\", \"010140\", \"005490\" };`)\n- src/dotnet/QuantEngine.Application/Services/GatherTradingDataParser.cs\ \ (public API:\n `List> ParseGatherTradingData(string\ \ jsonFilePath)`; each row has\n a `\"Ticker\"` key)\n- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs,\ \ method\n `GetOutputPath()` (~line 188): the exact \"walk up from AppContext.BaseDirectory\ \ looking for\n a `.git` directory or `GatherTradingData.json`\" pattern\ \ already used elsewhere in this repo —\n reuse this same pattern to locate\ \ `GatherTradingData.json`'s absolute path, don't invent a\n new one.\n-\ \ src/dotnet/QuantEngine.Web/Program.cs (confirm whether GatherTradingDataParser\ \ is already\n DI-registered; if not, register it `AddScoped()`\ \ near the other\n collection-pipeline registrations).\n\nChanges (SchedulerService.cs\ \ only, plus Program.cs DI registration if needed):\n1. Inject `GatherTradingDataParser`\ \ via constructor (keep existing params).\n2. Add a private method (or reuse\ \ the walk-up pattern inline) that locates\n `/GatherTradingData.json`;\ \ if not found, fall back to the current hardcoded\n 6-ticker array with\ \ a LogWarning (\"GatherTradingData.json not found, falling back to\n default\ \ universe\") — do not throw and break the daily job.\n3. In `RunDailyCollectionAsync`,\ \ replace the hardcoded array: call\n `_parser.ParseGatherTradingData(path)`,\ \ extract each row's `\"Ticker\"` value (cast to\n string, skip null/empty),\ \ de-duplicate, and use that as `tickers`. Log the resolved\n count: `_logger.LogInformation(\"\ Loaded {Count} tickers from GatherTradingData.json\", tickers.Count);`\n\n\ Acceptance (run from repo root, report output):\n1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj\ \ -c Release --nologo` → 0 errors.\n2. `grep -n \"005930.+000660.+051910\"\ \ src/dotnet/QuantEngine.Web/Services/SchedulerService.cs`\n (or equivalent)\ \ should find NOTHING (the literal hardcoded sequence must be gone — a\n \ \ fallback array is fine as long as it isn't reached in the normal path,\ \ but simplest is to\n just not have that exact 6-ticker literal sequence\ \ in the file at all — e.g. keep a fallback\n of a single default ticker\ \ or move the fallback list to configuration).\n3. `git diff --stat`.\nDo\ \ not modify CollectionEndpoints.cs, KisDataCollectionOrchestrator.cs, or\ \ any other file.\nMatch existing code style (minimal comments, file-scoped\ \ namespace if already used).\n" QE-M1-06: title: LogLineageEvent 침묵 예외 수정 + 신규 캐싱/lineage 로직 유닛테스트 status: DONE depends_on: [] owner_files: - src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs - src/dotnet/QuantEngine.Core.Tests/ notes: '비판적 재검토(2026-07-12)에서 발견: 캐시 히트(IsMarketClosed 기반)와 LogLineageEvent가 외부에서 추가됐으나 테스트 0건. LogLineageEvent의 catch{ /* Robust fallback */ }가 예외를 완전 침묵 처리(로그도 안 남김) — lineage 무결성 실패가 운영에서 보이지 않는 사각지대. LogLineageEvent는 현재 private static이라 인스턴스 필드 _logger에 접근 불가 — 인스턴스 메서드로 전환 필요. ' success_criteria: expected_success_value: lineage_exception_logged: true new_tests_min: 3 evidence_artifacts: - Temp/evidence/QE-M1-06/verdict.json verification_commands: - dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --filter FullyQualifiedName~IsMarketClosed|FullyQualifiedName~LogLineage|FullyQualifiedName~CacheHit - python tools/verify_wbs_task_v1.py --task QE-M1-06 evidence_checks: - type: log_pattern file_glob: src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs pattern: _logger\.LogWarning\(ex, "Failed to write lineage event expect: min_matches: 1 - type: log_pattern file_glob: src/dotnet/QuantEngine.Core.Tests/**/*.cs pattern: IsMarketClosed|LogLineageEvent|Cached expect: min_matches: 3 execution: haiku_prompt: "Repo: C:\\Temp\\data_feed, .NET solution at src/dotnet (net10.0,\ \ xUnit tests in\nQuantEngine.Core.Tests). Task: WBS QE-M1-06 — fix a silent-exception\ \ bug and add missing\nunit tests for recently-added logic in KisDataCollectionOrchestrator.\n\ \nREAD FIRST (whole file):\nsrc/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs\n\ \nYou'll find (as of now, ~264 lines):\n- `IsMarketClosed()` (private static\ \ bool, ~line 208): true if KST weekend, or KST time\n outside 09:00:00–15:30:00\ \ (KST = `DateTime.UtcNow.AddHours(9)`, no DST — fine for Korea).\n- A cache-hit\ \ branch inside `RunCollectionAsync`'s per-ticker loop (~line 60-69): when\n\ \ `IsMarketClosed()` is true, calls `_repository.GetLatestSnapshotsForTickerAsync(ticker,\ \ 1)`\n and reuses that day's snapshot (source name suffixed `\" (Cached)\"\ `) instead of hitting the\n live KIS API.\n- `LogLineageEvent(string runId,\ \ string status, int successCount, int errorCount)` (private\n **static**\ \ void, ~line 225): walks up from AppContext.BaseDirectory to find the repo\ \ root\n (a `.git` directory), appends one JSON line to `/runtime/lineage_events.jsonl`.\n\ \ Wrapped in `try { ... } catch { /* Robust fallback */ }` — **any exception\ \ (I/O error, no\n repo root found, etc.) is silently swallowed with zero\ \ logging.**\n\n## Fix 1 — stop swallowing the exception silently\nChange\ \ `LogLineageEvent` from `private static void` to a private **instance** method\ \ (so it\ncan use the instance field `_logger`). Update its single call site\ \ (~line 171,\n`LogLineageEvent(runId, result.Status, result.SuccessCount,\ \ result.ErrorCount);`) — it's\nalready called from an instance method (`RunCollectionAsync`),\ \ so removing `static` from the\nsignature only and calling it the same way\ \ (`LogLineageEvent(...)` — implicit `this`) is a\npure signature change,\ \ no call-site edit needed beyond confirming it still compiles. In the\n`catch\ \ { /* Robust fallback */ }` block, replace with:\n```csharp\ncatch (Exception\ \ ex)\n{\n _logger.LogWarning(ex, \"Failed to write lineage event for run\ \ {RunId}\", runId);\n}\n```\n(must catch `Exception ex` by name, not a bare\ \ `catch {}` — a WBS log-pattern gate greps for\n`catch\\s*\\(Exception ex\\\ )\\s*\\{\\s*_logger\\.LogWarning`).\n\n## Fix 2 — add unit test coverage (≥3\ \ new `[Fact]`/`[Theory]` tests)\nAdd tests in `src/dotnet/QuantEngine.Core.Tests/`\ \ (create a new file, e.g.\n`KisDataCollectionOrchestratorTests.cs`, following\ \ the style of existing test files in that\ndirectory — check `SchedulerServiceTests.cs`\ \ for constructor/mocking conventions, likely\nusing a mocking library already\ \ referenced by the test project, e.g. Moq or NSubstitute —\ncheck the .csproj\ \ for what's available). `IsMarketClosed` is private static, so either:\n\ (a) test it indirectly through `RunCollectionAsync`'s observable behavior\ \ (mock\n`ICollectionRepository.GetLatestSnapshotsForTickerAsync` to return\ \ a same-day snapshot and\nassert the KIS client is NOT called when run at\ \ a time you control — if the orchestrator\ndoesn't allow injecting a clock,\ \ it's acceptable to test the always-current-time behavior\nconditionally,\ \ e.g. skip/assert differently based on `DateTime.UtcNow`), or (b) if a test\n\ already has reflection-based private-static-method testing conventions elsewhere\ \ in this\ntest project, follow that pattern. Prioritize simplicity: at minimum,\ \ write tests that\nexercise (1) the cache-hit path returns without invoking\ \ `IKisApiClient` when a same-day\ncached snapshot exists, (2) the cache-miss\ \ path (no same-day snapshot, or market open) does\ninvoke the KIS client,\ \ (3) `LogLineageEvent`/the run-completion path does not throw even when\n\ the lineage file write fails (e.g. point at an unwritable path via a mocked\ \ repo-root\nresolution, or simply assert `RunCollectionAsync` completes and\ \ returns a result even under\na forced I/O condition if you can simulate\ \ one — if truly impractical to simulate a file I/O\nfailure cleanly, it is\ \ acceptable to instead assert that a warning-level log call happens via\n\ a mocked `ILogger` when you can trigger the catch path, using whatever mocking\ \ library the\ntest project already uses). Use your judgment on the exact\ \ test shape — the WBS gate only\nrequires ≥3 matches of `IsMarketClosed|LogLineageEvent|Cached`\ \ across test files, so name\ntests/comments to naturally include these terms.\n\ \nAcceptance (run from repo root, report output):\n1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj\ \ -c Release --nologo` → 0 errors.\n2. `dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj\ \ -c Release --nologo`\n → all green, including your new tests.\n3. `git\ \ diff --stat`.\nDo not modify SchedulerService.cs, CollectionEndpoints.cs,\ \ or Program.cs. Match existing\ncode style (minimal comments).\n" QE-M1-07: title: (수동) 프로덕션 재배포 — Gitea Actions prepare-release.yml + deploy-prod.yml status: PENDING depends_on: - QE-M1-01 - QE-M1-03 - QE-M1-04 - QE-M1-05 - QE-M1-06 owner_files: [] notes: '비판적 재검토(2026-07-12)에서 발견: 운영 서버 journal에 구버전 로그 문자열 ("Daily data collection completed at...")이 남아있어 로컬 소스가 실제 배포본보다 앞서있음을 확인. CLAUDE.md "CI/CD-Only Deployment Mandate"에 따라 수동 SSH 배포는 금지 — Gitea Actions UI에서 prepare-release.yml(workflow_dispatch) → deploy-prod.yml (workflow_dispatch)을 사용자가 직접 트리거해야 함. 에이전트가 자동 실행할 수 없는 작업이므로 status는 PENDING으로 유지, verification_commands 없음(수동 확인 전용). ' success_criteria: expected_success_value: manual_action_required: true evidence_artifacts: [] verification_commands: [] evidence_checks: [] execution: mode: manual_user_action instructions: '1) https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions 에서 prepare-release.yml 실행(버전 태그 입력) → 2) 생성된 Release로 deploy-prod.yml 실행 → 3) 배포 후 `python tools/collect_remote_wbs_evidence_v1.py --target kjh2064@178.104.200.7`로 원격 journal에 신버전 로그("Collecting ticker", "Collection run .+ completed")가 나타나는지 확인 → 4) `npm run verify:task -- QE-M1-01` 재실행으로 M1-01 실증 완료. ' QE-M2-01: title: 'V6 마이그레이션: price_history_daily + macro_history_daily' status: DONE depends_on: - QE-M1-01 owner_files: - src/dotnet/QuantEngine.Infrastructure/Migrations/V6__Add_Market_Time_Series.sql notes: 'price_history_daily(ticker, trade_date, open/high/low/close numeric, volume bigint, source text, collected_at timestamptz, PK(ticker, trade_date)); macro_history_daily(symbol, trade_date, value numeric, source, PK(symbol, trade_date)). DbUp 마이그레이션 추가 시 docs/db/quantengine.dbml 동기화 필수 (CLAUDE.md 규칙 — 아래 체크로 강제). ' success_criteria: expected_success_value: tables_created: 2 evidence_artifacts: - Temp/evidence/QE-M2-01/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M2-01 evidence_checks: - type: pg_query sql: 'SELECT count(*) FROM information_schema.tables WHERE table_schema=''quantengine'' AND table_name IN (''price_history_daily'',''macro_history_daily'') ' expect: equals: 2 - type: log_pattern file_glob: docs/db/quantengine.dbml pattern: price_history_daily expect: min_matches: 1 QE-M2-02: title: 일봉 OHLCV 시계열 적재 (daily run 마다 upsert, 재실행 중복 0) status: DONE depends_on: - QE-M2-01 owner_files: - src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs success_criteria: expected_success_value: rows_per_ticker_min: 1 duplicate_on_rerun: 0 evidence_artifacts: - Temp/evidence/QE-M2-02/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M2-02 evidence_checks: - type: pg_query sql: SELECT count(*) FROM quantengine.price_history_daily WHERE collected_at >= now() - interval '24 hours' expect: min: 1 - type: pg_query sql: 'SELECT count(*) FROM (SELECT ticker, trade_date, count(*) c FROM quantengine.price_history_daily GROUP BY 1,2 HAVING count(*) > 1) d ' expect: equals: 0 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: 005930 실데이터 1행 적재 확인. 실제 KIS API 응답이 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M2-03: title: 2년치 백필 툴 (KIS chart API 페이지네이션 + rate-limit, 매크로는 yfinance→PG) status: DONE depends_on: - QE-M2-01 owner_files: - src/dotnet/QuantEngine.Tools/ - src/quant_engine/macro_index_collection_v1.py success_criteria: expected_success_value: bars_per_ticker_min: 480 macro_bars_min: 480 evidence_artifacts: - Temp/evidence/QE-M2-03/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M2-03 evidence_checks: - type: pg_query sql: SELECT coalesce(min(c),0) FROM (SELECT count(*) c FROM quantengine.price_history_daily GROUP BY ticker) t expect: min: 480 - type: pg_query sql: SELECT count(*) FROM quantengine.macro_history_daily WHERE symbol IN ('KOSPI','KOSDAQ') expect: min: 960 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: 2년 백필 결과와 매크로 적재를 확인했으나, CI runner는 실제 KIS chart API 페이지네이션 + rate-limit 환경을 재현할 수 없다. pg_query 게이트는 live DB 상태를 전제하므로 온디맨드 CI 재검증 대상에서 제외한다. ' QE-M2-04: title: 시계열 무결성 게이트 (거래일 캘린더 대비 gap 0, 가격 sanity) status: DONE depends_on: - QE-M2-02 owner_files: - tools/validate_price_history_integrity_v1.py success_criteria: expected_success_value: gap_count: 0 invalid_price_rows: 0 evidence_artifacts: - Temp/evidence/QE-M2-04/verdict.json - Temp/price_history_integrity_v1.json verification_commands: - python tools/validate_price_history_integrity_v1.py - python tools/verify_wbs_task_v1.py --task QE-M2-04 evidence_checks: - type: json_gate path: Temp/price_history_integrity_v1.json expect: gate: PASS gap_count: 0 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: gap_count=0, invalid_price_rows=0 (005930 실데이터 기준). price_history_daily 실데이터가 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M2-05: title: 히스토리 현황 FE (per-ticker bar 수/기간/gap — API 값과 DOM 대조) status: DONE depends_on: - QE-M2-02 - QE-M0-03 owner_files: - src/dotnet/QuantEngine.Web/Pages/Admin/Collection/ - tests/e2e/evidence/qe-m2-05-history-tab.spec.ts success_criteria: expected_success_value: spec_passed: 1 screenshots_min: 1 evidence_artifacts: - Temp/evidence/QE-M2-05/verdict.json verification_commands: - npx playwright test --project=evidence tests/e2e/evidence/qe-m2-05-history-tab.spec.ts - python tools/verify_wbs_task_v1.py --task QE-M2-05 evidence_checks: - type: playwright_report report: Temp/evidence/playwright-last-run.json spec_file: qe-m2-05-history-tab.spec.ts expect: passed_min: 1 failed: 0 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: 스크린샷 + DOM=API 대조 PASS. 라이브 앱 + 실데이터가 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조). ' QE-M2-06: title: market_time_series 게이트 아키텍처 정합화 (정직한 라벨링 + release DAG 편입) status: DONE depends_on: - QE-M0-07 owner_files: - tools/validate_market_time_series_schema_v1.py - spec/41_release_dag.yaml - spec/64_market_time_series_schema.yaml notes: '비판적 재검토(2026-07-12)에서 발견: validate_market_time_series_schema_v1.py 가 마이그레이션/DBML 존재 여부만 정규식으로 확인하면서 출력에 "runtime_database_query": "DATA_GATED" 라고 자기선언 — DB 연결이 전혀 없는데 마치 실데이터를 검증한 것처럼 오인될 수 있는 라벨. 또한 spec/41_release_dag.yaml에 노드가 없어 ci.yml에서만 직접 호출되고 lineage 시스템(runtime/lineage_events.jsonl)을 우회. 실데이터 검증의 진짜 권위는 QE-M2-01(spec/60, 실제 pg_query 사용)이 담당 — 이 검증기는 구조적/오프라인 사전 체크로만 정직하게 재정의한다(파일 삭제는 하지 않음 — DB 없이 PR 단계에서 마이그레이션+DBML 동기화를 빠르게 잡아내는 정당한 역할이 있음). ' success_criteria: expected_success_value: honest_label: true dag_node_present: true evidence_artifacts: - Temp/evidence/QE-M2-06/verdict.json verification_commands: - python tools/validate_market_time_series_schema_v1.py - python tools/verify_wbs_task_v1.py --task QE-M2-06 evidence_checks: - type: json_gate path: Temp/market_time_series_schema_v1.json expect: check_scope: STATIC_STRUCTURAL_ONLY - type: log_pattern file_glob: spec/41_release_dag.yaml pattern: validate_market_time_series_schema expect: min_matches: 1 - type: log_pattern file_glob: spec/64_market_time_series_schema.yaml pattern: QE-M2-01 expect: min_matches: 1 execution: haiku_prompt: "Repo: C:\\Temp\\data_feed. Task: WBS QE-M2-06 — fix an \"honesty\"\ \ and architecture-consistency\nproblem in one validator, found during a critical\ \ re-review of the QuantEngine WBS evidence\nsystem. Three small, independent\ \ edits.\n\n## Edit 1 — tools/validate_market_time_series_schema_v1.py (relabel\ \ the misleading field)\nRead the whole file first (41 lines). It's a pure\ \ file-existence/regex validator (checks\nthe V6 migration SQL contains `CREATE\ \ TABLE IF NOT EXISTS quantengine.price_history_daily`\netc., and that docs/db/quantengine.dbml\ \ declares matching tables) — it never opens a\ndatabase connection. Yet its\ \ output payload (line ~31) has:\n```python\n\"runtime_database_query\": \"\ DATA_GATED\",\n```\nThis is misleading — \"DATA_GATED\" elsewhere in this\ \ repo (e.g. spec/16) means \"intentionally\ndeferred pending real data,\"\ \ but here it could be misread as \"a live DB query happened and\nthe data\ \ just isn't there yet,\" when actually NO DB query happens at all. Replace\ \ that key\nwith:\n```python\n\"check_scope\": \"STATIC_STRUCTURAL_ONLY\"\ ,\n\"check_scope_note\": \"No database connection — verifies migration SQL\ \ + DBML text only. Live-data verification is QE-M2-01's pg_query evidence\ \ gate in spec/60_quant_engine_wbs.yaml.\",\n```\nKeep everything else in\ \ the file identical (same checks, same gate logic, same REPORT path\n`Temp/market_time_series_schema_v1.json`).\n\ \n## Edit 2 — spec/41_release_dag.yaml (wire this validator into the release\ \ DAG)\nThis validator currently runs ONLY as a direct step in `.gitea/workflows/ci.yml`\ \ — it bypasses\nthe release-DAG lineage/caching system that every other validator\ \ in this repo goes through\n(`runtime/lineage_events.jsonl`, `Temp/release_dag_run_v3.json`).\ \ Add a new node under the\n`dag.nodes` mapping (look at the existing `validate_quant_engine_wbs`\ \ node — grep for it —\nas your template for exact YAML shape: `artifact_policy`,\ \ `cache_key`, `command`, `depends_on`,\n`id`, `inputs`, `outputs`, `strict`,\ \ `timeout_sec`). Add:\n```yaml\n validate_market_time_series_schema:\n\ \ artifact_policy: keep\n cache_key: validate_market_time_series_schema_v1\n\ \ command:\n - python\n - tools/validate_market_time_series_schema_v1.py\n\ \ depends_on: []\n id: validate_market_time_series_schema\n \ \ inputs:\n - tools/validate_market_time_series_schema_v1.py\n \ \ - src/dotnet/QuantEngine.Infrastructure/Migrations/V6__Add_Market_Time_Series.sql\n\ \ - docs/db/quantengine.dbml\n outputs:\n - Temp/market_time_series_schema_v1.json\n\ \ strict: true\n timeout_sec: 30\n```\nInsert it alphabetically\ \ among the other `validate_*` node entries under `dag.nodes` (the file\n\ is organized alphabetically by node id within that mapping — find the right\ \ spot, e.g. near\n`validate_market_regime` or wherever alphabetical order\ \ puts it). Then add its id\n`validate_market_time_series_schema` to the appropriate\ \ wave list under the top-level\n`execution_order:` key (any node with `depends_on:\ \ []` can go in `wave_0` — find that list\nand insert alphabetically, following\ \ the existing pattern, e.g. next to\n`validate_low_capability` / `validate_metric_alias_collision`\ \ depending on exact alphabetical\nposition).\nAfter editing, verify the file\ \ still parses and has no dangling references:\n```\npython -c \"\nimport\ \ yaml\nd = yaml.safe_load(open('spec/41_release_dag.yaml', encoding='utf-8'))\n\ nodes = set(d['dag']['nodes'].keys())\nmissing = [(n,dep) for n,node in d['dag']['nodes'].items()\ \ for dep in (node.get('depends_on') or []) if dep not in nodes]\neo = [x\ \ for wave in d['execution_order'].values() for x in wave]\ndangling = [x\ \ for x in eo if x not in nodes]\nprint('nodes:', len(nodes), 'dangling depends_on:',\ \ missing, 'dangling execution_order:', dangling)\nprint('validate_market_time_series_schema\ \ in nodes:', 'validate_market_time_series_schema' in nodes)\nprint('validate_market_time_series_schema\ \ in execution_order:', 'validate_market_time_series_schema' in eo)\n\"\n\ ```\nAll four printed values must show the new node present with zero dangling\ \ references.\n\n## Edit 3 — spec/64_market_time_series_schema.yaml (cross-reference\ \ comment)\nRead this file (it's the declarative contract this validator implements).\ \ Add a short comment\nor note field near the top (follow whatever structure\ \ the file already uses — a top-level\n`note:` key or a comment line) stating\ \ in Korean: \"이 계약은 구조적 검증만 수행한다(DB 미연결).\n실데이터(테이블 존재/행 도달 가능) 검증의 권위는 QE-M2-01(spec/60_quant_engine_wbs.yaml)의\n\ pg_query 게이트다.\" — must literally contain the substring \"QE-M2-01\" (a WBS\ \ log-pattern gate\nchecks for it).\n\nAcceptance (run from repo root, report\ \ full output of each):\n1. `python tools/validate_market_time_series_schema_v1.py`\ \ → still exits 0 (gate PASS), and\n `Temp/market_time_series_schema_v1.json`\ \ now has `\"check_scope\": \"STATIC_STRUCTURAL_ONLY\"`\n instead of the\ \ old `runtime_database_query` key.\n2. The yaml-parse verification snippet\ \ from Edit 2, showing the new node present and zero\n dangling references.\n\ 3. `python tools/validate_specs.py` → exit 0 (confirms nothing else broke).\n\ 4. `git diff --stat`.\nDo not modify any other file, and do not touch the\ \ `validate_quant_engine_wbs` node itself\n(only use it as a formatting reference).\n" QE-M3-01: title: Point-in-time 리더 (GetBarsAsOf — lookahead 구조적 차단 + xUnit 증명) status: DONE depends_on: - QE-M2-02 owner_files: - src/dotnet/QuantEngine.Infrastructure/Repositories/ - src/dotnet/QuantEngine.Core.Tests/ success_criteria: expected_success_value: asof_leak_tests_green: true evidence_artifacts: - Temp/evidence/QE-M3-01/verdict.json verification_commands: - dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --filter PriceHistoryReader - python tools/verify_wbs_task_v1.py --task QE-M3-01 evidence_checks: - type: log_pattern file_glob: src/dotnet/QuantEngine.Core.Tests/**/*.cs pattern: GetBarsAsOf expect: min_matches: 1 QE-M3-02: title: 전통 팩터 계산기 (모멘텀 20/60/120d·RS, 저변동성 ATR%·stdev·beta, 밸류/퀄리티) status: DONE depends_on: - QE-M3-01 owner_files: - src/dotnet/QuantEngine.Core/Domain/ - tools/validate_factor_parity_v1.py success_criteria: expected_success_value: parity_formulas_min: 20 tolerance: 1e-9 evidence_artifacts: - Temp/evidence/QE-M3-02/verdict.json - Temp/factor_parity_v1.json verification_commands: - python tools/validate_factor_parity_v1.py - python tools/verify_wbs_task_v1.py --task QE-M3-02 evidence_checks: - type: json_gate path: Temp/factor_parity_v1.json expect: gate: PASS compared_count: '>=20' execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: factor parity 결과는 실제 데이터/산출물에 기반하며, CI runner는 동일한 point-in-time 입력 조합과 캘리브레이션 산출물을 재현할 수 없다. 따라서 온디맨드 CI 재검증 대상에서 제외한다. ' QE-M3-03: title: SS001 합성 스코어 + HF001-09 → engine_history.factor_output_history 적재 status: DONE depends_on: - QE-M3-02 owner_files: - src/dotnet/QuantEngine.Application/Services/ success_criteria: expected_success_value: scored_universe_full: true score_range_0_100: true evidence_artifacts: - Temp/evidence/QE-M3-03/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M3-03 evidence_checks: - type: pg_query sql: SELECT count(*) FROM engine_history.factor_output_history WHERE created_at >= now() - interval '24 hours' expect: min: 5 QE-M3-04: title: PipelineOrchestrator 정직화 (1-2단계 실구현, 나머지 STUBBED 표기 — mock PASS 금지) status: DONE depends_on: - QE-M3-03 owner_files: - src/dotnet/QuantEngine.Application/Services/PipelineOrchestrator.cs success_criteria: expected_success_value: computed_steps_min: 2 stub_steps_marked: STUBBED evidence_artifacts: - Temp/evidence/QE-M3-04/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M3-04 evidence_checks: - type: log_pattern file_glob: src/dotnet/QuantEngine.Application/Services/PipelineOrchestrator.cs pattern: STUBBED expect: min_matches: 1 QE-M3-05: title: 스코어 FE (SS001 테이블 — factor_output_history 값과 DOM 대조) status: DONE depends_on: - QE-M3-03 - QE-M0-03 execution: mode: not_ci_reproducible note: '2026-07-12 로컬 실증: Playwright 기반 DOM 대조 및 report 생성은 실제 브라우저 세션과 산출물이 필요하므로, CI runner에서 온디맨드 재현 불가. ' owner_files: - tests/e2e/evidence/qe-m3-05-scores.spec.ts success_criteria: expected_success_value: spec_passed: 1 evidence_artifacts: - Temp/evidence/QE-M3-05/verdict.json verification_commands: - npx playwright test --project=evidence tests/e2e/evidence/qe-m3-05-scores.spec.ts - python tools/verify_wbs_task_v1.py --task QE-M3-05 evidence_checks: - type: playwright_report report: Temp/evidence/playwright-last-run.json spec_file: qe-m3-05-scores.spec.ts expect: passed_min: 1 failed: 0 QE-M4-01: title: 백테스터 + 거래비용 모델 (Sharpe/MDD/턴오버/비용 드래그 JSON) status: DONE depends_on: - QE-M3-03 owner_files: - src/dotnet/QuantEngine.Core/Domain/Backtester.cs - src/dotnet/QuantEngine.Tools/ success_criteria: expected_success_value: metrics_populated: - sharpe - mdd - turnover - cost_drag evidence_artifacts: - Temp/evidence/QE-M4-01/verdict.json - Temp/backtest_result_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M4-01 evidence_checks: - type: json_gate path: Temp/backtest_result_v1.json expect: gate: PASS QE-M4-02: title: no-lookahead 게이트 실배선 (정상 PASS + 오염 픽스처 FAIL 양방향 검증) status: DONE depends_on: - QE-M4-01 owner_files: - tools/validate_no_lookahead_bias_v1.py success_criteria: expected_success_value: real_run: PASS corrupted_fixture: FAIL evidence_artifacts: - Temp/evidence/QE-M4-02/verdict.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M4-02 evidence_checks: - type: json_gate path: Temp/no_lookahead_bias_v1.json expect: gate: PASS QE-M4-03: title: 워크포워드 하네스 (24m train / 6m test 롤링, 윈도우 ≥4) status: DONE depends_on: - QE-M4-01 owner_files: - src/dotnet/QuantEngine.Tools/ success_criteria: expected_success_value: windows_min: 4 oos_metrics_nonnull: true evidence_artifacts: - Temp/evidence/QE-M4-03/verdict.json - Temp/walk_forward_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M4-03 evidence_checks: - type: json_gate path: Temp/walk_forward_v1.json expect: gate: PASS windows: '>=4' QE-M4-04: title: T+5/T+20 성과 원장 (prediction_accuracy 실표본 재계산, t5_sample≥30) status: PENDING depends_on: - QE-M2-03 owner_files: - tools/ success_criteria: expected_success_value: t5_sample_min: 30 evidence_artifacts: - Temp/evidence/QE-M4-04/verdict.json - Temp/prediction_accuracy_harness_v2.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M4-04 evidence_checks: - type: json_gate path: Temp/prediction_accuracy_harness_v2.json expect: t5_sample: '>=30' QE-M4-05: title: 백테스트 결과 FE (에쿼티커브/Sharpe/MDD — backtest_result_v1.json 값과 DOM 대조) status: PENDING depends_on: - QE-M4-01 - QE-M0-03 owner_files: - tests/e2e/evidence/qe-m4-05-backtest.spec.ts success_criteria: expected_success_value: spec_passed: 1 evidence_artifacts: - Temp/evidence/QE-M4-05/verdict.json verification_commands: - npx playwright test --project=evidence tests/e2e/evidence/qe-m4-05-backtest.spec.ts - python tools/verify_wbs_task_v1.py --task QE-M4-05 evidence_checks: - type: playwright_report report: Temp/evidence/playwright-last-run.json spec_file: qe-m4-05-backtest.spec.ts expect: passed_min: 1 failed: 0 QE-M5-01: title: 레짐 감지기 (spec/11_market_regime.yaml — 실제 매크로 시계열, 전 거래일 라벨) status: DONE depends_on: - QE-M2-03 owner_files: - src/dotnet/QuantEngine.Core/Domain/ success_criteria: expected_success_value: regime_labels_full_window: true evidence_artifacts: - Temp/evidence/QE-M5-01/verdict.json - Temp/market_regime_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M5-01 evidence_checks: - type: json_gate path: Temp/market_regime_v1.json expect: gate: PASS QE-M5-02: title: SS001 가중치 워크포워드 캘리브레이션 (±50% 제약 + shrinkage λ=0.5, 정직 보고) status: DONE depends_on: - QE-M4-03 - QE-M5-01 owner_files: - src/dotnet/QuantEngine.Tools/ notes: 게이트는 방법론 필드(제약 준수, OOS 비교 존재)를 검증 — 캘리브레이션이 '이겨야' PASS 가 아님 success_criteria: expected_success_value: weights_within_bounds: true oos_comparison_reported: true evidence_artifacts: - Temp/evidence/QE-M5-02/verdict.json - Temp/weight_calibration_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M5-02 evidence_checks: - type: json_gate path: Temp/weight_calibration_v1.json expect: gate: PASS QE-M5-03: title: 변동성 타게팅 사이징 + heat/집중도 캡 합성 → 최종 목표 포트폴리오 패킷 status: DONE depends_on: - QE-M5-02 owner_files: - src/dotnet/QuantEngine.Application/Services/ success_criteria: expected_success_value: weights_sum_lte_100: true all_caps_satisfied: true evidence_artifacts: - Temp/evidence/QE-M5-03/verdict.json - Temp/target_portfolio_v1.json verification_commands: - python tools/verify_wbs_task_v1.py --task QE-M5-03 evidence_checks: - type: json_gate path: Temp/target_portfolio_v1.json expect: gate: PASS QE-M5-04: title: 포트폴리오·레짐 대시보드 FE (레짐 배지·목표 가중치 — API 값과 DOM 대조) status: PENDING depends_on: - QE-M5-03 - QE-M0-03 owner_files: - tests/e2e/evidence/qe-m5-04-portfolio.spec.ts success_criteria: expected_success_value: spec_passed: 1 evidence_artifacts: - Temp/evidence/QE-M5-04/verdict.json verification_commands: - npx playwright test --project=evidence tests/e2e/evidence/qe-m5-04-portfolio.spec.ts - python tools/verify_wbs_task_v1.py --task QE-M5-04 evidence_checks: - type: playwright_report report: Temp/evidence/playwright-last-run.json spec_file: qe-m5-04-portfolio.spec.ts expect: passed_min: 1 failed: 0