Files
trex-core/trex_core/application/ports.py
T

76 lines
2.5 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
from trex_core.application.auto_reconcile import HoldingsCostBasisEvidence
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.
"""
...
def get_cost_basis_evidence(self, portfolio_id: str) -> HoldingsCostBasisEvidence:
"""Return strict canonical evidence for automatic holding reconciliation.
Implementations must not fall back to legacy cost-basis fields.
"""
...