- Portfolio domain model + PortfolioItem schema에 display_name 필드 추가 - _SELECT_PORTFOLIO에 a.display_name JOIN 보강, _portfolio_to_item 매퍼 전달 - REQ-028 CHG-20260601-01/02: Implemented → Approved → Implemented - AC-13 신설 (display_name == joined assets.display_name) - BAT-009 verification.md: AC-13 매핑 + augmentation history 누적 - REL-001 verification checklist 재실행 evidence 갱신 (368 PASSED, 로컬 smoke 5건 통과) - tests/test_v1.py: dead 중복 GET /v1/assets 섹션 제거 (두 번째 동명 정의가 첫 번째를 override하던 잔재) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
14 KiB
14 KiB
REQ-028: GET /v1/portfolios 신설 (토큰 분기 조회)
- Status: Implemented
- Type: Interface
- Priority: High
- Owner: Justin Ko
Intent
- v1 API에 portfolio 조회 경로(
GET /v1/portfolios)를 신설해 service/user/admin 세 principal이 단일 엔드포인트로 분기 조회할 수 있도록 한다. - dcy-001 FR-5(REQ-013 Deprecated 처리)에서 회수된
GET /v1/assets통합 endpoint 정의를 본 신규 path로 이전한다(dcy-003 옵션 B 결정).
Requirement
- 신규 라우트
GET /v1/portfolios(Bearer 인증 필수). - 토큰 분기 규칙 (dcy-003 OQ-5 결정):
token_type='service': daemon-bound 조회.daemon_id is None이면 HTTP 403 fail-closed. 그 외managed_by_daemon_id = auth.daemon_id필터 +?market=<code>지원.token_type='client': HTTP 403 fail-closed (dcy-004 REQ-016 폐지 확정 정합).token_type ∈ {session, None(env)}:permission='admin'→ 기본owner_user_id = auth.user_id(본인 portfolio) 반환.?user_id=<id>지정 시 해당 user의 portfolio 반환.permission ∈ {read, trade-write}→owner_user_id = auth.user_id필터, 본인 portfolio만 반환.
?market=<code>필터: 모든 토큰 타입에서 지원.<code> = ANY(assets.markets)인 portfolio만 반환(catalog와 join).?include_disabled=true옵션 (dcy-003 R-2 완화): 기본 응답은status='disabled'인 portfolio 제외. opt-in 시에만 포함.- 응답 항목 schema: 신규
PortfolioItem(portfolio_id,asset_id,symbol,display_name,owner_user_id,broker,status,cash_balance, ...,tag_ids,managed_by_daemon_id, ...).asset_id는 catalog의assets.asset_id(UUID) 참조. GET /v1/portfolios의 각 item은 반드시portfolios JOIN assets결과를 기반으로 직렬화되어야 한다. 특히symbol/display_name은 linked asset row의assets.symbol/assets.display_name을 그대로 노출해야 하며, 클라이언트 캐시나 후속 catalog lookup으로 복원하는 모델은 정식 응답 계약이 아니다.assets.display_name이 NULL이면 응답display_name도null.- 응답: HTTP 200 +
PortfolioListResponse(portfolios: list[PortfolioItem],total: int).
Acceptance Criteria
- Given service token(daemon-bound,
daemon_id있음), WhenGET /v1/portfolios, Thenmanaged_by_daemon_id = auth.daemon_id인 portfolio 목록만 반환. - Given service token(
daemon_id없음), WhenGET /v1/portfolios, Then HTTP 403 fail-closed. - Given session/env token +
permission ∈ {read, trade-write}, WhenGET /v1/portfolios, Thenowner_user_id = auth.user_id인 portfolio 목록만 반환. - Given admin token (no
?user_id), WhenGET /v1/portfolios, Then admin 본인의 portfolio 목록 반환. - Given admin token +
?user_id=<target>, WhenGET /v1/portfolios, Then<target>user의 portfolio 목록 반환. - Given 임의 토큰 +
?market=KRX, WhenGET /v1/portfolios?market=KRX, Then'KRX' = ANY(assets.markets)인 portfolio만 반환. - Given disabled portfolio 1건 + active portfolio 1건 보유 user, When
GET /v1/portfolios(?include_disabled없음), Then active 1건만 반환. - Given AC-7 환경 +
?include_disabled=true, WhenGET /v1/portfolios?include_disabled=true, Then 2건 모두 반환. - Given client token, When
GET /v1/portfolios, Then HTTP 403 fail-closed. - Given Bearer token 없음, When
GET /v1/portfolios, Then HTTP 401. - Given 응답 portfolio 항목, When
asset_id필드 확인, Thenassets.asset_id(catalog UUID)를 참조한다. - Given
GET /v1/portfolios응답 item, Whensymbol필드를 확인, Then 값은 같은 row의asset_id가 가리키는 joinedassets.symbol과 일치한다. - Given
GET /v1/portfolios응답 item, Whendisplay_name필드를 확인, Then 값은 같은 row의asset_id가 가리키는 joinedassets.display_name과 일치한다. 참조 asset의display_name이 NULL이면 응답 필드도null.
Impacted Area
- Docs:
docs/interface-contract.md— GET /v1/portfolios 명세 +?market/?user_id/?include_disabled파라미터 (REQ-032)docs/runtime-flows.md— 토큰 분기 조회 flow + daemon-scoped 필터 (REQ-032)
- Modules:
app/api/v1_portfolios.py— GET 라우트 (REQ-025의 인증 헬퍼 재사용 + GET 전용 service 토큰 분기 추가);_portfolio_to_item매핑에display_name포함 (CHG-20260601-01)app/api/v1_assets.py— 기존list_assets함수에서 portfolio 조회 로직 제거(catalog 전용 회수). REQ-013 Deprecated 후속 정리.app/domain/models.py—Portfoliodataclass에display_name필드 추가 (CHG-20260601-01, asset join 결과 매핑)app/schemas/v1.py—PortfolioItem,PortfolioListResponse신규;PortfolioItem에display_name필드 추가 (CHG-20260601-01)app/services/portfolio_service.py:list_portfolios— token-type별 분기 +?market/?include_disabled처리. dcy-001 REQ-013 Deprecated 후속 작업 흡수.app/repositories/postgres.py:list_portfolios_with_strategies—managed_by_daemon_id필터 +marketsarray 필터 +include_disabled분기;assets.display_namejoin 보강 (CHG-20260601-01)
- Tests:
- 신규
tests/test_v1_portfolios_list.py— AC-1~11 검증;display_name응답 검증 (AC-13)
- 신규
Notes
- 본 REQ는 dcy-001 REQ-013 Deprecated(CHG-20260520-01)의 후속 신규 REQ. 토큰 분기 정책은 REQ-013의 본문을 path만 변경해 흡수.
GET /v1/assets(catalog 조회 의미)는 본 REQ 범위 밖이며 dcy-001 batch design 단계에서 REQ-010 보강 또는 신규 REQ로 처리.- service token이 NULL
daemon_id로 호출하면 fail-closed. NULL daemon legacy token 운영 정리는 dcy-004 REQ-023(migrate-null-daemonCLI)로 처리. ?include_disabled기본 false 정책은 R-2 완화의 핵심. REQ-027 soft delete와 정합.- Source: dcy-003 FR-5, OQ-5, NFR-1, NFR-3, R-2 완화. dcy-001 REQ-013 후속.
Change Log
CHG-20260601-02
- Date: 2026-06-01
- Author: Justin Ko
Change Summary
- Status: Approved → Implemented (CHG-20260601-01 augmentation에 대한 verification evidence 확보)
Intent
- CHG-20260601-01에서 신설된 AC-13(
display_name응답 joinedassets.display_name)이 구현·테스트·로컬 smoke 모두 evidence 확보됨. BAT-009 verification.md에 AC-13 매핑 누적 기록되었고 release-candidate 상태이므로confirm-req-implemented게이트 통과.
Acceptance Criteria Delta
- No change
Impacted Area Delta
- No change
Delivery Trace Delta
- Batches:
- BAT-009 (release-candidate) — augmentation 누적 verification 갱신
- Releases:
- REL-001 — local smoke evidence 기록 (rel-001:55)
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: implemented
- Follow-up Action: REL-001 실 prod/staging 배포 시 GET /v1/portfolios 응답에
display_name노출 최종 확인 (배포 후 smoke evidence)
CHG-20260601-01
- Date: 2026-06-01
- Author: Justin Ko
Change Summary
- Status: Implemented → Approved (응답 schema에
display_name필드 추가 augmentation. portfolio가 참조하는 asset의 display_name을 join하여 응답에 포함.)
Intent
- 현행
GET /v1/portfolios응답은asset_id,symbol만 노출하고display_name은 클라이언트가 별도GET /v1/assetscatalog 조회로 조립해야 한다. UI/CLI 표시 경로에서 N+1 호출 패턴이 발생. 본 augmentation으로 응답 item에display_name을 포함시켜 단일 호출로 표시 가능하도록 한다. 응답 schema는 확장형(필드 추가)이라 기존 클라이언트 호환 유지.
Acceptance Criteria Delta
- Added:
- AC-13 — Given
GET /v1/portfolios응답 item, Whendisplay_name필드 확인, Then 값은 같은 row의asset_id가 가리키는 joinedassets.display_name과 일치한다. 참조 asset의display_name이 NULL이면 응답 필드도null.
- AC-13 — Given
- 기존 AC-1~12: 변경 없음.
Impacted Area Delta
- Modules added:
app/domain/models.py—Portfoliodataclass에display_name: str | None = None필드 추가 (asset join 결과를 매핑하는 뷰 캐시)app/schemas/v1.py—PortfolioItem에display_name: str | None = None필드 추가app/api/v1_portfolios.py—_portfolio_to_item매핑에display_name포함app/repositories/postgres.py—list_portfolios등 portfolio fetching 경로에LEFT JOIN assets로display_name보강
- Tests added/updated:
tests/test_v1_portfolios_list.py—_make_portfolio헬퍼에display_name인자 추가 + 응답 검증 케이스 (이미 부분 작업 흔적)
Delivery Trace Delta
- Batches:
- BAT-009 (v1-portfolios-crud-core) — augmentation 누적, verification 재실행 필요
- Releases:
- REL-001 — scope 잔존, 본 augmentation 포함 (
docs/releases/rel-001_20260521_v1-api-full-surface.md)
- REL-001 — scope 잔존, 본 augmentation 포함 (
Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: no (응답 schema 확장. 기존 호출자 backwards-compat. 다만 repo join 회귀 검증 필요)
- Status Recommendation: revert-to-approved
- Follow-up Action: 구현 → pytest 회귀 → 로컬 smoke로 GET /v1/portfolios 응답에
display_name포함 확인 → confirm-req-implemented 재진입
Validation Plan
- pytest로 AC-13 검증; 로컬 smoke로 admin/session/service token GET /v1/portfolios 응답에
display_name필드 포함 확인.
CHG-20260524-02
- Date: 2026-05-24
- Author: Hermes Agent
Change Summary
- Status: Approved → Implemented
Intent
- BAT-014 verification 완료. GET
/v1/portfolios응답이portfolios JOIN assets결과에서symbol을 직접 반환함이 코드/테스트/문서로 재검증되었다.
Acceptance Criteria Delta
- No change
Impacted Area Delta
- No change
Delivery Trace Delta
- Batches: BAT-014 (release-candidate); BAT-009 response-contract evidence superseded by BAT-014
- Releases: pending
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-implemented
- Follow-up Action: release 단계에서 smoke 확인
CHG-20260524-01
- Date: 2026-05-24
- Author: Hermes Agent
Change Summary
GET /v1/portfolios응답을 joined asset result 기반으로 명확히 고정하고PortfolioItem.symbol계약을 추가하기 위해 REQ-028을 augment. 상태를 Implemented → Approved로 되돌림.
Intent
- trex-orbit 자동매매 데몬은
GET /v1/portfolios응답만으로 외부 시세 API 호출 대상 symbol을 바로 얻어야 한다. 기존 계약은asset_id만 제공해 클라이언트 측 catalog cache를 강제했으므로, server-side asset join 결과(assets.symbol)를 직접 응답에 포함하도록 명시한다.
Acceptance Criteria Delta
- Added:
- AC-12: GET 응답 item의
symbol은 joinedassets.symbol과 일치해야 함
- AC-12: GET 응답 item의
Impacted Area Delta
- Docs:
docs/interface-contract.mdGET/v1/portfolios예시/필드 정의에symbol및 join 기반 응답 설명 추가 필요 (REQ-032 후속) - Modules:
app/schemas/v1.py,app/api/v1_portfolios.py(_portfolio_to_item),app/repositories/postgres.py(_SELECT_PORTFOLIOjoin 결과를 응답까지 전달) - Tests:
tests/test_v1_portfolios_list.py에 list 응답symbol검증 추가 필요
Delivery Trace Delta
- Batches: BAT-009 evidence partially superseded; follow-up BAT-014 예정
- Releases: REL-001 response-contract evidence 일부 재검토 필요
Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes (response schema evidence partial)
- Status Recommendation: revert-to-approved
- Follow-up Action: BAT-014에서 GET
/v1/portfolios응답이 asset join 결과를 직접 반환하는지 회귀 검증
Validation Plan
tests/test_v1_portfolios_list.py에서 각 item의symbol필드 존재와asset_id가 가리키는 joined asset row의symbol일치 여부를 검증한다.
CHG-20260521-01
- Date: 2026-05-21
- Author: Justin Ko
Change Summary
- Status: Approved → Implemented
Intent
- BAT-009 confirm-batch-verification 완료. tests/test_v1_portfolios_list.py AC-1~11 전 케이스 pytest 통과 (2026-05-21, 298 PASSED). 토큰 분기 + status filter + daemon filter 모두 검증.
Acceptance Criteria Delta
- No change
Impacted Area Delta
- No change
Delivery Trace Delta
- Batches: BAT-009 (release-candidate)
- Releases: TBD
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-implemented
- Follow-up Action: 없음
CHG-20260520-02
- Date: 2026-05-20
- Author: Justin Ko
Change Summary
- Status: Proposed → Approved
Intent
- 승인 게이트 통과: 필수 필드(Status/Type/Priority/Owner/Intent/Requirement/AC/Impacted Area/Change Log) 모두 충족, AC 구체성 확인 완료, blocker 없음
Acceptance Criteria Delta
- No change
Impacted Area Delta
- No change
Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정)
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: promote-to-approved
- Follow-up Action: batch 계획 편입
CHG-20260520-01
- Date: 2026-05-20
- Author: Justin Ko
Change Summary
- 최초 생성 — dcy-003 confirm-discovery 후 draft-req 실행. dcy-003 FR-5 산출물 + REQ-013 Deprecated 후속.
Intent
- v1 portfolio 조회 신설 + 토큰 분기 정책 path 이전 + R-2 disabled 가시성 완화.
Acceptance Criteria Delta
- Added:
- AC-1~11 최초 작성
Impacted Area Delta
- Docs: docs/interface-contract.md, docs/runtime-flows.md (REQ-032)
- Modules: app/api/v1_portfolios.py, app/api/v1_assets.py(catalog 회수), app/schemas/v1.py, app/services/portfolio_service.py, app/repositories/postgres.py — 최초 지정
- Tests: tests/test_v1_portfolios_list.py (신규) — 최초 지정
Delivery Trace Delta
- Batches: none (미정)
- Releases: none (미정)
Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: keep-current
- Follow-up Action: confirm-req 후 batch 계획 편입; dcy-001 REQ-013 Deprecated 정리 후속
Validation Plan
- pytest TestClient로 AC-1~11 검증; 토큰 분기 +
?market+?user_id+?include_disabled시나리오