91 lines
3.3 KiB
Python
91 lines
3.3 KiB
Python
|
|
"""Create independent market-data integrity check and issue tables."""
|
||
|
|
|
||
|
|
from collections.abc import Sequence
|
||
|
|
|
||
|
|
import sqlalchemy as sa
|
||
|
|
from alembic import op
|
||
|
|
|
||
|
|
revision: str = "0003_market_integrity_checks"
|
||
|
|
down_revision: str | None = "0002_selection_results"
|
||
|
|
branch_labels: str | Sequence[str] | None = None
|
||
|
|
depends_on: str | Sequence[str] | None = None
|
||
|
|
|
||
|
|
|
||
|
|
def upgrade() -> None:
|
||
|
|
"""Create only check metadata; market facts remain untouched."""
|
||
|
|
|
||
|
|
op.create_table(
|
||
|
|
"market_integrity_check",
|
||
|
|
sa.Column("id", sa.String(36), primary_key=True),
|
||
|
|
sa.Column("status", sa.String(24), nullable=False),
|
||
|
|
sa.Column("window_start", sa.Date()),
|
||
|
|
sa.Column("window_end", sa.Date()),
|
||
|
|
sa.Column("target_count", sa.Integer(), nullable=False, server_default="0"),
|
||
|
|
sa.Column("checked_count", sa.Integer(), nullable=False, server_default="0"),
|
||
|
|
sa.Column("issue_count", sa.Integer(), nullable=False, server_default="0"),
|
||
|
|
sa.Column("error_type", sa.String(64)),
|
||
|
|
sa.Column("error_message", sa.Text()),
|
||
|
|
sa.Column(
|
||
|
|
"created_at",
|
||
|
|
sa.DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=sa.text("now()"),
|
||
|
|
),
|
||
|
|
sa.Column(
|
||
|
|
"updated_at",
|
||
|
|
sa.DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=sa.text("now()"),
|
||
|
|
),
|
||
|
|
sa.Column("finished_at", sa.DateTime(timezone=True)),
|
||
|
|
)
|
||
|
|
op.create_index(
|
||
|
|
"ix_market_integrity_check_status_created_at",
|
||
|
|
"market_integrity_check",
|
||
|
|
["status", "created_at"],
|
||
|
|
)
|
||
|
|
# A partial unique index makes the running claim atomic under concurrent
|
||
|
|
# POST requests; stale recovery first moves an abandoned row to failed.
|
||
|
|
op.create_index(
|
||
|
|
"uq_market_integrity_check_running",
|
||
|
|
"market_integrity_check",
|
||
|
|
["status"],
|
||
|
|
unique=True,
|
||
|
|
postgresql_where=sa.text("status = 'running'"),
|
||
|
|
)
|
||
|
|
op.create_table(
|
||
|
|
"market_integrity_issue",
|
||
|
|
sa.Column(
|
||
|
|
"check_id",
|
||
|
|
sa.String(36),
|
||
|
|
sa.ForeignKey("market_integrity_check.id", ondelete="CASCADE"),
|
||
|
|
nullable=False,
|
||
|
|
),
|
||
|
|
sa.Column("issue_key", sa.String(64), nullable=False),
|
||
|
|
sa.Column("item_kind", sa.String(24), nullable=False),
|
||
|
|
sa.Column("item_key", sa.String(128), nullable=False),
|
||
|
|
sa.Column("issue_type", sa.String(64), nullable=False),
|
||
|
|
sa.Column("message", sa.Text(), nullable=False),
|
||
|
|
sa.Column(
|
||
|
|
"created_at",
|
||
|
|
sa.DateTime(timezone=True),
|
||
|
|
nullable=False,
|
||
|
|
server_default=sa.text("now()"),
|
||
|
|
),
|
||
|
|
sa.PrimaryKeyConstraint("check_id", "issue_key"),
|
||
|
|
)
|
||
|
|
op.create_index("ix_market_integrity_issue_check_id", "market_integrity_issue", ["check_id"])
|
||
|
|
|
||
|
|
|
||
|
|
def downgrade() -> None:
|
||
|
|
"""Drop only the integrity report tables in dependency-safe order."""
|
||
|
|
|
||
|
|
op.drop_index("ix_market_integrity_issue_check_id", table_name="market_integrity_issue")
|
||
|
|
op.drop_table("market_integrity_issue")
|
||
|
|
op.drop_index("uq_market_integrity_check_running", table_name="market_integrity_check")
|
||
|
|
op.drop_index(
|
||
|
|
"ix_market_integrity_check_status_created_at",
|
||
|
|
table_name="market_integrity_check",
|
||
|
|
)
|
||
|
|
op.drop_table("market_integrity_check")
|