feat(slots): additive strategy fields + filter (REQ-116, BAT-055)
trex.slots.req wire 계약 additive 확장 — 기존 소비자(orb/tsl) backward compatible. - PortfolioItem: strategy_name(Optional[str]) + markets(list) 필드 추가 - hub_read.py: get_portfolios()에서 strategy_name/markets 파싱 추가 (safe default) - slot_to_dict(): strategy_name/strategy_params/markets 3필드 row에 추가 (SnapshotStore→PortfolioItem 경유) - make_slots_rpc_handler(): strategy 요청 파라미터 → Python 레벨 후처리 필터 (available_slots 계산 이후, REQ-073 AC-6 불변) - 테스트: AC-1~8 전수 + hub_read 파싱 + 회귀 갱신 (777 passed, 47 skipped) - docs/interface-contract.md: 요청 파라미터·응답 행 스키마·소스 테이블·REQ-074 AC-5 주석 갱신 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
This commit is contained in:
@@ -228,11 +228,14 @@ def make_slots_rpc_handler(
|
||||
max_active_slots: Optional[int] = None,
|
||||
strategy_state_store=None,
|
||||
):
|
||||
"""Build the RPC handler for `trex.slots.req` (REQ-073/074).
|
||||
"""Build the RPC handler for `trex.slots.req` (REQ-073/074/116).
|
||||
|
||||
Request: {filter?: str, spy?: bool}
|
||||
Request: {filter?: str, spy?: bool, strategy?: str}
|
||||
filter: "all"|"inactive"|"pending"|"holding"|"error" (default "all")
|
||||
spy=true: watchdog not updated (operator view, REQ-034 CHG-01)
|
||||
strategy: exact-match on strategy_name (REQ-116, optional). When specified
|
||||
only slots whose strategy_name == strategy are returned. AND-combined with
|
||||
filter. available_slots is always computed before this filter (REQ-073 AC-6).
|
||||
|
||||
Response: {slots: [...], available_slots: int|null, degraded: bool,
|
||||
reason: str|null, message: str|null}
|
||||
@@ -244,6 +247,9 @@ def make_slots_rpc_handler(
|
||||
body = req.payload or {}
|
||||
filter_val = str(body.get("filter") or "all").lower()
|
||||
spy = bool(body.get("spy", False))
|
||||
# REQ-116 (BAT-055): optional strategy filter — exact-match on strategy_name.
|
||||
# None means no filter (all slots returned regardless of strategy_name).
|
||||
strategy_filter = _opt_str(body.get("strategy"))
|
||||
|
||||
# issue #25: filter by status at the DB (REQ-002/006 list_slots
|
||||
# status_filter) instead of fetching every row and filtering in Python.
|
||||
@@ -320,11 +326,19 @@ def make_slots_rpc_handler(
|
||||
reason = reason_code.value if reason_code is not None else None
|
||||
message = msg
|
||||
|
||||
# REQ-073 AC-6: available_slots computed from instance-wide active count —
|
||||
# independent of strategy filter (REQ-116). Calculate before filtering.
|
||||
if max_active_slots is not None and max_active_slots > 0:
|
||||
available_slots = max(0, max_active_slots - slot_repo.count_active(instance_key))
|
||||
else:
|
||||
available_slots = None
|
||||
|
||||
# REQ-116 (BAT-055): strategy filter — Python-level post-filter after
|
||||
# available_slots is already computed. AND-combined with status filter.
|
||||
# Unmatched strategy (no slots) returns [] without error.
|
||||
if strategy_filter is not None:
|
||||
slot_rows = [r for r in slot_rows if r.get("strategy_name") == strategy_filter]
|
||||
|
||||
return RpcResponse(payload={
|
||||
"slots": slot_rows,
|
||||
"available_slots": available_slots,
|
||||
|
||||
Reference in New Issue
Block a user