<!-- task=m-backtest-analysis-surface/02+01_worker_analysis_store plan=0 tag=BAS-WORKER -->

# Plan - BAS-WORKER

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

`CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 사용자 결정, 외부 환경 준비, 범위 충돌이 없이는 안전하게 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 남기고 멈춘다. archive, `complete.log`, 최종 판정은 code-review 전용이다.

## 배경

Contract surface가 확정된 뒤 worker는 summary metrics, equity curve, run list/detail 조회를 실제 데이터로 제공해야 한다. 현재 engine은 final result만 만들고, Postgres schema는 `trades`와 `positions` JSON만 저장한다.

## 사용자 리뷰 요청 흐름

구현 중 차단 조건은 active `CODE_REVIEW-cloud-G08.md`의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 이를 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-analysis-surface.md`
- `packages/domain/backtest/types.go`
- `packages/domain/backtest/types_test.go`
- `services/worker/internal/backtest/engine.go`
- `services/worker/internal/backtest/engine_test.go`
- `services/worker/internal/backtest/fixture_test.go`
- `services/worker/internal/storage/ports.go`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/storage/postgres/mapping.go`
- `services/worker/internal/storage/postgres/mapping_test.go`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.down.sql`
- `services/worker/internal/storage/postgres/sqlc/queries.sql.go`
- `services/worker/internal/storage/postgres/sqlc/models.go`
- `services/worker/sqlc.yaml`
- `bin/worker-storage-gen`
- `bin/worker-storage-check`
- `bin/test`
- `bin/lint`
- `services/worker/go.mod`
- `packages/domain/go.mod`

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

- `fixture_test.go:161` verifies final cash/equity, trades, and positions only; it does not verify per-point equity curve or derived summary metrics.
- `mapping_test.go:120` verifies `BacktestResult` round-trip for current columns only; it does not cover summary/equity curve JSON or list/detail queries.
- No storage tests cover list runs, run detail, or compare-run input ordering.

### 심볼 참조

- Renamed/removed symbols: none planned. Additive fields/types and methods only.
- Existing `backtest.Result` call sites found in `packages/domain/backtest/types_test.go`, `services/worker/internal/backtest/engine.go`, `services/worker/internal/backtest/fixture_test.go`, `services/worker/internal/storage/ports.go`, `services/worker/internal/storage/postgres/store.go`, `services/worker/internal/storage/postgres/mapping.go`, `services/worker/internal/storage/postgres/mapping_test.go`.

### 분할 판단

- Split policy evaluated before writing plan files.
- This task depends on `01_contract_surface`.
- It owns domain model, engine result production, SQL schema/query generation, and storage tests.
- It is separate from parser/client boundary work because storage/migration risk and SQLC generation need independent review.

### 범위 결정 근거

- Include domain result fields, engine summary/equity curve production, Postgres schema/query/mapping/store support, and worker tests.
- Exclude protobuf schema edits except adapting to completed `01_contract_surface` generated types.
- Exclude API/client parser maps; those belong to `03+01,02_api_client_boundary` if not fully handled in `01_contract_surface`.
- No new Go dependencies are expected; use existing standard library, `pgx`, and generated `sqlc`.

### 빌드 등급

- build lane: `cloud-G08`; review lane: `cloud-G08`.
- Rationale: storage/migration plus generated SQLC and financial result calculation has higher blast radius.

## 구현 체크리스트

- [ ] [BAS-WORKER-1] domain `backtest.Result`에 summary metrics와 equity curve value objects를 추가한다.
- [ ] [BAS-WORKER-2] engine이 fixture daily bars에서 deterministic summary metrics와 equity curve를 생성한다. 검증: fixture 기반 result 조회 테스트가 통과한다.
- [ ] [BAS-WORKER-3] Postgres schema/query/store가 summary, equity curve, run list/detail, compare input 조회를 지원한다. 검증: worker/API/client 경계가 backtest result를 일관되게 다룬다.
- [ ] [BAS-WORKER-4] `bin/worker-storage-gen`, `bin/worker-storage-check`, `go test ./services/worker/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [BAS-WORKER-1] Add domain analysis result types

문제: `packages/domain/backtest/types.go:55`의 `Result`에는 `StartingCash`, `EndingEquity`, `Trades`, `Positions`만 있어 `summary-metrics`와 `equity-curve`를 domain model로 표현할 수 없다.

해결 방법:

Before:

```go
55 type Result struct {
56     RunID        RunID
57     StartingCash market.Price
58     EndingEquity market.Price
59     Trades       []TradeSummary
60     Positions    []PositionSummary
61 }
```

After:

```go
type SummaryMetrics struct {
    StartingCash market.Price
    EndingEquity market.Price
    TotalReturn  market.Decimal
    TradeCount   int
}

