156 lines
5.6 KiB
Python
156 lines
5.6 KiB
Python
"""Persist publication-owned sector daily aggregates for exact metric replay."""
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from alembic import op
|
|
|
|
revision: str = "0005_radar_daily_aggregate"
|
|
down_revision: str | None = "0004_sector_radar"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
"""Create source recovery links and exact multi-day metric inputs."""
|
|
|
|
op.create_table(
|
|
"sector_radar_publication_source",
|
|
sa.Column(
|
|
"publication_id",
|
|
sa.String(64),
|
|
sa.ForeignKey("sector_radar_publication.id", ondelete="CASCADE"),
|
|
nullable=False,
|
|
),
|
|
sa.Column("source_group", sa.String(32), nullable=False),
|
|
sa.Column("source_order", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"refresh_on_retry",
|
|
sa.Boolean(),
|
|
nullable=False,
|
|
server_default=sa.false(),
|
|
),
|
|
sa.Column(
|
|
"source_snapshot_id",
|
|
sa.String(64),
|
|
sa.ForeignKey("sector_radar_source_snapshot.id", ondelete="RESTRICT"),
|
|
nullable=False,
|
|
),
|
|
sa.PrimaryKeyConstraint("publication_id", "source_group", "source_order"),
|
|
sa.UniqueConstraint(
|
|
"publication_id",
|
|
"source_group",
|
|
"source_snapshot_id",
|
|
name="uq_sector_radar_publication_source_snapshot",
|
|
),
|
|
sa.CheckConstraint(
|
|
"source_group IN ('calendar', 'concept_indices', 'industry_indices', "
|
|
"'members', 'stock_basics', 'suspensions', 'daily', 'moneyflow_dc')",
|
|
name="ck_sector_radar_publication_source_group",
|
|
),
|
|
sa.CheckConstraint(
|
|
"source_order >= 0",
|
|
name="ck_sector_radar_publication_source_order",
|
|
),
|
|
)
|
|
op.create_index(
|
|
"ix_sector_radar_publication_source_snapshot",
|
|
"sector_radar_publication_source",
|
|
["source_snapshot_id"],
|
|
)
|
|
|
|
op.create_unique_constraint(
|
|
"uq_sector_radar_publication_id_date",
|
|
"sector_radar_publication",
|
|
["id", "target_trade_date"],
|
|
)
|
|
op.create_foreign_key(
|
|
"fk_sector_radar_ranking_publication_date",
|
|
"sector_radar_ranking",
|
|
"sector_radar_publication",
|
|
["publication_id", "trade_date"],
|
|
["id", "target_trade_date"],
|
|
ondelete="CASCADE",
|
|
)
|
|
|
|
op.create_table(
|
|
"sector_radar_daily_aggregate",
|
|
sa.Column("publication_id", sa.String(64), nullable=False),
|
|
sa.Column("trade_date", sa.Date(), nullable=False),
|
|
sa.Column("sector_type", sa.String(16), nullable=False),
|
|
sa.Column("sector_code", sa.String(16), nullable=False),
|
|
sa.Column("sector_name", sa.String(128), nullable=False),
|
|
sa.Column("member_count", sa.Integer(), nullable=False),
|
|
sa.Column("valid_sample_count", sa.Integer(), nullable=False),
|
|
sa.Column("net_amount_yuan", sa.Numeric(28, 6)),
|
|
sa.Column("turnover_yuan", sa.Numeric(28, 6)),
|
|
sa.Column("membership_coverage", sa.Numeric(8, 6), nullable=False),
|
|
sa.Column("moneyflow_coverage", sa.Numeric(8, 6), nullable=False),
|
|
sa.Column(
|
|
"created_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.text("now()"),
|
|
),
|
|
sa.PrimaryKeyConstraint("publication_id", "sector_type", "sector_code"),
|
|
sa.ForeignKeyConstraint(
|
|
["publication_id", "trade_date"],
|
|
["sector_radar_publication.id", "sector_radar_publication.target_trade_date"],
|
|
name="fk_sector_radar_daily_aggregate_publication_date",
|
|
ondelete="CASCADE",
|
|
),
|
|
sa.CheckConstraint(
|
|
"sector_type IN ('concept', 'industry')",
|
|
name="ck_sector_radar_daily_aggregate_type",
|
|
),
|
|
sa.CheckConstraint(
|
|
"member_count >= 0 AND valid_sample_count >= 0 AND valid_sample_count <= member_count",
|
|
name="ck_sector_radar_daily_aggregate_counts",
|
|
),
|
|
sa.CheckConstraint(
|
|
"membership_coverage >= 0 AND membership_coverage <= 1 "
|
|
"AND moneyflow_coverage >= 0 AND moneyflow_coverage <= 1",
|
|
name="ck_sector_radar_daily_aggregate_coverage",
|
|
),
|
|
sa.CheckConstraint(
|
|
"turnover_yuan IS NULL OR (turnover_yuan >= 0 AND "
|
|
"turnover_yuan NOT IN ('NaN'::numeric, 'Infinity'::numeric))",
|
|
name="ck_sector_radar_daily_aggregate_turnover",
|
|
),
|
|
sa.CheckConstraint(
|
|
"net_amount_yuan IS NULL OR net_amount_yuan NOT IN "
|
|
"('NaN'::numeric, 'Infinity'::numeric, '-Infinity'::numeric)",
|
|
name="ck_sector_radar_daily_aggregate_net_amount",
|
|
),
|
|
)
|
|
op.create_index(
|
|
"ix_sector_radar_daily_aggregate_history",
|
|
"sector_radar_daily_aggregate",
|
|
["trade_date", "sector_type", "sector_code"],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
"""Drop only the replay aggregate extension."""
|
|
|
|
op.drop_index(
|
|
"ix_sector_radar_daily_aggregate_history",
|
|
table_name="sector_radar_daily_aggregate",
|
|
)
|
|
op.drop_table("sector_radar_daily_aggregate")
|
|
op.drop_constraint(
|
|
"fk_sector_radar_ranking_publication_date",
|
|
"sector_radar_ranking",
|
|
type_="foreignkey",
|
|
)
|
|
op.drop_constraint(
|
|
"uq_sector_radar_publication_id_date",
|
|
"sector_radar_publication",
|
|
type_="unique",
|
|
)
|
|
op.drop_index(
|
|
"ix_sector_radar_publication_source_snapshot",
|
|
table_name="sector_radar_publication_source",
|
|
)
|
|
op.drop_table("sector_radar_publication_source")
|