Asset Catalog Model Implementation (BAT-001): - Introduce assets table with UUID PK, symbol UNIQUE, markets TEXT[] array - Define MarketCode enum (KRX, NXT, SOR, NASDAQ, NYSE, UPBIT, BITHUMB) - Add Asset domain model with catalog properties - Implement asset CRUD protocols in portfolio_repository - Add PostgreSQL asset CRUD operations with GIN index on markets Portfolio Model Refactoring (REQ-011): - Add portfolios.asset_id UUID FK to assets(asset_id) ON DELETE RESTRICT - Remove portfolios.symbol and portfolios.market columns - Update Portfolio domain model to reference asset_id instead of symbol/market - Refactor all portfolio queries to JOIN assets catalog - Update all repository methods to use asset references Data Migration (REQ-014): - Create 20260519_0017 Alembic revision for assets table creation - Create 20260519_0018 Alembic revision for atomic data migration: * Extract unique symbol combinations from portfolios * Aggregate markets using ARRAY_AGG(DISTINCT market) * Populate assets catalog with extracted data * Update portfolios.asset_id to reference new assets * Remove legacy symbol/market columns in single transaction - Implement migration with rollback support Requirements Approved (REQ-001~008): - REQ-001~008 transitioned from Proposed to Approved - Complete tag model requirement set with implementation readiness New Requirements Approved (REQ-009~015): - REQ-009: assets table schema (approved, included in BAT-001) - REQ-010: admin asset catalog CRUD (approved, future BAT-002) - REQ-011: portfolios asset_id FK (approved, included in BAT-001) - REQ-012: portfolio create catalog validation (approved, future BAT-002) - REQ-013: GET /v1/assets token branching (approved, future BAT-002) - REQ-014: portfolios data migration (approved, included in BAT-001) - REQ-015: asset catalog baseline docs (approved) Code Changes: - app/domain/models.py: Add Asset model, MarketCode enum, Portfolio asset_id FK - app/repositories/portfolio_repository.py: Define asset CRUD protocol - app/repositories/postgres.py: Implement asset operations with full CRUD - app/api/v1_assets.py: Remove symbol/market direct references - app/cli/commands/portfolio.py: Update portfolio commands for asset references - app/schemas/portfolio.py: Update portfolio schema with asset_id - All portfolio operations throughout codebase: Use asset references Testing: - Add test_asset_catalog.py for asset CRUD operations - Verify REQ-009 AC-1~5 (asset catalog creation, lookup, uniqueness) - Verify REQ-011 AC-1~5 (FK constraint, portfolio asset reference) - Verify REQ-014 AC-1~6 (migration atomicity, data correctness, rollback) - Update existing tests for asset model usage Documentation: - Create BAT-001 batch documents (planning, design, implementation, verification) - Update dcy-001 with implementation details and confirmed architecture - Add 8 new requirement documents (REQ-009~015) - Update docs/discovery/index.md with batch entry Impact: - Database: assets catalog table, portfolios refactored to reference assets - Migration: 2 new Alembic revisions (0017 assets, 0018 portfolio migration) - API: Symbol/market now accessed through asset references - CLI: Portfolio commands require asset reference pattern - All existing portfolio data preserved and correctly migrated
27 KiB
0. 문서 상태
이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다.
- 상태: confirmed
- 문서 ID: dcy-001_20260519_asset-catalog-and-user-portfolio-model
- 이슈명: 자산 관리 구조 재설계 — admin 관리 asset catalog와 user portfolio 분리 모델 도입
- 작성 시각(KST): 2026-05-19T00:00+09:00
- 마지막 갱신 시각(KST): 2026-05-19 (draft-req 완료 — REQ-009~015 생성)
- 대체됨: 없음
- 후속 Discovery 참조: 없음
- 생성된 REQ 참조: REQ-009, REQ-010, REQ-011, REQ-012, REQ-013, REQ-014, REQ-015
1. 계획 상태 요약
현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다.
- 연계 문서 현황:
- 관련 점수 파일: 미생성
- 관련 TODO/메모: 없음
- 해석:
- 현재
portfolios테이블은 asset 정의(symbol, broker)와 user position(cash_balance, quantity)을 하나의 엔티티로 혼합하여 관리한다. 이로 인해 admin이 거래 가능한 종목을 중앙 관리하는 것이 불가능하고, user가 임의 symbol/broker 조합으로 portfolio를 생성할 수 있어 거버넌스 공백이 생긴다. - 이 Discovery는 portfolio-hub의 핵심 도메인 모델을 재설계하는 첫 번째 단계로, admin이 관리하는 asset catalog와 user가 구성하는 portfolio(포지션)를 명확히 분리하는 새로운 아키텍처를 정의한다.
- 현재
2. 요구사항 판정 결과
이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다.
- 판정: 신규
- 근거
- 기존 SDLC 문서 전체(dcy-001
008, REQ-001029)가 현재 단일portfolios모델 위에 정의되어 있었으며, 2026-05-19 아키텍처 전환 결정에 따라 폐기되었다. 새 모델은 기존 어떤 Discovery와도 중복되지 않는다. - asset catalog + user portfolio 분리 모델은 기존 SDLC에서 다룬 적 없는 신규 도메인 구조다.
- 기존 SDLC 문서 전체(dcy-001
3. 문제점의 요약
문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다.
- 현재 상태
portfolios테이블이 asset 정의(symbol, market/markets)와 user 포지션(broker, cash_balance, asset_quantity, strategy, tags, comments)을 하나의 엔티티로 관리한다. "portfolio를 만든다" = "거래 가능한 종목을 등록한다"와 동일한 행위다.- admin이 거래 가능한 종목 목록을 중앙 통제할 방법이 없다. user 또는 admin 누구나 임의 symbol/broker 조합으로 portfolio를 생성할 수 있다.
- v1 API의
asset_id는 실제로는portfolio_id(user 포지션 ID)로, "자산"과 "포지션"의 개념이 혼용된다. - daemon은 portfolio(포지션) 단위로 배정되어 있어 동일 종목을 여러 user가 운용할 때 daemon 관리 구조가 복잡해진다.
- 기대 상태
assets테이블(admin 관리 catalog)에 거래 가능한 종목(symbol, market)이 등록되며, admin만 이 목록을 생성·수정·비활성화(deactivate)할 수 있다. broker는 catalog 속성이 아니다.- user는
assetscatalog에서 종목을 선택하고 거래에 사용할 broker를 지정하여 자신의portfolio(포지션)를 구성한다. 포지션에는 broker, cash_balance, asset_quantity, strategy, tags, comments, 거래 이력이 귀속된다. - v1 API의
asset_id는 catalog의 실제 asset을 참조하며 "자산"과 "포지션"의 개념이 명확히 분리된다. - daemon은 portfolio(포지션) 레벨로 배정된다. 하나의 daemon이 특정 user의 특정 portfolio들을 담당하며, 동일 종목이라도 user별로 별도 daemon 배정이 가능하다(
portfolios.managed_by_daemon_id유지).
- 이해관계자
- admin: 거래 가능한 종목을 중앙에서 등록·관리하는 운영자
- user: catalog에서 종목을 선택해 자신의 포지션을 구성하는 투자자
- daemon(trex-basic 등): 배정된 자산의 거래 체결 내역을 기록하는 서비스 에이전트
- repository-maintainers: 새 아키텍처 기반으로 API/CLI/DB를 설계·구현하는 개발팀
- 제약사항
- 기존
portfolios테이블 데이터(운영 중인 포지션)를 새assets+portfolios구조로 유실 없이 migration해야 한다. - Python/FastAPI/Typer/Postgres 기술 스택은 유지한다.
- Alembic 기반 migration 절차는 유지한다.
- dcy-002(태그 소유권 및 적용 대상 정책, confirmed)는 이 Discovery의
assets/portfolios테이블 확정을 구현 선행 조건으로 한다.assets.tag_ids UUID[]컬럼과portfolios.tag_ids UUID[]컬럼 추가는 dcy-002 구현 범위이며 이 Discovery에서 다루지 않는다.
- 기존
주요 사용자 시나리오
사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다.
- admin이
AAPL (markets: ['NASDAQ']),005930.KS (markets: ['KRX', 'NXT', 'SOR'])등을 asset catalog에 등록한다.markets가 빈 배열이면 현재 어떤 시장에서도 거래 불가 상태다. 등록되지 않은 종목은 user가 portfolio에 추가할 수 없다. - user가 catalog에 등록된
005930.KS종목을 선택하고 사용할 broker(예: kiwoom)를 지정하여 자신의 portfolio(포지션)를 생성하고, cash_balance와 전략을 설정한다. - daemon이
GET /v1/assets를 호출하면 자신에게managed_by_daemon_id로 배정된 portfolio 목록을 반환받고(각 항목의asset_id는 catalog UUID 참조), 그asset_id로POST /v1/assets/{asset_id}/ledger/trades를 호출해 체결 내역을 기록한다. - admin이 catalog에서 특정 asset을 비활성화(
status='inactive')하면 해당 종목에 대한 신규 portfolio 생성은 차단되지만 기존 portfolio는 보존된다. - user는 동일 asset에 대해 전략 A, B로 각각 portfolio를 구성할 수 있다(OQ-2 결정: 복수 portfolio 허용).
4. 이번에 정의할 변경
이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다.
assets테이블(admin 관리 catalog) 신규 도입 — symbol, market, status 등 종목 정의 데이터를 admin이 관리하는 독립 엔티티로 분리한다. broker는 종목 자체의 속성이 아니므로 포함하지 않는다.portfolios테이블 재설계 —asset_id FK → assets.asset_id를 포함한 user 포지션 구조로 변경. symbol/market은assets로 이전(market은 markets 배열로 집약)하고, broker는 portfolios에 유지하며, 나머지 포지션 데이터(cash_balance, quantity, strategy 등)는 그대로 남긴다.- 기존
portfolios데이터 migration — 현재 portfolios 행을assetscatalog 항목과portfolios포지션 항목으로 분리 이전한다.
영향 범위
변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다.
- 코드 영향:
app/domain/models.py:Asset도메인 모델 신규 추가,Portfolio모델에서 symbol/market 필드 제거 및asset_id참조로 대체. broker는 Portfolio 모델에 유지.app/repositories/postgres.py:assetsCRUD 메서드 신규 추가,portfolios쿼리에서assetsJOIN 반영.app/repositories/portfolio_repository.py: Protocol에 asset CRUD 선언 추가app/api/v1_assets.py: 토큰 타입(service/user/admin) 분기 로직 추가,?user_id파라미터 추가(admin 전용), 응답asset_id를 catalog UUID로 재정의app/api/portfolios.py: portfolio 생성 시asset_id지정 필수화. GET 목록 조회는GET /v1/assets로 흡수되며GET /portfolios는 점진적 deprecated 예정(mutation 경로는 별도 Discovery에서 v1으로 이전)app/services/portfolio_service.py: create_portfolio에asset_id검증 추가.app/cli/commands/asset.py: 신규 파일 —pfhub asset create/list/update/deactivate커맨드 그룹 구현 (admin 전용)app/cli/main.py:app.add_typer(asset_app, name="asset")추가app/cli/commands/portfolio.py: portfolio create 시--asset-id또는--symbol --broker조합으로 catalog 참조app/schemas/:AssetItem,CreatePortfolioRequest등 스키마 변경migrations/versions/:assets테이블 생성 +portfolios재설계(asset_id FK 추가, symbol/market 제거) + 데이터 이전 migration- ※ 태그 관련 변경(
Tag모델,tags테이블,asset_tags/portfolio_tags재설계, 태깅 검증 로직)은 모두 dcy-002 구현 범위
- 문서 영향:
docs/data-model.md:Asset엔티티 추가,Portfolio엔티티 재정의docs/interface-contract.md: v1 API asset catalog 기반 재정의docs/runtime-flows.md: admin asset 등록 → user portfolio 구성 → daemon 체결 기록 흐름 추가
- 운영 영향:
- admin이 신규 종목을 portfolio에 추가하기 전 catalog에 먼저 등록해야 하는 운영 절차 추가
- 기존 운영 중인 portfolios에 대해 migration 전 admin 확인 절차 필요
5. 요구사항 목록
실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다.
기능 요구사항
- FR-1:
assets테이블 신규 도입 — 필드:asset_id UUID PK,symbol TEXT NOT NULL UNIQUE,markets TEXT[] NOT NULL DEFAULT '{}',display_name TEXT,status TEXT NOT NULL DEFAULT 'active',created_at,updated_at. admin만 CRUD 가능.symbol단독 UNIQUE constraint 적용(symbol이 종목 식별자).markets배열에 거래 가능한 시장 코드를 복수 개 저장하며, 빈 배열은 현재 어떤 시장에서도 거래 불가를 의미한다.markets컬럼에 GIN 인덱스를 적용한다. 시장 코드 허용 값은 application-layer Python Enum으로 정의하며(예:MarketCode.KRX,MarketCode.NXT,MarketCode.SOR,MarketCode.NASDAQ,MarketCode.NYSE,MarketCode.UPBIT,MarketCode.BITTHUMB), API 수신 시 대문자 정규화 후 Enum 검증을 수행한다. DB CHECK constraint는 사용하지 않는다. broker는 종목 자체의 속성이 아니므로 포함하지 않는다. - FR-2: admin asset catalog CRUD 경로 제공 — admin CLI(
pfhub asset create/list/update/deactivate) 및 HTTP API(POST/GET/PATCH /v1/assets,PATCH /v1/assets/{asset_id}/deactivate)를 통해 admin이 거래 가능한 종목을 등록·관리한다. asset 삭제는 hard DELETE 없이status='inactive'로만 처리한다(soft delete). 비활성화된 asset을 참조하는 기존 portfolio는 그대로 보존되며 신규 portfolio 생성만 거부한다. - FR-3:
portfolios테이블 재설계 —asset_id UUID NOT NULL컬럼 추가 및FOREIGN KEY (asset_id) REFERENCES assets(asset_id) ON DELETE RESTRICT(DB-level FK constraint) 적용.symbol/market컬럼 제거.broker컬럼은 portfolios에 유지한다(user가 해당 포지션을 어느 증권사로 거래하는지는 포지션 레벨 속성).(owner_user_id, asset_id)unique constraint는 추가하지 않는다(동일 user가 동일 asset에 복수 portfolio 허용 — OQ-2 결정).managed_by_daemon_id는 기존과 동일하게 nullable soft reference로 유지하며, portfolio 생성 시 지정은 선택사항이고 NULL이면 fail-closed(ledger write, asset listing 불가)다. portfolio는 asset catalog에 등록된 종목만 참조할 수 있다. - FR-4: user portfolio 생성 시 catalog 검증 —
pfhub portfolio create(CLI) 호출 시 지정한asset_id가 catalog에status='active'로 존재하는지 검증. 미등록 또는 inactive asset이면 거부한다. user portfolio CRUD HTTP API는 Out of Scope이며 별도 Discovery에서 동일한 catalog 검증 정책을 따른다. - FR-5: v1 API
GET /v1/assets재정의 — 토큰 타입에 따라 아래와 같이 동작하는 통합 portfolio 조회 엔드포인트로 재정의한다. 응답 항목의asset_id는 catalog의assets.asset_id(UUID)를 참조한다.- service token (daemon): auth context에서
daemon_id를 자동 추출하여managed_by_daemon_id = auth.daemon_id필터링.daemon_id가 없으면 HTTP 403. - user token (로그인 사용자):
owner_user_id = auth.user_id필터링. 본인 portfolio만 반환. - admin token: 기본은
owner_user_id = auth.user_id(본인 portfolio).?user_id=<id>파라미터 지정 시 해당 user의 portfolio를 반환. - 모든 토큰 타입에서
?market=<code>파라미터를 지원하며, 지정 시<code> = ANY(assets.markets)인 portfolio만 반환. daemon은 현재 시간 기준 활성 시장 코드를 전달하여 거래 대상 portfolio를 필터링한다.
- service token (daemon): auth context에서
- FR-6: 기존
portfolios데이터 migration — 현재 portfolios 행의symbol을 기준으로 중복 없이assetscatalog 항목을 생성하고, 동일 symbol의 모든market값을ARRAY_AGG(DISTINCT market)으로 집약하여markets배열에 저장한다. 각 portfolio 행에asset_id FK를 연결한다.managed_by_daemon_id는 기존 값을 그대로 유지한다.
비기능 요구사항
- NFR-1: 기존
portfolios데이터 migration은 트랜잭션 내에서 원자적으로 실행되며 실패 시 롤백 가능해야 한다. - NFR-2: 새 구조에서 portfolio 조회 시
assets JOIN portfolios성능이 기존 단일 테이블 조회 대비 허용 범위 내여야 한다 (인덱스 설계 포함). - NFR-3: admin이 아닌 user 또는 daemon이 asset catalog를 직접 생성·수정·비활성화하려 하면 HTTP 403으로 거부한다.
범위 경계
이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다.
- In Scope
assets테이블 도입 및 admin CRUD 경로 (CLI + HTTP API)portfolios테이블 재설계 (asset_id FK추가, symbol/market 제거, broker 유지)- 기존 portfolios 데이터 migration
- v1
GET /v1/assets의 catalog 기반 재정의 docs/data-model.md,docs/interface-contract.md,docs/runtime-flows.md갱신
- Out of Scope
- user portfolio mutation HTTP API(POST 생성, PATCH 수정, DELETE 삭제, buy/sell/invest 등) — 별도 Discovery에서
/v1/경로로 이전 예정 /portfoliosGET 목록 조회의 v1 이전 이후GET /portfolios엔드포인트 제거 — 별도 Discovery에서 처리- 태그 소유권 모델 및 태깅 정책 전체 — dcy-002(confirmed) 구현 범위
assets.tag_ids UUID[]컬럼 추가 — dcy-002 구현 범위portfolios.tag_ids UUID[]컬럼 추가 및portfolio_tags테이블 재설계/제거 — dcy-002 구현 범위tags마스터 테이블 생성, 태그 CRUD API(/v1/tags), pfhub tag CLI — dcy-002 구현 범위
- user portfolio mutation HTTP API(POST 생성, PATCH 수정, DELETE 삭제, buy/sell/invest 등) — 별도 Discovery에서
6. 리스크/가정 목록
불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다.
리스크
- R-1 (High): 기존
portfolios데이터 migration 중 동일symbol을 가진 여러 portfolio 행이 서로 다른market값을 가질 수 있다. 이를 하나의 asset 행으로 병합하고markets배열로 집약하는 로직이 필요하며, 집약 규칙이 명확하지 않으면 market 누락이 발생할 수 있다.- 완화 방안: migration 전
SELECT symbol, ARRAY_AGG(DISTINCT market) FROM portfolios GROUP BY symbol로 symbol별 market 집합을 파악하고,ARRAY_AGG(DISTINCT market)결과를assets.markets에 그대로 저장한다. market 값이 NULL인 기존 행은 빈 배열로 처리한다.
- 완화 방안: migration 전
- R-2 (High):
portfolios테이블에서 symbol/market 컬럼 제거는 이 컬럼들을 직접 참조하는 모든 쿼리, API response, CLI 출력을 수정해야 한다. 누락된 참조가 있으면 런타임 오류가 발생한다.- 완화 방안: migration 전
grep -rn "\.symbol\|\.market" app/으로 참조 범위를 전수 조사하고 REQ Impacted Area에 명시한다. broker는 portfolios에 유지되므로 별도 조사 불필요.
- 완화 방안: migration 전
- R-3 (Medium):
managed_by_daemon_id IS NULL인 기존 portfolio가 migration 후에도 계속 동작 불가(fail-closed) 상태로 남는다. migration 자체를 막지는 않지만, daemon 미배정 portfolio의 운영 가시성 확보가 필요하다.- 완화 방안: migration 전
SELECT COUNT(*) FROM portfolios WHERE managed_by_daemon_id IS NULL로 미배정 portfolio 현황을 파악하고, 필요한 경우 migration 전 daemon 배정 절차를 선행한다.
- 완화 방안: migration 전
가정
- A-1: 현재
portfolios테이블에서 동일symbol을 가진 행들은 migration 시 하나의assetscatalog 항목으로 병합되며, 각 행의market값은ARRAY_AGG(DISTINCT market)으로 집약되어assets.markets에 저장된다. broker는 각 portfolio 행에 그대로 유지된다. - A-2:
assetscatalog의status필드는 최초active/inactive두 가지로 시작하며, 비활성 asset에 대한 신규 portfolio 생성은 거부한다. - A-3: 기존
portfolios.name은 user가 지정하는 포지션 이름으로 유지된다. asset catalog에는 별도display_name필드를 둔다. - A-4: portfolio 생성 시
managed_by_daemon_id지정은 선택사항(nullable)이며, 미배정 portfolio는 fail-closed 상태로 운영된다(ledger write·asset listing 불가). daemon 배정은 portfolio 생성과 분리된 admin 작업으로 처리한다.
오픈 질문
답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.
- OQ-1: daemon 배정 단위 — daemon을
assetscatalog 레벨(하나의 daemon이 모든 user의 해당 asset 거래 처리)에 배정할 것인가,portfolios포지션 레벨(user별 별도 daemon 배정)에 배정할 것인가?- 상태: Answered
- 결정: portfolio(포지션) 레벨 배정.
portfolios.managed_by_daemon_id방식 유지. 동일 종목이라도 user별 별도 daemon 배정 가능. - 반영 위치: Section 3 기대 상태(daemon 배정 단위 확정), FR-5(
GET /v1/assetsportfolio 기준 재정의)
- OQ-2: 동일 asset에 대해 한 user가 여러 portfolio(포지션)를 가질 수 있는가? (예: 같은 종목을 전략 A, B로 각각 운용)
- 상태: Answered
- 결정: 허용.
(owner_user_id, asset_id)unique constraint 추가하지 않는다.managed_by_daemon_id로 동일 asset의 복수 portfolio를 구별한다. - 반영 위치: FR-3(unique constraint 제외 및 managed_by_daemon_id 정책 명시)
- OQ-3: asset catalog의
DELETE동작 — catalog에서 asset을 삭제할 때 해당 asset을 참조하는 portfolios가 있으면 어떻게 처리하는가? (거부 / cascade 삭제 / soft delete)- 상태: Answered
- 결정: soft delete(
status='inactive'). hard DELETE 없음. 기존 portfolio 보존, 신규 portfolio 생성만 거부. - 반영 위치: FR-2(delete → deactivate로 변경, soft delete 정책 명시)
- OQ-4:
assets.markets배열에 저장하는 시장 코드의 허용 값 목록과 정규화 방식을 어떻게 정의하는가? (예: 'KRX', 'NXT', 'SOR', 'NASDAQ', 'NYSE', 'UPBIT' 등 — 대소문자 기준, DB CHECK constraint vs application-layer 정규화 vs 참조 테이블)- 상태: Answered
- 결정: application-layer Python Enum 정규화. 허용 값: KRX, NXT, SOR, NASDAQ, NYSE, UPBIT, BITTHUMB. API 수신 시 대문자 변환 후 Enum 검증. DB CHECK constraint 없음. 허용 값 변경 시 코드 배포만 필요.
- 반영 위치: FR-1(
markets컬럼 설명에 Enum 검증 방식 추가)
오픈 질문 처리 기준
Open: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.Answered: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다.Deferred: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다.Dropped: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다.- 처리 원칙 1: 질문만 남기지 말고 반드시
상태,처리 방안,종료 조건을 함께 적는다. - 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다.
- 처리 원칙 3: High 영향 질문은
Deferred처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다.
7. 초기 성공 기준
이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다.
- S-1: admin이 CLI 또는 HTTP API로 asset catalog에 종목을 등록하면 해당
asset_id가 생성되고, user가 그asset_id로 portfolio를 생성할 수 있다. - S-2: catalog에 없거나
status='inactive'인asset_id로 portfolio 생성을 시도하면 거부된다. - S-3: 기존 portfolios 데이터가 migration 완료 후
assets+portfolios구조로 유실 없이 이전되며, 기존 portfolio 기능(조회, 업데이트, 거래 기록)이 새 구조에서 동일하게 동작한다. - S-4: v1
GET /v1/assets가 daemon에managed_by_daemon_id로 배정된 portfolio 목록을 반환하며, 각 항목의asset_id가 catalog의 UUID를 참조한다. - S-5: admin이 asset을 비활성화(
status='inactive')한 후 해당asset_id로 신규 portfolio 생성을 시도하면 거부되고, 해당 asset을 참조하는 기존 portfolio는 보존된다. - S-6: user token으로
GET /v1/assets를 호출하면 해당 user의 portfolio 목록만 반환된다. admin token으로GET /v1/assets를 호출하면 admin 본인의 portfolio 목록이 반환되고,?user_id=<id>파라미터 지정 시 해당 user의 portfolio 목록이 반환된다.
측정 방식 및 데이터 출처
각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다.
- S-1 측정 방식: pytest로 admin asset create → user portfolio create(asset_id 지정) 시나리오 검증 / 데이터 출처: 신규
tests/test_asset_catalog.py - S-2 측정 방식: pytest로 미등록 asset_id로 portfolio create 시 오류 반환 확인 / 데이터 출처: 신규
tests/test_asset_catalog.py - S-3 측정 방식: migration 전후 portfolio 행 수 일치 및 기존 portfolio 기능 regression 테스트 통과 확인 / 데이터 출처: migration script 실행 결과 + 기존 테스트 suite
- S-4 측정 방식: daemon service-token으로
GET /v1/assets호출 시managed_by_daemon_id기반 portfolio 목록과 catalog UUID asset_id 반환 확인 / 데이터 출처: 신규tests/test_v1_asset_catalog.py - S-5 측정 방식: pytest로 admin asset 비활성화 → 해당
asset_id로 신규 portfolio 생성 시 거부 + 기존 portfolio 존재 보존 확인 / 데이터 출처: 신규tests/test_asset_catalog.py - S-6 측정 방식: pytest로 user token
GET /v1/assets호출 시 본인 portfolio만 반환 확인; admin token으로 본인 portfolio 반환 확인 +?user_id=<id>지정 시 해당 user portfolio 반환 확인 / 데이터 출처: 신규tests/test_v1_asset_catalog.py
8. REQ로 넘기기 전 확인 체크
REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다.
- 범위(In Scope/Out of Scope) 확정
- 기준: OQ-1(daemon 배정 단위), OQ-2(user당 동일 asset 복수 portfolio), OQ-3(asset 삭제 동작) 모두 Answered 처리되어 scope 경계 최종화됨
- 성공 지표의 측정 방식/데이터 출처 확정
- 기준: S-1~6 측정 방식이 검증 가능한 pytest 시나리오로 연결됨
- High 영향 리스크 우선순위 및 해소 책임자 지정
- 기준: R-1(migration 중복 처리 규칙), R-2(symbol/market 참조 전수 조사) 완화 방안 명시됨; draft-req 단계에서 REQ Impacted Area에 반영 예정
- 모든 오픈 질문(OQ)
Open상태 없음 확인- 기준: OQ-1/2/3/4 모두 Answered 처리 완료
- Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: repository-maintainers 중 1명이 Confirmed By에 명시
9. 파일 처리 결과
이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다.
- 처리 결과: 생성
- 생성 경로: docs/discovery/dcy-001_20260519_asset-catalog-and-user-portfolio-model.md
- 참조 문서:
- docs/data-model.md (현재 portfolios 모델 baseline — 이 Discovery로 재정의 예정)
- docs/interface-contract.md (현재 v1 API 계약 — 이 Discovery로 재정의 예정)
- docs/runtime-flows.md (현재 runtime flow baseline — 새 아키텍처 반영 예정)
10. 사용자 결정 필요 항목 요약
사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.
DECIDE
{{DECIDE_DAEMON_ASSIGNMENT_LEVEL}}: Decided — portfolio(포지션) 레벨 배정.portfolios.managed_by_daemon_id유지. (OQ-1 Answered 2026-05-19){{DECIDE_MULTI_PORTFOLIO_PER_ASSET}}: Decided — 허용.(owner_user_id, asset_id)unique constraint 없음.managed_by_daemon_id로 구별. (OQ-2 Answered 2026-05-19){{DECIDE_ASSET_DELETE_BEHAVIOR}}: Decided — soft delete(status='inactive'). hard DELETE 없음. 기존 portfolio 보존, 신규 생성 거부. (OQ-3 Answered 2026-05-19){{DECIDE_MARKET_CODE_NORMALIZATION}}: Decided — application-layer Python Enum 정규화. DB CHECK constraint 없음. 허용 값 변경 시 코드 배포만 필요. (OQ-4 Answered 2026-05-19)
CONFIRM
{{CONFIRM_MIGRATION_DEDUP_RULE}}: Confirmed — A-1(가정)에 동일 내용 명시됨. 동일 symbol → assets 단일 행 병합, market → ARRAY_AGG(DISTINCT market) 집약, broker → 각 portfolio 행 유지. (A-1 참조, 2026-05-19)
DATA
- 없음
11. Discovery Freeze
REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다.
섹션 참조
- 범위: # 5. 요구사항 목록 > 범위 경계
- 리스크/오픈 질문: # 6. 리스크/가정 목록
- 성공 기준: # 7. 초기 성공 기준
- 인계 전 확인: # 8. REQ로 넘기기 전 확인 체크
- 사용자 결정 필요: # 10. 사용자 결정 필요 항목 요약
Freeze 플래그
- Handoff Decision: 승인
- Handoff Rationale: OQ-1~4 모두 Answered, 플레이스홀더 전체 해소, 저장소 사실 관계 일치 확인. REQ 초안 작성 가능.
- Ready for REQ Drafting: true
- Confirmed By: Justin
- Confirmed At (KST): 2026-05-19T00:00+09:00