feat(selection): expose run sector aggregates and sector filter on results API
- sector_radar: add batch sector-count aggregation and sector member lookup
over the strict last-good membership snapshot (postgres + in-memory fakes)
- selection: add SelectionSectorReader port, list_sector_counts use case,
and sector_stock_codes filtering via run identity resolution; queries stay
inside the selection context per ADR 0001
- http: add GET /api/v1/selection/sectors and forward sector param on
/results and /runs/{run_id}
- fix stale positional args in pattern-scoring run tests; cover new behavior
with read-service, application, and HTTP contract tests
This commit is contained in:
@@ -31,6 +31,52 @@ class SelectionResultQuery:
|
||||
search: str | None = None
|
||||
category: SelectionSignalCategoryFilter | None = None
|
||||
sort: SelectionResultSort = "code"
|
||||
sector: str | None = None
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SelectionSectorCount:
|
||||
"""One sector and the number of this run's selected stocks it contains."""
|
||||
|
||||
sector_code: str
|
||||
sector_name: str
|
||||
stock_count: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SelectionSectorMembership:
|
||||
"""Point-in-time sector membership aggregates for a set of stock codes."""
|
||||
|
||||
snapshot_trade_date: date | None
|
||||
sector_counts: tuple[SelectionSectorCount, ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SelectionRunIdentity:
|
||||
"""Minimal run locator used to resolve date-dependent filters."""
|
||||
|
||||
run_id: str
|
||||
target_trade_date: date
|
||||
|
||||
|
||||
class SelectionSectorReader(Protocol):
|
||||
"""Port to the sector-radar context's point-in-time membership reads."""
|
||||
|
||||
def sector_counts(
|
||||
self,
|
||||
stock_codes: Sequence[str],
|
||||
target_trade_date: date,
|
||||
*,
|
||||
sector_type: str = "concept",
|
||||
) -> SelectionSectorMembership: ...
|
||||
|
||||
def sector_member_codes(
|
||||
self,
|
||||
target_trade_date: date,
|
||||
sector_code: str,
|
||||
*,
|
||||
sector_type: str = "concept",
|
||||
) -> tuple[str, ...]: ...
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -139,6 +185,7 @@ class SelectionRunStore(Protocol):
|
||||
run_id: str,
|
||||
*,
|
||||
query: SelectionResultQuery | None = None,
|
||||
sector_stock_codes: Sequence[str] | None = None,
|
||||
) -> SelectionRun | None: ...
|
||||
|
||||
def get_latest_run(
|
||||
@@ -147,8 +194,17 @@ class SelectionRunStore(Protocol):
|
||||
target_trade_date: date | None = None,
|
||||
*,
|
||||
query: SelectionResultQuery | None = None,
|
||||
sector_stock_codes: Sequence[str] | None = None,
|
||||
) -> SelectionRun | None: ...
|
||||
|
||||
def get_run_identity(self, run_id: str) -> SelectionRunIdentity | None: ...
|
||||
|
||||
def get_latest_run_identity(
|
||||
self,
|
||||
strategy: SelectionStrategyName,
|
||||
target_trade_date: date | None = None,
|
||||
) -> SelectionRunIdentity | None: ...
|
||||
|
||||
|
||||
class SelectionUniverseReader(Protocol):
|
||||
"""Read a qualified market-data source snapshot for one strategy run."""
|
||||
|
||||
Reference in New Issue
Block a user