geo/backend/alembic/versions/f6g8h0i2de56_add_analytics_...

125 lines
5.0 KiB
Python

"""Add analytics tables (publish_records, content_metrics, optimization_insights)
Revision ID: f6g8h0i2de56
Revises: e5f7g9h1cd45
Create Date: 2026-05-23 14:00:00.000000
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "f6g8h0i2de56"
down_revision: Union[str, None] = "e5f7g9h1cd45"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
# ------------------------------------------------------------------ #
# 1. publish_records
# ------------------------------------------------------------------ #
op.create_table(
"publish_records",
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
sa.Column("organization_id", sa.String(36), nullable=False),
sa.Column("content_title", sa.String(200), nullable=False),
sa.Column("content_id", sa.String(36), nullable=True),
sa.Column("platform", sa.String(50), nullable=False),
sa.Column("published_url", sa.String(500), nullable=True),
sa.Column("status", sa.String(20), server_default="draft", nullable=False),
sa.Column("published_at", sa.DateTime(), nullable=True),
sa.Column(
"created_at",
sa.DateTime(),
server_default=sa.text("now()"),
nullable=False,
),
)
op.create_index("idx_publish_records_organization_id", "publish_records", ["organization_id"])
op.create_index("idx_publish_records_platform", "publish_records", ["platform"])
op.create_index("idx_publish_records_status", "publish_records", ["status"])
op.create_index("idx_publish_records_created_at", "publish_records", ["created_at"])
# ------------------------------------------------------------------ #
# 2. content_metrics
# ------------------------------------------------------------------ #
op.create_table(
"content_metrics",
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
sa.Column(
"publish_record_id",
sa.String(36),
sa.ForeignKey("publish_records.id", ondelete="CASCADE"),
nullable=False,
),
sa.Column(
"recorded_at",
sa.DateTime(),
server_default=sa.text("now()"),
nullable=False,
),
# 互动指标
sa.Column("views", sa.Integer(), server_default="0", nullable=False),
sa.Column("likes", sa.Integer(), server_default="0", nullable=False),
sa.Column("comments", sa.Integer(), server_default="0", nullable=False),
sa.Column("shares", sa.Integer(), server_default="0", nullable=False),
sa.Column("bookmarks", sa.Integer(), server_default="0", nullable=False),
# GEO核心指标
sa.Column("ai_citation_count", sa.Integer(), server_default="0", nullable=False),
sa.Column("search_impressions", sa.Integer(), server_default="0", nullable=False),
sa.Column("search_clicks", sa.Integer(), server_default="0", nullable=False),
# 阅读指标
sa.Column("avg_read_duration", sa.Float(), server_default="0.0", nullable=False),
sa.Column("read_completion_rate", sa.Float(), server_default="0.0", nullable=False),
)
op.create_index(
"idx_content_metrics_publish_record_id", "content_metrics", ["publish_record_id"]
)
op.create_index("idx_content_metrics_recorded_at", "content_metrics", ["recorded_at"])
# ------------------------------------------------------------------ #
# 3. optimization_insights
# ------------------------------------------------------------------ #
op.create_table(
"optimization_insights",
sa.Column("id", sa.String(36), primary_key=True, nullable=False),
sa.Column("organization_id", sa.String(36), nullable=False),
sa.Column("content_id", sa.String(36), nullable=True),
sa.Column("insight_type", sa.String(30), nullable=False),
sa.Column("title", sa.String(200), nullable=False),
sa.Column("description", sa.Text(), nullable=False),
sa.Column("recommendation", sa.Text(), nullable=False),
sa.Column("severity", sa.String(20), server_default="info", nullable=False),
sa.Column("applied", sa.Boolean(), server_default="false", nullable=False),
sa.Column(
"created_at",
sa.DateTime(),
server_default=sa.text("now()"),
nullable=False,
),
)
op.create_index(
"idx_optimization_insights_organization_id",
"optimization_insights",
["organization_id"],
)
op.create_index(
"idx_optimization_insights_insight_type",
"optimization_insights",
["insight_type"],
)
op.create_index(
"idx_optimization_insights_created_at",
"optimization_insights",
["created_at"],
)
def downgrade() -> None:
op.drop_table("optimization_insights")
op.drop_table("content_metrics")
op.drop_table("publish_records")