refactor(selection): 统一信号与股票处理逻辑,重构相关数据结构与接口,更新前端展示以支持股票信息

This commit is contained in:
yuxuanhui
2026-08-28 11:39:05 +08:00
parent 3da992700b
commit ad9545ef55
12 changed files with 408 additions and 189 deletions
@@ -284,7 +284,7 @@ class PostgresSelectionRunRepository(SelectionRunStore):
*,
query: SelectionResultQuery | None = None,
) -> SelectionRun | None:
"""Read one run with filtered, paged signals and item failures."""
"""Read one run with filtered, stock-paged signals and item failures."""
try:
with self._connection() as connection:
@@ -361,27 +361,47 @@ class PostgresSelectionRunRepository(SelectionRunStore):
""",
(run_id,),
).fetchall()
signal_filter, signal_parameters = _signal_filter(query, run_id)
signal_total_row = connection.execute(
f"SELECT COUNT(*) FROM selection_signal WHERE {signal_filter}",
tuple(signal_parameters),
stock_filter, stock_parameters = _stock_filter(query, run_id)
stock_total_row = connection.execute(
f"SELECT COUNT(DISTINCT ts_code) FROM selection_signal WHERE {stock_filter}",
tuple(stock_parameters),
).fetchone()
signal_total = int(signal_total_row[0] or 0) if signal_total_row else 0
stock_total = int(stock_total_row[0] or 0) if stock_total_row else 0
offset = (query.page - 1) * query.page_size
signal_rows = connection.execute(
f"""
SELECT
ts_code, name, target_trade_date, strategy, category, close, details
FROM selection_signal
WHERE {signal_filter}
ORDER BY ts_code, {_SIGNAL_ORDER_SQL}
LIMIT %s OFFSET %s
""",
tuple((*signal_parameters, query.page_size, offset)),
).fetchall()
stock_rows = cast(
list[tuple[object, ...]],
connection.execute(
f"""
SELECT DISTINCT ts_code
FROM selection_signal
WHERE {stock_filter}
ORDER BY ts_code
LIMIT %s OFFSET %s
""",
tuple((*stock_parameters, query.page_size, offset)),
).fetchall(),
)
stock_codes = [str(value[0]) for value in stock_rows]
signal_rows = (
cast(
list[tuple[object, ...]],
connection.execute(
f"""
SELECT
ts_code, name, target_trade_date, strategy, category, close, details
FROM selection_signal
WHERE run_id = %s AND ts_code = ANY(%s)
ORDER BY ts_code, {_SIGNAL_ORDER_SQL}
""",
(run_id, stock_codes),
).fetchall(),
)
if stock_codes
else []
)
signals = tuple(
sorted(
(_signal_from_row(cast(tuple[object, ...], value)) for value in signal_rows),
(_signal_from_row(value) for value in signal_rows),
key=lambda signal: (
signal.ts_code,
_SIGNAL_PRIORITY.get(signal.category, len(_SIGNAL_PRIORITY)),
@@ -430,7 +450,7 @@ class PostgresSelectionRunRepository(SelectionRunStore):
finished_at=cast(datetime | None, row[15]),
items=items,
signals=signals,
signals_total=signal_total,
stocks_total=stock_total,
)
@contextmanager
@@ -481,8 +501,13 @@ def _signal_from_row(row: tuple[object, ...]) -> SelectionSignal:
)
def _signal_filter(query: SelectionResultQuery, run_id: str) -> tuple[str, list[object]]:
"""Build the parameterized WHERE clause shared by count and page reads."""
def _stock_filter(query: SelectionResultQuery, run_id: str) -> tuple[str, list[object]]:
"""Build the signal predicate used to select distinct matching stocks.
A category narrows which stocks qualify for the page. Once a stock
qualifies, the repository loads every signal for that stock so callers
can present all independently persisted categories together.
"""
clauses = ["run_id = %s"]
parameters: list[object] = [run_id]