Files
trex-core/trex_core/application/ports.py
T
genichinandClaude Opus 4.8 07b393d27d feat(core-B): 운영자 sync/verify CLI + cold-start onboarding (BAT-051, REQ-105/106, PR #32)
dcy-034 마지막 조각. sync(슬롯 qty←브로커+warning해제+OQ-A 전이엣지+FR-7 평단seed)/verify(C 재대조·강제해제없음). core-A QC minor 2건 정리 동봉. QC(opus) PASS, 683 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VHtM5Ws1HKCKQ4JGi6GRb5
2026-06-26 09:53:49 +09:00

67 lines
2.2 KiB
Python

"""Application ports (REQ-007). The intent dedup/audit store contract.
Kept in the application layer (not domain) so the pure slot domain stays
unaware of the intent concept. Concrete implementations live in adapters.
"""
from __future__ import annotations
from typing import Optional, Protocol
from uuid import UUID
class IntentStore(Protocol):
"""Durable idempotency + audit store for accepted intents (dcy-002 OQ-1).
`try_insert` is the dedup gate: it returns False if `intent_id` already
exists, which prevents a duplicate intent from re-driving a slot
transition (NFR-3). `finalize` records the decision after processing.
"""
def try_insert(self, intent_id: str, intent_type: str, payload: dict) -> bool:
"""Insert a new intent row in 'processing' state.
Returns True if newly inserted, False if `intent_id` already exists.
"""
...
def get(self, intent_id: str) -> Optional["IntentRecord"]:
"""Return the stored intent record, or None."""
...
def finalize(
self,
intent_id: str,
status: str,
slot_id: Optional[UUID],
reason: Optional[str],
) -> None:
"""Update an intent row with its final decision (admitted/rejected)."""
...
class IntentRecord:
"""Snapshot of an intent_log row (duck-typed; see adapters)."""
intent_id: str
intent_type: str
status: str
slot_id: Optional[UUID]
reason: Optional[str]
class PortfolioHubCostBasisPort(Protocol):
"""FR-7 (dcy-034 / REQ-105): portfolio-hub WAC (weighted average cost) read port.
Wraps `GET /v1/portfolios/{id}/holdings/cost-basis` from portfolio-hub REQ-043.
When unavailable or when the portfolio has no holding, implementations return None.
sync() degrades gracefully: None => skip avg_price seed + WARN log.
"""
def get_cost_basis(self, portfolio_id: str) -> Optional[float]:
"""Return the weighted average cost (WAC) for the portfolio's current holding.
Returns None when the portfolio has no holding, the endpoint is unavailable,
or any error occurs. The caller must handle None as a graceful-degrade signal.
"""
...