271 lines
12 KiB
Python
271 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import enum
|
|
from datetime import date as DateType, datetime as DateTimeType
|
|
|
|
from sqlalchemy import Boolean, Date, DateTime, Enum, ForeignKey, Integer, JSON, String, Text, func
|
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
|
|
from app.database import Base
|
|
|
|
|
|
def enum_values(enum_cls: type[enum.Enum]) -> Enum:
|
|
return Enum(enum_cls, values_callable=lambda members: [member.value for member in members], native_enum=False)
|
|
|
|
|
|
class UserRole(str, enum.Enum):
|
|
admin = "admin"
|
|
reviewer = "reviewer"
|
|
operator = "operator"
|
|
|
|
|
|
class ProcessStatus(str, enum.Enum):
|
|
unprocessed = "unprocessed"
|
|
processing = "processing"
|
|
parsed = "parsed"
|
|
pending_review = "pending_review"
|
|
completed = "completed"
|
|
failed = "failed"
|
|
|
|
|
|
class RiskLevel(str, enum.Enum):
|
|
low = "low"
|
|
medium = "medium"
|
|
high = "high"
|
|
forbidden = "forbidden"
|
|
|
|
|
|
class ReviewStatus(str, enum.Enum):
|
|
pending = "pending"
|
|
reviewing = "reviewing"
|
|
approved = "approved"
|
|
needs_revision = "needs_revision"
|
|
rejected = "rejected"
|
|
forbidden = "forbidden"
|
|
|
|
|
|
class DesensitizationStatus(str, enum.Enum):
|
|
not_needed = "not_needed"
|
|
needed = "needed"
|
|
done = "done"
|
|
unknown = "unknown"
|
|
|
|
|
|
class CourseStage(str, enum.Enum):
|
|
awareness = "觉知"
|
|
origin = "原生"
|
|
unity = "合一"
|
|
vitality = "生机"
|
|
heart_light = "心光"
|
|
camp_general = "大本营综合"
|
|
unknown = "不确定"
|
|
|
|
|
|
class PrimaryTopic(str, enum.Enum):
|
|
parent_child = "亲子关系"
|
|
marriage = "婚姻关系"
|
|
family_of_origin = "原生家庭"
|
|
emotion = "情绪模式"
|
|
course = "课程学习"
|
|
growth = "个人成长"
|
|
family_system = "家族系统"
|
|
relationship_drain = "关系内耗"
|
|
action = "行动力与拖延"
|
|
other = "其他"
|
|
|
|
|
|
class AuditStatus(str, enum.Enum):
|
|
draft = "draft"
|
|
pending = "pending"
|
|
approved = "approved"
|
|
needs_revision = "needs_revision"
|
|
disabled = "disabled"
|
|
|
|
|
|
class CallStatus(str, enum.Enum):
|
|
callable = "callable"
|
|
not_callable = "not_callable"
|
|
need_review = "need_review"
|
|
forbidden = "forbidden"
|
|
|
|
|
|
class TaskType(str, enum.Enum):
|
|
scheduled_scan = "scheduled_scan"
|
|
manual_scan = "manual_scan"
|
|
manual_process = "manual_process"
|
|
ai_cleaning = "ai_cleaning"
|
|
feishu_sync = "feishu_sync"
|
|
|
|
|
|
class TaskStatus(str, enum.Enum):
|
|
pending = "pending"
|
|
running = "running"
|
|
success = "success"
|
|
failed = "failed"
|
|
partial_success = "partial_success"
|
|
|
|
|
|
class TargetType(str, enum.Enum):
|
|
raw_qa = "raw_qa"
|
|
standard_qa = "standard_qa"
|
|
session = "session"
|
|
|
|
|
|
class AuditAction(str, enum.Enum):
|
|
approve = "approve"
|
|
revise = "revise"
|
|
reject = "reject"
|
|
forbid = "forbid"
|
|
mark_high_risk = "mark_high_risk"
|
|
mark_desensitization = "mark_desensitization"
|
|
mark_callable = "mark_callable"
|
|
disable = "disable"
|
|
|
|
|
|
class TimestampMixin:
|
|
created_at: Mapped[DateTimeType] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
updated_at: Mapped[DateTimeType] = mapped_column(
|
|
DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False
|
|
)
|
|
|
|
|
|
class User(Base, TimestampMixin):
|
|
__tablename__ = "users"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
name: Mapped[str] = mapped_column(String(120), nullable=False)
|
|
email: Mapped[str] = mapped_column(String(255), unique=True, index=True, nullable=False)
|
|
role: Mapped[UserRole] = mapped_column(enum_values(UserRole), default=UserRole.reviewer, nullable=False)
|
|
|
|
|
|
class FeishuSession(Base, TimestampMixin):
|
|
__tablename__ = "feishu_sessions"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
session_code: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
|
title: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
date: Mapped[DateType | None] = mapped_column(Date, nullable=True)
|
|
teachers: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
feishu_doc_url: Mapped[str | None] = mapped_column(String(1024), nullable=True)
|
|
feishu_record_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
|
source_file_name: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
source_text: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
process_status: Mapped[ProcessStatus] = mapped_column(
|
|
enum_values(ProcessStatus), default=ProcessStatus.unprocessed, nullable=False, index=True
|
|
)
|
|
qa_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
pending_review_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
approved_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
standard_qa_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
failed_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
raw_items: Mapped[list["RawQAItem"]] = relationship(back_populates="session", cascade="all, delete-orphan")
|
|
standard_items: Mapped[list["StandardQAItem"]] = relationship(back_populates="session")
|
|
|
|
|
|
class RawQAItem(Base, TimestampMixin):
|
|
__tablename__ = "raw_qa_items"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
session_id: Mapped[int] = mapped_column(ForeignKey("feishu_sessions.id"), index=True, nullable=False)
|
|
qa_code: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
|
raw_question: Mapped[str] = mapped_column(Text, nullable=False)
|
|
normalized_question: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
raw_answer: Mapped[str] = mapped_column(Text, nullable=False)
|
|
normalized_answer: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
suggested_standard_question: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
suggested_standard_answer: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
answer_person: Mapped[str | None] = mapped_column(String(120), nullable=True)
|
|
primary_topic: Mapped[PrimaryTopic] = mapped_column(enum_values(PrimaryTopic), default=PrimaryTopic.other, nullable=False)
|
|
problem_tags: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
course_stage: Mapped[CourseStage] = mapped_column(enum_values(CourseStage), default=CourseStage.unknown, nullable=False)
|
|
audience_tags: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
emotion_intensity: Mapped[str | None] = mapped_column(String(32), nullable=True)
|
|
risk_level: Mapped[RiskLevel] = mapped_column(enum_values(RiskLevel), default=RiskLevel.medium, nullable=False, index=True)
|
|
risk_types: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
risk_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
need_desensitization: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
|
desensitization_status: Mapped[DesensitizationStatus] = mapped_column(
|
|
enum_values(DesensitizationStatus), default=DesensitizationStatus.unknown, nullable=False
|
|
)
|
|
review_status: Mapped[ReviewStatus] = mapped_column(
|
|
enum_values(ReviewStatus), default=ReviewStatus.pending, nullable=False, index=True
|
|
)
|
|
reviewer_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
review_notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
suggested_to_standard_qa: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
|
|
source_timestamp: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
|
|
session: Mapped[FeishuSession] = relationship(back_populates="raw_items")
|
|
reviewer: Mapped[User | None] = relationship()
|
|
standard_item: Mapped["StandardQAItem | None"] = relationship(back_populates="source_raw_qa", uselist=False)
|
|
|
|
|
|
class StandardQAItem(Base, TimestampMixin):
|
|
__tablename__ = "standard_qa_items"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
standard_code: Mapped[str] = mapped_column(String(64), unique=True, index=True, nullable=False)
|
|
source_raw_qa_id: Mapped[int] = mapped_column(ForeignKey("raw_qa_items.id"), index=True, nullable=False)
|
|
session_id: Mapped[int] = mapped_column(ForeignKey("feishu_sessions.id"), index=True, nullable=False)
|
|
standard_question: Mapped[str] = mapped_column(Text, nullable=False)
|
|
standard_answer: Mapped[str] = mapped_column(Text, nullable=False)
|
|
similar_questions: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
primary_topic: Mapped[PrimaryTopic] = mapped_column(enum_values(PrimaryTopic), default=PrimaryTopic.other, nullable=False)
|
|
problem_tags: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
course_stage: Mapped[CourseStage] = mapped_column(enum_values(CourseStage), default=CourseStage.unknown, nullable=False)
|
|
audience_tags: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
answer_boundary: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
forbidden_expressions: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False)
|
|
risk_level: Mapped[RiskLevel] = mapped_column(enum_values(RiskLevel), default=RiskLevel.medium, nullable=False, index=True)
|
|
audit_status: Mapped[AuditStatus] = mapped_column(enum_values(AuditStatus), default=AuditStatus.approved, nullable=False)
|
|
call_status: Mapped[CallStatus] = mapped_column(enum_values(CallStatus), default=CallStatus.not_callable, nullable=False)
|
|
last_reviewer_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
disabled_reason: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
source_raw_qa: Mapped[RawQAItem] = relationship(back_populates="standard_item")
|
|
session: Mapped[FeishuSession] = relationship(back_populates="standard_items")
|
|
last_reviewer: Mapped[User | None] = relationship()
|
|
|
|
|
|
class TaskRun(Base):
|
|
__tablename__ = "task_runs"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
task_type: Mapped[TaskType] = mapped_column(enum_values(TaskType), nullable=False, index=True)
|
|
task_status: Mapped[TaskStatus] = mapped_column(enum_values(TaskStatus), default=TaskStatus.pending, nullable=False, index=True)
|
|
started_at: Mapped[DateTimeType | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
ended_at: Mapped[DateTimeType | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
sessions_scanned: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
sessions_processed: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
qa_created: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
qa_failed: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
retry_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
|
|
created_at: Mapped[DateTimeType] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
|
|
class AuditLog(Base):
|
|
__tablename__ = "audit_logs"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id"), nullable=True)
|
|
target_type: Mapped[TargetType] = mapped_column(enum_values(TargetType), nullable=False, index=True)
|
|
target_id: Mapped[int] = mapped_column(Integer, index=True, nullable=False)
|
|
action: Mapped[AuditAction] = mapped_column(enum_values(AuditAction), nullable=False)
|
|
before_status: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
after_status: Mapped[str | None] = mapped_column(String(64), nullable=True)
|
|
comment: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
created_at: Mapped[DateTimeType] = mapped_column(DateTime(timezone=True), server_default=func.now(), nullable=False)
|
|
|
|
user: Mapped[User | None] = relationship()
|
|
|
|
|
|
class SystemSetting(Base, TimestampMixin):
|
|
__tablename__ = "system_settings"
|
|
|
|
id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True)
|
|
key: Mapped[str] = mapped_column(String(128), unique=True, index=True, nullable=False)
|
|
value: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|