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

@@ -2,6 +2,7 @@ from app.models.admin import Admin, Role
from app.models.agent_batch import AgentBatchTest, AgentBatchTestItem
from app.models.ai_config import ContentGenerationConfig, ModelConfig, Prompt, SystemConfig
from app.models.base import Base
from app.models.behavior import UserBehaviorEvent
from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan, UserEntitlement, UserEntitlementLog
from app.models.feedback import MessageFeedback
@@ -70,6 +71,7 @@ __all__ = [
"ShareDraft",
"TeacherHelpCard",
"User",
"UserBehaviorEvent",
"UserExternalIdentity",
"UserEntitlement",
"UserEntitlementLog",

View File

@@ -0,0 +1,32 @@
from __future__ import annotations
from datetime import datetime
from sqlalchemy import BigInteger, DateTime, Index, Integer, String, UniqueConstraint, func
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base
PRIMARY_KEY_TYPE = BigInteger().with_variant(Integer, "sqlite")
class UserBehaviorEvent(Base):
__tablename__ = "sys_user_behavior_event"
__table_args__ = (
UniqueConstraint("client_event_id", name="uq_user_behavior_client_event"),
Index("ix_user_behavior_occurred", "occurred_at", "id"),
Index("ix_user_behavior_user_occurred", "user_id", "occurred_at", "id"),
Index("ix_user_behavior_code_occurred", "event_code", "occurred_at", "id"),
)
id: Mapped[int] = mapped_column(PRIMARY_KEY_TYPE, primary_key=True, autoincrement=True)
client_event_id: Mapped[str] = mapped_column(String(36), nullable=False)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
event_code: Mapped[str] = mapped_column(String(64), nullable=False)
event_name: Mapped[str] = mapped_column(String(100), nullable=False)
event_type: Mapped[str] = mapped_column(String(20), nullable=False)
target_type: Mapped[str | None] = mapped_column(String(30), nullable=True)
target_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
occurred_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), nullable=False)

View File

@@ -70,7 +70,6 @@ class TopicSession(Base):
message_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
token_input: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
token_output: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
quota_deducted: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
recommended_homework: Mapped[str | None] = mapped_column(Text, nullable=True)
help_card_generated: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
share_draft_generated: Mapped[int] = mapped_column(Integer, default=0, nullable=False)

View File

@@ -18,12 +18,10 @@ class EntitlementPlan(Base, TimestampMixin):
plan_type: Mapped[str] = mapped_column(String(30), index=True, nullable=False)
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
validity_days: Mapped[int | None] = mapped_column(Integer, nullable=True)
monthly_topic_limit: Mapped[int | None] = mapped_column(Integer, nullable=True)
enable_growth_profile: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
enable_periodic_reports: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
allow_help_card: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
allow_share_draft: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
deduct_quota: Mapped[int] = mapped_column(Integer, default=1, nullable=False)
status: Mapped[int] = mapped_column(Integer, default=1, index=True, nullable=False)
sort_order: Mapped[int] = mapped_column(Integer, default=0, nullable=False)

View File

@@ -131,7 +131,7 @@ class PeriodicReport(Base):
id: Mapped[int] = mapped_column(PRIMARY_KEY_TYPE, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(ForeignKey("sys_user.id"), index=True, nullable=False)
schema_version: Mapped[int] = mapped_column(Integer, default=2, nullable=False)
schema_version: Mapped[int] = mapped_column(Integer, default=3, nullable=False)
report_type: Mapped[str] = mapped_column(String(30), nullable=False)
period_start: Mapped[datetime] = mapped_column(DateTime, nullable=False)
period_end: Mapped[datetime] = mapped_column(DateTime, nullable=False)
@@ -139,6 +139,8 @@ class PeriodicReport(Base):
content: Mapped[str] = mapped_column(Text, default="", nullable=False)
source_summary_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
source_topic_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
source_message_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
source_report_ids: Mapped[str | None] = mapped_column(Text, nullable=True)
model_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
status: Mapped[str] = mapped_column(String(20), default="success", index=True, nullable=False)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)