<!-- task=m-backtest-multi-timeframe-coverage/03_monthly_aggregation plan=0 tag=REFACTOR -->

# Plan - Monthly Aggregation

## 이 파일을 읽는 구현 에이전트에게

구현 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우는 것이다. 구현 후 active 파일을 그대로 두고 리뷰 준비를 보고한다. 종결, archive 이동, `complete.log` 작성은 code-review 전용이다.

선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/서비스 차단, 일반 범위 조정, 검증 공백은 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다.

## 배경

SDD D02는 월봉 source of truth를 provider 월봉과 병행하지 않고 일봉 기반 deterministic aggregation 하나로 둔다. 현재 codebase에는 monthly vocabulary와 capability rejection은 있지만, 동일 daily fixture에서 월봉 OHLCV와 provenance를 생성하는 경계가 없다. 이 계획은 저장소 스키마를 흔들지 않고 worker 내부 aggregation 함수와 stable fixture evidence를 추가한다.

## 사용자 리뷰 요청 흐름

사용자 리뷰 요청은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에만 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 내용을 따른다. 직접 사용자 프롬프트는 금지하며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
  - `monthly-bars`: 월봉을 일봉 aggregation으로 생성하고, 생성 기준과 provenance를 저장/출력한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
  - `S04`: task=`monthly-bars`; evidence=`deterministic monthly OHLCV fixture와 provenance evidence`
- Completion mode: spec-check-on-pass

## 분석 결과

### 읽은 파일

- `AGENTS.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `packages/domain/market/types.go`
- `services/worker/internal/providers/kis/daily_itemchartprice.go`
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
- `services/worker/internal/marketdata/importer/importer.go`
- `services/worker/internal/marketdata/importer/importer_test.go`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/backtest/bar_source.go`
- `services/worker/internal/backtest/bar_source_test.go`

### 테스트 환경 규칙

- `test_env=local`.
- worker 내부 market data 경계 변경이므로 `agent-test/local/worker-smoke.md`를 적용한다.
- fixture 파일과 task artifact 변경 확인에는 `operations-smoke`의 `git diff --check`를 보조로 사용한다.
- 적용 명령: `go test ./services/worker/internal/marketdata/...`, `go test ./services/worker/...`, `git diff --check`.
- fallback 없음. `<확인 필요>` 값 없음.

### 테스트 커버리지 공백

- monthly aggregation 함수 없음. 신규 package test가 필요하다.
- provenance durable metadata 저장소 없음. SDD는 1차 완료 근거를 stable CLI/JSONL 또는 fixture output으로 허용하므로 fixture output을 먼저 남기고 DB metadata는 후속 확장으로 둔다.
- decimal 비교/합산 helper 없음. 신규 test가 high/low 선택, open/close 순서, volume sum을 검증해야 한다.

### 심볼 참조

- renamed/removed symbol 없음.
- 추가 예정 symbol은 신규 package 내부에 한정한다. call site integration은 이 plan에서 fixture/test 중심으로 제한한다.

### 분할 판단

- split policy를 plan 작성 전에 평가했다.
- 공유 task group: `agent-task/m-backtest-multi-timeframe-coverage/`
- sibling split:
  - `01_import_timeframe_contract`: independent contract foundation.
  - `02+01_minute_import_rejection`: dependent minute rejection.
  - `03_monthly_aggregation`: 이 plan, independent.
- predecessor 없음. 이 plan은 `01` 완료 없이 시작 가능하다.

### 범위 결정 근거

- DB migration은 제외한다. `bars` table은 이미 `(instrument_id, timeframe, timestamp)` key를 갖고 있고 provenance는 SDD가 fixture/output 1차 근거를 허용한다.
- provider 월봉 import는 제외한다. D02는 일봉 기반 deterministic aggregation 하나를 source of truth로 정했다.
- backtest run selector integration은 후속 `backtest-semantics` epic에서 다룬다.

### 빌드 등급

- `local-G05`: worker 내부 순수 aggregation과 fixture/test 중심이며, 저장소 또는 public API를 바꾸지 않는다.

