docs(proofline): record issue 23 specification

This commit is contained in:
2026-08-13 14:47:14 +09:00
parent 8dcdbdc9e0
commit 92df8e0c5a
11 changed files with 362 additions and 2 deletions
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0001"
status: active
---
# 포트폴리오 목록의 ledger 투자금 집계 필드
## Criterion
`GET /v1/portfolios`의 각 `PortfolioItem`은 기존 응답 필드를 유지하면서 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 float 값으로 포함해야 한다. 전자는 순투자현금, 후자는 누적 수익 인출액을 뜻하며, 해당 portfolio에 유효한 manual ledger가 없거나 집계 결과가 없으면 두 값은 각각 `0.0`이어야 한다.
## Verification
- 성공 조건: ledger 집계가 있는 portfolio는 두 필드에 해당 집계값을 반환하고, ledger 집계가 없는 portfolio는 두 필드를 `0.0`으로 반환한다.
- 실패·경계 조건: 빈 목록은 `portfolios: []`, `total: 0`을 반환하며 신규 필드 추가 때문에 기존 응답 필드가 제거되거나 값의 의미가 바뀌지 않는다.
- 검사 방법: `tests/test_v1_portfolios_list.py`의 목록 집계값 결합·기본값·빈 목록·기존 필드 회귀 테스트와 OpenAPI schema의 `PortfolioItem`을 검사한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0002"
status: active
---
# REVERSAL-aware manual ledger 산식과 목록·단건 일치
## Criterion
`net_contributed_capital`은 `REVERSAL`되지 않은 manual `INVEST` 합계에서 `REVERSAL`되지 않은 manual `WITHDRAW` 합계를 뺀 값이어야 하고, `cumulative_profit_withdrawn`은 `REVERSAL`되지 않은 manual `PROFIT` 합계여야 한다. `REVERSAL`이 참조한 `INVEST`, `WITHDRAW`, `PROFIT` 원거래는 모든 return aggregate에서 제외하고 `REVERSAL` 자체는 금액으로 합산하지 않으며, 목록과 단건 `returns/summary`는 같은 유효 원장 판정과 네 집계값 의미를 사용해야 한다.
## Verification
- 성공 조건: 미취소 `INVEST`, `WITHDRAW`, `PROFIT` 조합에서 `PROFIT`은 `WITHDRAW`와 분리되고 목록과 단건 summary의 `total_invested`, `total_withdrawn`, `net_contributed_capital`, `cumulative_profit_withdrawn`이 일치한다.
- 실패·경계 조건: `INVEST`, `WITHDRAW`, `PROFIT`을 각각 reversal하면 해당 원거래 효과가 제거되며 reversal row 자체가 합계에 더해지지 않는다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`의 shared batch semantics와 `tests/test_v1_portfolios_manual_ledger.py`의 profit 분리 및 세 action reversal 회귀를 실행한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0003"
status: active
---
# 다중 포트폴리오 grouped aggregate와 N+1 방지
## Criterion
목록용 repository 계약은 반환 대상인 여러 portfolio ID의 유효 manual ledger 집계를 하나의 grouped aggregate query로 계산해 portfolio ID별 결과를 반환해야 하며, 목록 크기에 비례하는 portfolio별 ledger query를 수행해서는 안 된다. 입력 ID 목록이 비어 있으면 schema 확인이나 DB 연결·query 없이 빈 mapping을 반환해야 한다.
## Verification
- 성공 조건: 둘 이상의 portfolio ID를 한 번에 전달했을 때 단일 query가 실행되고 각 ID의 합계가 독립된 mapping으로 반환된다.
- 실패·경계 조건: 빈 ID 입력은 `{}`를 반환하고 DB connection 및 schema/query 호출이 발생하지 않는다.
- 검사 방법: `tests/test_portfolio_ledger_aggregates.py`에서 `GROUP BY ledger.portfolio_id`, portfolio ID 배열 parameter, 단일 `execute`, 빈 입력 no-call을 검사하고 `tests/test_v1_portfolios_list.py`에서 route가 summary batch를 한 번 요청하는지 확인한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0004"
status: active
---
# 목록 가시성·필터 범위의 ledger 집계 격리
## Criterion
`GET /v1/portfolios`는 기존 client token 거부, service token daemon scope, session owner scope, admin `user_id` scope와 `include_disabled`, `market` 필터를 적용한 뒤 실제 반환할 portfolio ID만 ledger aggregate에 전달해야 한다. 반환 범위 밖 portfolio의 ledger를 집계 요청하거나 응답에 노출해서는 안 되며, 여러 portfolio의 금액은 ID 사이에서 섞이지 않아야 한다.
## Verification
- 성공 조건: owner/admin/service 및 `include_disabled`·`market` 조합마다 반환된 portfolio ID 목록과 aggregate 요청 ID 목록이 정확히 일치하고 각 항목은 자기 ID의 합계만 받는다.
- 실패·경계 조건: 권한 또는 필터로 제외된 portfolio ID는 aggregate 요청과 응답 모두에 나타나지 않고, client token 거부와 기존 scope 동작이 유지된다.
- 검사 방법: `tests/test_v1_portfolios_list.py`에서 fake repository의 `summary_requests` 기록과 기존 인증·필터 matrix를 함께 검사한다.
+16
View File
@@ -0,0 +1,16 @@
---
id: "ac-0005"
status: active
---
# additive API·문서 호환성과 reversal 회귀 보존
## Criterion
ledger 투자금 집계 확장은 기존 `GET /v1/portfolios` 및 단건 returns summary 계약에 대한 additive 변경이어야 한다. OpenAPI와 repository-owned interface/runtime 문서는 `net_contributed_capital`을 순투자현금, `cumulative_profit_withdrawn`을 누적 수익 인출액으로 정의하고 `portfolio-hub`가 ledger 권위 값을 제공함을 명시해야 하며, 기존 이중 reversal 거부 동작을 유지해야 한다.
## Verification
- 성공 조건: 기존 목록 필드와 token/filter 동작이 보존되고 `docs/interface-contract.md`, `docs/runtime-flows.md`, OpenAPI schema가 두 필드의 산식·REVERSAL·권위 경계를 일관되게 설명한다.
- 실패·경계 조건: `PROFIT`을 `WITHDRAW` 또는 전체 실현손익으로 설명하지 않고, 동일 원거래를 두 번 reversal하려는 기존 요청은 계속 거부된다.
- 검사 방법: 목록·manual ledger 회귀 테스트와 문서·OpenAPI 검사를 실행하고 focused suite 및 전체 프로젝트 suite를 사용해 기존 동작 회귀 여부를 판정한다.
+2 -2
View File
@@ -1,5 +1,5 @@
{
"schema_version": 1,
"next_line_number": 1,
"next_ac_number": 1
"next_line_number": 2,
"next_ac_number": 6
}
+141
View File
@@ -0,0 +1,141 @@
---
id: "dcy-0001"
status: confirmed
---
# 포트폴리오 목록 응답에 ledger 기반 투자금 집계 추가
## Problem
TREX 프로젝트 보드의 포트폴리오 테이블 개선 초안에서 `portfolio-hub` 소유 구현 범위를 분리한다. 소비자 `fourblocks2020/trex-web`이 포트폴리오 행마다 단건 returns summary를 N+1로 요청하거나 ledger event를 자체 재해석하지 않고도 투자금액과 수익 지표를 표시할 수 있도록, `GET /v1/portfolios` 목록 응답에 `portfolio-hub` 권위의 manual ledger 집계가 필요하다.
당시 `GET /v1/portfolios`는 `PortfolioListResponse{portfolios, total}`을 반환하고 각 `PortfolioItem`에 `cash_balance`, `asset_quantity`, `price`, `profit_buffer` 등을 포함했지만 투자 원금 집계는 제공하지 않았다. 반면 기존 `GET /v1/portfolios/{portfolio_id}/returns/summary`는 `total_invested`, `total_withdrawn`, `net_contributed_capital`, `cumulative_profit_withdrawn`을 제공했다. 목록과 단건 API의 의미가 분리되면 소비자가 별도 조회·재계산을 하게 되고, `PROFIT`과 `WITHDRAW` 또는 취소된 원거래를 잘못 해석할 위험이 있었다.
## Evidence
### 원문과 승인 근거
- GitHub Issue #23: https://github.com/fourblocks2020/portfolio-hub/issues/23
- 제목: `포트폴리오 목록 응답에 ledger 기반 투자금 집계 추가`
- 생성: 2026-08-13
- 상태: CLOSED
- 상위 보드 초안: https://github.com/orgs/fourblocks2020/projects/7
- 소비자 구현 이슈: https://github.com/fourblocks2020/trex-web/issues/13
- 요청자 `genichin`은 2026-08-13 OQ-23-1에서 A안인 **유효 원장 기준 집계**를 선택했다.
- 요청자는 2026-08-13 이 문서를 #23 원문에 기반한 승인 상태의 Discovery로 작성하도록 명시했다.
### 당시 계약
- `GET /v1/portfolios`는 `PortfolioListResponse{portfolios, total}`을 반환했다.
- `PortfolioItem`에는 투자 원금 집계가 없었다.
- 단건 `GET /v1/portfolios/{portfolio_id}/returns/summary`는 다음 네 값을 제공했다.
- `total_invested`
- `total_withdrawn`
- `net_contributed_capital`
- `cumulative_profit_withdrawn`
- PostgreSQL 산식은 manual ledger에서 `INVEST`, `WITHDRAW`, `PROFIT`을 분리했다.
- `net_contributed_capital = INVEST - WITHDRAW`
- `cumulative_profit_withdrawn = PROFIT`
- `PROFIT`은 투자금 회수인 `WITHDRAW`와 의미가 다르다.
### 확정 결정: OQ-23-1 — REVERSAL은 원거래 효과를 상쇄한다
- 결정: A안, 유효 원장 기준 집계
- 결정자: 요청자 `genichin`
- 결정일: 2026-08-13
- `REVERSAL`이 참조하는 `INVEST`, `WITHDRAW`, `PROFIT` 원거래는 순투자현금·누적 수익 인출액 집계에서 제외한다.
- `REVERSAL` 원장 자체를 금액 action으로 다시 합산하지 않는다.
- `net_contributed_capital`과 `cumulative_profit_withdrawn`은 역사적 action 단순 누계가 아니라 현재 유효한 manual ledger 기준 금액이다.
- 포트폴리오 목록과 기존 단건 `returns/summary`는 동일한 유효 원장 판정과 산식을 사용한다.
- 미취소 거래, `INVEST` 취소, `WITHDRAW` 취소, `PROFIT` 취소를 회귀 검증하며 기존 이중 reversal 거부 규칙을 유지한다.
- 향후 단순 역사 누계가 필요하면 별도 필드로 추가하고 기존 두 필드의 의미를 조용히 되돌리지 않는다.
- 이 결정으로 원문의 Open Question은 모두 해소됐다.
### 현재 구현 증거
- PR #24의 merge commit `35e85981af845489236c18d709d0545c830e7f03`이 `main`에 반영됐다.
- 구현 경로:
- `app/schemas/v1.py`
- `app/repositories/portfolio_repository.py`
- `app/repositories/postgres.py`
- `app/api/v1_portfolios.py`
- `docs/interface-contract.md`
- `docs/runtime-flows.md`
- 회귀 경로:
- `tests/test_portfolio_ledger_aggregates.py`
- `tests/test_v1_portfolios_list.py`
- `tests/test_v1_portfolios_manual_ledger.py`
- 당시 저장된 실행 증거는 focused **38 passed**, clean detached worktree 전체 **598 passed, 6 skipped**, compileall·`git diff --check`·Ruff fatal 규칙(E9/F63/F7/F82) 통과, 독립 read-only 리뷰 PASS였다. 이는 `.proofline/lines/line-0001/implementation-progress.md`와 `implementation-complete.md`에 원문 댓글로 보존한다.
## Scope
### 목록 응답 additive 필드
- `PortfolioItem`에 다음 additive 필드를 제공한다.
- `net_contributed_capital: float = 0.0`
- `cumulative_profit_withdrawn: float = 0.0`
- API·문서·소비자 표시 명칭과 의미를 다음과 같이 통일한다.
- `net_contributed_capital`: **순투자현금** — 현재까지 외부에서 순유입된 투자 현금, `유효 INVEST 합계 - 유효 WITHDRAW 합계`.
- `cumulative_profit_withdrawn`: **누적 수익 인출액** — 유효 `PROFIT`으로 기록되어 인출된 수익금 누계이며 매매상 실현손익 전체를 뜻하지 않는다.
- manual ledger가 없는 포트폴리오는 두 값을 `0.0`으로 반환한다.
- 기존 필드를 제거하거나 의미를 변경하지 않는 additive API 확장으로 제공한다.
### 다중 포트폴리오 집계 repository 계약
- 목록 route가 실제로 반환할 portfolio ID 집합을 입력받아 portfolio ID별 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 집계한다.
- `source = 'manual'`이고 `side IN ('invest', 'withdraw', 'profit')`인 원장 중 `REVERSAL.target_ledger_entry_id`로 참조되지 않은 유효 원장만 집계한다.
- `REVERSAL` 자체는 투자·회수·수익 금액으로 합산하지 않는다.
- 목록과 단건 summary는 동일한 유효 원장 판정과 집계 구현을 사용한다.
- 여러 포트폴리오는 portfolio ID별로 격리한다.
- 한 번의 grouped aggregate query를 사용하며 portfolio별 N+1 ledger 조회를 금지한다.
- 빈 ID 목록은 DB를 조회하지 않고 빈 결과를 반환한다.
### 목록 route 결합과 가시성
- 기존 owner/service/admin 가시성과 `include_disabled`, `market` 필터를 먼저 적용한다.
- 필터링된 반환 대상 portfolio ID만 집계 repository에 전달한다.
- 목록 assembler가 집계값을 각 `PortfolioItem`에 결합한다.
- 집계 결과에 없는 portfolio ID는 두 값을 `0.0`으로 채운다.
- 기존 client token 거부, service token daemon scope, session owner scope, admin `user_id` scope, `include_disabled`, `market` 동작을 유지한다.
- owner/admin/service 또는 필터 범위 밖의 ledger를 집계하거나 노출하지 않는다.
- 빈 목록은 `portfolios: []`, `total: 0`을 정상 반환한다.
### 단건 summary 호환성
- `GET /v1/portfolios/{portfolio_id}/returns/summary`를 유지한다.
- 목록과 단건 endpoint는 동일한 유효 원장 판정과 `INVEST`, `WITHDRAW`, `PROFIT` 산식을 사용한다.
- `PROFIT`을 `WITHDRAW`에 합산하지 않는다.
- `REVERSAL`된 원거래는 `total_invested`, `total_withdrawn`, `net_contributed_capital`, `cumulative_profit_withdrawn`에서 제외한다.
- 기존 이중 reversal 거부 동작을 유지한다.
### 문서와 소비자 계약
- OpenAPI schema와 interface/API 문서에 목록의 additive 필드, 산식, REVERSAL 정책을 반영한다.
- 두 필드는 `portfolio-hub` ledger 권위 집계이며 `trex-web`이 재계산하지 않는다.
- `portfolio-hub` 변경을 먼저 제공한 뒤 `trex-web`이 새 필드를 필수 소비한다.
- `trex-web`은 `net_contributed_capital`을 투자금액으로 사용한다.
### Acceptance admission
기존 ProofLine AC가 없는 초기 project이므로 이 Line은 다음 version-independent product behavior를 새 AC로 `create`한다. 과거 구현 결과·PR·테스트 횟수는 AC가 아니라 Evidence와 비정식 증적 문서에 보존한다.
- 목록 응답의 additive 투자금 집계와 ledger 없음 기본값
- REVERSAL-aware manual ledger 산식과 목록·단건 의미 일치
- grouped aggregate와 N+1 방지 및 빈 입력 무조회
- 기존 권한·필터 이후 반환 대상만 집계하는 격리
- additive 호환성, 문서 의미, 기존 이중 reversal 거부 회귀
## Out of Scope
- `trex-web` 저장소의 소비자 UI 구현 자체는 이 repository-local Line에서 변경하지 않는다.
- 배포, 운영 반영, 릴리스 승인 또는 실제 PostgreSQL 운영 데이터 변경은 승인하지 않는다.
- `PROFIT`을 매매상 실현손익 전체로 재정의하지 않는다.
- 단순 역사 누계가 별도 지표로 필요하더라도 기존 두 필드의 의미를 변경하지 않는다.
- 행별 단건 summary 호출 또는 소비자 측 ledger 재해석을 대안으로 허용하지 않는다.
- 이 사후 문서화는 #23 구현이 ProofLine 정식 시퀀스로 수행됐다는 주장, 공식 delivery closure 또는 배포 완료 주장을 만들지 않는다.
## Risks and Unknowns
- 실제 PostgreSQL integration fixture로 `ANY(%s)` 배열 바인딩, NUMERIC aggregate, REVERSAL row 동작을 직접 입증하는 보강은 당시 독립 리뷰의 비차단 Low 권고로 남았다.
- 운영 규모에서 reversal anti-join의 `EXPLAIN (ANALYZE, BUFFERS)`를 관찰하고 필요하면 partial index를 검토하는 성능 후속이 남았다.
- 위 두 항목은 승인된 필드 의미나 현재 구현 범위를 변경하지 않는 후속 검증·운영 관찰 사항이다.
@@ -0,0 +1,44 @@
# 구현 완료 — GitHub Issue #23 비정식 증적
> 이 문서는 ProofLine canonical artifact가 아니다. #23 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다.
> Source: https://github.com/fourblocks2020/portfolio-hub/issues/23#issuecomment-5275868771
> Author: `genichin`
> Created: `2026-08-13T04:13:06Z`
> Updated: `2026-08-13T04:13:06Z`
---
## 구현 완료
`feat/23-ledger-investment-aggregates` 브랜치에서 #23 구현과 로컬 검증을 완료했습니다.
### 구현 내용
- `PortfolioItem`에 다음 additive 필드를 추가했습니다.
- `net_contributed_capital`: 유효 manual `INVEST` 합계 - 유효 manual `WITHDRAW` 합계
- `cumulative_profit_withdrawn`: 유효 manual `PROFIT` 합계
- 목록에 반환되는 portfolio ID만 한 번에 처리하는 grouped ledger aggregate를 구현했습니다.
- manual `REVERSAL.target_ledger_entry_id`가 참조한 `INVEST`·`WITHDRAW`·`PROFIT` 원거래를 집계에서 제외했습니다.
- `REVERSAL` 자체는 금액 action으로 합산하지 않습니다.
- 기존 단건 `GET /v1/portfolios/{portfolio_id}/returns/summary`도 같은 집계 구현을 사용하도록 통일했습니다.
- 유효 ledger가 없는 portfolio는 두 신규 필드를 `0.0`으로 반환합니다.
- interface contract와 runtime flow 문서를 갱신했습니다.
### 검증 결과
HEAD 기반 clean detached worktree에 #23 변경만 적용하여 검증했습니다.
- Focused tests: **38 passed**
- 전체 회귀 테스트: **598 passed, 6 skipped**
- Python `compileall`: **통과**
- `git diff --check`: **통과**
- Ruff fatal 규칙 (`E9`, `F63`, `F7`, `F82`): **통과**
- 독립 read-only 코드 리뷰: **PASS**
- Blocking / High / Medium finding: **없음**
### 비차단 후속 권고
- 실제 PostgreSQL integration fixture에서 `ANY(%s)` 배열 바인딩, `NUMERIC` aggregate, 실제 REVERSAL row 제외 결과를 추가 검증할 수 있습니다.
- 운영 규모 ledger를 대상으로 anti-join 실행계획을 관찰하고, 필요하면 reversal partial index를 검토할 수 있습니다.
> 현재 상태는 **로컬 구현·검증 완료**입니다. 커밋·푸시·배포·릴리스 완료를 의미하지 않습니다.
@@ -0,0 +1,46 @@
# 구현 진행 상황 — GitHub Issue #23 비정식 증적
> 이 문서는 ProofLine canonical artifact가 아니다. #23 구현 당시 GitHub 댓글을 출처와 함께 원문 보존한 비정식 증적이다.
> Source: https://github.com/fourblocks2020/portfolio-hub/issues/23#issuecomment-5275751156
> Author: `genichin`
> Created: `2026-08-13T03:53:46Z`
> Updated: `2026-08-13T04:06:33Z`
---
## 구현 진행 상황
- **대상:** #23 포트폴리오 목록 응답에 ledger 기반 투자금 집계 추가
- **브랜치:** `feat/23-ledger-investment-aggregates`
- **기준:** `origin/main` (`34792bdb5c38dc0b79ffa45391779e7667c51948`)
- **상태:** 구현·검증·독립 리뷰 완료 (커밋·푸시 전)
- **최종 업데이트:** 2026-08-13
### 구현 계획 및 작업 목록
- [x] #23의 확정 요구사항과 OQ-23-1 결정(A안: 유효 원장 기준)을 재확인한다.
- [x] 최신 `origin/main`에서 전용 구현 브랜치를 생성한다.
- [x] 기존 repository protocol, PostgreSQL adapter, in-memory/test doubles, 목록 route 조립 경계를 확인한다.
- [x] REVERSAL-aware 집계와 목록 응답에 대한 실패 테스트를 먼저 추가하고 RED를 확인한다.
- [x] `PortfolioItem`에 `net_contributed_capital`, `cumulative_profit_withdrawn` additive 필드를 추가한다.
- [x] 여러 portfolio ID를 한 번에 처리하는 grouped ledger aggregate repository 계약을 구현한다.
- [x] `REVERSAL.target_ledger_entry_id`가 참조하는 `INVEST`/`WITHDRAW`/`PROFIT` 원거래를 집계에서 제외한다.
- [x] 기존 단건 `returns/summary`를 같은 유효 원장 집계 의미로 정렬한다.
- [x] 가시성·`include_disabled`·`market` 필터가 적용된 반환 대상 ID만 집계해 목록 항목에 결합한다.
- [x] ledger가 없는 항목은 두 신규 값을 `0.0`으로 반환한다.
- [x] API/interface 문서에 순투자현금·누적 수익 인출액 의미와 REVERSAL 정책을 반영한다.
- [x] 집중 테스트를 통과시킨다.
- [x] 전체 테스트·lint/static check·diff hygiene를 통과시킨다.
- [x] 독립 코드 리뷰를 수행하고 발견 사항을 해소한다.
- [x] 최종 변경 파일·테스트 결과·잔여 위험을 이 댓글에 기록한다.
### 진행 기록
- 2026-08-13: 구현 브랜치 생성 및 #23 확정 결정 재확인.
- 2026-08-13: repository/API/test 경계 조사 완료. `uv run --isolated --extra dev pytest -q tests/test_portfolio_ledger_aggregates.py tests/test_v1_portfolios_list.py tests/test_v1_portfolios_manual_ledger.py` 실행 결과 **6 failed, 32 passed**로 신규 계약의 RED를 확인함.
- 2026-08-13: schema, repository protocol, PostgreSQL grouped aggregate, list route 결합, 단건 summary 공용화 구현. 동일 집중 명령 재실행 결과 **38 passed**.
- 2026-08-13: `docs/interface-contract.md`, `docs/runtime-flows.md`에 필드 의미·유효 원장·grouped aggregate·목록/단건 일관성 계약 반영.
- 2026-08-13: 저장소 내부 잔존 `.worktrees/`와 로컬 `.env`가 원본 작업트리 전체 테스트를 오염시켜, HEAD에서 clean detached worktree(`/tmp/portfolio-hub-23-verify`)를 만들고 #23 diff만 적용해 재검증. 전체 **598 passed, 6 skipped**, focused **38 passed**, compileall·`git diff --check`·Ruff fatal 규칙(E9/F63/F7/F82) 통과.
- 2026-08-13: 독립 read-only 코드 리뷰 **PASS**. Blocking/High/Medium finding 없음. 비차단 Low 후속 권고: (1) 실제 PostgreSQL integration fixture로 `ANY(%s)`·NUMERIC aggregate·REVERSAL 결과 검증, (2) 운영 규모에서 anti-join `EXPLAIN (ANALYZE, BUFFERS)` 관찰 및 필요 시 reversal partial index 검토.
> 각 작업이 실제로 완료되고 검증될 때마다 이 댓글의 체크박스와 진행 기록을 갱신한다. 체크 표시는 구현 완료 또는 해당 검증의 실제 통과를 의미하며, 배포·릴리스 완료를 의미하지 않는다.
+3
View File
@@ -0,0 +1,3 @@
---
id: "line-0001"
---
+46
View File
@@ -0,0 +1,46 @@
---
id: "req-0001"
status: approved
discovery: "dcy-0001"
criteria:
create:
- "ac-0001"
- "ac-0002"
- "ac-0003"
- "ac-0004"
- "ac-0005"
update: []
retire: []
satisfy: []
---
# 포트폴리오 목록 응답에 ledger 기반 투자금 집계 추가
## Objective
`GET /v1/portfolios`가 소비자 측 N+1 단건 조회나 ledger 재해석 없이 포트폴리오별 순투자현금과 누적 수익 인출액을 제공하도록 하고, 그 값이 기존 단건 returns summary와 동일한 유효 manual ledger 의미를 유지하게 한다.
## Scope
- `PortfolioItem`에 `net_contributed_capital`과 `cumulative_profit_withdrawn`을 additive 필드로 제공한다.
- manual `INVEST`, `WITHDRAW`, `PROFIT`과 이를 취소하는 `REVERSAL`을 기준으로 현재 유효한 원거래만 집계한다.
- 여러 portfolio ID를 하나의 grouped aggregate 경로에서 처리하고 목록의 가시성·필터가 확정한 반환 대상에만 결과를 결합한다.
- 기존 단건 `returns/summary`와 목록이 동일한 repository 집계 의미를 사용한다.
- API/interface 문서와 회귀 테스트가 필드 의미, 기본값, 격리, 성능 경계 및 기존 동작 보존을 표현한다.
## Constraints
- `net_contributed_capital`은 유효 `INVEST - WITHDRAW`, `cumulative_profit_withdrawn`은 유효 `PROFIT`이며 두 의미를 합치지 않는다.
- `REVERSAL`이 참조한 원거래는 제외하고 `REVERSAL` 자체는 금액 action으로 합산하지 않는다.
- ledger가 없거나 집계 결과가 없는 portfolio의 신규 필드는 `0.0`이다.
- 기존 필드·권한·필터·이중 reversal 거부 계약은 유지한다.
- portfolio-hub가 ledger 권위 값을 제공하며 소비자는 이를 재계산하지 않는다.
- 이 승인된 사양은 이미 `main`에 존재하는 #23 구현 의미를 정확히 기술하지만, 사후 문서화 자체가 정식 ProofLine 구현 시퀀스 또는 delivery closure를 소급 생성하지 않는다.
## Non-Goals
- `trex-web` 저장소의 UI 구현을 변경하지 않는다.
- 배포, 운영 반영, 릴리스 승인 또는 실제 운영 데이터 변경을 승인하지 않는다.
- `PROFIT`을 매매상 실현손익 전체로 재정의하지 않는다.
- 단순 역사 누계를 기존 두 필드의 의미로 대체하지 않는다.
- ProofLine 정식 시퀀스로 구현됐다는 소급 주장을 만들지 않는다.