# BAT-058 Design - Status: in-progress - Batch: BAT-058 ## 핵심 설계 결정 ### 1) cost-basis 응답 파싱 규칙 - `portfolio-hub` 응답에서 `cost_basis_per_unit`(canonical)을 최우선으로 사용 - canonical key가 존재하면 `avg_cost/wac/cost_basis`로 fallback 금지 - canonical이 없을 때만 legacy chain(`avg_cost`, `wac`, `cost_basis`)를 순차적으로 시도 - 유효성: `None` 금지, 숫자 변환 가능, 0보다 큰 값만 허용 ### 2) 진단값 분리 - adapter 수준에서 기존 `get_cost_basis()`는 값만 반환 - 새 진단 API `get_cost_basis_with_diagnostics()` 추가: - `(value, source, reason)` - public `cost_basis_source`는 `canonical` / `legacy` / `none`만 허용한다. adapter의 `avg_cost` / `wac` / `cost_basis` alias는 `legacy`로 정규화하며, 진단 port의 미승인/비문자 source는 `none`으로 정규화한다. - public `cost_basis_reason`는 `missing`, `invalid`, `http_status_<3-digit>`, `transport`, `invalid_json`, `invalid_payload`, `not_configured`, `diagnostic_exception`, `diagnostic_source_invalid` 또는 유효한 값이 seed되었을 때의 `null`만 허용한다. `http_status_<3-digit>`만 동적 형식이며 provider field name, parser detail, 임의 오류 텍스트는 공개 값으로 허용하지 않는다. - adapter parser의 field-level detail은 private implementation detail이다. sync 결과와 `sync --dry-run/--live` 출력에는 위 source/reason taxonomy만 노출한다. ### 3) sync fail-closed 정책 - `slot.status == INACTIVE`, `broker_qty > 0`, 동기화 결과 `avg_price` 미확보 시: - `dry_run=True`: write 없음 preview 반환 (`after`는 before와 동일), `skipped=True`, `skip_reason=cost_basis_unavailable` - `dry_run=False`: no-write fail-closed. `error='cost_basis_unavailable'`, `skipped=True`, `after=before` - 기존 `HOLDING` 및 `PENDING_*`, `ERROR` 경로는 기존 규칙 유지 ### 4) 로그/출력 - sync 결과 객체에 아래 필드 추가: `seeded_avg_price`, `cost_basis_source`, `cost_basis_reason` - CLI 출력에서 위 값 표시 - 실패 시 reason을 노출해 운영자가 어떤 fallback/오류가 발생했는지 즉시 확인 가능 ## 비(非)설계 대상 - `portfolio-hub` API 스키마 변경/인증 변경 - 실제 live sync 수행이나 운영 배포 실행 ## 검증 전략 - adapter 단위 테스트: canonical 우선/legacy fallback/invalid canonical non-fallback/이유 분리 - sync application 테스트: inactive fail-closed와 dry-run preview, holding 기본 동기화 동작 회귀 보존 - 최소 통합: 기존 테스트셋에서 관련 케이스가 모두 통과되는지 확인