"""Schemas for v1 (Consumer-Driven Contract) API endpoints.""" from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Any, Literal from pydantic import BaseModel, Field # --------------------------------------------------------------------------- # GET/POST/PATCH /v1/tags (REQ-004) # --------------------------------------------------------------------------- class TagItem(BaseModel): tag_id: str name: str is_admin_tag: bool is_active: bool created_at: datetime | None = None class TagCreateRequest(BaseModel): name: str = Field(..., min_length=1) class TagListResponse(BaseModel): tags: list[TagItem] total: int # --------------------------------------------------------------------------- # POST/DELETE /v1/assets/{id}/tags, /v1/portfolios/{id}/tags (REQ-005) # --------------------------------------------------------------------------- class TagApplyRequest(BaseModel): tag_id: str = Field(..., min_length=1) class TagApplyResponse(BaseModel): tag_id: str status: str # --------------------------------------------------------------------------- # GET /v1/assets # --------------------------------------------------------------------------- class TagRefItem(BaseModel): tag_id: str archived: bool = False class AssetItem(BaseModel): asset_id: str symbol: str status: str cash: float quantity: float broker_type: str strategy_id: str | None = None strategy_type: str | None = None strategy_params: dict[str, Any] = {} updated_at: datetime | None = None # Additional informational fields name: str | None = None price: float | None = None profit_buffer: float | None = None tags: list[str] | None = None tag_ids: list[TagRefItem] | None = None owner_user_id: str | None = None created_at: datetime | None = None last_trade_at: datetime | None = None class AssetsResponse(BaseModel): assets: list[AssetItem] total: int # --------------------------------------------------------------------------- # GET /v1/user_info # --------------------------------------------------------------------------- class BrokerItem(BaseModel): broker: str account_ref: str credential_payload: dict[str, Any] | None = None credential_version: int = 1 last_rotated_at: datetime | None = None class UserInfoResponse(BaseModel): runtime_id: str | None = None daemon_id: str | None = None trading_enabled: bool disabled_reason: str | None = None brokers: list[BrokerItem] = [] # Additional informational fields user_id: str username: str email: str | None = None permission: str # --------------------------------------------------------------------------- # POST /v1/assets/{asset_id}/ledger/trades # --------------------------------------------------------------------------- class LedgerTradeRequest(BaseModel): order_id: str = Field(..., min_length=1) execution_id: str = Field(..., min_length=1) side: Literal["buy", "sell"] quantity: Decimal = Field(..., gt=0) price: Decimal = Field(..., gt=0) fee_amount: Decimal = Field(default=Decimal("0"), ge=0) tax_amount: Decimal = Field(default=Decimal("0"), ge=0) executed_at: datetime class LedgerTradeResponse(BaseModel): ledger_entry_id: int asset_id: str order_id: str execution_id: str side: str quantity: float price: float fee_amount: float tax_amount: float executed_at: datetime recorded_at: datetime | None = None status: Literal["new", "duplicate"] = "new" # --------------------------------------------------------------------------- # POST/PATCH/DELETE/GET /v1/portfolios (REQ-025, REQ-026, REQ-027, REQ-028) # --------------------------------------------------------------------------- class PortfolioCreateRequest(BaseModel): asset_id: str = Field(..., min_length=1) broker: str = Field(..., min_length=1) name: str | None = None cash_balance: float = Field(default=0.0, ge=0.0) asset_quantity: float = Field(default=0.0, ge=0.0) asset_price: float = Field(default=0.0, ge=0.0) profit_buffer: float = Field(default=0.0, ge=0.0) strategy_name: str | None = None strategy_params: dict[str, Any] = Field(default_factory=dict) managed_by_daemon_id: str | None = None user_id: str | None = None # admin-only: override owner class PortfolioUpdateRequest(BaseModel): broker: str | None = None status: str | None = None name: str | None = None strategy_name: str | None = None strategy_params: dict[str, Any] | None = None asset_price: float | None = Field(default=None, ge=0.0) managed_by_daemon_id: str | None = None class PortfolioItem(BaseModel): portfolio_id: str asset_id: str | None = None owner_user_id: str | None = None broker: str status: str name: str cash_balance: float = 0.0 asset_quantity: float = 0.0 price: float = 0.0 profit_buffer: float = 0.0 tag_ids: list[str] = Field(default_factory=list) managed_by_daemon_id: str | None = None created_at: datetime | None = None updated_at: datetime | None = None class PortfolioListResponse(BaseModel): portfolios: list[PortfolioItem] total: int # --------------------------------------------------------------------------- # GET /v1/portfolios/{id}/tags, CRUD /v1/portfolios/{id}/comments (REQ-030, REQ-031) # --------------------------------------------------------------------------- class PortfolioTagListResponse(BaseModel): tags: list[TagItem] total: int class PortfolioCommentItem(BaseModel): comment_id: str portfolio_id: str content: str created_at: datetime | None = None updated_at: datetime | None = None class PortfolioCommentRequest(BaseModel): content: str = Field(..., min_length=1) class PortfolioCommentUpdateRequest(BaseModel): content: str = Field(..., min_length=1) class PortfolioCommentListResponse(BaseModel): comments: list[PortfolioCommentItem] total: int # --------------------------------------------------------------------------- # POST/PATCH /v1/assets (admin catalog management — REQ-010) # --------------------------------------------------------------------------- class AssetCatalogCreateRequest(BaseModel): symbol: str = Field(..., min_length=1) markets: list[str] = Field(default_factory=list) display_name: str | None = None status: str = "active" class AssetCatalogUpdateRequest(BaseModel): markets: list[str] | None = None display_name: str | None = None status: str | None = None class AssetCatalogResponse(BaseModel): asset_id: str symbol: str markets: list[str] display_name: str | None = None status: str created_at: datetime | None = None updated_at: datetime | None = None class AssetCatalogListResponse(BaseModel): items: list[AssetCatalogResponse] total: int