xxy aa98ea2623 @
Initial commit

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@
2026-06-05 18:45:29 +08:00

504 lines
26 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
database/models.py
SQLAlchemy ORM 模型,与 db.md / init.sql 对应。
"""
from datetime import datetime
from typing import Optional
from sqlalchemy import Boolean, DateTime, Float, ForeignKey, Integer, JSON, String, Text, UniqueConstraint
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
class Base(DeclarativeBase):
pass
class Project(Base):
"""项目表(统一:知识库 + 撰写)"""
__tablename__ = "projects"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
uuid: Mapped[str] = mapped_column(
String(32),
unique=True,
nullable=False,
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
doc_count: Mapped[int] = mapped_column(Integer, default=0)
eval_reports_count: Mapped[int] = mapped_column(Integer, default=0)
total_size: Mapped[str] = mapped_column(String(32), default="0 B")
tags: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
status: Mapped[str] = mapped_column(String(16), default="active")
color: Mapped[str] = mapped_column(String(16), default="#3b82f6")
sync_suppressed_table_names: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
kb_documents: Mapped[list["KbDocument"]] = relationship(
"KbDocument", back_populates="project", cascade="all, delete-orphan"
)
kb_directories: Mapped[list["KbDirectory"]] = relationship(
"KbDirectory", back_populates="project", cascade="all, delete-orphan"
)
write_documents: Mapped[list["WriteDocumentModel"]] = relationship(
"WriteDocumentModel", back_populates="project", cascade="all, delete-orphan"
)
class WriteDocumentModel(Base):
"""撰写文档表后评价报告。project_id 关联 projects.uuid"""
__tablename__ = "write_documents"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
title: Mapped[str] = mapped_column(String(255), nullable=False)
content: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
word_count: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
status: Mapped[str] = mapped_column(String(16), default="draft")
sort_order: Mapped[int] = mapped_column(Integer, default=0)
project: Mapped["Project"] = relationship("Project", back_populates="write_documents")
doc_versions: Mapped[list["DocumentVersion"]] = relationship(
"DocumentVersion", back_populates="document", cascade="all, delete-orphan"
)
class DocumentVersion(Base):
"""撰写文档版本表(对应 doc_versions"""
__tablename__ = "doc_versions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
document_id: Mapped[str] = mapped_column(
ForeignKey("write_documents.id", ondelete="CASCADE"), nullable=False
)
version: Mapped[str] = mapped_column(String(32), nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
citation_payload: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
saved_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
author: Mapped[str] = mapped_column(String(64), nullable=False)
note: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
document: Mapped["WriteDocumentModel"] = relationship("WriteDocumentModel", back_populates="doc_versions")
class KbDocument(Base):
"""知识库文档表。project_id 关联 projects.uuid。status: 0=失败 2=排队中 3=处理中 4=可用"""
__tablename__ = "kb_documents"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
directory_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_directories.id", ondelete="SET NULL"), nullable=True
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
upload_filename: Mapped[Optional[str]] = mapped_column(
String(255), nullable=True
) # 上传/解压时的原始文件名(含扩展名),与智能展示名 name 区分
size: Mapped[str] = mapped_column(String(32), nullable=False)
file_path: Mapped[Optional[str]] = mapped_column(String(512), nullable=True) # 仅目录路径,不含文件名
storage_rel_path: Mapped[Optional[str]] = mapped_column(
String(512), nullable=True
) # 项目内完整相对路径(含文件名),用于精确定位磁盘文件
word_count: Mapped[int] = mapped_column(Integer, default=0)
uploaded_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
status: Mapped[int] = mapped_column(Integer, default=2) # 0=失败 2=排队中 3=处理中 4=可用
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
category: Mapped[Optional[str]] = mapped_column(String(32), nullable=True, default=None)
project: Mapped["Project"] = relationship("Project", back_populates="kb_documents")
directory: Mapped[Optional["KbDirectory"]] = relationship("KbDirectory", back_populates="documents")
class KbDirectory(Base):
"""知识库目录表。project_id 关联 projects.uuidparent_id 形成目录树。"""
__tablename__ = "kb_directories"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
parent_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_directories.id", ondelete="CASCADE"), nullable=True
)
name: Mapped[str] = mapped_column(String(255), nullable=False)
full_path: Mapped[str] = mapped_column(String(1024), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
project: Mapped["Project"] = relationship("Project", back_populates="kb_directories")
documents: Mapped[list["KbDocument"]] = relationship("KbDocument", back_populates="directory")
class Task(Base):
"""独立后台任务表pdf2md 转换和 element-agent 要素抽取。"""
__tablename__ = "tasks"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project: Mapped[str] = mapped_column(String(64), nullable=False)
task_type: Mapped[int] = mapped_column(Integer, nullable=False)
file_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
file_path: Mapped[Optional[str]] = mapped_column(String(1024), nullable=True)
status: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
payload_json: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
result_path: Mapped[Optional[str]] = mapped_column(String(1024), nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
add_time: Mapped[datetime] = mapped_column(DateTime, nullable=False)
finish_time: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
class ElementTable(Base):
__tablename__ = "element_tables"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
table_type: Mapped[str] = mapped_column(String(32), nullable=False) # global/time
table_name: Mapped[str] = mapped_column(String(255), nullable=False)
year: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
is_time_dimension: Mapped[bool] = mapped_column(Boolean, default=False)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
# JSON 数组字符串row_key 列表sync 模版时跳过为这些行补格子,避免用户删行后一同步又出现
sync_suppressed_row_keys: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# JSON 数组:界面行键展示顺序(含用户加行)
custom_row_order: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ElementCell(Base):
__tablename__ = "element_cells"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
table_id: Mapped[str] = mapped_column(ForeignKey("element_tables.id", ondelete="CASCADE"), nullable=False)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
row_key: Mapped[str] = mapped_column(String(255), nullable=False)
col_key: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
year: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
source_document_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_documents.id", ondelete="SET NULL"), nullable=True
)
source_line_no: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_line_end: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_quote: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
extraction_batch_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
extraction_model: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
source_type: Mapped[Optional[str]] = mapped_column(String(16), nullable=True) # extract | manual
conflict_status: Mapped[str] = mapped_column(String(16), default="none")
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ExtractionResult(Base):
__tablename__ = "extraction_results"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
document_id: Mapped[str] = mapped_column(ForeignKey("kb_documents.id", ondelete="CASCADE"), nullable=False)
batch_id: Mapped[str] = mapped_column(String(64), nullable=False)
result_type: Mapped[str] = mapped_column(String(16), nullable=False) # table/element
table_type: Mapped[Optional[str]] = mapped_column(String(32), nullable=True)
table_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
year: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
item_key: Mapped[str] = mapped_column(String(255), nullable=False)
item_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
source_line_no: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_line_end: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
raw_payload: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
extracted_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) # 抽取业务时间(旧库迁移前可为空)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ElementExtractionResult(Base):
"""
要素抽取结果明细表(面向“细则章节/小节提示词 -> 项目材料”抽取)。
字段对齐(用户侧语义):
- 表类型 -> table_type
- 年份 -> year
- 表名称 -> table_name
- 时间 -> extracted_at
- 键 -> item_key
- 值 -> item_value
- 来源文档ID -> source_document_id
- 来源行数 -> source_line_no / source_line_end
"""
__tablename__ = "element_extraction_results"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
table_type: Mapped[str] = mapped_column(String(32), nullable=False)
year: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
table_name: Mapped[str] = mapped_column(String(255), nullable=False)
extracted_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
item_key: Mapped[str] = mapped_column(String(255), nullable=False)
item_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
source_document_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_documents.id", ondelete="SET NULL"), nullable=True
)
source_line_no: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_line_end: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ElementConflict(Base):
__tablename__ = "element_conflicts"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
table_id: Mapped[Optional[str]] = mapped_column(ForeignKey("element_tables.id", ondelete="SET NULL"), nullable=True)
cell_id: Mapped[Optional[str]] = mapped_column(ForeignKey("element_cells.id", ondelete="SET NULL"), nullable=True)
item_key: Mapped[str] = mapped_column(String(255), nullable=False)
old_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
new_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
selected_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
source_document_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_documents.id", ondelete="SET NULL"), nullable=True
)
source_line_no: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
status: Mapped[str] = mapped_column(String(16), default="pending")
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class DocumentMarkdown(Base):
__tablename__ = "document_markdowns"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
document_id: Mapped[str] = mapped_column(ForeignKey("kb_documents.id", ondelete="CASCADE"), nullable=False)
extracted_filename: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
markdown_content: Mapped[str] = mapped_column(Text, nullable=False)
content_hash: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class DocumentChunk(Base):
__tablename__ = "document_chunks"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
document_id: Mapped[str] = mapped_column(ForeignKey("kb_documents.id", ondelete="CASCADE"), nullable=False)
markdown_id: Mapped[Optional[str]] = mapped_column(ForeignKey("document_markdowns.id", ondelete="CASCADE"), nullable=True)
heading: Mapped[Optional[str]] = mapped_column(String(512), nullable=True)
chunk_text: Mapped[str] = mapped_column(Text, nullable=False)
chunk_index: Mapped[int] = mapped_column(Integer, default=0)
source_line_start: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_line_end: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
vector_id: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ReportTemplate(Base):
__tablename__ = "report_templates"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
is_default: Mapped[bool] = mapped_column(Boolean, default=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ReportTemplateSection(Base):
__tablename__ = "report_template_sections"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
template_id: Mapped[str] = mapped_column(ForeignKey("report_templates.id", ondelete="CASCADE"), nullable=False)
section_key: Mapped[str] = mapped_column(String(64), nullable=False)
section_title: Mapped[str] = mapped_column(String(255), nullable=False)
section_prompt: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
section_output_contract: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
section_order: Mapped[int] = mapped_column(Integer, default=0)
examples: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ReportGenerationJob(Base):
__tablename__ = "report_generation_jobs"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
template_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("report_templates.id", ondelete="SET NULL"), nullable=True
)
status: Mapped[str] = mapped_column(String(16), default="pending") # pending/running/completed/failed
progress: Mapped[int] = mapped_column(Integer, default=0)
current_section_key: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
requested_by: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
options: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
snapshot: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
class ReportGenerationChapter(Base):
__tablename__ = "report_generation_chapters"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
job_id: Mapped[str] = mapped_column(
ForeignKey("report_generation_jobs.id", ondelete="CASCADE"), nullable=False
)
section_key: Mapped[str] = mapped_column(String(64), nullable=False)
section_title: Mapped[str] = mapped_column(String(255), nullable=False)
section_order: Mapped[int] = mapped_column(Integer, default=0)
status: Mapped[str] = mapped_column(String(16), default="pending") # pending/running/completed/failed
content: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
prompt_text: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
evidence_payload: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
validation_payload: Mapped[Optional[dict]] = mapped_column(JSON, nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
completed_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
class ReportSectionReference(Base):
"""章节参考范文(独立于模板配置,用于报告生成时拼入 prompt"""
__tablename__ = "report_section_references"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
template_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("report_templates.id", ondelete="CASCADE"), nullable=True
)
source_file: Mapped[str] = mapped_column(String(255), nullable=False)
section_key: Mapped[str] = mapped_column(String(64), nullable=False)
section_title: Mapped[str] = mapped_column(String(255), nullable=False)
section_order: Mapped[int] = mapped_column(Integer, default=0)
content: Mapped[str] = mapped_column(Text, nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class Department(Base):
__tablename__ = "department"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(255), nullable=False)
parent_id: Mapped[Optional[str]] = mapped_column(ForeignKey("departments.id", ondelete="SET NULL"), nullable=True)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
username: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
password_hash: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
department_id: Mapped[Optional[str]] = mapped_column(ForeignKey("departments.id", ondelete="SET NULL"), nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class Role(Base):
__tablename__ = "roles"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
updated_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class Permission(Base):
__tablename__ = "permissions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
perm_key: Mapped[str] = mapped_column(String(128), nullable=False, unique=True)
perm_type: Mapped[str] = mapped_column(String(32), nullable=False) # menu/project
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class RolePermission(Base):
__tablename__ = "role_permissions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
role_id: Mapped[str] = mapped_column(ForeignKey("roles.id", ondelete="CASCADE"), nullable=False)
permission_id: Mapped[str] = mapped_column(ForeignKey("permissions.id", ondelete="CASCADE"), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class UserRole(Base):
__tablename__ = "user_roles"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
role_id: Mapped[str] = mapped_column(ForeignKey("roles.id", ondelete="CASCADE"), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ProjectMember(Base):
__tablename__ = "project_members"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
user_id: Mapped[str] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"), nullable=False)
role: Mapped[str] = mapped_column(String(32), default="editor")
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class ProjectDepartment(Base):
"""项目可见部门:绑定后,仅这些部门下的用户可访问(另有管理员与 project_members 例外)。"""
__tablename__ = "project_departments"
__table_args__ = (UniqueConstraint("project_id", "department_id", name="uq_project_department"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
department_id: Mapped[str] = mapped_column(ForeignKey("departments.id", ondelete="CASCADE"), nullable=False)
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)
class FillRecord(Base):
"""回填记录:每次要素回填均留痕,支持证据追溯。"""
__tablename__ = "fill_records"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("projects.uuid", ondelete="CASCADE"), nullable=False)
cell_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("element_cells.id", ondelete="SET NULL"), nullable=True
)
table_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("element_tables.id", ondelete="SET NULL"), nullable=True
)
row_key: Mapped[str] = mapped_column(String(255), nullable=False)
col_key: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
year: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
filled_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
previous_value: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
source_document_id: Mapped[Optional[str]] = mapped_column(
ForeignKey("kb_documents.id", ondelete="SET NULL"), nullable=True
)
source_document_name: Mapped[Optional[str]] = mapped_column(String(255), nullable=True)
source_line_no: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_line_end: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
source_quote: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
confidence: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
extraction_batch_id: Mapped[Optional[str]] = mapped_column(String(64), nullable=True)
extraction_model: Mapped[Optional[str]] = mapped_column(String(128), nullable=True)
fill_type: Mapped[str] = mapped_column(String(16), nullable=False, default="auto")
created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)