refactor(selection): drop residual concept-sector surface from sub-industry path

- remove the sector_type parameter from the selection port, adapter, and
  use case; the adapter now passes SectorType.INDUSTRY directly, deleting
  the dead concept vocabulary mapping
- stop parsing concepts/concept_total/concept_limit from the sector-radar
  membership response in the web API layer and narrow the response type to
  industries only
- update tests and the task PRD accordingly; backend pyright errors drop
  from the 16 baseline to 14
This commit is contained in:
yuxuanhui
2026-09-06 16:15:08 +08:00
parent 7f93d6b0f5
commit 07c5b25043
10 changed files with 18 additions and 83 deletions
@@ -646,11 +646,7 @@ class RunZhixingB1:
if self.sector_reader is None:
return None
return self.sector_reader.sector_member_codes(
target_trade_date,
sector_code,
sector_type="industry",
)
return self.sector_reader.sector_member_codes(target_trade_date, sector_code)
def list_sector_counts(
self,
@@ -684,11 +680,7 @@ class RunZhixingB1:
if self.sector_reader is None or not stock_codes:
return SelectionSectorMembership(snapshot_trade_date=None, sector_counts=())
return self.sector_reader.sector_counts(
stock_codes,
target_trade_date,
sector_type="industry",
)
return self.sector_reader.sector_counts(stock_codes, target_trade_date)
def _to_item(
@@ -66,16 +66,12 @@ class SelectionSectorReader(Protocol):
self,
stock_codes: Sequence[str],
target_trade_date: date,
*,
sector_type: str = "industry",
) -> SelectionSectorMembership: ...
def sector_member_codes(
self,
target_trade_date: date,
sector_code: str,
*,
sector_type: str = "industry",
) -> tuple[str, ...]: ...
@@ -2,7 +2,8 @@
The selection context owns no sector-membership storage. This adapter keeps
the port contract local to selection while delegating point-in-time reads to
the sector-radar application service in the composition root.
the sector-radar application service in the composition root. Selection only
speaks the sub-industry vocabulary, so the radar sector type is fixed here.
"""
from __future__ import annotations
@@ -17,20 +18,6 @@ from zhixing_server.modules.selection.domain.runs import (
SelectionSectorMembership,
)
SELECTION_SECTOR_TYPES: dict[str, SectorType] = {
"concept": SectorType.CONCEPT,
"industry": SectorType.INDUSTRY,
}
def _sector_type(value: str) -> SectorType:
"""Map the public sector-type vocabulary onto the radar domain enum."""
try:
return SELECTION_SECTOR_TYPES[value]
except KeyError:
raise ValueError(f"unsupported sector type: {value}") from None
class SectorRadarSelectionReader:
"""Resolve selection sector aggregates through the sector-radar context."""
@@ -42,15 +29,13 @@ class SectorRadarSelectionReader:
self,
stock_codes: Sequence[str],
target_trade_date: date,
*,
sector_type: str = "industry",
) -> SelectionSectorMembership:
"""Return per-sector stock counts for one run's selected stocks."""
snapshot = self._reader.sector_counts(
stock_codes,
target_trade_date,
sector_type=_sector_type(sector_type),
sector_type=SectorType.INDUSTRY,
)
return SelectionSectorMembership(
snapshot_trade_date=snapshot.trade_date,
@@ -68,17 +53,15 @@ class SectorRadarSelectionReader:
self,
target_trade_date: date,
sector_code: str,
*,
sector_type: str = "industry",
) -> tuple[str, ...]:
"""Return one sector's member stock codes on the aligned snapshot."""
snapshot = self._reader.sector_member_codes(
target_trade_date,
sector_code,
sector_type=_sector_type(sector_type),
sector_type=SectorType.INDUSTRY,
)
return snapshot.stock_codes
__all__ = ["SELECTION_SECTOR_TYPES", "SectorRadarSelectionReader"]
__all__ = ["SectorRadarSelectionReader"]
@@ -41,17 +41,15 @@ class FakeSectorReader:
) -> None:
self.counts = counts
self.member_codes = member_codes
self.count_calls: list[tuple[tuple[str, ...], date, str]] = []
self.member_calls: list[tuple[date, str, str]] = []
self.count_calls: list[tuple[tuple[str, ...], date]] = []
self.member_calls: list[tuple[date, str]] = []
def sector_counts(
self,
stock_codes: Sequence[str],
target_trade_date: date,
*,
sector_type: str = "industry",
) -> SelectionSectorMembership:
self.count_calls.append((tuple(stock_codes), target_trade_date, sector_type))
self.count_calls.append((tuple(stock_codes), target_trade_date))
return SelectionSectorMembership(
snapshot_trade_date=target_trade_date,
sector_counts=self.counts,
@@ -61,10 +59,8 @@ class FakeSectorReader:
self,
target_trade_date: date,
sector_code: str,
*,
sector_type: str = "industry",
) -> tuple[str, ...]:
self.member_calls.append((target_trade_date, sector_code, sector_type))
self.member_calls.append((target_trade_date, sector_code))
return self.member_codes
@@ -161,7 +157,7 @@ def test_list_sector_counts_aggregates_only_selected_stocks() -> None:
aggregates = service.list_sector_counts("zhixing_b1")
assert isinstance(aggregates, SelectionSectorAggregates)
assert sector_reader.count_calls == [(("000001.SZ", "000002.SZ"), TARGET, "industry")]
assert sector_reader.count_calls == [(("000001.SZ", "000002.SZ"), TARGET)]
assert aggregates.snapshot_trade_date == TARGET
assert aggregates.sector_type == "industry"
assert aggregates.sectors == sector_reader.counts
@@ -198,7 +194,7 @@ def test_get_latest_resolves_sector_filter_against_run_snapshot() -> None:
)
assert run is store.run
assert sector_reader.member_calls == [(TARGET, "BK0001.DC", "industry")]
assert sector_reader.member_calls == [(TARGET, "BK0001.DC")]
assert store.sector_codes_seen == ("000001.SZ", "000002.SZ")
assert store.queries[-1].sector is None
assert store.queries[-1].page == 2
@@ -239,7 +235,7 @@ def test_get_run_resolves_sector_filter_against_run_snapshot() -> None:
run = service.get_run("run-1", query=SelectionResultQuery(sector="BK0001.DC"))
assert run is store.run
assert sector_reader.member_calls == [(TARGET, "BK0001.DC", "industry")]
assert sector_reader.member_calls == [(TARGET, "BK0001.DC")]
assert store.sector_codes_seen == ("000001.SZ",)