# req-016: portfolios 테이블 UUID 전환 및 데이터 마이그레이션 - Status: Implemented - Type: Migration - Priority: High - Owner: repository-maintainers ## Intent - 현재 사용자가 직접 지정하는 TEXT PRIMARY KEY(`portfolio_id`)를 서버 생성 UUID로 교체하고, 기존 `portfolio_id` 문자열을 `name` 컬럼으로 이전한다. - `(owner_user_id, name)` 복합 UNIQUE 제약을 DB 레벨에서 도입해 사용자 범위 내 이름 유일성을 보장한다. - 기존 데이터와 FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)을 단일 Alembic revision으로 atomic하게 마이그레이션한다. ## Requirement - 단일 Alembic revision을 추가해 아래를 수행한다. 1. `portfolios` 테이블에 `name` TEXT 컬럼을 추가한다. 2. 기존 `portfolio_id` 값을 `name` 컬럼으로 복사한다 (변환 없이 그대로). 3. 각 기존 레코드에 `uuid.uuid4()`로 새 UUID를 생성해 `portfolio_id` 컬럼 값을 교체한다. 4. FK 참조 테이블(`portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`)의 `portfolio_id` 컬럼 값도 새 UUID로 함께 갱신한다. 5. `(owner_user_id, name)` 복합 UNIQUE 제약을 `portfolios` 테이블에 추가한다. 6. `Portfolio` dataclass(`app/domain/models.py`)의 `portfolio_id` 필드를 UUID 문자열로 유지하고 `name` 필드를 명확히 선언한다. 7. `app/repositories/postgres.py`의 `create_portfolio()` 메서드가 서버에서 `uuid.uuid4()`로 생성된 UUID를 `portfolio_id`로 사용한다. - 마이그레이션은 IF NOT EXISTS 패턴을 준수해 재실행 시 오류가 없어야 한다(REQ-002 준수). - FK 참조 테이블과의 갱신은 단일 트랜잭션 내에서 atomic하게 처리한다. - 마이그레이션 완료 후 FK 고아 레코드(orphan) 0건을 검증하는 쿼리를 revision 안에 포함한다. ## Acceptance Criteria 1. Given 기존 `portfolio_id` 문자열로 운영 중인 portfolios 레코드가 있을 때, When 신규 Alembic revision을 `alembic upgrade head`로 실행하면, Then 각 레코드의 `name` 컬럼에 기존 `portfolio_id` 값이 그대로 복사되고, `portfolio_id`는 UUID 형식(`re.match(r'^[0-9a-f-]{36}$', ...)`)으로 교체된다. 2. Given 마이그레이션 완료 후, When `portfolio_ledger`, `portfolio_comments`, `portfolio_tags`, `ledger_trades`의 `portfolio_id` 컬럼을 확인하면, Then 고아 레코드(FK 불일치 레코드)가 0건이다. 3. Given `(owner_user_id, name)` 복합 UNIQUE 제약이 적용된 상태에서, When 동일 `owner_user_id`와 동일 `name`으로 portfolio 레코드를 추가하면, Then DB 레벨에서 UNIQUE 제약 위반 오류가 발생한다. 4. Given 동일 `name`이지만 다른 `owner_user_id`로 portfolio 레코드를 추가하면, Then 정상 삽입된다. 5. Given 신규 Alembic revision을 이미 적용한 환경에서, When 동일 revision을 재실행하면, Then 오류 없이 완료된다(IF NOT EXISTS 준수). 6. Given `app/repositories/postgres.py`의 `create_portfolio()` 메서드가 실행되면, Then 서버에서 `uuid.uuid4()`로 생성된 UUID가 `portfolio_id`로 저장된다. ## Impacted Area - Docs: `docs/data-model.md` (portfolios 테이블 스키마 변경 반영 — REQ-019에서 처리) - Modules: - `migrations/versions/`: 신규 Alembic revision 파일 - `app/domain/models.py`: Portfolio dataclass `portfolio_id` 및 `name` 필드 - `app/repositories/postgres.py`: `create_portfolio()` UUID 생성, `get_portfolio_by_name_and_user()` 신규 메서드 - Tests: - 신규 `tests/test_portfolio_uuid.py` 또는 `tests/test_cli.py` — portfolio_id UUID 형식 검증, 동일 name 독립 생성 테스트, (owner_user_id, name) UNIQUE 제약 동작 확인 ## Notes - 기존 `portfolio_id` 문자열은 `name` 컬럼으로 변환 없이 복사한다(Discovery A-3 확정). - `portfolio_id` 컬럼 타입은 TEXT 유지(PostgreSQL UUID 타입으로의 전환은 이번 범위 밖). - `app/domain/models.py`의 `Portfolio.portfolio_id`는 UUID 문자열로 의미가 바뀌나 Python 타입(`str`)은 유지. - `get_portfolio_by_name_and_user()` 메서드는 REQ-017(CLI 식별 방식 전환)에서도 사용한다. - Source Discovery: dcy-004_20260511_portfolio-id-uuid-migration-and-user-scoped-name-resolution (FR-1, FR-5, NFR-1) ## Change Log ### CHG-20260511-01 - Date: 2026-05-11 - Author: repository-maintainers #### Change Summary - 최초 생성 — dcy-004 draft-req에 의해 생성 #### Intent - dcy-004 Discovery의 FR-1(portfolios.portfolio_id UUID 교체 및 name 컬럼 추가), FR-5(기존 데이터 마이그레이션), NFR-1(단일 revision, IF NOT EXISTS, atomic 트랜잭션) 반영 #### Acceptance Criteria Delta - Added: - 1~6 (최초 생성) #### Impacted Area Delta - Docs: - docs/data-model.md: portfolios 테이블 스키마 변경 (REQ-019에서 처리) - Modules: - migrations/versions/: 신규 revision 추가 - app/domain/models.py: Portfolio dataclass 변경 - app/repositories/postgres.py: create_portfolio UUID 생성, get_portfolio_by_name_and_user() 추가 - Tests: - tests/test_portfolio_uuid.py 또는 tests/test_cli.py: UUID 형식 검증, UNIQUE 제약 동작 확인 #### Delivery Trace Delta - Batches: none (batch 미결정) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: confirm-req → suggest-batch-reqs → draft-batch #### Validation Plan - pytest: portfolio_id UUID 형식 검증, FK 고아 레코드 0건 확인, (owner_user_id, name) UNIQUE 제약 동작 확인, IF NOT EXISTS 재실행 안전성 확인 ### CHG-20260511-02 - Date: 2026-05-11 - Author: repository-maintainers #### Change Summary - Approved 전환 — confirm-req 게이트 통과 #### Intent - Delivery 입력으로 사용할 수 있도록 REQ-016을 승인 상태로 승격한다. #### Acceptance Criteria Delta - Added: - none - Updated: - none - Removed: - none #### Impacted Area Delta - Docs: - none - Modules: - none - Tests: - none #### Delivery Trace Delta - Batches: - none - Releases: - none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: keep-current - Follow-up Action: suggest-batch-reqs로 batch grouping 검토 #### Validation Plan - confirm-req 게이트 재평가 결과: 필수 메타데이터, 구체적 Acceptance Criteria, Impacted Area, Change Log, blocker 없음 확인 ### CHG-20260511-03 - Date: 2026-05-11 - Author: repository-maintainers #### Change Summary - Implemented 전환 — BAT-006 verification passed, confirm-req-implemented 게이트 통과 #### Intent - BAT-006 verification.md의 REQ-016 Acceptance Mapping이 모든 AC에 대해 test evidence를 포함하고 있으며, Blocking Issues 없음으로 구현 완료 상태로 전환한다. #### Acceptance Criteria Delta - none #### Impacted Area Delta - none #### Delivery Trace Delta - Batches: BAT-006 (release-candidate) - Releases: none #### Revalidation Impact - Reverification Needed: no - Existing Implementation Invalidated: no - Status Recommendation: implemented - Follow-up Action: draft-release (BAT-006) #### Validation Plan - BAT-006 verification.md AC-1~6 모두 test evidence 연결 확인 (tests/test_portfolio_uuid.py 14/14 passed, full suite 129/129 passed)