fix(cli): BAT-022 slots 명령이 universe 대신 실제 슬롯 상태 표시 (dcy-016 후속)

`trex-core slots` 가 snapshot RPC 의 universe `assets`(portfolio)를 슬롯 상태로
오인 표시하던 갭 정정. snapshot envelope 이 슬롯 상태를 운반하지 않아 발생
(10개 슬롯인데 universe 1개·status 없음 — BAT-007 HTTP 슬롯 read 제거 후
슬롯 상태 read 표면 부재가 근인).

- REQ-028 (CHG-20260608-01): snapshot envelope 에 read-only per-slot `slots[]`
  additive 추가(AC-4). strategy `assets[]`(universe, no-price) 불변.
- REQ-032 (CHG-20260608-03): `_cmd_slots` 가 envelope `slots[]`(실제 슬롯 상태)
  표시 + `--status` 를 슬롯 status 기준 client-side 필터 + watchdog 결합.
  `universe` 는 `assets[]` 유지. `universe --json` 도 universe 데이터로 scope.
- canonical `portfolio.slot_to_dict` 단일화 — BAT-007 stub / BAT-008 envelope
  두 경로가 동일 슬롯 스키마(외부명 `entry_price` 유지, dcy-012 호환).
- interface-contract 에 `slots[]` 계약 + `assets[]` vs `slots[]` 구분 반영.

검증: 단위 338 passed + ruff clean + 신규 코드 데몬으로 실 PostgreSQL/RabbitMQ
운영 재현 PASS(slots=10·status·DB 일치·universe 분리·비침습). REQ-028/032
Implemented, BAT-022 release-candidate. pytest 인프라 게이트는 Human Approval
Memo 수용(운영 재현이 실 경로 커버).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-08 19:54:42 +09:00
co-authored by Claude Opus 4.8
parent a0a149114d
commit 63da87c9b3
16 changed files with 574 additions and 46 deletions
+37
View File
@@ -266,6 +266,11 @@ class SnapshotEnvelope:
degraded: bool
reason: Optional[str]
message: Optional[str] = None
# REQ-028 CHG-20260608-01 (BAT-022): operator-facing per-slot state list,
# additive and independent of strategy-facing `assets[]` (universe). Lets
# `trex-core slots` show the daemon's real slot table (idle/holding/pending)
# instead of universe portfolios. Always a list ([] when no slots).
slots: list[dict] = field(default_factory=list)
def to_dict(self) -> dict:
return {
@@ -275,6 +280,7 @@ class SnapshotEnvelope:
"degraded": self.degraded,
"reason": self.reason,
"message": self.message,
"slots": list(self.slots),
}
@@ -290,6 +296,32 @@ def _asset_to_dict(item: PortfolioItem) -> dict:
}
def slot_to_dict(s) -> dict:
"""Canonical operator-facing per-slot state shape (REQ-028 AC-4, BAT-022).
Shared by the BAT-008 envelope (`build_snapshot_envelope`) and the BAT-007
stub-shape handler so `trex-core slots` sees an identical slot dict on either
path. Unlike `_asset_to_dict` (strategy universe, no-price), this is
operator-only and exposes prices. External read field name `entry_price`
is kept for contract compatibility (dcy-012 / NFR-3 — internally backed by
the renamed `limit_price`, the pending order limit cleared once holding).
"""
return {
"slot_id": str(s.slot_id),
"trading_date": s.trading_date.isoformat(),
"status": s.status.value,
"symbol": s.symbol,
"qty": float(s.qty) if s.qty is not None else None,
"avg_price": float(s.avg_price) if s.avg_price is not None else None,
"entry_price": float(s.limit_price) if s.limit_price is not None else None,
"allocated_capital": float(s.allocated_capital) if s.allocated_capital is not None else None,
"strategy_id": s.strategy_id,
"sell_strategy_id": s.sell_strategy_id,
"portfolio_id": s.portfolio_id,
"forced_close_at": s.forced_close_at.isoformat() if s.forced_close_at else None,
}
def _sanitize(message: str) -> str:
"""Strip credentials/URLs/tokens — REQ-028 AC-2 sanitization."""
if not message:
@@ -395,6 +427,10 @@ def build_snapshot_envelope(
d["asset_quantity"] = holding_qty[pid]
out.append(d)
# REQ-028 CHG-20260608-01 (BAT-022): operator-facing per-slot state — the
# full daemon slot table for `day`, independent of the asset view filter.
slot_states = [slot_to_dict(s) for s in slots]
reason_code, message = store.degraded_state()
return SnapshotEnvelope(
assets=out,
@@ -403,4 +439,5 @@ def build_snapshot_envelope(
degraded=reason_code is not None,
reason=reason_code.value if reason_code is not None else None,
message=message,
slots=slot_states,
)