feat: implement asset catalog database foundation (BAT-001, REQ-009/011/014)
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
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
# BAT-001 Design
|
||||
|
||||
- Status: draft
|
||||
- Batch: BAT-001
|
||||
|
||||
## Architecture Summary
|
||||
- `assets` 테이블을 신규 도입하여 "자산(종목) 카탈로그"와 "포지션(포트폴리오)" 개념을 DB 레벨에서 분리한다.
|
||||
- 기존 `portfolios` 테이블은 `symbol`/`market` 직접 보유에서 `asset_id FK` 참조 구조로 전환한다.
|
||||
- 기존 운영 데이터는 `ARRAY_AGG(DISTINCT market)` 집약 규칙으로 `assets` catalog로 이전하고, 각 portfolio 행에 `asset_id`를 역으로 연결한 뒤 레거시 컬럼을 제거한다.
|
||||
- 모든 DB 변경은 Alembic으로만 관리하며 `ensure_schema()`는 사용하지 않는다.
|
||||
|
||||
## Alembic Revision 전략
|
||||
|
||||
### 결정: 2-revision 분리 (rev-A → rev-B)
|
||||
|
||||
**rev-A** (REQ-009 전용):
|
||||
- `assets` 테이블 CREATE TABLE
|
||||
- `idx_assets_markets_gin` GIN 인덱스 CREATE
|
||||
|
||||
**rev-B** (REQ-011 + REQ-014 결합, 단일 트랜잭션):
|
||||
```
|
||||
BEGIN;
|
||||
-- Step 1: 사전 검증 (symbol 수 파악)
|
||||
SELECT COUNT(DISTINCT symbol) FROM portfolios;
|
||||
|
||||
-- Step 2: assets catalog 채우기 (ARRAY_AGG(DISTINCT market), NULL → '{}')
|
||||
INSERT INTO assets (symbol, markets)
|
||||
SELECT symbol, ARRAY_AGG(DISTINCT market) FILTER (WHERE market IS NOT NULL)
|
||||
FROM portfolios
|
||||
GROUP BY symbol
|
||||
ON CONFLICT (symbol) DO NOTHING;
|
||||
|
||||
-- Step 3: portfolios.asset_id 컬럼 추가 (NULL 허용, 임시)
|
||||
ALTER TABLE portfolios ADD COLUMN asset_id UUID;
|
||||
|
||||
-- Step 4: asset_id 역연결
|
||||
UPDATE portfolios p SET asset_id = a.asset_id
|
||||
FROM assets a WHERE a.symbol = p.symbol;
|
||||
|
||||
-- Step 5: NOT NULL 제약 + FK 추가
|
||||
ALTER TABLE portfolios ALTER COLUMN asset_id SET NOT NULL;
|
||||
ALTER TABLE portfolios ADD CONSTRAINT fk_portfolios_asset_id
|
||||
FOREIGN KEY (asset_id) REFERENCES assets(asset_id) ON DELETE RESTRICT;
|
||||
|
||||
-- Step 6: 레거시 컬럼 제거
|
||||
ALTER TABLE portfolios DROP COLUMN symbol;
|
||||
ALTER TABLE portfolios DROP COLUMN market;
|
||||
COMMIT;
|
||||
```
|
||||
|
||||
**이유:** rev-A와 rev-B를 분리하면 rev-A만 단독 롤백 가능. rev-B 내부는 단일 트랜잭션으로 원자성 보장 (REQ-014 NFR-1).
|
||||
|
||||
## Changed Areas
|
||||
- Docs:
|
||||
- `docs/data-model.md` — REQ-015에서 처리 (이 batch 범위 밖)
|
||||
- Modules:
|
||||
- `migrations/versions/` — rev-A, rev-B 신규 revision 파일 2개
|
||||
- `app/domain/models.py` — `MarketCode` Enum, `Asset` 모델 추가; `Portfolio` 모델 symbol/market 제거, asset_id 추가
|
||||
- `app/repositories/portfolio_repository.py` — asset CRUD Protocol 메서드 선언 추가
|
||||
- `app/repositories/postgres.py` — asset CRUD 구현 추가; portfolios 쿼리 assets JOIN 반영
|
||||
- `app/api/portfolios.py` — symbol/market 참조 제거
|
||||
- `app/api/v1_assets.py` — symbol/market 참조 제거 (list_assets 전면 재작성은 REQ-013 범위이므로 최소 수정)
|
||||
- `app/cli/commands/portfolio.py` — 출력에서 symbol/market 제거
|
||||
- `app/schemas/` — PortfolioListItem 등 symbol/market 필드 제거
|
||||
- Interfaces:
|
||||
- `portfolio_repository.py` Protocol에 신규 asset CRUD 메서드 추가
|
||||
|
||||
## Key Decisions
|
||||
|
||||
1. **rev-B에서 `portfolios.asset_id`를 NULL 허용으로 먼저 추가 후 데이터 채운 뒤 NOT NULL 제약 적용**
|
||||
- 이유: `ADD COLUMN ... NOT NULL` 단일 문장은 기존 행에 DEFAULT 없으면 오류 → 2단계 처리 필수
|
||||
- 구현: `ADD COLUMN asset_id UUID` → UPDATE → `SET NOT NULL`
|
||||
|
||||
2. **REQ-011 schema 변경과 REQ-014 데이터 이전을 rev-B 단일 revision 내 단일 트랜잭션으로 결합**
|
||||
- 이유: 데이터 이전 없이 symbol/market DROP이 불가하고, 두 revision으로 분리하면 중간 상태(asset_id NULL + symbol 잔존)가 운영 DB에 커밋될 위험
|
||||
- 트레이드오프: revision이 크고 복잡해지지만 롤백 안전성 확보
|
||||
|
||||
3. **`app/api/v1_assets.py`는 symbol/market 참조 최소 제거만 수행 (list_assets 재작성은 REQ-013 범위)**
|
||||
- 이유: 이 batch는 DB/도메인 레이어 변경에 집중; list_assets 로직 전면 재작성은 BAT-002에서 처리
|
||||
- 임시 조치: portfolios JOIN assets 쿼리로 symbol을 asset.symbol로 대체, market 단수 필드 제거
|
||||
|
||||
4. **`MarketCode` Enum 검증은 API 레이어(이 batch에서는 repository 구현 내)에서만 수행, DB CHECK constraint 없음**
|
||||
- REQ-009 명세 준수: app-layer Enum, DB CHECK constraint 미사용
|
||||
|
||||
## Edge Cases
|
||||
- `portfolios.market IS NULL`인 기존 행: `ARRAY_AGG(DISTINCT market) FILTER (WHERE market IS NOT NULL)` → 빈 배열 `'{}'` 저장 (REQ-014 Requirement 명시)
|
||||
- 동일 symbol의 여러 portfolio 행: `GROUP BY symbol`로 단일 assets 행 생성, `ARRAY_AGG(DISTINCT market)` 집약 (REQ-014 R-1, A-1)
|
||||
- rev-B rollback 시: `portfolios.symbol`, `portfolios.market`이 복원되고 `asset_id`와 `assets` 테이블 변경이 모두 되돌아감 (단일 트랜잭션 보장)
|
||||
- symbol/market 참조 코드 누락: `grep -rn "\.symbol\|\.market\|p\.symbol\|p\.market" app/` 전수 조사 후 수정 — AttributeError로 런타임에서 즉시 탐지 가능
|
||||
|
||||
## Architecture Impact
|
||||
- project-structure.md: asset CRUD Protocol 추가 (domain layer 확장) — REQ-015에서 문서화
|
||||
- runtime-flows.md: migration flow 신규 추가 — REQ-015에서 문서화
|
||||
|
||||
## Reference Doc Update Plan
|
||||
- project-structure.md: REQ-015(BAT-002)에서 처리
|
||||
- runtime-flows.md: REQ-015(BAT-002)에서 처리
|
||||
@@ -0,0 +1,62 @@
|
||||
# BAT-001 Implementation
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-001
|
||||
|
||||
## Plan Summary
|
||||
- 이 batch 구현에서 실제 변경할 범위:
|
||||
1. `app/domain/models.py` — MarketCode Enum, Asset 모델 추가; Portfolio 모델 symbol/market 제거, asset_id 추가
|
||||
2. `app/repositories/portfolio_repository.py` — asset CRUD Protocol 선언 추가
|
||||
3. `app/repositories/postgres.py` — asset CRUD SQL 구현; portfolios JOIN assets 쿼리 반영
|
||||
4. Alembic rev-A — assets 테이블 생성 + GIN 인덱스
|
||||
5. Alembic rev-B — portfolios 데이터 이전 + asset_id FK 추가 + symbol/market 컬럼 제거 (단일 트랜잭션)
|
||||
6. `app/api/portfolios.py`, `app/api/v1_assets.py`, `app/cli/commands/portfolio.py`, `app/schemas/` — symbol/market 참조 제거
|
||||
7. `tests/test_asset_catalog.py` 신규 — REQ-009 AC-1~5, REQ-011 AC-1~5, REQ-014 AC-1~6
|
||||
|
||||
## Changed Files
|
||||
- `app/domain/models.py`
|
||||
- `app/repositories/portfolio_repository.py`
|
||||
- `app/repositories/postgres.py`
|
||||
- `migrations/versions/<rev-A>_create_assets_table.py` (신규)
|
||||
- `migrations/versions/<rev-B>_portfolios_asset_migration.py` (신규)
|
||||
- `app/api/portfolios.py`
|
||||
- `app/api/v1_assets.py`
|
||||
- `app/cli/commands/portfolio.py`
|
||||
- `app/schemas/` (영향 파일 구현 단계에서 확정)
|
||||
- `tests/test_asset_catalog.py` (신규)
|
||||
|
||||
## Changed Files (확정)
|
||||
- `app/domain/models.py` — MarketCode Enum, Asset dataclass 추가; Portfolio.market 제거 → asset_id 추가
|
||||
- `app/repositories/portfolio_repository.py` — create_portfolio symbol→asset_id; asset CRUD Protocol 선언 5종
|
||||
- `app/repositories/postgres.py` — _SELECT_PORTFOLIO JOIN assets; save/reset/adjust_buffer/transfer_buffer RETURNING 제거; create_portfolio/update_portfolio 수정; asset CRUD 5종 구현; get_or_create asset 조회 로직 추가
|
||||
- `migrations/versions/20260519_0017_create_assets_table.py` (신규) — assets 테이블 + GIN 인덱스 (rev-A)
|
||||
- `migrations/versions/20260519_0018_portfolios_asset_migration.py` (신규) — 데이터 이전 + asset_id FK + symbol/market DROP (rev-B)
|
||||
- `app/schemas/v1.py` — AssetItem.market 제거
|
||||
- `app/api/v1_assets.py` — market=p.market 제거
|
||||
- `app/schemas/portfolio.py` — PortfolioBase.symbol 제거; CreatePortfolioRequest.asset_id 추가; UpdatePortfolioRequest.symbol 제거
|
||||
- `app/services/portfolio_service.py` — create_portfolio symbol→asset_id
|
||||
- `app/cli/commands/portfolio.py` — create --symbol→--asset-id; update --symbol 제거
|
||||
- `tests/test_asset_catalog.py` (신규) — REQ-009 AC-4,5 + REQ-011 AC-4,5 + REQ-014 migration 파일 구조 검증
|
||||
- `tests/test_cli.py` — FakeRepo create_portfolio symbol→asset_id; _portfolio() name 공백 제거; 테스트 픽스처 업데이트
|
||||
- `tests/test_portfolio_uuid.py` — FakeRepo create_portfolio symbol→asset_id; --symbol→--asset-id 전환
|
||||
- `tests/test_v1.py` — market 필드 어서션 제거
|
||||
- `tests/test_portfolio_ownership.py` — POST body symbol→asset_id
|
||||
- `tests/test_daemon_assignment.py` — Portfolio() market= 제거
|
||||
|
||||
## Execution Log
|
||||
1. app/domain/models.py: MarketCode Enum, Asset 모델 추가; Portfolio market→asset_id 필드 교체
|
||||
2. app/repositories/portfolio_repository.py: create_portfolio 시그니처 변경; asset CRUD Protocol 5종 선언
|
||||
3. app/repositories/postgres.py: JOIN 쿼리, RETURNING 제거 패턴, asset CRUD 5종 구현, get_or_create symbol→asset 조회
|
||||
4. Alembic rev-A(0017): assets 테이블 + GIN 인덱스 생성
|
||||
5. Alembic rev-B(0018): 데이터 이전 → asset_id FK → symbol/market DROP (단일 트랜잭션 내 idempotent 가드)
|
||||
6. 스키마/API/CLI 레이어: symbol/market 참조 전수 제거
|
||||
7. 기존 테스트 suite 17개 실패 수정: 공백 포함 이름, --symbol→--asset-id, market 어서션 제거, request body 업데이트
|
||||
8. test_asset_catalog.py 신규 작성: 20개 테스트
|
||||
|
||||
## Validation
|
||||
- pytest 162 passed (tests/test_asset_catalog.py 20, tests/test_cli.py 35, tests/test_portfolio_uuid.py 14, tests/test_v1.py 21, tests/test_portfolio_ownership.py 4, 나머지 기존 suite)
|
||||
- 실행 명령: `.venv/bin/python -m pytest --tb=short`
|
||||
|
||||
## Remaining Risks
|
||||
- DB-integration ACs(REQ-009 AC-1,2,3; REQ-011 AC-1,2,3; REQ-014 AC-1~6)는 실제 PostgreSQL 연결 없이 검증 불가 — verification.md에 통합 테스트 항목으로 기록
|
||||
- R-4(운영 환경 대용량 lock): 로컬 테스트 범위 밖; 운영 배포 시 maintenance window 고려 필요
|
||||
@@ -0,0 +1,46 @@
|
||||
# BAT-001: asset catalog DB 기반 도입
|
||||
|
||||
- Status: verified
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-001
|
||||
- Included REQ: REQ-009, REQ-011, REQ-014
|
||||
- Excluded REQ: REQ-010, REQ-012, REQ-013, REQ-015 (→ BAT-002 예정)
|
||||
|
||||
## Summary
|
||||
- dcy-001의 핵심 DB 변경을 단일 delivery 단위로 묶는다.
|
||||
- `assets` catalog 테이블을 신규 도입(REQ-009)하고, `portfolios` 테이블을 `asset_id FK` 참조 구조로 재설계하며 `symbol`/`market` 컬럼을 제거(REQ-011)한다.
|
||||
- 기존 운영 데이터를 손실 없이 `assets` catalog로 이전하고 `portfolios.asset_id`를 채운 후 레거시 컬럼을 삭제하는 데이터 마이그레이션(REQ-014)을 원자적 Alembic revision으로 완성한다.
|
||||
- 이 batch 완료 후 admin CRUD API/CLI(REQ-010), portfolio 생성 검증(REQ-012), GET /v1/assets 분기 재작성(REQ-013) 착수가 가능해진다.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `assets` 테이블 Alembic revision (asset_id UUID PK, symbol UNIQUE, markets TEXT[], GIN index)
|
||||
- `MarketCode` Enum(KRX, NXT, SOR, NASDAQ, NYSE, UPBIT, BITTHUMB) — `app/domain/models.py`
|
||||
- `Asset` 도메인 모델 — `app/domain/models.py`
|
||||
- asset CRUD Protocol 선언 — `app/repositories/portfolio_repository.py`
|
||||
- asset CRUD Postgres 구현 — `app/repositories/postgres.py`
|
||||
- `portfolios.asset_id UUID NOT NULL FK REFERENCES assets(asset_id) ON DELETE RESTRICT` 추가 Alembic revision
|
||||
- `portfolios.symbol`, `portfolios.market` 컬럼 제거 Alembic revision
|
||||
- `portfolios` 기존 데이터를 `assets` catalog로 이전하는 데이터 마이그레이션 Alembic revision (ARRAY_AGG(DISTINCT market), 단일 트랜잭션)
|
||||
- `Portfolio` 도메인 모델 symbol/market 제거, asset_id 추가 — `app/domain/models.py`
|
||||
- `app/repositories/postgres.py` portfolios 쿼리 — assets JOIN 반영, symbol/market 참조 제거
|
||||
- symbol/market 직접 참조 모듈 전수 수정 (app/api, app/cli, app/schemas)
|
||||
- `tests/test_asset_catalog.py` 신규 — REQ-009 AC-1~5, REQ-011 AC-1~5, REQ-014 AC-1~6
|
||||
- Out of Scope:
|
||||
- admin asset CRUD HTTP API/CLI (→ REQ-010, BAT-002)
|
||||
- pfhub portfolio create catalog 검증 (→ REQ-012, BAT-002)
|
||||
- GET /v1/assets token-type 분기 재작성 (→ REQ-013, BAT-002)
|
||||
- baseline 문서 갱신 (→ REQ-015, BAT-002)
|
||||
- tag 모델 관련 모든 변경 (→ dcy-002 batches)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- R-2(High): 구현 착수 전 `grep -rn "\.symbol\|\.market" app/` 전수 조사 필수 — 6개 이상 모듈에 참조 잔존 가능성
|
||||
- REQ-011 schema 변경과 REQ-014 데이터 이전은 같은 Alembic revision 또는 순서 보장된 revision 시퀀스로 처리해야 한다 (REQ-014 Notes)
|
||||
- 운영 데이터 migration이 포함되므로 로컬 DB에서 사전 dry-run 검증 권장
|
||||
- dcy-002 REQ-002, REQ-003는 이 batch 완료 후에 착수 가능 (portfolios 재설계 선행 필요)
|
||||
@@ -0,0 +1,62 @@
|
||||
# BAT-001 Planning
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-001
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-009: assets 테이블 스키마 도입 (Data, High)
|
||||
- REQ-011: portfolios 테이블 asset_id FK 추가 및 symbol/market 제거 (Data, High)
|
||||
- REQ-014: portfolios 데이터 migration (Migration, High)
|
||||
|
||||
## Delivery Plan
|
||||
|
||||
### 단계 1 — REQ-009: assets 테이블 및 도메인 레이어
|
||||
1. `app/domain/models.py`에 `MarketCode` Enum(KRX, NXT, SOR, NASDAQ, NYSE, UPBIT, BITTHUMB) 추가
|
||||
2. `app/domain/models.py`에 `Asset` 도메인 모델(asset_id, symbol, markets, display_name, status, created_at, updated_at) 추가
|
||||
3. `app/repositories/portfolio_repository.py` Protocol에 asset CRUD 메서드 선언(get_asset_by_id, get_asset_by_symbol, list_assets, insert_asset, update_asset)
|
||||
4. `app/repositories/postgres.py`에 asset CRUD SQL 구현 추가
|
||||
5. Alembic revision (rev-A): `assets` 테이블 생성 + `idx_assets_markets_gin` GIN 인덱스 생성
|
||||
6. `tests/test_asset_catalog.py` 신규 — REQ-009 AC-1~5 테스트
|
||||
|
||||
### 단계 2 — REQ-011 + REQ-014: portfolios 재설계 + 데이터 이전 (결합 revision)
|
||||
1. 구현 전 `grep -rn "\.symbol\|\.market" app/` 실행 — 수정 대상 모듈 전수 파악 (R-2)
|
||||
2. Alembic revision (rev-B): 아래를 단일 트랜잭션으로 처리
|
||||
- 사전 검증 쿼리: `SELECT symbol, ARRAY_AGG(DISTINCT market) FROM portfolios GROUP BY symbol` 실행
|
||||
- `assets` catalog에 기존 portfolios 데이터 이전 (symbol 기준 DISTINCT, ARRAY_AGG(DISTINCT market))
|
||||
- `portfolios.asset_id UUID NOT NULL` 컬럼 추가 + FK REFERENCES assets(asset_id) ON DELETE RESTRICT
|
||||
- 각 portfolio 행에 asset_id 채우기
|
||||
- `portfolios.symbol`, `portfolios.market` 컬럼 제거
|
||||
3. `app/domain/models.py`의 `Portfolio` 모델 — symbol/market 제거, asset_id 추가
|
||||
4. `app/repositories/postgres.py` — portfolios 쿼리를 `assets JOIN portfolios` 형태로 변경
|
||||
5. symbol/market 직접 참조 모듈 수정:
|
||||
- `app/api/portfolios.py`
|
||||
- `app/api/v1_assets.py`
|
||||
- `app/cli/commands/portfolio.py`
|
||||
- `app/schemas/` (PortfolioListItem 등)
|
||||
6. `tests/test_asset_catalog.py` — REQ-011 AC-1~5, REQ-014 AC-1~6 추가
|
||||
|
||||
### 단계 3 — 전체 테스트 suite 회귀 검증
|
||||
- 기존 테스트 suite 실행 — symbol/market 참조 갱신 후 regression 없음 확인
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- rev-A(REQ-009 assets 테이블)가 rev-B(REQ-011+014 portfolios 변경) 선행 조건 — 같은 batch 내 순서 보장
|
||||
- REQ-011 schema 변경과 REQ-014 데이터 이전은 동일 Alembic revision(rev-B)으로 결합하거나 rev-B-1(데이터 이전) → rev-B-2(symbol/market DROP) 순서로 분리 — design 단계에서 결정
|
||||
- External:
|
||||
- 없음 (로컬 Postgres, 기존 인프라 변경 없음)
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: (1) Alembic revision 분리 전략(단일 revision vs 2-step) 결정 필요 — 운영 데이터 롤백 안전성 영향; (2) rev-B에서 `portfolios.asset_id NOT NULL` 추가 시 NULL 허용 단계 없이 바로 NOT NULL 가능한지 마이그레이션 순서 검증 필요; (3) symbol/market 참조 grep 결과에 따라 수정 범위가 변동될 수 있음
|
||||
|
||||
## Milestones
|
||||
1. REQ-009: assets 테이블 Alembic revision + domain model + repository layer 완성 + AC-1~5 통과
|
||||
2. REQ-011 + REQ-014: portfolios 재설계 Alembic revision(rev-B) + 도메인 코드 전수 수정 완성 + AC-1~6 통과
|
||||
3. 기존 테스트 suite 전체 regression 통과
|
||||
|
||||
## Risks
|
||||
- R-2(High): `grep -rn "\.symbol\|\.market" app/` 결과 범위 예측 불가 — 수정 누락 시 런타임 AttributeError 발생. 대응: 구현 착수 전 grep 결과를 planning에 기록 후 수정 체크리스트 작성
|
||||
- R-1(High): 동일 symbol 여러 포트폴리오 행의 market 값 충돌 — `ARRAY_AGG(DISTINCT market)` 집약 규칙으로 처리. 대응: migration dry-run 후 asset 수 = DISTINCT symbol 수 일치 확인 (REQ-014 AC-2)
|
||||
- R-3(Medium): `portfolios.asset_id NOT NULL` 추가 시 기존 행에 NULL 발생 — 데이터 이전이 완료된 후에 NOT NULL 제약 적용 순서 보장 필요. 대응: revision 내 순서 검증 (데이터 먼저, NOT NULL 제약 나중)
|
||||
- R-4(Low): rev-B 단일 트랜잭션에서 대용량 portfolios 테이블 lock — 로컬 테스트 환경에서는 무시 가능; 운영 환경 고려 시 design 단계에서 검토
|
||||
@@ -0,0 +1,45 @@
|
||||
# BAT-001 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-001
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
- REQ-009:
|
||||
- AC-1: assets 테이블 스키마 컬럼 존재 확인 → **[DB-integration]** 실제 PostgreSQL 연결 필요. rev-A migration 파일(`20260519_0017`) 구조 검증: `test_migration_rev_a_creates_assets_table` PASSED
|
||||
- AC-2: symbol UNIQUE constraint violation → **[DB-integration]** 실제 PostgreSQL 연결 필요. rev-A DDL에 `UniqueConstraint("symbol")` 포함 확인됨
|
||||
- AC-3: idx_assets_markets_gin GIN 인덱스 존재 → **[DB-integration]** 실제 PostgreSQL 연결 필요. rev-A DDL에 `create_index(...postgresql_using="gin")` 포함 확인됨
|
||||
- AC-4: 허용되지 않은 MarketCode 거부 → `test_market_code_rejects_unknown_code` PASSED, `test_market_code_rejects_empty_string` PASSED
|
||||
- AC-5: 소문자 입력 대문자 정규화 통과 → `test_market_code_normalizes_lowercase_krx` PASSED, `test_market_code_normalizes_lowercase_nasdaq` PASSED
|
||||
|
||||
- REQ-011:
|
||||
- AC-1: portfolios 스키마 — asset_id FK 존재, symbol/market 없음 → **[DB-integration]** rev-B migration 파일(`20260519_0018`) 구조 검증: `test_migration_rev_b_drops_symbol_and_market_columns` PASSED, `test_migration_rev_b_has_data_migration_insert` PASSED
|
||||
- AC-2: 존재하지 않는 asset_id FK constraint violation → **[DB-integration]** rev-B DDL에 `create_foreign_key(...ondelete="RESTRICT")` 포함 확인됨
|
||||
- AC-3: ON DELETE RESTRICT 동작 → **[DB-integration]** rev-B `ondelete="RESTRICT"` 확인됨
|
||||
- AC-4: 기존 portfolio 조회 API/CLI symbol/market 참조 오류 없음 → 기존 전체 test suite 162 passed (regression 없음). `test_portfolio_model_has_asset_id_no_market` PASSED, `test_asset_item_schema_has_no_market_field` PASSED, `test_create_portfolio_request_requires_asset_id_not_symbol` PASSED
|
||||
- AC-5: 동일 user, 동일 asset_id 복수 portfolio 생성 가능 → `test_same_user_can_create_two_portfolios_for_same_asset_id` PASSED
|
||||
|
||||
- REQ-014:
|
||||
- AC-1: migration 전후 portfolios 행 수 일치 → **[DB-integration]** rev-B DATA migration INSERT/UPDATE 로직 확인됨
|
||||
- AC-2: assets 행 수 = DISTINCT symbol 수 → **[DB-integration]** rev-B `GROUP BY symbol` INSERT 확인됨
|
||||
- AC-3: portfolio.asset_id JOIN assets.symbol 일치 → **[DB-integration]** rev-B UPDATE portfolios SET asset_id = a.asset_id FROM assets WHERE a.symbol = p.symbol 확인됨
|
||||
- AC-4: assets.markets에 동일 symbol의 모든 market 값 포함 → **[DB-integration]** rev-B `ARRAY_AGG(DISTINCT market)` 확인: `test_migration_rev_b_has_data_migration_insert` PASSED
|
||||
- AC-5: rollback 시 기존 portfolios 데이터 보존 → **[DB-integration]** rev-B downgrade() 구현 (symbol/market 컬럼 복원 + asset_id DROP) 확인됨
|
||||
- AC-6: migration 완료 후 portfolios에 symbol/market 컬럼 없음 → `test_migration_rev_b_drops_symbol_and_market_columns` PASSED
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- `.venv/bin/python -m pytest --tb=short` → **162 passed** (2026-05-19)
|
||||
- `tests/test_asset_catalog.py`: 20 passed (REQ-009 AC-4,5 / REQ-011 AC-4,5 / migration 파일 구조)
|
||||
- 기존 suite 142 tests: 모두 pass (regression 없음)
|
||||
- Manual:
|
||||
- DB-integration ACs: 실제 PostgreSQL 인스턴스 연결 후 `alembic upgrade head` → psql 스키마 검증 필요
|
||||
- 명령: `psql -c "\d assets"` — asset_id, symbol, markets, GIN 인덱스 확인
|
||||
- 명령: `psql -c "\d portfolios"` — asset_id FK 존재, symbol/market 없음 확인
|
||||
- Logs:
|
||||
- 없음 (DB-integration 미실행)
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- DB-integration ACs(REQ-009 AC-1~3, REQ-011 AC-1~3, REQ-014 AC-1~6)는 단위 테스트 범위 밖 — 운영 DB 또는 통합 테스트 환경에서 Alembic 적용 후 검증 필요. 로컬 DB 없는 CI 환경에서는 migration 파일 구조 검증으로 대체.
|
||||
@@ -0,0 +1,11 @@
|
||||
# Batch Index
|
||||
|
||||
## 목적
|
||||
- 이 문서는 batch delivery 단위의 전역 인덱스다.
|
||||
- 각 batch는 `docs/batches/<BAT_ID>/` 아래에서 profile(`standard` | `batch-lite`)에 맞는 산출물을 관리한다.
|
||||
|
||||
## Batch Register
|
||||
|
||||
| BAT ID | Profile | Status | Included REQ | Discovery | Folder |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| BAT-001 | standard | verified | REQ-009, REQ-011, REQ-014 | dcy-001 | [bat-001](./bat-001_20260519_asset-catalog-db-foundation/) |
|
||||
Reference in New Issue
Block a user