| 1 | """Authenticated regional watchlist contracts and durable research projection.""" |
| 2 | from __future__ import annotations |
| 3 | |
| 4 | import asyncio |
| 5 | from decimal import Decimal |
| 6 | from typing import Any, Literal |
| 7 | from uuid import UUID |
| 8 | |
| 9 | from pydantic import BaseModel, ConfigDict, Field |
| 10 | |
| 11 | |
| 12 | MarketRegion = Literal["INDIA", "EUROPE", "USA"] |
| 13 | |
| 14 | |
| 15 | class EnsureDefaultWatchlistRequest(BaseModel): |
| 16 | region: MarketRegion |
| 17 | |
| 18 | |
| 19 | class AddWatchlistInstrumentRequest(BaseModel): |
| 20 | model_config = ConfigDict(populate_by_name=True) |
| 21 | |
| 22 | global_instrument_id: UUID = Field(alias="globalInstrumentId") |
| 23 | source_period: Literal["DAY", "WEEK", "MONTH", "YEAR"] | None = Field( |
| 24 | default=None, alias="sourcePeriod" |
| 25 | ) |
| 26 | source_performance_pct: Decimal | None = Field(default=None, alias="sourcePerformancePct") |
| 27 | |
| 28 | def portfolio_payload(self) -> dict[str, Any]: |
| 29 | return { |
| 30 | "globalInstrumentId": str(self.global_instrument_id), |
| 31 | "sourcePeriod": self.source_period, |
| 32 | "sourcePerformancePct": float(self.source_performance_pct) if self.source_performance_pct is not None else None, |
| 33 | } |
| 34 | |
| 35 | |
| 36 | async def watchlist_research_projection(orchestrator, payload: dict, **kwargs) -> dict: |
| 37 | """Compose membership with existing durable company research; never refresh.""" |
| 38 | memberships = payload.get("instruments", []) |
| 39 | |
| 40 | async def project(membership: dict) -> dict: |
| 41 | instrument_id = UUID(str(membership["globalInstrumentId"])) |
| 42 | metadata = membership.get("instrument") |
| 43 | company = await orchestrator.read_global_company_state( |
| 44 | instrument_id, |
| 45 | metadata=metadata if isinstance(metadata, dict) else None, |
| 46 | **kwargs, |
| 47 | ) |
| 48 | return { |
| 49 | "globalInstrumentId": str(instrument_id), |
| 50 | "companyName": (metadata or {}).get("canonicalName") if isinstance(metadata, dict) else None, |
| 51 | "ticker": (metadata or {}).get("primarySymbol") if isinstance(metadata, dict) else None, |
| 52 | "exchange": (metadata or {}).get("primaryExchange") if isinstance(metadata, dict) else None, |
| 53 | "country": (metadata or {}).get("country") if isinstance(metadata, dict) else None, |
| 54 | "currency": (metadata or {}).get("currency") if isinstance(metadata, dict) else None, |
| 55 | "assetType": (metadata or {}).get("assetType") if isinstance(metadata, dict) else None, |
| 56 | "held": False, |
| 57 | "sourcePeriod": membership.get("sourcePeriod"), |
| 58 | "sourcePerformancePct": membership.get("sourcePerformancePct"), |
| 59 | "addedAt": membership.get("addedAt"), |
| 60 | "company": company.model_dump(mode="json", by_alias=True), |
| 61 | } |
| 62 | |
| 63 | instruments = await asyncio.gather(*(project(value) for value in memberships)) |
| 64 | return {"watchlist": payload.get("watchlist", {}), "instruments": instruments} |