# [REQ] `GET /v1/portfolios` 응답에 종목 코드(`symbol`) 포함 ## 요약 `GET /v1/portfolios` 응답의 `PortfolioItem` 에 종목 코드(`symbol`) 필드를 추가해주세요. 현재는 `asset_id` (UUID) 만 반환되어, 종목 코드가 필요한 클라이언트가 별도 카탈로그 조회로 매핑해야 합니다. ## 배경 trex-orbit 자동매매 데몬은 매 사이클마다 `GET /v1/portfolios` 로 daemon-scoped portfolio 리스트를 받아 운용 대상 종목을 결정합니다. 이후 외부 데이터 서비스 (`kis-data-hub`) 에 시세 / 분봉을 요청하려면 종목 코드 (예: `"005930"`) 가 필요한데, 현재 `PortfolioItem` 에는 `asset_id` (UUID) 만 있고 `symbol` 이 빠져 있어 다음 우회가 필요합니다. 1. trex-orbit 부팅 시 asset 카탈로그를 별도로 조회해 `asset_id → symbol` 캐시 구성 2. 사이클 중 `asset_id` 마다 캐시 lookup 3. 캐시 미스 시 portfolio-hub 에 추가 조회 이 과정에서 캐시 stale / 신규 asset 등록 후 동기화 지연 / 클라이언트마다 캐시 로직 중복 같은 운영 부담이 발생합니다. ## 현재 상태 **Endpoint**: `GET /v1/portfolios` ([`app/api/v1_portfolios.py:224`](app/api/v1_portfolios.py#L224)) **응답 스키마**: [`PortfolioItem`](app/schemas/v1.py#L165) ```python class PortfolioItem(BaseModel): portfolio_id: str asset_id: str | None = None # UUID owner_user_id: str | None = None broker: str status: str name: str cash_balance: float = 0.0 asset_quantity: float = 0.0 price: float = 0.0 profit_buffer: float = 0.0 tag_ids: list[str] = Field(default_factory=list) managed_by_daemon_id: str | None = None created_at: datetime | None = None updated_at: datetime | None = None # symbol 없음 ``` 종목 코드는 별도 `AssetItem` 에만 존재 ([`app/schemas/v1.py:55`](app/schemas/v1.py#L55)): ```python class AssetItem(BaseModel): asset_id: str symbol: str # ← 여기 있음 status: str ... ``` ## 요청 변경 ### 1. 스키마 `PortfolioItem` 에 `symbol` 필드 추가: ```python class PortfolioItem(BaseModel): portfolio_id: str asset_id: str | None = None symbol: str | None = None # ← 추가 요청 owner_user_id: str | None = None ... ``` ### 2. 응답 채움 로직 `GET /v1/portfolios` 의 `_portfolio_to_item` 빌더가 `portfolios.asset_id → assets.symbol` join 결과를 활용해 `symbol` 을 채워주세요. 이미 repository 단계에서 join 되어 있다면 변환 로직만 추가하면 됩니다. ### 3. 단건 / 관련 엔드포인트도 동일 적용 다음 엔드포인트의 응답에도 `symbol` 포함: - `POST /v1/portfolios` (생성 후 반환) - `PATCH /v1/portfolios/{portfolio_id}` - `DELETE /v1/portfolios/{portfolio_id}` (soft delete 응답) - 기타 `PortfolioItem` 을 반환하는 모든 경로 ## 정당성 1. **클라이언트 단순화**: daemon 클라이언트 (trex-orbit 등) 가 portfolio 정보만 받으면 즉시 외부 시세 API 호출 가능. 추가 카탈로그 조회 / 캐시 관리 불필요. 2. **일관성**: `cash_balance`, `asset_quantity`, `price` 등 자산 관련 필드는 이미 PortfolioItem 에 포함됨. `symbol` 도 자산 식별의 핵심이므로 같이 포함되는 것이 자연스러움. 3. **stale cache 위험 제거**: 클라이언트 캐시 + miss fallback 패턴은 카탈로그 변경 시 일시적 불일치 가능. server 측 join 으로 항상 최신 매핑 제공. 4. **운영 부담 감소**: 캐시 무효화 / 갱신 / 재로드 로직이 클라이언트마다 중복 구현되는 것을 회피. ## 호환성 `symbol` 은 신규 필드이며 `Optional[str]` 로 추가하면 기존 클라이언트에 영향 없음 (additive change). ## 영향 범위 - [`app/api/v1_portfolios.py`](app/api/v1_portfolios.py) — `_portfolio_to_item` 에 symbol 채움 - [`app/schemas/v1.py`](app/schemas/v1.py#L165) — `PortfolioItem` 에 `symbol` 필드 추가 - [`app/repositories/`](app/repositories/) — `list_portfolios` / `get_portfolio` 가 asset join 결과를 들고 오도록 보강 (이미 그렇다면 변경 없음) - [`docs/interface-contract.md`](docs/interface-contract.md#L321) — `GET /v1/portfolios` 응답 예시 갱신 - 테스트 — `PortfolioItem` 응답 형식 검증 케이스 추가 ## 검수 기준 1. `GET /v1/portfolios` 호출 시 각 item 에 `symbol` 필드가 포함됨 2. `symbol` 값은 해당 `asset_id` 의 `AssetItem.symbol` 과 일치 (server side join) 3. 단건 조회 / 생성 / 수정 / 삭제 응답도 동일하게 `symbol` 포함 4. `interface-contract.md` 의 `GET /v1/portfolios` 응답 예시에 `symbol` 명시 5. 기존 테스트 회귀 없음 + 신규 케이스 추가 ## 우선순위 **Medium** — trex-orbit 사이드에서 캐시 기반 우회가 가능해 즉시 차단 이슈는 아니지만, 운영 본격 시작 전 정리하면 lifecycle 비용을 크게 줄임. ## 관련 문서 - trex-orbit `temp/plan.md` 8.4.1 step 3 (asset_id → symbol resolve) — 본 이슈의 우회 구현 단계 - trex-orbit `temp/plan.md` 8.6.4.1 전제 시점 — universe 결정 과정에서 symbol resolve 가 필요한 이유 ## 작성자 / 일자 - 요청자: trex-orbit 팀 (genichin@gmail.com) - 작성일: 2026-05-24