deerflow-code/offline-backend-20260512/backend/packages/harness/deerflow/persistence/enterprise_research/model.py
2026-09-07 18:24:55 +08:00

78 lines
4.9 KiB
Python

"""ORM rows for the independent enterprise-research workbench."""
from __future__ import annotations
from datetime import UTC, datetime
from sqlalchemy import Index, String, Text, UniqueConstraint
from sqlalchemy.orm import Mapped, mapped_column
from deerflow.persistence.base import Base
from deerflow.persistence.types import BeijingDateTime, PortableJSON, PortableLongText
class EnterpriseResearchTaskRow(Base):
"""A user-owned research task plus its bounded WeKnora evidence snapshot."""
__tablename__ = "enterprise_research_tasks"
__table_args__ = (Index("ix_enterprise_research_tasks_owner_updated", "user_id", "updated_at"),)
id: Mapped[str] = mapped_column(String(64), primary_key=True)
user_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
subject: Mapped[str] = mapped_column(String(256), nullable=False)
focus: Mapped[str] = mapped_column(Text, nullable=False, default="", server_default="")
template_id: Mapped[str] = mapped_column(String(64), nullable=False)
knowledge_base_ids: Mapped[list[str]] = mapped_column(PortableJSON(), nullable=False, default=list)
queries: Mapped[list[str]] = mapped_column(PortableJSON(), nullable=False, default=list)
# Nullable keeps upgrades safe: the generic schema reconciler can add this
# field to a phase-one table without an unsafe MySQL TEXT default.
selected_source_ids: Mapped[list[str] | None] = mapped_column(PortableJSON(), nullable=True, default=list)
# A WeKnora result can contain a long document chunk. MySQL TEXT is only
# 64 KiB, so evidence uses LONGTEXT through PortableLongText.
sources_json: Mapped[str] = mapped_column(PortableLongText(), nullable=False, default="[]", server_default="[]")
plan_markdown: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
report_markdown: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
report_summary: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
report_model_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
active_report_job_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
status: Mapped[str] = mapped_column(String(32), nullable=False, default="draft", server_default="draft")
warning: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
created_at: Mapped[datetime] = mapped_column(BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC))
updated_at: Mapped[datetime] = mapped_column(
BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)
)
class EnterpriseResearchReportJobRow(Base):
"""A report job whose plan/evidence are frozen when the user starts it."""
__tablename__ = "enterprise_research_report_jobs"
__table_args__ = (
Index("ix_enterprise_research_report_jobs_task_owner_updated", "task_id", "user_id", "updated_at"),
UniqueConstraint("active_dedupe_key", name="uq_enterprise_research_report_jobs_active"),
)
id: Mapped[str] = mapped_column(String(64), primary_key=True)
task_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
user_id: Mapped[str] = mapped_column(String(128), nullable=False, index=True)
status: Mapped[str] = mapped_column(String(32), nullable=False, default="queued", server_default="queued")
phase: Mapped[str] = mapped_column(String(32), nullable=False, default="queued", server_default="queued")
model_name: Mapped[str | None] = mapped_column(String(256), nullable=True)
# Present only for queued/running rows. NULL terminal rows keep unlimited
# history while this key rejects cross-process duplicate starts.
active_dedupe_key: Mapped[str | None] = mapped_column(String(256), nullable=True)
action: Mapped[str | None] = mapped_column(String(32), nullable=True, default="initial")
instruction: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
style: Mapped[str | None] = mapped_column(String(32), nullable=True)
target_length: Mapped[int | None] = mapped_column(nullable=True)
plan_snapshot: Mapped[str] = mapped_column(PortableLongText(), nullable=False)
sources_json: Mapped[str] = mapped_column(PortableLongText(), nullable=False)
original_report: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
report_markdown: Mapped[str] = mapped_column(PortableLongText(), nullable=False, default="", server_default="")
report_summary: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
error_message: Mapped[str | None] = mapped_column(PortableLongText(), nullable=True)
created_at: Mapped[datetime] = mapped_column(BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC))
updated_at: Mapped[datetime] = mapped_column(
BeijingDateTime(), nullable=False, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)
)