type EquityPoint struct {
    Timestamp time.Time
    Equity    market.Price
}

type Result struct {
    RunID        RunID
    StartingCash market.Price
    EndingEquity market.Price
    Trades       []TradeSummary
    Positions    []PositionSummary
    Summary      SummaryMetrics
    EquityCurve  []EquityPoint
}
```

수정 파일 및 체크리스트:

- [ ] `packages/domain/backtest/types.go` adds `SummaryMetrics` and `EquityPoint`.
- [ ] `packages/domain/backtest/types_test.go` asserts result carries summary and equity curve values.
- [ ] Use `market.Decimal` string values; do not introduce floats.

테스트 작성: update `TestResultCarriesTradeAndPositionSummary` or add `TestResultCarriesAnalysisSummaryAndEquityCurve`.

중간 검증:

```bash
go test ./packages/domain/...
```

Expected: exits 0. Use `-count=1` only if new tests are flaky; otherwise cache output is acceptable.

### [BAS-WORKER-2] Compute summary and equity curve in engine

문제: `services/worker/internal/backtest/engine.go:74` loops bars but only calculates final `endingEquity` at line 122. There is no time-series equity after each bar and no derived total return/trade count.

해결 방법:

Before:

```go
121 // Calculate ending equity
122 endingEquity, err := portfolio.Equity()
...
141 result := backtest.Result{
142     RunID:        run.ID,
143     StartingCash: startingCash,
144     EndingEquity: endingEquity,
145     Trades:       trades,
146     Positions:    positions,
147 }
```

After:

```go
var equityCurve []backtest.EquityPoint
...
equity, err := portfolio.Equity()
if err != nil { ... }
equityCurve = append(equityCurve, backtest.EquityPoint{Timestamp: bar.Timestamp, Equity: equity})
...
result := backtest.Result{
    RunID:        run.ID,
    StartingCash: startingCash,
    EndingEquity: endingEquity,
    Trades:       trades,
    Positions:    positions,
    Summary: backtest.SummaryMetrics{
        StartingCash: startingCash,
        EndingEquity: endingEquity,
        TotalReturn:  backtest.TotalReturn(startingCash, endingEquity),
        TradeCount:   len(trades),
    },
    EquityCurve: equityCurve,
}
```

If adding a helper for total return, keep it in `packages/domain/backtest` and cover zero/invalid starting cash behavior.

수정 파일 및 체크리스트:

- [ ] `services/worker/internal/backtest/engine.go` records one equity point per processed bar after marking current close.
- [ ] `services/worker/internal/backtest/fixture_test.go` verifies deterministic curve length, timestamps, final point, trade count, and total return.
- [ ] `services/worker/internal/backtest/engine_test.go` updates assertions if `Result` construction or interfaces change.

테스트 작성: add assertions to `TestEngineProducesDeterministicResultFromFixtureBars` and `TestEngineStoresAndQueriesFixtureResult`.

중간 검증:

```bash
go test ./services/worker/internal/backtest/...
```

Expected: exits 0.

### [BAS-WORKER-3] Persist and query analysis results

문제: `000001_worker_backbone.up.sql:35` stores result cash/equity/trades/positions only, `queries.sql:66` has `GetResult` only, and `storage/ports.go:31` exposes only `UpsertResult`/`GetResult`.

해결 방법:

Before:

```sql
35 CREATE TABLE IF NOT EXISTS backtest_results (
36     run_id TEXT PRIMARY KEY REFERENCES backtest_runs(id) ON DELETE CASCADE,
37     starting_cash_currency TEXT NOT NULL,
38     starting_cash_amount NUMERIC NOT NULL,
39     ending_equity_currency TEXT NOT NULL,
40     ending_equity_amount NUMERIC NOT NULL,
41     trades JSONB NOT NULL,
42     positions JSONB NOT NULL
43 );
```

After:

```sql
CREATE TABLE IF NOT EXISTS backtest_results (
    run_id TEXT PRIMARY KEY REFERENCES backtest_runs(id) ON DELETE CASCADE,
    starting_cash_currency TEXT NOT NULL,
    starting_cash_amount NUMERIC NOT NULL,
    ending_equity_currency TEXT NOT NULL,
    ending_equity_amount NUMERIC NOT NULL,
    total_return NUMERIC NOT NULL,
    trade_count INTEGER NOT NULL,
    trades JSONB NOT NULL,
    positions JSONB NOT NULL,
    equity_curve JSONB NOT NULL
);
```

Add SQLC queries:

```sql
-- name: ListRuns :many
SELECT id, strategy_id, market, timeframe, from_time, to_time, status, created_at, updated_at
FROM backtest_runs
WHERE ($1::text = '' OR status = $1)
ORDER BY created_at DESC, id ASC;

