"""选股 API(M6.3):提交/查询选股,结果落库可复现。 POST /api/selections 同步执行一次选股并落库 → {selection_id, result} GET /api/selections/{id} 读回某次选股完整结果 GET /api/selections 历史选股元数据(可过滤 as_of/method) 同步执行:单日全市场因子评分/条件计算量轻(秒级);未来若超时再迁 Job。 """ from __future__ import annotations from datetime import date from typing import Annotated from fastapi import APIRouter, HTTPException, Query from pydantic import BaseModel from app.api.deps import ( DbSession, SelectionRepoDep, SelectionServiceDep, ) from app.application.services.job_executor import new_id from app.domain.entities.selection import SelectionMeta, SelectionQuery, SelectionResult router = APIRouter(prefix="/selections", tags=["selections"]) class SelectionRun(BaseModel): selection_id: str result: SelectionResult @router.post("", response_model=SelectionRun, summary="执行一次选股(同步)并落库") def run_selection( query: SelectionQuery, service: SelectionServiceDep, selection_repo: SelectionRepoDep, session: DbSession, ) -> SelectionRun: result = service.select(query) selection_id = new_id("SEL") selection_repo.save(selection_id, result) session.commit() return SelectionRun(selection_id=selection_id, result=result) @router.get("/{selection_id}", response_model=SelectionResult, summary="读回一次选股结果") def get_selection( selection_id: str, selection_repo: SelectionRepoDep, ) -> SelectionResult: result = selection_repo.get(selection_id) if result is None: raise HTTPException(status_code=404, detail=f"选股记录 {selection_id} 不存在") return result _AsOfQuery = Annotated[date | None, Query(description="按选股时点过滤")] _MethodQuery = Annotated[str | None, Query(pattern="^(score|condition)$")] _LimitQuery = Annotated[int, Query(ge=1, le=200)] @router.get("", response_model=list[SelectionMeta], summary="历史选股元数据列表") def list_selections( selection_repo: SelectionRepoDep, as_of: _AsOfQuery = None, method: _MethodQuery = None, limit: _LimitQuery = 20, ) -> list[SelectionMeta]: return selection_repo.list_recent(as_of=as_of, method=method, limit=limit)