REQ-055 Approved (confirm-req, 팀장) — /v1/portfolios에 active_strategy_id/strategy_name/strategy_params/markets additive 노출. tvr e2e 배관 ①
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
# REQ-055: GET /v1/portfolios 응답에 전략·시장 메타데이터 노출 (additive)
|
||||
|
||||
- Status: Approved
|
||||
- Type: Interface
|
||||
- Priority: High
|
||||
- Owner: Justin Ko
|
||||
|
||||
## Intent
|
||||
|
||||
- `GET /v1/portfolios`(및 단건 `GET /v1/portfolios/{id}` 존재 시 동일)의 `PortfolioItem` 응답에 전략 셋 바인딩 정보(`active_strategy_id`, `strategy_name`, `strategy_params`)와 거래 가능 시장 목록(`markets`)을 additive 필드로 노출한다.
|
||||
- trex-core `hub_read.get_portfolios()`가 응답 dict에서 `active_strategy_id` · `strategy_params`를 파싱 준비(hub_read.py:168-169) 중이나, `PortfolioItem` 스키마에 해당 필드가 미노출되어 항상 `None`/`KeyError` 위험 상태다. 본 REQ가 이 갭을 닫는다.
|
||||
- tvr-v1(dcy-001) 전략 설정의 단일 진실원천이 `portfolio-hub.strategy_sets`로 확정(사장님 확정)된 데 따라, 소비자가 전략 이름·파라미터를 별도 조회 없이 획득할 수 있도록 한다.
|
||||
- 기존 필드·정렬·인증·페이지네이션은 불변(하위호환 additive 확장).
|
||||
|
||||
## Requirement
|
||||
|
||||
- **additive 필드 4개**를 `PortfolioItem` 스키마와 `GET /v1/portfolios` 응답(및 단건 조회 엔드포인트 응답)에 추가한다.
|
||||
|
||||
| 필드 | 타입 | 설명 |
|
||||
|---|---|---|
|
||||
| `active_strategy_id` | `str \| null` | `portfolios.active_strategy_id` 참조 ID. 미바인딩 시 `null`. |
|
||||
| `strategy_name` | `str \| null` | `strategy_sets.name`(JOIN 해석). 미바인딩 또는 strategy_set row 없으면 `null`. |
|
||||
| `strategy_params` | `dict` | `strategy_sets.config`(JSON 문자열) 파싱 결과. 미바인딩·빈 config·파싱 불가 시 `{}`. 파싱 불가 시 서버 WARNING 로그 1건 출력(500 금지). |
|
||||
| `markets` | `list[str]` | `portfolios → assets.markets` 조인 결과. asset 미연결이거나 markets 빈 배열이면 `[]`. |
|
||||
|
||||
- **N+1 금지**: strategy_sets는 list 조회 시 일괄 조인(LEFT JOIN strategy_sets on active_strategy_id) 또는 일괄 IN-fetch 방식으로 처리한다. 단건 조회도 동일 접근 적용.
|
||||
- **파싱 에러 격리**: `strategy_sets.config`가 유효하지 않은 JSON이면 `strategy_params = {}`를 반환하고, 서버 측에서 WARNING 수준 로그를 출력한다. HTTP 응답은 200을 유지한다(500 금지).
|
||||
- **기존 필드 불변**: `portfolio_id`, `asset_id`, `symbol`, `display_name`, `owner_user_id`, `broker`, `status`, `name`, `cash_balance`, `asset_quantity`, `price`, `profit_buffer`, `tag_ids`, `managed_by_daemon_id`, `created_at`, `updated_at` 필드는 변경 없이 유지된다.
|
||||
- **권한 모델 불변**: 토큰 분기(service/session/admin) 및 필터(`?market`, `?include_disabled`, `?user_id`) 동작은 REQ-028 계약을 그대로 따른다.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
1. **Given** `active_strategy_id`가 바인딩된 포트폴리오, **When** `GET /v1/portfolios`, **Then** 해당 item에 `active_strategy_id`(비null), `strategy_name`(strategy_sets.name 값), `strategy_params`(config 파싱 dict) 세 필드가 정상 값으로 반환된다.
|
||||
|
||||
2. **Given** `active_strategy_id`가 `null`인 포트폴리오, **When** `GET /v1/portfolios`, **Then** `active_strategy_id=null`, `strategy_name=null`, `strategy_params={}` 로 반환된다.
|
||||
|
||||
3. **Given** `active_strategy_id`가 존재하나 해당 strategy_set row가 삭제된 포트폴리오(dangling FK), **When** `GET /v1/portfolios`, **Then** `active_strategy_id`는 원래 값, `strategy_name=null`, `strategy_params={}` 로 반환되며 500이 발생하지 않는다.
|
||||
|
||||
4. **Given** strategy_sets.config가 유효하지 않은 JSON 문자열인 포트폴리오, **When** `GET /v1/portfolios`, **Then** `strategy_params={}` 로 반환되고, 서버 로그에 WARNING 1건이 기록되며 HTTP 응답은 200이다.
|
||||
|
||||
5. **Given** `assets.markets = ['KRX', 'NXT']`인 asset에 연결된 포트폴리오, **When** `GET /v1/portfolios`, **Then** `markets=['KRX', 'NXT']` 로 반환된다.
|
||||
|
||||
6. **Given** `assets.markets = []`(빈 배열)인 asset에 연결된 포트폴리오, **When** `GET /v1/portfolios`, **Then** `markets=[]` 로 반환된다.
|
||||
|
||||
7. **Given** 임의 유효 토큰으로 `GET /v1/portfolios`, **When** 응답의 기존 필드(`portfolio_id`, `broker`, `status`, `name`, `symbol`, `display_name`, `owner_user_id`, `cash_balance`, `asset_quantity`, `price`, `profit_buffer`, `tag_ids`, `managed_by_daemon_id`, `created_at`, `updated_at`)를 확인, **Then** 기존 값이 변경 없이 그대로 반환된다(회귀 없음).
|
||||
|
||||
8. **Given** service token(daemon-bound) 또는 session/admin token, **When** `GET /v1/portfolios`, **Then** REQ-028의 토큰 분기 필터(daemon_id·owner_user_id·admin `?user_id`)가 4개 신규 필드 추가 이후에도 동일하게 동작한다.
|
||||
|
||||
9. **Given** strategy_name="tvr_v1", config=`{"target_weight": 0.5, "rebalance_band_pct": 0.05, "min_trade_krw": 10000}`인 strategy_set이 바인딩된 포트폴리오, **When** `GET /v1/portfolios`, **Then** `strategy_params` 응답 dict가 원본 JSON과 동일 키·값을 갖는다.
|
||||
|
||||
10. **Given** 복수의 포트폴리오를 가진 사용자(일부는 strategy 바인딩, 일부는 미바인딩), **When** `GET /v1/portfolios`, **Then** 각 포트폴리오의 `active_strategy_id`·`strategy_name`·`strategy_params`·`markets`가 개별적으로 정확하게 반환된다(리스트 조회 정합성).
|
||||
|
||||
## Impacted Area
|
||||
|
||||
- Docs:
|
||||
- `docs/interface-contract.md` — `GET /v1/portfolios` `PortfolioItem` 응답 필드 정의에 4개 신규 필드 추가 명세
|
||||
|
||||
- Modules:
|
||||
- `app/schemas/v1.py` — `PortfolioItem`에 `active_strategy_id: str | None = None`, `strategy_name: str | None = None`, `strategy_params: dict[str, Any] = Field(default_factory=dict)`, `markets: list[str] = Field(default_factory=list)` 추가
|
||||
- `app/domain/models.py` — `Portfolio` dataclass에 `strategy_name: str | None = None`, `strategy_params: dict[str, Any] = Field(default_factory=dict)`, `markets: list[str]` 필드 추가 (strategy_sets JOIN 결과 수용)
|
||||
- `app/repositories/postgres.py` — `_SELECT_PORTFOLIO` 쿼리에 `LEFT JOIN strategy_sets ss ON p.active_strategy_id = ss.id` 추가 및 `ss.name`, `ss.config`, `a.markets` 컬럼 포함. `_row_to_portfolio` 파싱 보강(config JSON 파싱 + 오류 격리). 단건 get/list 경로 모두 반영.
|
||||
- `app/api/v1_portfolios.py` — `_portfolio_to_item` 매핑에 4개 신규 필드 포함
|
||||
|
||||
- Tests:
|
||||
- `tests/test_v1_portfolios_list.py` — AC-1~10 검증 케이스 추가(전략 바인딩/미바인딩/dangling/파싱불가/markets/기존필드 회귀)
|
||||
|
||||
## Notes
|
||||
|
||||
- **Discovery 참조**:
|
||||
- `trex-tvr-v1 docs/discovery/dcy-001_20260702_target-value-rebalancing.md` (confirmed): 전략 설정 단일 진실원천 = `portfolio-hub.strategy_sets`(name="tvr_v1", config=JSON 문자열). 사장님 확정.
|
||||
- `trex-tvr-v1 docs/discovery/dcy-002` v3 FR-8: `assets.markets`를 거래 가능 시장 목록으로 정의(["KRX","NXT"]; `[]`=KRX only 보수 해석).
|
||||
|
||||
- **소비자 정합 (필드명 고정)**:
|
||||
- trex-core `hub_read.py:168-169`가 응답 dict에서 `active_strategy_id` · `strategy_params` 키를 파싱 준비 중. 본 REQ에서 정의하는 필드명은 이 키 이름과 정확히 일치해야 한다. 이름 변경은 소비자 코드 변경을 유발하므로 본 REQ 범위 밖.
|
||||
|
||||
- **배포 순서**: 본 REQ(①포트폴리오 응답에 전략·시장 메타 실어주기) → trex-core slots 소비 확장(②흘려주기) → tvr e2e 검증. 본 REQ가 배포되지 않으면 trex-core가 읽을 데이터 없음.
|
||||
|
||||
- **레거시(OQ-1, 비차단)**: 기존 `strategy_sets`에 name="target_value", config=`{}` 레코드 및 이를 참조하는 포트폴리오 2개가 존재. name="tvr_v1" 확정에 따른 레거시 마이그레이션(rename/backfill) 여부는 사장님 확인 후 별도 처리. 본 REQ 노출 로직은 레거시 존재와 무관하게 동작(config `{}` → `strategy_params={}` 정상 처리).
|
||||
|
||||
- **DB 마이그레이션 불필요**: `strategy_sets` 테이블과 `portfolios.active_strategy_id` 컬럼은 이미 존재. `assets.markets` 컬럼도 존재. 본 REQ는 쿼리 JOIN 확장과 응답 스키마 변경만 필요하며 alembic 마이그레이션은 발생하지 않는다.
|
||||
|
||||
- **OQ-1(미결)**: `strategy_sets.config` 컬럼 타입이 `TEXT`(JSON 문자열). 응답 `strategy_params`는 서버 측에서 파싱해 dict로 직렬화. 향후 `JSONB` 타입 전환 고려 가능하나 본 REQ 범위 밖.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260704-02
|
||||
- Date: 2026-07-04
|
||||
- Author: 팀장(Claude) — confirm-req
|
||||
|
||||
#### Change Summary
|
||||
- Proposed → Approved: 팀장 검토·승인. tvr e2e 배관 ①(실어주기). 배포 순서 ①→trex-core②. OQ-1(필드명 고정)·OQ-2(레거시 target_value, 비차단) 인지.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added/Updated/Removed: 없음
|
||||
|
||||
#### Revalidation Impact
|
||||
- Reverification Needed: no / Existing Implementation Invalidated: no
|
||||
- Status Recommendation: Approved
|
||||
|
||||
### CHG-20260704-01
|
||||
|
||||
- Date: 2026-07-04
|
||||
- Author: portfolio-hub-dev (Claude)
|
||||
|
||||
#### Change Summary
|
||||
|
||||
- 최초 생성 — trex-tvr-v1 dcy-001 confirmed Discovery 근거로 draft-req 실행. 상태 Proposed.
|
||||
|
||||
#### Intent
|
||||
|
||||
- `GET /v1/portfolios` 응답에 전략·시장 메타데이터를 additive 노출해 trex-core 소비자의 전략 파라미터 단일 조회 경로를 확보한다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
|
||||
- Added: AC-1~10 최초 작성
|
||||
|
||||
#### Impacted Area Delta
|
||||
|
||||
- Docs: docs/interface-contract.md — PortfolioItem 필드 추가 명세
|
||||
- Modules: app/schemas/v1.py, app/domain/models.py, app/repositories/postgres.py, app/api/v1_portfolios.py
|
||||
- Tests: tests/test_v1_portfolios_list.py
|
||||
|
||||
#### Delivery Trace Delta
|
||||
|
||||
- Batches: none (미정, confirm-req 이후 suggest-batch-reqs)
|
||||
- Releases: none (미정)
|
||||
|
||||
#### Revalidation Impact
|
||||
|
||||
- Reverification Needed: no (신규 REQ, 기존 구현 없음)
|
||||
- Existing Implementation Invalidated: no
|
||||
- Status Recommendation: keep-current (Proposed)
|
||||
- Follow-up Action: confirm-req(팀장 승인) → suggest-batch-reqs → batch 계획 편입
|
||||
|
||||
#### Validation Plan
|
||||
|
||||
- pytest TestClient로 AC-1~10 검증. 전략 바인딩·미바인딩·dangling FK·파싱불가·markets 유무·기존 필드 회귀·토큰 분기 시나리오 포함.
|
||||
+8
-7
@@ -9,21 +9,21 @@
|
||||
2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다.
|
||||
3. 작성 또는 수정 후 누락과 모호성을 점검한다.
|
||||
4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다.
|
||||
5. `Proposed` requirement를 승인하려면 승인 준비 상태와 blocker를 점검한다.
|
||||
5. `Approved` requirement를 승인하려면 승인 준비 상태와 blocker를 점검한다.
|
||||
6. `Approved` requirement는 구현 이슈나 작업 항목으로 연결한다.
|
||||
7. 구현과 검증이 끝난 requirement는 `confirm-req-implemented` skill로 완료 전환 가능 여부를 점검한다.
|
||||
8. requirement 변경 시 `change-req` skill을 기준으로 해당 REQ 파일의 `Change Log`를 먼저 갱신한 뒤 본문을 수정한다.
|
||||
9. 구현 전에는 관련 REQ 파일과 최근 `Change Log`를 기준으로 변경 범위를 판단한다.
|
||||
|
||||
## 상태 정의
|
||||
- Proposed: 초안 상태이며 구현 기준으로 확정되지 않음
|
||||
- Approved: 초안 상태이며 구현 기준으로 확정되지 않음
|
||||
- Approved: 구현 기준으로 승인됨
|
||||
- Implemented: 구현과 검증이 완료됨
|
||||
- Deprecated: 더 이상 유지하지 않음
|
||||
|
||||
## Status Transition Evidence
|
||||
|
||||
### Proposed -> Approved
|
||||
### Approved -> Approved
|
||||
- `Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`가 모두 존재해야 한다.
|
||||
- `Type`은 아래 분류 도표에서 정확하게 하나로 지정되어야 한다.
|
||||
- `Acceptance Criteria`는 구현 판단에 사용할 수 있을 정도로 구체적이어야 한다.
|
||||
@@ -41,7 +41,7 @@
|
||||
- 기존 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`)을 수정하기 전에는 새 `Change Log` 항목을 만든다.
|
||||
- `Change Log`에는 최소한 `Acceptance Criteria Delta`, `Impacted Area Delta`, `Delivery Trace Delta`, `Revalidation Impact`, `Validation Plan`이 포함되어야 한다.
|
||||
- 변경이 기존 구현 또는 verification evidence를 무효화하면 `Implemented` requirement는 `Approved`로 되돌린다.
|
||||
- 변경이 승인 전제 자체를 다시 검토하게 만들면 `Approved` 또는 `Implemented` requirement를 `Proposed`로 되돌릴 수 있다.
|
||||
- 변경이 승인 전제 자체를 다시 검토하게 만들면 `Approved` 또는 `Implemented` requirement를 `Approved`로 되돌릴 수 있다.
|
||||
- 상태 되돌림이 발생하면 `Requirement Register`와 `Recent Change Log Summary`도 함께 갱신한다.
|
||||
|
||||
## Requirement 타입 분류 도표
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
## Next Requirement ID
|
||||
|
||||
- Current: `REQ-055`
|
||||
- Current: `REQ-056`
|
||||
- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다.
|
||||
|
||||
## Requirement Register
|
||||
@@ -135,11 +135,12 @@
|
||||
| REQ-052 | 사용자 소유 스코프 인증정보 관리 HTTP API | Interface | Implemented | High | Justin Ko | [req-052](./Interface/req-052_user-owned-credential-management-api.md) |
|
||||
| REQ-053 | admin 데몬 CRUD HTTP API | Interface | Implemented | High | Justin Ko | [req-053](./Interface/req-053_admin-daemon-crud-api.md) |
|
||||
| REQ-054 | 인증정보 관리 보안 격리 — write-only·secret 미반환·로그 미노출 | Non-Functional | Implemented | High | Justin Ko | [req-054](./Non-Functional/req-054_credential-management-security-write-only.md) |
|
||||
| REQ-055 | GET /v1/portfolios 응답에 전략·시장 메타데이터 노출 (additive) | Interface | Approved | High | Justin Ko | [req-055](./Interface/req-055_v1-portfolios-strategy-market-meta-exposure.md) |
|
||||
|
||||
## Recent Change Log Summary
|
||||
|
||||
- 2026-07-04: REQ-055 신규 생성 `Approved` (draft-req) — trex-tvr-v1 dcy-001 confirmed Discovery 근거. `GET /v1/portfolios` 응답에 `active_strategy_id`·`strategy_name`·`strategy_params`·`markets` additive 노출. DB 마이그레이션 불필요(기존 컬럼·테이블 활용), N+1 금지·파싱 에러 격리(500 금지) 포함. confirm-req(팀장 승인) 대기.
|
||||
- 2026-06-28: REQ-052/053/054 `Approved` → `Implemented` (confirm-req-implemented) — BAT-022 PR #19 mergeCommit 1b78c18, QC PASS-WITH-NOTES(opus, blocker 0). 565 passed(+leak 테스트), AC 전건·secret 미반환·소유 스코프·REQ-051 회귀 PASS. Major 보안 #3(owner 재할당 credential leak) 원자화 수정(83e6dde) 반영 — 단일 트랜잭션+선삭제+leak 방지 테스트 3종. REQ-053 owner 재할당 원자화 수정 명시.
|
||||
- 2026-06-28: REQ-052/053/054 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-011 추적·AC testable·blocker 0. 소유 스코프 음성(REQ-052 AC-6/9/10)·owner 재할당+인증정보 무효화(REQ-053 AC-4/5)·secret 미반환·로그 미노출·REQ-051 회귀(REQ-054) 확인. admin도 인증정보는 소유 스코프 전용(self-service 결정 정합). 엔드포인트 경로(OQ-1/2) design 위임. 다음=suggest-batch-reqs.
|
||||
- 2026-06-28: REQ-052/053/054 `Approved` → `Approved` (confirm-req 팀장 승인) — dcy-011 추적·AC testable·blocker 0. 소유 스코프 음성(REQ-052 AC-6/9/10)·owner 재할당+인증정보 무효화(REQ-053 AC-4/5)·secret 미반환·로그 미노출·REQ-051 회귀(REQ-054) 확인. admin도 인증정보는 소유 스코프 전용(self-service 결정 정합). 엔드포인트 경로(OQ-1/2) design 위임. 다음=suggest-batch-reqs.
|
||||
- 2026-06-28: REQ-047 `Approved` → `Implemented` (confirm-req-implemented) — BAT-021 PR #18 머지(7164f9e), 실 dev PG 통합검증 AC-1~7 PASS(Step1→backfill→Step2→downgrade), QC PASS-WITH-NOTES(blocker 0). **Step2(PK 전환) 실 운영 DB 적용은 별도 release 결재 게이트**(런북 `docs/runbooks/bat-021-req047-step2-migration-runbook.md` 엄수, 배포순서 Step1→backfill→Step2→코드배포, alembic stamp 사전확인). 운영 적용 시 AC-3/4/5 실운영 evidence 재확보.
|
||||
- 2026-06-28: REQ-046/048/049/050/051 `Approved` → `Implemented` (confirm-req-implemented) — BAT-020 PR #17 머지(d4acfd4), QC PASS-WITH-NOTES(blocker 0), 테스트 통과. REQ-047은 실 운영 DB 적용·실 PostgreSQL 통합검증이 비가역 사장님 결재 게이트 미완 → Approved 유지. AC-3/4/5(Step2 적용 후 schema) real-DB evidence 대기. 결재·적용 시 TODO: ensure_schema()·upsert ON CONFLICT를 (daemon_id,broker)로 갱신.
|
||||
- 2026-06-28: REQ-046~051 `Proposed` → `Approved` (confirm-req 팀장 승인) — dcy-010 daemon-scoped broker credential. 전건 dcy-010 FR/NFR 추적·AC testable·blocker 0 확인. 단계적 마이그레이션(REQ-047 Step1/2)·**REQ-047 Step2 PK 전환은 비가역 결재 게이트**·2데몬 격리(REQ-049 AC-4/5)·보안 격리(REQ-051) 반영. 비차단 design 위임 2건(REQ-048 구현방식, REQ-050 slug/UUID·과도기 정책).
|
||||
|
||||
Reference in New Issue
Block a user