feat: 完善周期报告与用户行为分析

- 支持可配置周报月报模板与登录后异步补生成\n- 增加用户行为埋点和后台分析页面\n- 移除主题额度并保留实修回顾结算\n- 修复历史会话续聊上下文丢失
This commit is contained in:
2026-08-19 11:53:34 +08:00
parent 833763c461
commit efe835be81
75 changed files with 3325 additions and 578 deletions

View File

@@ -0,0 +1,65 @@
"""remove monthly topic quota while preserving practice review data
Revision ID: 0036_remove_topic_quota
Revises: 0035_content_gen_variables
"""
import sqlalchemy as sa
from alembic import op
revision = "0036_remove_topic_quota"
down_revision = "0035_content_gen_variables"
branch_labels = None
depends_on = None
OLD_REVIEW_KEY = "topic_auto_settle_successful_rounds"
NEW_REVIEW_KEY = "practice_review_auto_settle_successful_rounds"
def _rename_config_key(old_key: str, new_key: str) -> None:
connection = op.get_bind()
old_exists = connection.execute(
sa.text("SELECT COUNT(*) FROM sys_system_config WHERE config_key = :key"),
{"key": old_key},
).scalar()
if not old_exists:
return
new_exists = connection.execute(
sa.text("SELECT COUNT(*) FROM sys_system_config WHERE config_key = :key"),
{"key": new_key},
).scalar()
if new_exists:
connection.execute(
sa.text("DELETE FROM sys_system_config WHERE config_key = :key"),
{"key": old_key},
)
return
connection.execute(
sa.text("UPDATE sys_system_config SET config_key = :new_key WHERE config_key = :old_key"),
{"new_key": new_key, "old_key": old_key},
)
def upgrade() -> None:
_rename_config_key(OLD_REVIEW_KEY, NEW_REVIEW_KEY)
op.drop_column("sys_entitlement_plan", "deduct_quota")
op.drop_column("sys_entitlement_plan", "monthly_topic_limit")
op.drop_column("sys_topic_session", "quota_deducted")
def downgrade() -> None:
op.add_column(
"sys_topic_session",
sa.Column("quota_deducted", sa.Integer(), nullable=False, server_default="0"),
)
op.add_column(
"sys_entitlement_plan",
sa.Column("monthly_topic_limit", sa.Integer(), nullable=True),
)
op.add_column(
"sys_entitlement_plan",
sa.Column("deduct_quota", sa.Integer(), nullable=False, server_default="1"),
)
_rename_config_key(NEW_REVIEW_KEY, OLD_REVIEW_KEY)

View File

@@ -0,0 +1,38 @@
"""track chat and weekly-report sources for configurable periodic reports
Revision ID: 0037_report_sources
Revises: 0036_remove_topic_quota
"""
import sqlalchemy as sa
from alembic import op
revision = "0037_report_sources"
down_revision = "0036_remove_topic_quota"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.add_column("sys_periodic_report", sa.Column("source_message_ids", sa.Text(), nullable=True))
op.add_column("sys_periodic_report", sa.Column("source_report_ids", sa.Text(), nullable=True))
op.alter_column(
"sys_periodic_report",
"schema_version",
server_default="3",
existing_type=sa.Integer(),
existing_nullable=False,
)
def downgrade() -> None:
op.alter_column(
"sys_periodic_report",
"schema_version",
server_default="2",
existing_type=sa.Integer(),
existing_nullable=False,
)
op.drop_column("sys_periodic_report", "source_report_ids")
op.drop_column("sys_periodic_report", "source_message_ids")

View File

@@ -0,0 +1,42 @@
"""add user behavior analytics
Revision ID: 0038_user_behavior
Revises: 0037_report_sources
"""
from alembic import op
import sqlalchemy as sa
revision = "0038_user_behavior"
down_revision = "0037_report_sources"
branch_labels = None
depends_on = None
def upgrade() -> None:
op.create_table(
"sys_user_behavior_event",
sa.Column("id", sa.BigInteger(), autoincrement=True, nullable=False),
sa.Column("client_event_id", sa.String(length=36), nullable=False),
sa.Column("user_id", sa.BigInteger(), nullable=False),
sa.Column("event_code", sa.String(length=64), nullable=False),
sa.Column("event_name", sa.String(length=100), nullable=False),
sa.Column("event_type", sa.String(length=20), nullable=False),
sa.Column("target_type", sa.String(length=30), nullable=True),
sa.Column("target_id", sa.BigInteger(), nullable=True),
sa.Column("occurred_at", sa.DateTime(), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.text("CURRENT_TIMESTAMP"), nullable=False),
sa.PrimaryKeyConstraint("id"),
sa.UniqueConstraint("client_event_id", name="uq_user_behavior_client_event"),
)
op.create_index("ix_user_behavior_occurred", "sys_user_behavior_event", ["occurred_at", "id"])
op.create_index("ix_user_behavior_user_occurred", "sys_user_behavior_event", ["user_id", "occurred_at", "id"])
op.create_index("ix_user_behavior_code_occurred", "sys_user_behavior_event", ["event_code", "occurred_at", "id"])
def downgrade() -> None:
op.drop_index("ix_user_behavior_code_occurred", table_name="sys_user_behavior_event")
op.drop_index("ix_user_behavior_user_occurred", table_name="sys_user_behavior_event")
op.drop_index("ix_user_behavior_occurred", table_name="sys_user_behavior_event")
op.drop_table("sys_user_behavior_event")