- domain/repositories/selection.py + SelectionMeta:选股 Repository Protocol
- models/selection.py + migration a6c91d4e7f20:selection_snapshot(查询/统计快照)+
selection_result(逐候选:rank/score/factor_values/filter_status/reason JSON)
- repositories/selection_impl.py:save/get/list_recent(读回重建 SelectionResult)
- api/selections.py:POST /api/selections(同步执行+落库,返回 selection_id+result)、
GET /{id}、GET 列表(as_of/method 过滤);deps 装配 SelectionService/Repo
- config._build_mysql_url:仅编码破坏 URL 结构的字符(修 Alembic configparser 遇 %21 崩)
- 迁移已在 MySQL 应用(alembic head a6c91d4e7f20);tests/test_selections_api.py 4 例
提交→读回一致/404/列表过滤/condition;全量 pytest 通过
63 lines
2.3 KiB
Python
63 lines
2.3 KiB
Python
"""selection_snapshot / selection_result 表(M6.3 选股落库)
|
|
|
|
Revision ID: a6c91d4e7f20
|
|
Revises: d3f6c9a21b04
|
|
Create Date: 2026-09-08
|
|
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "a6c91d4e7f20"
|
|
down_revision: str | None = "d3f6c9a21b04"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"selection_snapshot",
|
|
sa.Column("id", sa.String(length=32), nullable=False),
|
|
sa.Column("as_of", sa.Date(), nullable=False),
|
|
sa.Column("method", sa.String(length=16), nullable=False),
|
|
sa.Column("query_json", sa.Text(), nullable=False),
|
|
sa.Column("statistics_json", sa.Text(), nullable=False),
|
|
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
sa.PrimaryKeyConstraint("id"),
|
|
)
|
|
op.create_index("ix_selection_snapshot_as_of", "selection_snapshot", ["as_of"])
|
|
op.create_index("ix_selection_snapshot_created_at", "selection_snapshot", ["created_at"])
|
|
|
|
op.create_table(
|
|
"selection_result",
|
|
sa.Column(
|
|
"id",
|
|
sa.BigInteger().with_variant(sa.Integer(), "sqlite"),
|
|
autoincrement=True,
|
|
nullable=False,
|
|
),
|
|
sa.Column("selection_id", sa.String(length=32), nullable=False),
|
|
sa.Column("symbol", sa.String(length=12), nullable=False),
|
|
sa.Column("rank", sa.Integer(), nullable=False),
|
|
sa.Column("score", sa.Numeric(precision=14, scale=6), nullable=False),
|
|
sa.Column("factor_values_json", sa.Text(), nullable=True),
|
|
sa.Column("filter_status_json", sa.Text(), nullable=True),
|
|
sa.Column("reason_json", sa.Text(), nullable=True),
|
|
sa.Column("created_at", sa.DateTime(), nullable=False),
|
|
sa.PrimaryKeyConstraint("id"),
|
|
)
|
|
op.create_index("ix_selection_result_selection_id", "selection_result", ["selection_id"])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_selection_result_selection_id", table_name="selection_result")
|
|
op.drop_table("selection_result")
|
|
op.drop_index("ix_selection_snapshot_created_at", table_name="selection_snapshot")
|
|
op.drop_index("ix_selection_snapshot_as_of", table_name="selection_snapshot")
|
|
op.drop_table("selection_snapshot")
|