feat: 태그 HTTP API(BAT-004) 및 pfhub tag CLI + baseline 문서 갱신(BAT-005)
BAT-004 (REQ-004, REQ-005):
- app/api/v1_tags.py: POST /v1/tags, GET /v1/tags, PATCH /v1/tags/{id}/deactivate
- app/api/v1_portfolios.py: POST/DELETE /v1/portfolios/{id}/tags
- app/api/v1_assets.py: POST/DELETE /v1/assets/{id}/tags 엔드포인트 추가
- app/schemas/v1.py: TagItem, TagCreateRequest, TagListResponse, TagApplyRequest/Response 추가
- app/repositories/portfolio_repository.py, postgres.py: append/remove_portfolio_tag_id 추가
- app/main.py: v1_portfolios_router, v1_tags_router 등록
- tests/test_tags_http_api.py: 22개 HTTP API 테스트
BAT-005 (REQ-007, REQ-008):
- app/cli/commands/tag.py: pfhub tag create/list/deactivate/apply/remove 7개 커맨드 신규
- app/cli/main.py: tag_app 등록
- app/cli/commands/portfolio.py: pfhub portfolio tag add/list/remove 완전 제거(breaking change)
- app/cli/core/utils.py: print_tag_table + list[Tag] render 분기 추가
- tests/test_tags_cli.py: REQ-007 AC-1~9 커버 24개 CLI 테스트 신규
- tests/test_cli.py: 제거된 portfolio tag 테스트 2개 삭제
- docs/data-model.md, interface-contract.md, project-structure.md, runtime-flows.md: 태그 모델 baseline 갱신(REQ-008)
- docs/batches/: BAT-004, BAT-005 전체 산출물(release-candidate)
247 passed
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+44
-5
@@ -4,8 +4,8 @@
|
||||
- Baseline Seed: .stagepilot/bootstrap/baseline.yaml
|
||||
- Status: confirmed
|
||||
- Owner: repository-maintainers
|
||||
- Last Updated (KST): 2026-05-13 00:00
|
||||
- Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-008, bat-010
|
||||
- Last Updated (KST): 2026-05-20 00:00
|
||||
- Source Discovery / Batch: bootstrap-baseline, bat-001, bat-006, bat-008, bat-010, bat-003, bat-004, bat-005
|
||||
|
||||
## Project Summary
|
||||
- PortfolioHub exposes portfolio management and trading ledger workflows through a FastAPI service and a companion CLI.
|
||||
@@ -27,13 +27,49 @@
|
||||
|
||||
## Model Summary
|
||||
|
||||
- Portfolio :: owned investment portfolio with balances, strategy references, tags, comments, and ledger history
|
||||
- Portfolio :: owned investment portfolio with balances, strategy references, tag_ids, comments, and ledger history
|
||||
- User :: authenticated principal that owns portfolios and receives client or service tokens
|
||||
- Daemon :: stable logical identity for a trading daemon process; registered per user; bound to service tokens and assigned to assets
|
||||
- AuthToken :: issued client or service token with permission, runtime_id, and optional daemon_id binding
|
||||
- BrokerCredential :: encrypted broker API credentials per user-broker pair
|
||||
- LedgerTrade :: idempotent trade execution record keyed by (asset_id, order_id, execution_id)
|
||||
- Asset :: catalog entry representing a tradeable asset; portfolios reference assets via FK
|
||||
- Tag :: master tag record with ownership (created_by_user_id), admin flag, and active/inactive lifecycle
|
||||
|
||||
## Entity: `Tag`
|
||||
|
||||
- Purpose: represent a named master tag with ownership and admin classification; applied to portfolios via tag_ids UUID[] column
|
||||
- Source of Truth: tags table (added by BAT-003 migration 20260519_0017)
|
||||
- Lifecycle: created (is_active=true) → optionally deactivated (is_active=false); no hard delete
|
||||
|
||||
### Key Fields
|
||||
|
||||
- tag_id :: uuid :: server-generated UUID primary key (gen_random_uuid())
|
||||
- name :: text UNIQUE :: tag label; normalized to lowercase and stripped; unique across all tags
|
||||
- created_by_user_id :: text :: user_id of the creator; used for portfolio tag apply policy
|
||||
- is_admin_tag :: boolean :: if true, tag was created with admin permission; can be applied to any portfolio regardless of ownership
|
||||
- is_active :: boolean :: inactive tags cannot be applied; existing tag_ids arrays are not modified on deactivation
|
||||
- created_at :: timestamptz :: creation timestamp (server_default=NOW())
|
||||
|
||||
### Uniqueness Constraints
|
||||
|
||||
- tags_name_key :: UNIQUE (name) :: one tag entry per unique lowercase name
|
||||
|
||||
### Relationships
|
||||
|
||||
- Portfolio :: portfolios.tag_ids UUID[] soft-references tags.tag_id (no FK constraint); array_append/array_remove manage membership
|
||||
|
||||
### State Rules
|
||||
|
||||
- Only is_active=true tags can be applied via POST /v1/assets/{id}/tags or POST /v1/portfolios/{id}/tags.
|
||||
- Deactivating a tag does not remove it from portfolios.tag_ids arrays (lazy cleanup).
|
||||
- Admin tags (is_admin_tag=true) can be applied to any portfolio; non-admin tags can only be applied to portfolios owned by the tag creator.
|
||||
- Asset tag application requires is_admin_tag=true (asset tags are always admin-controlled).
|
||||
|
||||
### Removed Entity: `portfolio_tags` (BAT-003)
|
||||
|
||||
- The old `portfolio_tags` table (string-keyed free-form tags per portfolio) was replaced by `portfolios.tag_ids UUID[]` in BAT-003.
|
||||
- `portfolio_tags` DROP was included in migration 20260519_0017.
|
||||
|
||||
## Entity: `Asset`
|
||||
|
||||
@@ -83,6 +119,7 @@
|
||||
- cash_balance :: double precision :: current cash snapshot after the latest ledger event
|
||||
- asset_quantity :: double precision :: current holdings snapshot after the latest ledger event
|
||||
- managed_by_daemon_id :: text|null :: soft reference to daemons.id; controls daemon-scoped API access; NULL = unassigned (fail-closed for ledger writes and asset listing); added by migration 20260513_0016
|
||||
- tag_ids :: uuid[] :: array of tag UUIDs from the tags master table; replaces the old string-based portfolio_tags table (BAT-003 migration 20260519_0017); default empty array
|
||||
|
||||
### Uniqueness Constraints
|
||||
|
||||
@@ -94,14 +131,15 @@
|
||||
- User :: owner_user_id links a portfolio to its owning user when ownership is assigned
|
||||
- Daemon :: managed_by_daemon_id links a portfolio to exactly one daemon; NULL means unassigned
|
||||
- StrategySet :: active_strategy_id and avabile_strategies connect reusable strategy definitions
|
||||
- LedgerEntry, PortfolioComment, PortfolioTag :: child records are keyed by portfolio_id (UUID)
|
||||
- LedgerEntry, PortfolioComment :: child records are keyed by portfolio_id (UUID)
|
||||
- Tag :: tag_ids UUID[] references tags.tag_id (no FK — soft reference for flexibility)
|
||||
|
||||
### State Rules
|
||||
|
||||
- Portfolio names are required, must be unique per owner_user_id, and are validated at the schema layer before persistence.
|
||||
- portfolio_id is server-generated at creation; clients must never supply or assume a specific portfolio_id value.
|
||||
- Balance-changing commands must update snapshot fields and append a ledger row together.
|
||||
- Tags are unique per portfolio and comments are retrieved in created_at descending order.
|
||||
- tag_ids stores UUID references to active tags; duplicates are prevented by conditional array_append; comments are retrieved in created_at descending order.
|
||||
|
||||
## Entity: `User`
|
||||
|
||||
@@ -275,6 +313,7 @@
|
||||
|
||||
## Change Log
|
||||
|
||||
- 2026-05-20 KST: bat-005 — REQ-008 AC-1: added Entity: Tag (tags master table, BAT-003 migration 20260519_0017); updated Portfolio.tag_ids UUID[] field; removed portfolio_tags table note; updated Portfolio Relationships and State Rules; updated Model Summary.
|
||||
- 2026-05-19 KST: bat-002 — REQ-015: added Entity: Asset (asset catalog with symbol UNIQUE, markets[], status); updated Portfolio.asset_id FK and symbol/market field notes.
|
||||
- 2026-05-13 KST: bat-010 — REQ-009/023/028: added Entity: Daemon; added portfolios.managed_by_daemon_id; added auth_tokens.daemon_id; updated AuthToken State Rules (runtime_id vs daemon_id distinction, fail-closed policy); updated Portfolio Relationships; updated Model Summary.
|
||||
- 2026-05-11 KST: bat-008 — REQ-011 AC-2: added portfolios.market column; added Entity: AuthToken (runtime_id field); added Entity: BrokerCredential; added Entity: LedgerTrade; updated Model Summary.
|
||||
|
||||
Reference in New Issue
Block a user