feat(req-002): implement portfolio tagging and tag filters
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# REQ-002: 포트폴리오 태깅 및 태그 기반 검색 지원
|
||||
|
||||
- Status: Proposed
|
||||
- Status: Approved
|
||||
- Type: Interface
|
||||
- Priority: Medium
|
||||
- Owner: Justin
|
||||
@@ -12,27 +12,30 @@
|
||||
## Requirement
|
||||
- 시스템은 각 포트폴리오에 대해 하나 이상의 태그를 추가하고 제거할 수 있어야 한다.
|
||||
- 시스템은 HTTP API와 CLI를 통해 동일한 포트폴리오 태깅 기능을 제공해야 한다.
|
||||
- 시스템은 태그 추가 시 입력 문자열의 앞뒤 공백을 제거한 값을 저장 및 비교 기준으로 사용해야 하며, 정규화 결과가 빈 문자열이면 validation 오류를 반환해야 한다.
|
||||
- 시스템은 태그 문자열의 대소문자, 내부 공백, 네임스페이스 구분자(`:`)를 정규화 과정에서 변경하지 않아야 하며, 앞뒤 공백 제거 외의 추가 정규화는 수행하지 않아야 한다.
|
||||
- 시스템은 둘 이상의 태그를 조건으로 포트폴리오를 검색할 수 있어야 하며, 검색 요청에서 `AND` 또는 `OR` 매칭 방식을 선택할 수 있어야 한다.
|
||||
- 시스템은 다운스트림 소비자가 태그 기반 목록 조회 시 사용할 수 있도록 포함 태그(`includeTags`)와 제외 태그(`excludeTags`)를 동시에 지원해야 한다.
|
||||
- 시스템은 HTTP API의 포트폴리오 목록 조회 인터페이스와 CLI의 동일 의미 인터페이스에서 포함 태그(`includeTags`), 제외 태그(`excludeTags`), 포함 태그 매칭 방식(`matchMode`: `AND` 또는 `OR`)을 동시에 지원해야 한다.
|
||||
- 시스템은 `matchMode`를 `includeTags` 집합에만 적용해야 하며, `excludeTags`는 요청된 태그 중 하나라도 일치하면 해당 포트폴리오를 결과에서 제외해야 한다.
|
||||
- 시스템은 태그 기반 조회에서 `grade:*`, `blacklist:*`와 같은 태그 네임스페이스를 그대로 보존하고 필터 조건으로 사용할 수 있어야 한다.
|
||||
- 시스템은 공백만으로 이루어지지 않은 태그 문자열을 추가 대상으로 받아들여야 한다.
|
||||
- 시스템은 동일 포트폴리오에 이미 연결된 동일한 태그 문자열을 다시 추가하려는 요청에 대해 `409 Conflict` 또는 이에 대응하는 CLI 오류를 반환해야 하며, 기존 태그 상태를 변경하지 않아야 한다.
|
||||
- 시스템은 동일 포트폴리오에 이미 연결된 태그와 앞뒤 공백 제거 후 동일한 태그 문자열을 다시 추가하려는 요청에 대해 `409 Conflict` 또는 이에 대응하는 CLI 오류를 반환해야 하며, 기존 태그 상태를 변경하지 않아야 한다.
|
||||
- 시스템은 태그 추가·삭제·검색 요청에 대해 기존 포트폴리오 접근 권한 정책을 적용해야 하며, 인증 실패 또는 권한 부족 시 기존 인증 오류 계약에 따라 요청을 거부해야 한다.
|
||||
- 시스템은 존재하지 않는 `portfolio_id`를 대상으로 한 태그 추가·삭제 요청에 대해 Not Found 오류를 반환해야 한다.
|
||||
- 시스템은 태그 검색 결과에서 각 포트폴리오를 중복 없이 한 번만 반환해야 한다.
|
||||
- 시스템은 태그 문자열 길이 또는 포트폴리오당 태그 개수에 대해 애플리케이션 정의 최대 제한만을 이유로 요청을 거부하지 않아야 한다.
|
||||
|
||||
## Acceptance Criteria
|
||||
1. Given 기존 포트폴리오가 존재하고 권한이 있는 사용자가 API 또는 CLI로 새 태그를 추가할 때, When 공백만으로 이루어지지 않은 태그 문자열을 제출하면, Then 시스템은 해당 포트폴리오에 태그를 연결하고 후속 조회에서 그 태그가 포함된 상태를 반환해야 한다.
|
||||
1. Given 기존 포트폴리오가 존재하고 권한이 있는 사용자가 API 또는 CLI로 새 태그를 추가할 때, When 공백만으로 이루어지지 않은 태그 문자열을 제출하면, Then 시스템은 입력값의 앞뒤 공백을 제거한 태그를 해당 포트폴리오에 연결하고 후속 조회에서 그 정규화된 태그가 포함된 상태를 반환해야 한다.
|
||||
2. Given 특정 포트폴리오에 이미 연결된 태그가 있을 때, When 사용자가 API 또는 CLI로 해당 태그를 제거하면, Then 시스템은 성공 응답을 반환하고 후속 조회 및 태그 검색 결과에서 그 연결을 노출하지 않아야 한다.
|
||||
3. Given 여러 포트폴리오에 서로 다른 태그 조합이 연결되어 있을 때, When 사용자가 API 또는 CLI로 둘 이상의 태그와 `AND` 모드를 지정해 검색하면, Then 시스템은 요청한 모든 태그를 포함한 포트폴리오만 반환해야 한다.
|
||||
4. Given 여러 포트폴리오에 서로 다른 태그 조합이 연결되어 있을 때, When 사용자가 API 또는 CLI로 둘 이상의 태그와 `OR` 모드를 지정해 검색하면, Then 시스템은 요청한 태그 중 하나 이상을 포함한 포트폴리오를 반환해야 한다.
|
||||
5. Given 동일 포트폴리오에 이미 동일한 태그 문자열이 존재할 때, When 사용자가 API 또는 CLI로 같은 태그를 다시 추가하면, Then 시스템은 `409 Conflict` 응답 또는 이에 대응하는 CLI 오류를 반환하고 중복 태그 연결을 생성하지 않아야 한다.
|
||||
3. Given 여러 포트폴리오에 서로 다른 태그 조합이 연결되어 있을 때, When 사용자가 API 또는 CLI로 `includeTags`에 둘 이상의 태그와 `matchMode=AND`를 지정해 검색하면, Then 시스템은 요청한 모든 포함 태그를 가진 포트폴리오만 반환해야 한다.
|
||||
4. Given 여러 포트폴리오에 서로 다른 태그 조합이 연결되어 있을 때, When 사용자가 API 또는 CLI로 `includeTags`에 둘 이상의 태그와 `matchMode=OR`를 지정해 검색하면, Then 시스템은 요청한 포함 태그 중 하나 이상을 가진 포트폴리오를 반환해야 한다.
|
||||
5. Given 동일 포트폴리오에 이미 앞뒤 공백 제거 후 동일한 태그 문자열이 존재할 때, When 사용자가 API 또는 CLI로 같은 태그를 다시 추가하면, Then 시스템은 `409 Conflict` 응답 또는 이에 대응하는 CLI 오류를 반환하고 중복 태그 연결을 생성하지 않아야 한다.
|
||||
6. Given 인증되지 않았거나 해당 포트폴리오에 접근 권한이 없는 사용자가 API 또는 CLI로 태그 추가·삭제·검색을 시도할 때, When 요청을 전송하면, Then 시스템은 기존 인증/인가 계약에 따라 `401` 또는 `403` 응답을 반환하고 태그 상태를 변경하지 않아야 한다.
|
||||
7. Given 존재하지 않는 `portfolio_id`가 요청에 포함될 때, When 사용자가 API 또는 CLI로 태그 추가 또는 삭제를 시도하면, Then 시스템은 `404` 응답 또는 이에 대응하는 CLI 오류를 반환하고 어떤 태그 연결도 생성·변경하지 않아야 한다.
|
||||
8. Given 포트폴리오에 이미 여러 태그가 연결되어 있고 사용자가 긴 태그 문자열을 추가할 때, When API 또는 CLI로 태그 추가를 요청하면, Then 시스템은 애플리케이션 정의 최대 길이 제한 또는 최대 개수 제한만을 이유로 요청을 거부하지 않아야 한다.
|
||||
8. Given 포트폴리오에 이미 여러 태그가 연결되어 있고 사용자가 긴 태그 문자열을 추가할 때, When API 또는 CLI로 태그 추가를 요청하면, Then 시스템은 앞뒤 공백 제거 후 빈 문자열이 아닌 한 애플리케이션 정의 최대 길이 제한 또는 최대 개수 제한만을 이유로 요청을 거부하지 않아야 한다.
|
||||
9. Given `OR` 모드의 다중 태그 검색에서 하나의 포트폴리오가 두 개 이상의 요청 태그와 동시에 일치할 때, When 사용자가 검색을 수행하면, Then 시스템은 해당 포트폴리오를 결과에 한 번만 반환해야 한다.
|
||||
10. Given 소비 서비스가 `includeTags=["grade:A", "grade:B"]`, `excludeTags=["blacklist:trade"]` 조건으로 목록 조회를 요청한 상태, When 태그 필터 검색을 수행하면, Then 시스템은 포함 조건을 만족하고 제외 조건을 만족하지 않는 항목만 반환해야 한다.
|
||||
10. Given 소비 서비스가 `includeTags=["grade:A", "grade:B"]`, `excludeTags=["blacklist:trade"]`, `matchMode=OR` 조건으로 목록 조회를 요청한 상태, When 태그 필터 검색을 수행하면, Then 시스템은 `grade:A` 또는 `grade:B` 중 하나 이상을 포함하고 `blacklist:trade`를 하나라도 포함하지 않는 항목만 반환해야 한다.
|
||||
|
||||
## Impacted Area
|
||||
- Docs: `docs/technical/api-contracts.md`, `docs/technical/persistence-and-migrations.md`, `docs/SRS/index.md`, `docs/SRS/traceability-matrix.md`
|
||||
@@ -43,10 +46,74 @@
|
||||
- 본 requirement의 검색 범위는 다중 태그 검색을 포함하며, 호출자는 `AND` 또는 `OR` 모드를 선택할 수 있다.
|
||||
- 태그 최대 길이와 포트폴리오당 최대 태그 개수에 대한 애플리케이션 정의 상한은 두지 않는다.
|
||||
- 동일 포트폴리오에 같은 태그 문자열을 다시 추가하는 요청은 `409 Conflict` 또는 이에 대응하는 CLI 오류로 처리한다.
|
||||
- 태그 정규화는 앞뒤 공백 제거만 수행하며, 대소문자와 내부 문자열은 보존한다.
|
||||
- HTTP API와 CLI는 `includeTags`, `excludeTags`, `matchMode`에 대해 동일 의미의 검색 계약을 제공한다.
|
||||
- 특정 소비 서비스의 입력 고정/스냅샷 책임은 소비 서비스 측 requirement에서 관리하며, portfolio-hub는 태그 등록/검색 계약만 제공한다.
|
||||
|
||||
## Change Log
|
||||
|
||||
### CHG-20260414-02
|
||||
- Date: 2026-04-14
|
||||
- Author: GitHub Copilot
|
||||
|
||||
#### Change Summary
|
||||
- REQ-002를 Proposed에서 Approved로 전환했다.
|
||||
|
||||
#### Intent
|
||||
- 태그 정규화 규칙과 태그 검색 입력 계약이 승인 기준을 충족했음을 반영하기 위함이다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- 없음
|
||||
- Updated:
|
||||
- Status를 Approved로 전환
|
||||
- Removed:
|
||||
- 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md`
|
||||
- `docs/SRS/index.md`
|
||||
- `docs/SRS/traceability-matrix.md`
|
||||
- Modules:
|
||||
- 없음
|
||||
- Tests:
|
||||
- 없음
|
||||
|
||||
#### Validation Plan
|
||||
- 승인 후 technical spec 동기화 시 REQ-002의 정규화 및 검색 계약이 관련 technical 문서에 동일하게 반영되는지 확인한다.
|
||||
|
||||
### CHG-20260414-01
|
||||
- Date: 2026-04-14
|
||||
- Author: GitHub Copilot
|
||||
|
||||
#### Change Summary
|
||||
- REQ-002의 태그 정규화 규칙과 태그 검색 입력 계약을 구체화해 승인 blocker를 해소할 수 있도록 보강했다.
|
||||
|
||||
#### Intent
|
||||
- 태그 중복 판단과 include/exclude 검색 동작이 구현마다 다르게 해석되지 않도록 인터페이스 계약을 고정하기 위함이다.
|
||||
|
||||
#### Acceptance Criteria Delta
|
||||
- Added:
|
||||
- 앞뒤 공백 제거 기반 태그 정규화 규칙
|
||||
- `includeTags`, `excludeTags`, `matchMode`의 결합 규칙
|
||||
- Updated:
|
||||
- AC1, AC3, AC4, AC5, AC8, AC10의 입력/비교 기준 명확화
|
||||
- Removed:
|
||||
- 없음
|
||||
|
||||
#### Impacted Area Delta
|
||||
- Docs:
|
||||
- `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md`
|
||||
- `docs/SRS/index.md`
|
||||
- Modules:
|
||||
- tag input normalization 및 검색 query parser 구현 경로
|
||||
- Tests:
|
||||
- 태그 정규화, 중복 추가 충돌, include/exclude 조합 검색 검증 시나리오
|
||||
|
||||
#### Validation Plan
|
||||
- 문서 리뷰 시 태그 정규화 규칙과 `includeTags`/`excludeTags`/`matchMode` 조합 규칙이 각각 독립 테스트로 매핑 가능한지 확인한다.
|
||||
|
||||
### CHG-20260413-05
|
||||
- Date: 2026-04-13
|
||||
- Author: GitHub Copilot
|
||||
|
||||
+3
-3
@@ -22,16 +22,16 @@
|
||||
| REQ ID | Title | Type | Status | Priority | Owner | File |
|
||||
|---|---|---|---|---|---|---|
|
||||
| REQ-001 | 포트폴리오 댓글형 메모 이력 지원 | Interface | Approved | Medium | Justin | `docs/SRS/Interface/REQ-001_portfolio-comment-history.md` |
|
||||
| REQ-002 | 포트폴리오 태깅 및 태그 기반 검색 지원 | Interface | Proposed | Medium | Justin | `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md` |
|
||||
| REQ-002 | 포트폴리오 태깅 및 태그 기반 검색 지원 | Interface | Approved | Medium | Justin | `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md` |
|
||||
| REQ-003 | 포트폴리오 필수 필드 및 거래소/시장 데이터 정의 | Data | Proposed | Medium | Justin | `docs/SRS/Data/REQ-003_portfolio-required-fields.md` |
|
||||
| REQ-004 | 포트폴리오 소유주 기반 접근 제어 | Interface | Proposed | High | Justin | `docs/SRS/Interface/REQ-004_portfolio-ownership-access-control.md` |
|
||||
|
||||
## Recent Change Log Summary
|
||||
- 2026-04-14 — `REQ-002`를 Approved로 전환했다.
|
||||
- 2026-04-14 — `REQ-002`에 태그 정규화 규칙과 `includeTags`/`excludeTags`/`matchMode` 검색 계약을 추가해 승인 blocker를 줄였다.
|
||||
- 2026-04-14 — `REQ-004`에 사용자 귀속 봇 principal(`bot_principals.owner_user_id`) 및 봇 토큰 실효 사용자 매핑 규칙(AC6, AC7)을 추가했다.
|
||||
- 2026-04-13 — `REQ-004` 포트폴리오 소유주(`owner_user_id`) 및 소유권 기반 접근 제어 requirement를 추가했다.
|
||||
- 2026-04-13 — `REQ-003`에서 태그 필터 기반 조회의 결정성 유지 책임을 소비 서비스(stock-picker)로 이관했다.
|
||||
- 2026-04-13 — `REQ-003`에서 `sourceVersion`/`sourceTimestamp` 요구를 제거하고 태그 필터 기반 조회 결정성 계약으로 정리했다.
|
||||
- 2026-04-13 — `REQ-003`의 소비자 표현을 특정 서비스 전용에서 태그 기반 조회 소비 서비스 일반 계약으로 정리했다.
|
||||
|
||||
## Status Transition Evidence
|
||||
- `Proposed` → `Approved`: Owner가 지정되고 requirement 문서 리뷰가 완료되며 acceptance criteria가 누락 없이 검토되어야 한다.
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
| REQ ID | AC Mapping | Requirement File | Implementation Issue | PR | Test Evidence | Requirement Status | Delivery Status | Last Updated |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
| REQ-001 | AC1, AC2, AC3, AC4, AC5, AC6, AC7 | `docs/SRS/Interface/REQ-001_portfolio-comment-history.md` | #2 | TBD | TBD | Approved | In Progress | 2026-04-10 |
|
||||
| REQ-002 | AC1, AC2, AC3, AC4, AC5, AC6, AC7, AC8, AC9 | `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md` | TBD | TBD | TBD | Proposed | Planned | 2026-04-10 |
|
||||
| REQ-002 | AC1, AC2, AC3, AC4, AC5, AC6, AC7, AC8, AC9, AC10 | `docs/SRS/Interface/REQ-002_portfolio-tagging-and-search.md` | TBD | TBD | `tests/test_portfolio_tags.py`, `tests/test_cli.py`, local `pytest` pass (65 passed, 2026-04-14) | Approved | Verified | 2026-04-14 |
|
||||
| REQ-003 | AC1, AC2, AC3, AC4, AC5 | `docs/SRS/Data/REQ-003_portfolio-required-fields.md` | TBD | TBD | TBD | Proposed | Planned | 2026-04-10 |
|
||||
| REQ-004 | AC1, AC2, AC3, AC4, AC5, AC6, AC7 | `docs/SRS/Interface/REQ-004_portfolio-ownership-access-control.md` | TBD | TBD | `tests/test_portfolio_ownership.py`, `tests/test_users.py`, `tests/test_auth.py`, `tests/test_portfolio_comments.py`, local `pytest` pass (2026-04-14) | Proposed | In Progress | 2026-04-14 |
|
||||
|
||||
|
||||
Reference in New Issue
Block a user