5.7 KiB
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-001Source issuehttps://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-hubf79f3c79b5e9c7e13fb28260af71e7e56cf575f5다.app/repositories/postgres.py::get_all_trades_for_pnl()은 기업행동 metadata를LedgerEntry.corporate_action에 보존하며executed_at ASC, ledger_entry_id ASC순서로 반환한다.E-003app/api/v1_portfolios.py::get_holdings_cost_basis()는 ledger entry를 domain replay row로 변환할 때side,quantity,price,fee_amount,executed_at만 전달하므로corporate_actionmetadata를 버린다. 이후 portfolio snapshot 수량과calculate_current_wac()의 unit WAC를 곱하여 총원가를 다시 만든다.E-004app/domain/pnl.py::calculate_current_wac()에는stock_consolidation분기가 이미 있으나 metadata가 없으면 running state에서 값을 추정하는 fallback이 있고, 반환형CurrentWacResult는 authoritative total을 보존하지 않는다.E-005app/schemas/v1.py::HoldingsCostBasisResponse는cost_basis_per_unit과total_cost_basis를 이미 nullable로 정의하므로 신규 wire field나 DB migration 없이 fail-closed 응답을 표현할 수 있다.E-006docs/interface-contract.md는 stock consolidation의total_cost_basis를 authoritative pre-action acquisition cost로 정의하고, persisted 8-decimal WAC의 반올림 residue가 정상임을 명시한다.E-007activeAC-0037은 joined repository read와 corporate-action metadata mapping 보존을 소유하지만,req-0007은 WAC·원가 회계 규칙 변경을 명시적으로 제외한다. Authoritative total을 holdings 응답까지 보존하는 동작은 별도의 active AC가 필요하다.
Scope
S-001holdings endpoint가LedgerEntry.corporate_action의quantity_before,quantity_after,total_cost_basis와 식별·순서 metadata를 domain replay row에 전달하도록 한다. 이는 existingAC-0037을satisfy한다.S-002유효한stock_consolidation을 replay할 때 running quantity를quantity_after로 바꾸고, authoritativetotal_cost_basis를 보존하며,cost_basis_per_unit = total_cost_basis / quantity_after로 계산한다. 이 endpoint 원가 계약은 지속적인 독립 검증을 위해 새 AC를create한다.S-003quantity_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-005interface contract에 holdings cost-basis의 기업행동 원가 출처와 계산식을 기록한다.
Out of Scope
OS-001trex-core source·허용오차·slot 상태 변경, broker 주문, B warning 삭제 또는 production data 보정.OS-0020.1%,1,000원 상한, 소수점 8자리 일치 같은 추가 tolerance 정책.OS-003corporate-action write transaction, idempotency, recovery, cash, profit buffer, portfolio reference price 또는 DB schema 변경.OS-004holdings 응답에 신규 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-001Issue #37에 기록된 정상 동작과 비범위가 이번 delivery의 제품 결정이다. Discovery confirmation에서 이 scope를 canonical하게 확정한다.A-002repository가 제공하는executed_at ASC, ledger_entry_id ASC순서를 endpoint에서 재정렬하지 않고 그대로 replay한다.