feat(slot): BAT-029 M1 — intent 계약 키 slot_id 통일 (dcy-020/REQ-061)

매수 계약 키를 portfolio_id → slot_id 로 이동(매도는 이미 slot_id). 외부 전략
무중단 위해 portfolio_id 레거시 dual-accept.

- BuyIntent: slot_id 필드 추가, portfolio_id Optional 완화
- _admit_buy: 자산 중심 모드 우선 분기; _admit_buy_asset_centric 가 slot_id(canonical)
  또는 portfolio_id(legacy dual-accept)로 슬롯 해소, symbol 은 슬롯 portfolio_id 로 resolve.
  unknown_slot/unknown_portfolio 사유 구분.
- messaging_handlers: buy 파싱에 slot_id 수용
- 신규 테스트: slot_id 매수 / unknown_slot / 기존 dual-accept 회귀

검증: 전체 389 passed / 36 skipped, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:09:47 +09:00
co-authored by Claude Opus 4.8
parent e10ac519c7
commit b80407098e
3 changed files with 63 additions and 19 deletions
+5 -4
View File
@@ -72,13 +72,14 @@ def make_intent_rpc_handler(
intent_type = str(body.get("intent_type") or "").lower()
try:
if intent_type == "buy":
# dcy-011 / REQ-021: buy payload carries portfolio_id (required);
# symbol is NOT a client field — admission resolves it from the
# universe. portfolio_id absence is rejected at admission (FR-1).
# dcy-011 / REQ-021: buy payload carries portfolio_id (legacy) or,
# in asset-centric mode (dcy-020/REQ-061), slot_id (canonical key).
# symbol is NOT a client field — resolved from the universe.
buy = BuyIntent(
intent_id=intent_id,
trading_date=_parse_date(body.get("trading_date")),
portfolio_id=_opt_str(body.get("portfolio_id")) or "",
portfolio_id=_opt_str(body.get("portfolio_id")) or None,
slot_id=(UUID(str(body["slot_id"])) if body.get("slot_id") else None),
qty=_opt_float(body.get("qty")),
allocated_capital=_opt_float(body.get("allocated_capital")),
entry_price=_opt_float(body.get("entry_price")),