## 구현 체크리스트

- [ ] `services/worker/internal/marketdata/aggregation` package를 추가하고 daily bars를 monthly bars와 provenance로 변환하는 순수 함수를 작성한다.
- [ ] aggregation rule을 고정한다: 월별 정렬, open=첫 daily open, close=마지막 daily close, high=max high, low=min low, volume=sum, timestamp=해당 월 1일 UTC, output timeframe=`market.TimeframeMonthly`.
- [ ] mixed instrument, non-daily timeframe, mixed currency, invalid decimal input을 error로 거부한다.
- [ ] 동일 daily fixture에서 deterministic monthly OHLCV와 provenance JSON fixture를 생성/검증한다. 검증: 동일 daily fixture에서 deterministic monthly OHLCV가 생성된다.
- [ ] `go test ./services/worker/internal/marketdata/...`, `go test ./services/worker/...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Monthly Aggregation Core

#### 문제

SDD `Interface Contract`는 월봉 provenance와 deterministic aggregation을 요구하지만, `rg --sort path -n "Aggregate|Monthly|provenance"` 결과 worker marketdata 영역에 월봉 생성 함수가 없다.

#### 해결 방법

새 worker 내부 package를 만든다. 입력은 `[]market.Bar`, 출력은 `[]market.Bar`와 provenance slice/struct다. 입력을 timestamp 기준으로 stable sort하고, `market.TimeframeDaily`만 받는다. 월별 grouping은 UTC 기준 `time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)`를 monthly timestamp로 쓴다.

#### 수정 파일 및 체크리스트

- [ ] `services/worker/internal/marketdata/aggregation/monthly.go`: aggregator와 provenance type.
- [ ] `services/worker/internal/marketdata/aggregation/monthly_test.go`: deterministic output tests.

#### 테스트 작성

- `TestAggregateDailyToMonthlyDeterministicOHLCV`: unordered daily input에서 월별 output ordering과 OHLCV를 검증한다.
- `TestAggregateDailyToMonthlyRejectsMixedInputs`: mixed instrument/timeframe/currency/invalid decimal을 검증한다.

#### 중간 검증

```bash
go test ./services/worker/internal/marketdata/aggregation
```

기대 결과: 신규 package test 통과.

### [REFACTOR-2] Provenance Fixture Evidence

#### 문제

Milestone task는 "생성 기준과 provenance를 저장/출력"을 요구하고, SDD는 1차 완료 근거로 stable fixture output을 허용한다. 현재 월봉 provenance fixture가 없다.

#### 해결 방법

기존 worker testdata convention을 따라 deterministic daily input과 expected monthly/provenance JSON을 둔다. provenance는 source timeframe, target timeframe, instrument id, source range, source count, aggregation rule version을 포함한다. test는 fixture를 marshal/compare하거나 expected fixture를 decode해 output과 비교한다.

#### 수정 파일 및 체크리스트

- [ ] `services/worker/internal/marketdata/aggregation/testdata/monthly_daily_input.json`
- [ ] `services/worker/internal/marketdata/aggregation/testdata/monthly_expected.json`
- [ ] `services/worker/internal/marketdata/aggregation/monthly_test.go`

#### 테스트 작성

- fixture comparison test를 추가한다. JSON key order는 Go struct marshal 또는 decoded semantic comparison 중 하나로 안정화한다.

#### 중간 검증

```bash
go test ./services/worker/internal/marketdata/aggregation
```

기대 결과: fixture comparison 통과.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/worker/internal/marketdata/aggregation/monthly.go` | REFACTOR-1 |
| `services/worker/internal/marketdata/aggregation/monthly_test.go` | REFACTOR-1, REFACTOR-2 |
| `services/worker/internal/marketdata/aggregation/testdata/*.json` | REFACTOR-2 |

## 최종 검증

```bash
go test ./services/worker/internal/marketdata/...
go test ./services/worker/...
git diff --check
```

기대 결과: 모든 명령 exit code 0. Go test cache output은 허용하지만, 신규 aggregation package는 최초 구현 시 실제 실행 결과를 review stub에 붙인다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
