"""年度报告 — seed the wrapped.year_end_push cron (no table). The 年度报告 itself is a read-only aggregate (no table) and the opt-out lives in the generic preferences KV (no migration). This migration only seeds the once-a-year year-end push cron row (Dec 31). Idempotent. Revision ID: 320 Revises: 319 """ from __future__ import annotations import uuid import sqlalchemy as sa from alembic import op revision = "320" down_revision = "319" branch_labels = None depends_on = None JOB = "wrapped.year_end_push" def upgrade() -> None: bind = op.get_bind() job_exists = bind.execute( sa.text("SELECT 1 FROM scheduled_jobs WHERE name = :n LIMIT 1"), {"n": JOB}, ).scalar() if not job_exists: bind.execute( sa.text( """ INSERT INTO scheduled_jobs (id, name, display_name, description, handler_key, trigger_type, interval_seconds, cron_expr, timezone, enabled, config, run_count, failure_count, created_at, updated_at) VALUES (:id, :name, :display_name, :description, :name, 'cron', NULL, '0 1 31 12 *', 'UTC', 1, '{}', 0, 0, NOW(), NOW()) """ ), { "id": str(uuid.uuid4()), "name": JOB, "display_name": "年度报告 · 年底推送", "description": ( "每年 12/31 01:00 UTC 给已选择接收的在职员工推送年度报告飞书卡片。" "护栏:一年一次幂等标记 + opt-out 偏好 + 每轮上限 + 逐人隔离。" ), }, ) def downgrade() -> None: bind = op.get_bind() bind.execute( sa.text("DELETE FROM scheduled_jobs WHERE name = :n"), {"n": JOB}, )