-- name: GetRunWithResult :one
SELECT ...
```

Use either a joined detail query or compose `GetRun` + `GetResult` in store; prefer simple composition unless SQLC row shape is cleaner.

수정 파일 및 체크리스트:

- [ ] `services/worker/internal/storage/ports.go` adds list/detail methods with domain types.
- [ ] `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql` adds summary/equity curve columns.
- [ ] `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.down.sql` remains consistent.
- [ ] `services/worker/internal/storage/postgres/queries/queries.sql` adds list/detail queries.
- [ ] Run `bin/worker-storage-gen` to update `sqlc` generated files.
- [ ] `services/worker/internal/storage/postgres/mapping.go` maps summary/equity curve JSON.
- [ ] `services/worker/internal/storage/postgres/store.go` implements new store methods.
- [ ] `services/worker/internal/storage/postgres/mapping_test.go` covers round-trip summary/equity curve and list/detail row mapping.

테스트 작성: add storage mapping tests for `SummaryMetrics`, `EquityCurve`, and missing result behavior.

중간 검증:

```bash
bin/worker-storage-gen
bin/worker-storage-check
go test ./services/worker/internal/storage/postgres/...
```

Expected: all commands exit 0. If local infra is unavailable, `bin/worker-storage-check` should still run because it is generation drift only.

### [BAS-WORKER-4] Worker verification

문제: Domain, engine, SQLC, and Postgres mapping changes must pass focused worker checks before API/client integration starts.

해결 방법: run domain, worker, storage drift, and whitespace checks.

수정 파일 및 체크리스트:

- [ ] Run `go test ./packages/domain/...`.
- [ ] Run `go test ./services/worker/...`.
- [ ] Run `bin/worker-storage-check`.
- [ ] Run `git diff --check`.

테스트 작성: no extra tests beyond BAS-WORKER-1..3.

중간 검증:

```bash
go test ./packages/domain/...
go test ./services/worker/...
bin/worker-storage-check
git diff --check
```

Expected: all commands exit 0. Go test cache output is acceptable after changed packages have run at least once in this implementation.

## 의존 관계 및 구현 순서

Directory dependency `02+01_worker_analysis_store` means `01_contract_surface` must have `complete.log` before this task starts. Do not begin if `agent-task/m-backtest-analysis-surface/01_contract_surface/complete.log` is absent.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/domain/backtest/types.go` | BAS-WORKER-1 |
| `packages/domain/backtest/types_test.go` | BAS-WORKER-1 |
| `services/worker/internal/backtest/engine.go` | BAS-WORKER-2 |
| `services/worker/internal/backtest/engine_test.go` | BAS-WORKER-2 |
| `services/worker/internal/backtest/fixture_test.go` | BAS-WORKER-2 |
| `services/worker/internal/storage/ports.go` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.down.sql` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/queries/queries.sql` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/sqlc/queries.sql.go` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/sqlc/models.go` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/mapping.go` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/store.go` | BAS-WORKER-3 |
| `services/worker/internal/storage/postgres/mapping_test.go` | BAS-WORKER-3 |

## 최종 검증

```bash
go test ./packages/domain/...
go test ./services/worker/...
bin/worker-storage-check
git diff --check
```

Expected: all commands exit 0. Go test cache output is acceptable after changed packages have run once in this implementation.

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