去掉 uq_standings_league_season_team,改为 (league, season, team, available_at) 唯一; 每次采集 INSERT 新行(available_at=now),ON CONFLICT DO NOTHING,不覆盖旧行。 standings_slice(before):DISTINCT ON (team_id) WHERE available_at<=cutoff ORDER available_at DESC;before=None → cutoff=now()。 公开 list_standings 取每队最新可用快照(子查询 max available_at)。 迁移 0023 + 切片/路由/docs 同步;测试 test_p0_standings_cutoff(5/5)。 284 测试全绿。
59 lines
2.3 KiB
Python
59 lines
2.3 KiB
Python
"""P0-02: 积分榜改为追加快照(append-only) + available_at
|
|
|
|
去掉 uq_standings_league_season_team(league,season,team 唯一),
|
|
改为 (league, season, team, available_at) 唯一;
|
|
每次采集 INSERT 新行(available_at=now),支持回测还原历史榜单。
|
|
|
|
Revision ID: 0023_standings_append_only
|
|
Revises: 0022_match_score_status
|
|
Create Date: 2026-09-22
|
|
"""
|
|
|
|
from typing import Sequence, Union
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision: str = '0023_standings_append_only'
|
|
down_revision: Union[str, None] = '0022_match_score_status'
|
|
branch_labels: Union[str, Sequence[str], None] = None
|
|
depends_on: Union[str, Sequence[str], None] = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
from datetime import datetime, timezone
|
|
|
|
# 1) 加 available_at 列(非空,默认 now;存量回填 retrieved_at 或 now)
|
|
op.add_column(
|
|
'standings',
|
|
sa.Column('available_at', sa.DateTime(timezone=True), nullable=False,
|
|
server_default=sa.func.now()),
|
|
)
|
|
# 存量行: available_at 取 retrieved_at(若存在)否则 now
|
|
op.execute("UPDATE standings SET available_at = COALESCE(retrieved_at, NOW())")
|
|
|
|
# 2) 去旧唯一约束,加新唯一约束(league, season, team, available_at)
|
|
op.drop_constraint('uq_standings_league_season_team', 'standings', type_='unique')
|
|
op.drop_index('ix_standings_leason_season_pos', table_name='standings')
|
|
op.create_index('ix_standings_league_season_pos', 'standings', ['league_id', 'season', 'position'])
|
|
op.create_unique_constraint(
|
|
'uq_standings_league_season_team_available', 'standings',
|
|
['league_id', 'season', 'team_id', 'available_at'],
|
|
)
|
|
op.create_index(
|
|
'ix_standings_league_season_team_available', 'standings',
|
|
['league_id', 'season', 'team_id', 'available_at'],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index('ix_standings_league_season_team_available', table_name='standings')
|
|
op.drop_constraint('uq_standings_league_season_team_available', 'standings', type_='unique')
|
|
op.drop_index('ix_standings_league_season_pos', table_name='standings')
|
|
op.create_index('ix_standings_leason_season_pos', 'standings', ['league_id', 'season', 'position'])
|
|
op.create_unique_constraint(
|
|
'uq_standings_league_season_team', 'standings',
|
|
['league_id', 'season', 'team_id'],
|
|
)
|
|
op.drop_column('standings', 'available_at')
|