148 lines
5.6 KiB
Python
148 lines
5.6 KiB
Python
"""init content tables
|
||
|
||
Revision ID: 0001_init_content_tables
|
||
Revises:
|
||
Create Date: 2026-02-01
|
||
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
from alembic import op
|
||
import sqlalchemy as sa
|
||
from sqlalchemy.dialects import mysql
|
||
|
||
|
||
# revision identifiers, used by Alembic.
|
||
revision = "0001_init_content_tables"
|
||
down_revision = None
|
||
branch_labels = None
|
||
depends_on = None
|
||
|
||
|
||
def upgrade() -> None:
|
||
op.create_table(
|
||
"contents",
|
||
sa.Column(
|
||
"content_id",
|
||
mysql.BIGINT(unsigned=True),
|
||
primary_key=True,
|
||
autoincrement=True,
|
||
comment="文案唯一 ID(自增;文案微调时保持不变)",
|
||
),
|
||
sa.Column("text_en", sa.Text(), nullable=True, comment="英文文案(可空;若为空则必须提供 text_tc)"),
|
||
sa.Column("text_tc", sa.Text(), nullable=True, comment="繁体中文文案(可空;若为空则必须提供 text_en)"),
|
||
sa.Column("author_id", sa.String(length=255), nullable=True, comment="作者/来源 ID(可空;用于多样性与频控)"),
|
||
sa.Column("template_id", sa.String(length=255), nullable=True, comment="模板 ID(可空;用于多样性与频控)"),
|
||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="创建时间"),
|
||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="更新时间"),
|
||
sa.CheckConstraint(
|
||
"(text_en IS NOT NULL) OR (text_tc IS NOT NULL)",
|
||
name="chk_contents_text_present",
|
||
),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
op.create_index("idx_contents_author_id", "contents", ["author_id"], unique=False)
|
||
op.create_index("idx_contents_template_id", "contents", ["template_id"], unique=False)
|
||
|
||
op.create_table(
|
||
"content_profiles",
|
||
sa.Column(
|
||
"content_id",
|
||
mysql.BIGINT(unsigned=True),
|
||
sa.ForeignKey("contents.content_id", ondelete="CASCADE"),
|
||
primary_key=True,
|
||
comment="FK -> contents.content_id",
|
||
),
|
||
sa.Column(
|
||
"stage",
|
||
sa.Enum("general", "expecting", "parenting", "unknown", name="content_stage"),
|
||
server_default="general",
|
||
nullable=False,
|
||
comment="母职阶段定位(general/expecting/parenting/unknown)",
|
||
),
|
||
sa.Column("emotion_score", sa.Numeric(3, 2), nullable=True, comment="情绪调性 0~1;NULL 表示 general"),
|
||
sa.Column(
|
||
"context_suitability_json",
|
||
sa.JSON(),
|
||
nullable=False,
|
||
comment="各 context 的适配度(JSON:0/0.5/1;必须包含 5 个 key)",
|
||
),
|
||
sa.Column(
|
||
"need_suitability_json",
|
||
sa.JSON(),
|
||
nullable=False,
|
||
comment="各 need 的适配度(JSON:0/0.5/1;必须包含 5 个 key)",
|
||
),
|
||
sa.Column(
|
||
"personalization_power",
|
||
sa.SmallInteger(),
|
||
server_default="0",
|
||
nullable=False,
|
||
comment="个性化力度(约定只允许 0/5/10,分别映射 0/0.5/1)",
|
||
),
|
||
sa.Column(
|
||
"review_confidence",
|
||
sa.Numeric(3, 2),
|
||
nullable=True,
|
||
comment="标注置信度 0~1;NULL 表示由推荐侧按 0.7 兜底",
|
||
),
|
||
sa.Column(
|
||
"is_safe_pool",
|
||
sa.Boolean(),
|
||
server_default=sa.text("0"),
|
||
nullable=False,
|
||
comment="是否属于通用安全池(L3 兜底)",
|
||
),
|
||
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="画像更新时间"),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
op.create_index("idx_profiles_is_safe_pool", "content_profiles", ["is_safe_pool"], unique=False)
|
||
op.create_index("idx_profiles_personalization_power", "content_profiles", ["personalization_power"], unique=False)
|
||
op.create_index("idx_profiles_stage", "content_profiles", ["stage"], unique=False)
|
||
|
||
op.create_table(
|
||
"content_risk_flags",
|
||
sa.Column(
|
||
"id",
|
||
mysql.BIGINT(unsigned=True),
|
||
primary_key=True,
|
||
autoincrement=True,
|
||
comment="主键",
|
||
),
|
||
sa.Column(
|
||
"content_id",
|
||
mysql.BIGINT(unsigned=True),
|
||
sa.ForeignKey("contents.content_id", ondelete="CASCADE"),
|
||
nullable=False,
|
||
comment="FK -> contents.content_id",
|
||
),
|
||
sa.Column(
|
||
"flag",
|
||
sa.String(length=64),
|
||
nullable=False,
|
||
comment="风险标记(unsafe_for_* / block_* / soft_*)",
|
||
),
|
||
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False, comment="创建时间"),
|
||
sa.UniqueConstraint("content_id", "flag", name="uniq_content_flag"),
|
||
mysql_charset="utf8mb4",
|
||
)
|
||
op.create_index("idx_content_id", "content_risk_flags", ["content_id"], unique=False)
|
||
op.create_index("idx_flag", "content_risk_flags", ["flag"], unique=False)
|
||
|
||
|
||
def downgrade() -> None:
|
||
op.drop_index("idx_flag", table_name="content_risk_flags")
|
||
op.drop_index("idx_content_id", table_name="content_risk_flags")
|
||
op.drop_table("content_risk_flags")
|
||
|
||
op.drop_index("idx_profiles_stage", table_name="content_profiles")
|
||
op.drop_index("idx_profiles_personalization_power", table_name="content_profiles")
|
||
op.drop_index("idx_profiles_is_safe_pool", table_name="content_profiles")
|
||
op.drop_table("content_profiles")
|
||
|
||
op.drop_index("idx_contents_template_id", table_name="contents")
|
||
op.drop_index("idx_contents_author_id", table_name="contents")
|
||
op.drop_table("contents")
|
||
|