From 92df8e0c5a744cda465ae00d5c14d2b58b4ed984 Mon Sep 17 00:00:00 2001 From: Justin Ko Date: Thu, 13 Aug 2026 14:47:14 +0900 Subject: [PATCH] docs(proofline): record issue 23 specification --- .proofline/criteria/ac-0001.md | 16 ++ .proofline/criteria/ac-0002.md | 16 ++ .proofline/criteria/ac-0003.md | 16 ++ .proofline/criteria/ac-0004.md | 16 ++ .proofline/criteria/ac-0005.md | 16 ++ .proofline/identities.json | 4 +- .proofline/lines/line-0001/dcy-0001.md | 141 ++++++++++++++++++ .../evidence/implementation-complete.md | 44 ++++++ .../evidence/implementation-progress.md | 46 ++++++ .proofline/lines/line-0001/line-0001.md | 3 + .proofline/lines/line-0001/req-0001.md | 46 ++++++ 11 files changed, 362 insertions(+), 2 deletions(-) create mode 100644 .proofline/criteria/ac-0001.md create mode 100644 .proofline/criteria/ac-0002.md create mode 100644 .proofline/criteria/ac-0003.md create mode 100644 .proofline/criteria/ac-0004.md create mode 100644 .proofline/criteria/ac-0005.md create mode 100644 .proofline/lines/line-0001/dcy-0001.md create mode 100644 .proofline/lines/line-0001/evidence/implementation-complete.md create mode 100644 .proofline/lines/line-0001/evidence/implementation-progress.md create mode 100644 .proofline/lines/line-0001/line-0001.md create mode 100644 .proofline/lines/line-0001/req-0001.md diff --git a/.proofline/criteria/ac-0001.md b/.proofline/criteria/ac-0001.md new file mode 100644 index 0000000..f0694a9 --- /dev/null +++ b/.proofline/criteria/ac-0001.md @@ -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`을 검사한다. diff --git a/.proofline/criteria/ac-0002.md b/.proofline/criteria/ac-0002.md new file mode 100644 index 0000000..3c2d13c --- /dev/null +++ b/.proofline/criteria/ac-0002.md @@ -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 회귀를 실행한다. diff --git a/.proofline/criteria/ac-0003.md b/.proofline/criteria/ac-0003.md new file mode 100644 index 0000000..39d2c65 --- /dev/null +++ b/.proofline/criteria/ac-0003.md @@ -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를 한 번 요청하는지 확인한다. diff --git a/.proofline/criteria/ac-0004.md b/.proofline/criteria/ac-0004.md new file mode 100644 index 0000000..57d4a52 --- /dev/null +++ b/.proofline/criteria/ac-0004.md @@ -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를 함께 검사한다. diff --git a/.proofline/criteria/ac-0005.md b/.proofline/criteria/ac-0005.md new file mode 100644 index 0000000..a11e852 --- /dev/null +++ b/.proofline/criteria/ac-0005.md @@ -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를 사용해 기존 동작 회귀 여부를 판정한다. diff --git a/.proofline/identities.json b/.proofline/identities.json index 7365b4e..f9b6298 100644 --- a/.proofline/identities.json +++ b/.proofline/identities.json @@ -1,5 +1,5 @@ { "schema_version": 1, - "next_line_number": 1, - "next_ac_number": 1 + "next_line_number": 2, + "next_ac_number": 6 } diff --git a/.proofline/lines/line-0001/dcy-0001.md b/.proofline/lines/line-0001/dcy-0001.md new file mode 100644 index 0000000..ae595d5 --- /dev/null +++ b/.proofline/lines/line-0001/dcy-0001.md @@ -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를 검토하는 성능 후속이 남았다. +- 위 두 항목은 승인된 필드 의미나 현재 구현 범위를 변경하지 않는 후속 검증·운영 관찰 사항이다. diff --git a/.proofline/lines/line-0001/evidence/implementation-complete.md b/.proofline/lines/line-0001/evidence/implementation-complete.md new file mode 100644 index 0000000..3db2254 --- /dev/null +++ b/.proofline/lines/line-0001/evidence/implementation-complete.md @@ -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를 검토할 수 있습니다. + +> 현재 상태는 **로컬 구현·검증 완료**입니다. 커밋·푸시·배포·릴리스 완료를 의미하지 않습니다. diff --git a/.proofline/lines/line-0001/evidence/implementation-progress.md b/.proofline/lines/line-0001/evidence/implementation-progress.md new file mode 100644 index 0000000..007a281 --- /dev/null +++ b/.proofline/lines/line-0001/evidence/implementation-progress.md @@ -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 검토. + +> 각 작업이 실제로 완료되고 검증될 때마다 이 댓글의 체크박스와 진행 기록을 갱신한다. 체크 표시는 구현 완료 또는 해당 검증의 실제 통과를 의미하며, 배포·릴리스 완료를 의미하지 않는다. diff --git a/.proofline/lines/line-0001/line-0001.md b/.proofline/lines/line-0001/line-0001.md new file mode 100644 index 0000000..2fbd17f --- /dev/null +++ b/.proofline/lines/line-0001/line-0001.md @@ -0,0 +1,3 @@ +--- +id: "line-0001" +--- diff --git a/.proofline/lines/line-0001/req-0001.md b/.proofline/lines/line-0001/req-0001.md new file mode 100644 index 0000000..eae2571 --- /dev/null +++ b/.proofline/lines/line-0001/req-0001.md @@ -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 정식 시퀀스로 구현됐다는 소급 주장을 만들지 않는다.