Files
portfolio-hub/.proofline/lines/line-0008/dcy-0008.md
T

5.7 KiB

id, status
id status
dcy-0008 confirmed

기업행동 원가를 WAC replay에 반영 (#37)

Problem

기업행동이 기록된 portfolio에서 GET /v1/portfolios/{portfolio_id}/holdings/cost-basis가 기업행동 후 수량과 기업행동 전 WAC를 섞어 반환한다. 019685 사례에서 portfolio snapshot 수량은 726이지만 endpoint는 병합 전 단가 1,086.2341459366392를 사용하여 총원가를 788,605.9899500001로 계산한다. 기업행동 감사 기록은 병합 후 수량 726과 authoritative total_cost_basis=1,577,211.97990000을 이미 보유한다. 이 불완전 응답은 downstream의 안전 gate를 활성화하여 자동 reconcile을 중단시킨다.

Evidence

  • E-001 Source issue https://github.com/fourblocks2020/portfolio-hub/issues/37은 운영 사례, 확인된 원인, 수정 사양과 acceptance criteria를 기록한다. 확인 시점의 issue body SHA-256은 79acbcd09e22c309a8438efde32ff12b9414815557e28ad36802a71c9200bdec, updated_at은 2026-09-19T02:20:12Z다.
  • E-002 기준 source는 portfolio-hub f79f3c79b5e9c7e13fb28260af71e7e56cf575f5다. app/repositories/postgres.py::get_all_trades_for_pnl()은 기업행동 metadata를 LedgerEntry.corporate_action에 보존하며 executed_at ASC, ledger_entry_id ASC 순서로 반환한다.
  • E-003 app/api/v1_portfolios.py::get_holdings_cost_basis()는 ledger entry를 domain replay row로 변환할 때 side, quantity, price, fee_amount, executed_at만 전달하므로 corporate_action metadata를 버린다. 이후 portfolio snapshot 수량과 calculate_current_wac()의 unit WAC를 곱하여 총원가를 다시 만든다.
  • E-004 app/domain/pnl.py::calculate_current_wac()에는 stock_consolidation 분기가 이미 있으나 metadata가 없으면 running state에서 값을 추정하는 fallback이 있고, 반환형 CurrentWacResult는 authoritative total을 보존하지 않는다.
  • E-005 app/schemas/v1.py::HoldingsCostBasisResponse는 cost_basis_per_unit과 total_cost_basis를 이미 nullable로 정의하므로 신규 wire field나 DB migration 없이 fail-closed 응답을 표현할 수 있다.
  • E-006 docs/interface-contract.md는 stock consolidation의 total_cost_basis를 authoritative pre-action acquisition cost로 정의하고, persisted 8-decimal WAC의 반올림 residue가 정상임을 명시한다.
  • E-007 active AC-0037은 joined repository read와 corporate-action metadata mapping 보존을 소유하지만, req-0007은 WAC·원가 회계 규칙 변경을 명시적으로 제외한다. Authoritative total을 holdings 응답까지 보존하는 동작은 별도의 active AC가 필요하다.

Scope

  • S-001 holdings endpoint가 LedgerEntry.corporate_action의 quantity_before, quantity_after, total_cost_basis와 식별·순서 metadata를 domain replay row에 전달하도록 한다. 이는 existing AC-0037을 satisfy한다.
  • S-002 유효한 stock_consolidation을 replay할 때 running quantity를 quantity_after로 바꾸고, authoritative total_cost_basis를 보존하며, cost_basis_per_unit = total_cost_basis / quantity_after로 계산한다. 이 endpoint 원가 계약은 지속적인 독립 검증을 위해 새 AC를 create한다.
  • S-003 quantity_after가 없거나 0 이하이거나 total_cost_basis가 없으면 기업행동 전 WAC로 추정하지 않는다. holdings 응답의 두 원가 필드를 null로 반환하고 기존 data_completeness_warning 필드에 기업행동 metadata 부족을 구분하는 안정적인 문자열을 반환한다. 신규 response field는 만들지 않는다.
  • S-004 정상 기업행동, metadata 부족, 기업행동 이후 BUY/SELL, BUY/SELL-only 회귀와 API metadata 전달을 domain·API·PostgreSQL integration 수준에서 검증한다. 기존 trex-core source를 변경하지 않고 consumer compatibility를 검증한다.
  • S-005 interface contract에 holdings cost-basis의 기업행동 원가 출처와 계산식을 기록한다.

Out of Scope

  • OS-001 trex-core source·허용오차·slot 상태 변경, broker 주문, B warning 삭제 또는 production data 보정.
  • OS-002 0.1%, 1,000원 상한, 소수점 8자리 일치 같은 추가 tolerance 정책.
  • OS-003 corporate-action write transaction, idempotency, recovery, cash, profit buffer, portfolio reference price 또는 DB schema 변경.
  • OS-004 holdings 응답에 신규 reason field를 추가하거나 기존 BUY/SELL-only WAC 계약을 변경하는 일.

Risks and Unknowns

Risks

  • R-001 현재 WAC domain은 float를 사용한다. 저장된 8자리 average_price_after를 authoritative total 재구성에 사용하면 반올림 residue가 생기므로, total은 별도 replay state로 보존하고 endpoint가 그 값을 그대로 반환해야 한다.
  • R-002 기업행동 metadata 부족 상태에서 기존 WAC를 남기면 warning과 함께 잘못된 원가가 다시 노출될 수 있다. unusable 상태를 결과형에서 명시적으로 표현하고 두 원가 필드를 함께 null로 만드는 회귀가 필요하다.
  • R-003 기업행동 이후 BUY/SELL이 있으면 authoritative total도 거래와 함께 증감해야 한다. unit WAC와 quantity만 갱신하고 total을 고정하면 후속 거래가 있는 portfolio에서 잘못된 값을 반환하므로 running total과 unit WAC의 관계를 함께 검증해야 한다.

Assumptions

  • A-001 Issue #37에 기록된 정상 동작과 비범위가 이번 delivery의 제품 결정이다. Discovery confirmation에서 이 scope를 canonical하게 확정한다.
  • A-002 repository가 제공하는 executed_at ASC, ledger_entry_id ASC 순서를 endpoint에서 재정렬하지 않고 그대로 replay한다.