"""因子目录 API:/api/factors(M7.1 起读 DB factor_definition)。 目录为空时自动从代码注册表 seed(幂等);随后可登记自定义因子元数据。 响应为 FactorDefinition 实体(含 requires 列表等)。 """ from __future__ import annotations from fastapi import APIRouter from app.api.deps import DbSession, FactorRepoDep from app.application.services.factor_catalog import seed_registry_factors from app.domain.entities.factor import FactorDefinition from app.quant.factors import list_factors router = APIRouter(prefix="/factors", tags=["factors"]) @router.get("", summary="因子目录(含元数据,来自 factor_definition 表)") def list_factor_catalog(factor_repo: FactorRepoDep, session: DbSession) -> list[FactorDefinition]: """读目录;**缺失的注册表因子当场补齐**(幂等),稳态下零写入。 历史 bug:原先只在「表为空」时 seed,于是表非空后**代码里新增的因子永远进不了目录**—— 实测表内 9 条、注册表 11 条,`dividend_yield` / `dividend_yield_ttm` 长期缺失, 前端因子下拉选不到「股息率」、归档页也查不到它的方向与含义(违反 §7 不静默)。 现在按「注册表有、库里没有」的差集触发 upsert:既保证目录与可计算因子一致, 又不会覆盖用户登记的自定义因子元数据(只补不删)。 """ existing = factor_repo.list() missing = {d.name for d in list_factors()} - {f.name for f in existing} if not existing or missing: seed_registry_factors(factor_repo, session) existing = factor_repo.list() return existing