deepdata: core product code (backend, frontend, runtime, deepflow module)

This commit is contained in:
figmar
2026-08-30 19:23:38 +08:00
commit bd3db30be9
1405 changed files with 271727 additions and 0 deletions
@@ -0,0 +1,37 @@
from contextvars import ContextVar
from typing import List, Optional
_current_user: ContextVar[Optional[str]] = ContextVar("_current_user", default=None)
SYSTEM_USER = "system"
class DataScopeHandle:
"""
Hold current user info in a ContextVar and provide helpers for SQLAlchemy filters.
"""
@staticmethod
def set_user_info(user: Optional[str]) -> None:
if user is None or user == "":
# set explicit None
_current_user.set(None)
else:
_current_user.set(user)
@staticmethod
def remove_user_info() -> None:
_current_user.set(None)
@staticmethod
def get_user_info() -> Optional[str]:
return _current_user.get()
@staticmethod
def allowed_users() -> List[str]:
"""
Return list of allowed creators: current user + system.
"""
user = DataScopeHandle.get_user_info()
if not user:
return []
return [user, SYSTEM_USER]
@@ -0,0 +1,42 @@
from .dataset_management import (
Dataset,
DatasetTag,
DatasetFiles,
DatasetStatistics,
Tag,
)
from .user_management import User
from .annotation_management import AnnotationTemplate, LabelingProject
from .data_evaluation import EvaluationTask, EvaluationItem
from .operator import Operator, Category, CategoryRelation, OperatorRelease
from .chunk_upload import ChunkUploadPreRequest
from .knowledge_gen import KnowledgeBase, RagFile
from .sys_param import SysParam
__all__ = [
"Dataset",
"DatasetTag",
"DatasetFiles",
"DatasetStatistics",
"Tag",
"User",
"AnnotationTemplate",
"LabelingProject",
"EvaluationTask",
"EvaluationItem",
"Operator",
"Category",
"CategoryRelation",
"OperatorRelease",
"ChunkUploadPreRequest",
"KnowledgeBase",
"RagFile",
"SysParam",
]
@@ -0,0 +1,85 @@
"""Tables of Annotation Management Module"""
import uuid
from sqlalchemy import Column, String, Boolean, TIMESTAMP, Text, Integer, JSON, ForeignKey
from sqlalchemy.sql import func
from app.db.models.base_entity import BaseEntity
class AnnotationTemplate(BaseEntity):
"""标注配置模板模型"""
__tablename__ = "t_dm_annotation_templates"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(100), nullable=False, comment="模板名称")
description = Column(String(500), nullable=True, comment="模板描述")
data_type = Column(String(50), nullable=False, comment="数据类型: image/text/audio/video/timeseries")
labeling_type = Column(String(50), nullable=False, comment="标注类型: classification/detection/segmentation/ner/relation/etc")
configuration = Column(JSON, nullable=False, comment="标注配置(包含labels定义等)")
style = Column(String(32), nullable=False, comment="样式配置: horizontal/vertical")
category = Column(String(50), default='custom', comment="模板分类: medical/general/custom/system")
built_in = Column(Boolean, default=False, comment="是否系统内置模板")
version = Column(String(20), default='1.0', comment="模板版本")
deleted_at = Column(TIMESTAMP, nullable=True, comment="删除时间(软删除)")
def __repr__(self):
return f"<AnnotationTemplate(id={self.id}, name={self.name}, data_type={self.data_type})>"
@property
def is_deleted(self) -> bool:
"""检查是否已被软删除"""
return self.deleted_at is not None
class LabelingProject(BaseEntity):
"""标注项目模型"""
__tablename__ = "t_dm_labeling_projects"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
dataset_id = Column(String(36), nullable=False, comment="数据集ID")
name = Column(String(100), nullable=False, comment="项目名称")
labeling_project_id = Column(String(8), nullable=False, comment="Label Studio项目ID")
template_id = Column(String(36), ForeignKey('t_dm_annotation_templates.id', ondelete='SET NULL'), nullable=True, comment="使用的模板ID")
configuration = Column(JSON, nullable=True, comment="项目配置(可能包含对模板的自定义修改)")
progress = Column(JSON, nullable=True, comment="项目进度信息")
deleted_at = Column(TIMESTAMP, nullable=True, comment="删除时间(软删除)")
def __repr__(self):
return f"<LabelingProject(id={self.id}, name={self.name}, dataset_id={self.dataset_id})>"
@property
def is_deleted(self) -> bool:
"""检查是否已被软删除"""
return self.deleted_at is not None
class AutoAnnotationTask(BaseEntity):
"""自动标注任务模型,对应表 t_dm_auto_annotation_tasks"""
__tablename__ = "t_dm_auto_annotation_tasks"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(255), nullable=False, comment="任务名称")
dataset_id = Column(String(36), nullable=False, comment="数据集ID")
dataset_name = Column(String(255), nullable=True, comment="数据集名称(冗余字段,方便查询)")
config = Column(JSON, nullable=False, comment="任务配置(模型规模、置信度等)")
file_ids = Column(JSON, nullable=True, comment="要处理的文件ID列表,为空则处理数据集所有图像")
status = Column(String(50), nullable=False, default="pending", comment="任务状态: pending/running/completed/failed")
progress = Column(Integer, default=0, comment="任务进度 0-100")
total_images = Column(Integer, default=0, comment="总图片数")
processed_images = Column(Integer, default=0, comment="已处理图片数")
detected_objects = Column(Integer, default=0, comment="检测到的对象总数")
output_path = Column(String(500), nullable=True, comment="输出路径")
error_message = Column(Text, nullable=True, comment="错误信息")
completed_at = Column(TIMESTAMP, nullable=True, comment="完成时间")
deleted_at = Column(TIMESTAMP, nullable=True, comment="删除时间(软删除)")
def __repr__(self) -> str: # pragma: no cover - repr 简单返回
return f"<AutoAnnotationTask(id={self.id}, name={self.name}, status={self.status})>"
@property
def is_deleted(self) -> bool:
"""检查是否已被软删除"""
return self.deleted_at is not None
@@ -0,0 +1,51 @@
from sqlalchemy import Column, String, TIMESTAMP, Text
from sqlalchemy.orm import declarative_base
from sqlalchemy.sql import func
Base = declarative_base()
class BaseEntity(Base):
"""
Common base entity with audit fields.
Subclasses may set `__ignore_data_scope__ = True` to opt-out of data-scope filtering.
"""
__abstract__ = True
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(),
comment="更新时间")
created_by = Column(String(255), nullable=True, comment="创建者")
updated_by = Column(String(255), nullable=True, comment="更新者")
# default: do enforce data scope unless subclass sets this to True
__ignore_data_scope__ = False
class LineageNode(Base):
"""数据血缘:节点表(实体对象)"""
__tablename__ = "t_lineage_node"
id = Column(String(36), primary_key=True, comment="节点ID")
graph_id = Column(String(36), nullable=True, comment="图ID")
node_type = Column(String(64), nullable=False, comment="节点类型")
name = Column(String(256), nullable=False, comment="节点名称")
description = Column(Text, nullable=True, comment="节点描述")
node_metadata = Column(Text, nullable=True, comment="节点扩展信息(JSON)")
class LineageEdge(Base):
"""数据血缘:边表(处理流程)"""
__tablename__ = "t_lineage_edge"
id = Column(String(36), primary_key=True, comment="边ID")
graph_id = Column(String(36), nullable=True, comment="图ID")
process_id = Column(String(36), nullable=True, comment="处理流程ID")
edge_type = Column(String(64), nullable=False, comment="边类型")
name = Column(String(256), nullable=True, comment="边名称")
description = Column(Text, nullable=True, comment="边描述")
edge_metadata = Column(Text, nullable=True, comment="边扩展信息(JSON)")
from_node_id = Column(String(36), nullable=False, comment="源节点ID")
to_node_id = Column(String(36), nullable=False, comment="目标节点ID")
@@ -0,0 +1,38 @@
"""
Chunk Upload Database Model
分片上传数据库模型
"""
from sqlalchemy import Column, String, Integer, DateTime
from sqlalchemy.sql import func
from app.db.models.base_entity import Base
class ChunkUploadPreRequest(Base):
"""分片上传预请求"""
__tablename__ = "t_chunk_upload_request"
id = Column(String(36), primary_key=True, comment="请求ID")
total_file_num = Column(Integer, nullable=False, comment="总文件数")
uploaded_file_num = Column(Integer, nullable=True, comment="已上传文件数")
upload_path = Column(String(512), nullable=False, comment="文件路径")
timeout = Column(DateTime, nullable=False, comment="上传请求超时时间")
service_id = Column(String(64), nullable=True, comment="上传请求所属服务ID")
check_info = Column(String(512), nullable=True, comment="业务信息")
def increment_uploaded_file_num(self):
"""增加已上传文件数"""
if self.uploaded_file_num is None:
self.uploaded_file_num = 1
else:
self.uploaded_file_num += 1
def is_upload_complete(self) -> bool:
"""检查是否已完成上传"""
return (self.uploaded_file_num is not None and
self.uploaded_file_num == self.total_file_num)
def is_request_timeout(self) -> bool:
"""检查是否已超时"""
from datetime import datetime
return self.timeout is not None and datetime.utcnow() > self.timeout
@@ -0,0 +1,69 @@
from sqlalchemy import Column, String, BigInteger, Integer, TIMESTAMP, Index
from app.db.models.base_entity import BaseEntity, Base
class CleaningTask(BaseEntity):
"""Data cleaning task entity"""
__tablename__ = "t_clean_task"
id = Column(String(36), primary_key=True, comment="Task ID")
name = Column(String(255), nullable=False, comment="Task name")
description = Column(String(1024), nullable=True, comment="Task description")
status = Column(String(50), nullable=False, default="PENDING", comment="Task status: PENDING, RUNNING, COMPLETED, STOPPED, FAILED")
src_dataset_id = Column(String(36), nullable=False, comment="Source dataset ID")
src_dataset_name = Column(String(255), nullable=False, comment="Source dataset name")
dest_dataset_id = Column(String(36), nullable=True, comment="Destination dataset ID")
dest_dataset_name = Column(String(255), nullable=True, comment="Destination dataset name")
before_size = Column(BigInteger, nullable=True, comment="Data size before cleaning")
after_size = Column(BigInteger, nullable=True, comment="Data size after cleaning")
file_count = Column(Integer, nullable=True, comment="Total file count")
retry_count = Column(Integer, default=0, nullable=False, comment="Retry count")
started_at = Column(TIMESTAMP, nullable=True, comment="Task start time")
finished_at = Column(TIMESTAMP, nullable=True, comment="Task finish time")
__table_args__ = (
Index("ix_clean_task_status", "status"),
Index("ix_clean_task_created_at", "created_at"),
)
class CleaningTemplate(BaseEntity):
"""Data cleaning template entity"""
__tablename__ = "t_clean_template"
id = Column(String(36), primary_key=True, comment="Template ID")
name = Column(String(255), nullable=False, comment="Template name")
description = Column(String(1024), nullable=True, comment="Template description")
class CleaningResult(Base):
"""Data cleaning result entity"""
__tablename__ = "t_clean_result"
instance_id = Column(String(36), primary_key=True, comment="Instance ID (task or template ID)")
src_file_id = Column(String(36), primary_key=True, comment="Source file ID")
dest_file_id = Column(String(36), nullable=True, comment="Destination file ID")
src_name = Column(String(512), nullable=True, comment="Source file name")
dest_name = Column(String(512), nullable=True, comment="Destination file name")
src_type = Column(String(50), nullable=True, comment="Source file type")
dest_type = Column(String(50), nullable=True, comment="Destination file type")
src_size = Column(BigInteger, nullable=True, comment="Source file size")
dest_size = Column(BigInteger, nullable=True, comment="Destination file size")
status = Column(String(50), nullable=True, comment="Cleaning status: COMPLETED, FAILED, etc.")
result = Column(String(1024), nullable=True, comment="Cleaning result message")
__table_args__ = (
Index("ix_clean_result_instance_id", "instance_id"),
Index("ix_clean_result_instance_status", "instance_id", "status"),
)
class OperatorInstance(Base):
"""Operator instance in task or template"""
__tablename__ = "t_operator_instance"
instance_id = Column(String(36), primary_key=True, comment="Instance ID (task or template ID)")
operator_id = Column(String(36), primary_key=True, comment="Operator ID")
op_index = Column(Integer, nullable=False, comment="Operator execution order")
settings_override = Column(String(4096), nullable=True, comment="Operator settings override (JSON)")
@@ -0,0 +1,56 @@
import uuid
from sqlalchemy import Column, String, Text, TIMESTAMP, Integer, BigInteger, Numeric, JSON, Boolean
from sqlalchemy.sql import func
from app.db.models.base_entity import BaseEntity
class CollectionTemplate(BaseEntity):
"""归集模板表(UUID 主键) -> t_dc_collection_templates"""
__tablename__ = "t_dc_collection_templates"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="模板ID(UUID)")
name = Column(String(255), nullable=False, comment="模板名称")
description = Column(Text, nullable=True, comment="模板描述")
source_type = Column(String(64), nullable=False, comment="源数据源类型")
source_name = Column(String(64), nullable=False, comment="源数据源名称")
target_type = Column(String(64), nullable=False, comment="目标数据源类型")
target_name = Column(String(64), nullable=False, comment="目标数据源名称")
template_content = Column(JSON, nullable=False, comment="模板内容")
built_in = Column(Boolean, default=False, comment="是否系统内置模板")
class CollectionTask(BaseEntity):
"""归集任务表(UUID 主键) -> t_dc_collection_tasks"""
__tablename__ = "t_dc_collection_tasks"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(255), nullable=False, comment="任务名称")
description = Column(Text, nullable=True, comment="任务描述")
sync_mode = Column(String(20), nullable=False, server_default="ONCE", comment="同步模式:ONCE/SCHEDULED")
template_id = Column(String(36), nullable=False, comment="归集模板ID")
template_name = Column(String(255), nullable=False, comment="归集模板名称")
target_path = Column(String(1000), nullable=True, server_default="", comment="目标存储路径")
config = Column(JSON, nullable=False, comment="归集配置(DataX配置),包含源端和目标端配置信息")
schedule_expression = Column(String(255), nullable=True, comment="Cron调度表达式")
status = Column(String(20), nullable=True, server_default="DRAFT", comment="任务状态:DRAFT/READY/RUNNING/SUCCESS/FAILED/STOPPED")
retry_count = Column(Integer, nullable=True, server_default="3", comment="重试次数")
timeout_seconds = Column(Integer, nullable=True, server_default="3600", comment="超时时间(秒)")
last_execution_id = Column(String(36), nullable=True, comment="最后执行ID(UUID)")
class TaskExecution(BaseEntity):
"""任务执行记录表(UUID 主键) -> t_dc_task_executions"""
__tablename__ = "t_dc_task_executions"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="执行记录ID(UUID)")
task_id = Column(String(36), nullable=False, comment="任务ID")
task_name = Column(String(255), nullable=False, comment="任务名称")
status = Column(String(20), nullable=True, server_default="RUNNING", comment="执行状态:RUNNING/SUCCESS/FAILED/STOPPED")
log_path = Column(String(1000), nullable=True, server_default="", comment="日志文件路径")
started_at = Column(TIMESTAMP, nullable=True, comment="开始时间")
completed_at = Column(TIMESTAMP, nullable=True, comment="完成时间")
duration_seconds = Column(Integer, nullable=True, server_default="0", comment="执行时长(秒)")
error_message = Column(Text, nullable=True, comment="错误信息")
@@ -0,0 +1,73 @@
"""
Tables for Data Evaluation module
Derived from scripts/db/data-evaluation-init.sql
- t_de_eval_task
- t_de_eval_item
"""
import uuid
from sqlalchemy import Column, String, Text, Float, TIMESTAMP, ForeignKey, Integer
from sqlalchemy.sql import func
from app.db.models.base_entity import BaseEntity
class EvaluationTask(BaseEntity):
"""评估任务表(UUID 主键) -> t_de_eval_task
Columns per data-evaluation-init.sql:
id, name, description, task_type, source_type, source_id, source_name,
status, eval_process, eval_promt, eval_config, created_at, updated_at,
created_by, updated_by
"""
__tablename__ = "t_de_eval_task"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(255), nullable=False, comment="评估任务名称")
description = Column(Text, nullable=True, comment="评估任务描述")
task_type = Column(String(50), nullable=False, comment="评估任务类型:QA")
source_type = Column(String(36), nullable=True, comment="待评估对象类型:DATASET/SYNTHESIS")
source_id = Column(String(36), nullable=True, comment="待评估对象ID")
source_name = Column(String(255), nullable=True, comment="待评估对象名称")
status = Column(String(50), server_default="PENDING", nullable=False, comment="状态:PENDING/RUNNING/COMPLETED/STOPPED/FAILED")
eval_method = Column(String(50), server_default="AUTO", nullable=False, comment="评估方式:AUTO/MANUAL")
eval_process = Column(Float, nullable=False, server_default="0", comment="评估进度")
eval_prompt = Column(Text, nullable=True, comment="评估提示词")
eval_config = Column(Text, nullable=True, comment="评估配置")
class EvaluationFile(BaseEntity):
"""评估条目表(UUID 主键) -> t_de_eval_file"""
__tablename__ = "t_de_eval_file"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
task_id = Column(String(36), ForeignKey('t_de_eval_task.id'), nullable=False, comment="评估任务ID")
file_id = Column(String(36), ForeignKey('t_dm_dataset_files.id'), nullable=True, comment="文件ID")
file_name = Column(String(255), nullable=False, comment="文件名")
error_message = Column(Text, nullable=True, comment="错误信息")
total_count = Column(Integer, nullable=False, default=0, comment="总数")
evaluated_count = Column(Integer, nullable=False, default=0, comment="已评估数")
class EvaluationItem(BaseEntity):
"""评估条目表(UUID 主键) -> t_de_eval_item
Columns per data-evaluation-init.sql:
id, task_id, item_id, eval_score, eval_result, status
"""
__tablename__ = "t_de_eval_item"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
task_id = Column(String(36), ForeignKey('t_de_eval_task.id'), nullable=False, comment="评估任务ID")
file_id = Column(String(36), ForeignKey('t_dm_dataset_files.id'), nullable=True, comment="文件ID")
item_id = Column(String(36), nullable=False, comment="评估条目ID")
eval_content = Column(Text, nullable=True, comment="评估内容")
eval_score = Column(Float, nullable=False, server_default="0", comment="评估分数")
eval_result = Column(Text, nullable=True, comment="评估结果")
status = Column(String(50), server_default="PENDING", nullable=False, comment="状态:PENDING/EVALUATED")
@@ -0,0 +1,177 @@
import uuid
from sqlalchemy import Column, String, Text, Integer, JSON, TIMESTAMP, func
from app.db.models.base_entity import Base, BaseEntity
from app.module.generation.schema.generation import CreateSynthesisTaskRequest
async def save_synthesis_task(db_session, synthesis_task: CreateSynthesisTaskRequest):
"""保存数据合成任务。
注意:当前 MySQL 表 `t_data_synth_instances` 结构中只包含 synth_type / synth_config 等字段,
没有 model_id、text_split_config、source_file_id、result_data_location 等列,因此这里只保存
与表结构一致的字段,其他信息由上层逻辑或其它表负责管理。
"""
gid = str(uuid.uuid4())
# 兼容旧请求结构:从请求对象中提取必要字段,
# - 合成类型:synthesis_type -> synth_type
# - 合成配置:text_split_config + synthesis_config 合并后写入 synth_config
synth_task_instance = DataSynthInstance(
id=gid,
name=synthesis_task.name,
description=synthesis_task.description,
status="pending",
synth_type=synthesis_task.synthesis_type.value,
progress=0,
synth_config=synthesis_task.synth_config.model_dump(),
total_files=len(synthesis_task.source_file_id or []),
processed_files=0,
total_chunks=0,
processed_chunks=0,
total_synth_data=0,
created_at=func.now(),
updated_at=func.now(),
created_by="system",
updated_by="system",
)
db_session.add(synth_task_instance)
await db_session.commit()
await db_session.refresh(synth_task_instance)
return synth_task_instance
class DataSynthInstance(BaseEntity):
"""数据合成任务表,对应表 t_data_synth_instances
create table if not exists t_data_synth_instances
(
id VARCHAR(36) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci PRIMARY KEY COMMENT 'UUID',
name VARCHAR(255) NOT NULL COMMENT '任务名称',
description TEXT COMMENT '任务描述',
status VARCHAR(20) COMMENT '任务状态',
synth_type VARCHAR(20) NOT NULL COMMENT '合成类型',
progress INT DEFAULT 0 COMMENT '任务进度(百分比)',
synth_config JSON NOT NULL COMMENT '合成配置',
total_files INT DEFAULT 0 COMMENT '总文件数',
processed_files INT DEFAULT 0 COMMENT '已处理文件数',
total_chunks INT DEFAULT 0 COMMENT '总文本块数',
processed_chunks INT DEFAULT 0 COMMENT '已处理文本块数',
total_synth_data INT DEFAULT 0 COMMENT '总合成数据量',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
created_by VARCHAR(255) COMMENT '创建者',
updated_by VARCHAR(255) COMMENT '更新者'
) COMMENT='数据合成任务表(UUID 主键)';
"""
__tablename__ = "t_data_synth_instances"
id = Column(String(36), primary_key=True, index=True, comment="UUID")
name = Column(String(255), nullable=False, comment="任务名称")
description = Column(Text, nullable=True, comment="任务描述")
status = Column(String(20), nullable=True, comment="任务状态")
# 与数据库字段保持一致:synth_type / synth_config
synth_type = Column(String(20), nullable=False, comment="合成类型")
progress = Column(Integer, nullable=False, default=0, comment="任务进度(百分比)")
synth_config = Column(JSON, nullable=False, comment="合成配置")
total_files = Column(Integer, nullable=False, default=0, comment="总文件数")
processed_files = Column(Integer, nullable=False, default=0, comment="已处理文件数")
total_chunks = Column(Integer, nullable=False, default=0, comment="总文本块数")
processed_chunks = Column(Integer, nullable=False, default=0, comment="已处理文本块数")
total_synth_data = Column(Integer, nullable=False, default=0, comment="总合成数据量")
class DataSynthesisFileInstance(BaseEntity):
"""数据合成文件任务表,对应表 t_data_synthesis_file_instances
create table if not exists t_data_synthesis_file_instances (
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
synthesis_instance_id VARCHAR(36) COMMENT '数据合成任务ID',
file_name VARCHAR(255) NOT NULL COMMENT '文件名',
source_file_id VARCHAR(255) NOT NULL COMMENT '原始文件ID',
target_file_location VARCHAR(1000) NOT NULL COMMENT '目标文件存储位置',
status VARCHAR(20) COMMENT '任务状态',
total_chunks INT DEFAULT 0 COMMENT '总文本块数',
processed_chunks INT DEFAULT 0 COMMENT '已处理文本块数',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
created_by VARCHAR(255) COMMENT '创建者',
updated_by VARCHAR(255) COMMENT '更新者'
) COMMENT='数据合成文件任务表(UUID 主键)';
"""
__tablename__ = "t_data_synthesis_file_instances"
id = Column(String(36), primary_key=True, index=True, comment="UUID")
synthesis_instance_id = Column(
String(36),
nullable=False,
comment="数据合成任务ID",
index=True,
)
file_name = Column(String(255), nullable=False, comment="文件名")
source_file_id = Column(String(255), nullable=False, comment="原始文件ID")
target_file_location = Column(String(1000), nullable=True, comment="目标文件存储位置")
status = Column(String(20), nullable=True, comment="任务状态")
total_chunks = Column(Integer, nullable=False, default=0, comment="总文本块数")
processed_chunks = Column(Integer, nullable=False, default=0, comment="已处理文本块数")
class DataSynthesisChunkInstance(Base):
"""数据合成分块任务表,对应表 t_data_synthesis_chunk_instances
create table if not exists t_data_synthesis_chunk_instances (
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
synthesis_file_instance_id VARCHAR(36) COMMENT '数据合成文件任务ID',
chunk_index INT COMMENT '分块索引',
chunk_content TEXT COMMENT '分块内容',
metadata JSON COMMENT '分块元数据'
) COMMENT='数据合成分块任务表(UUID 主键)';
"""
__tablename__ = "t_data_synthesis_chunk_instances"
id = Column(String(36), primary_key=True, index=True, comment="UUID")
synthesis_file_instance_id = Column(
String(36),
nullable=False,
comment="数据合成文件任务ID",
index=True,
)
chunk_index = Column(Integer, nullable=True, comment="分块索引")
chunk_content = Column(Text, nullable=True, comment="分块内容")
# SQLAlchemy Declarative 保留了属性名 'metadata',这里使用 chunk_metadata 作为属性名,
# 底层列名仍为 'metadata' 以保持与表结构兼容。
chunk_metadata = Column("metadata", JSON, nullable=True, comment="分块元数据")
class SynthesisData(Base):
"""数据合成结果表,对应表 t_synthesis_data
create table if not exists t_synthesis_data (
id VARCHAR(36) PRIMARY KEY COMMENT 'UUID',
data json COMMENT '合成的数据',
synthesis_file_instance_id VARCHAR(36) COMMENT '数据合成文件任务ID',
chunk_instance_id VARCHAR(36) COMMENT '分块任务ID'
) COMMENT='数据合成任务队列表(UUID 主键)';
"""
__tablename__ = "t_data_synthesis_data"
id = Column(String(36), primary_key=True, index=True, comment="UUID")
data = Column(JSON, nullable=True, comment="合成的数据")
synthesis_file_instance_id = Column(
String(36),
nullable=False,
comment="数据合成文件任务ID",
index=True,
)
chunk_instance_id = Column(
String(36),
nullable=False,
comment="分块任务ID",
index=True,
)
@@ -0,0 +1,111 @@
"""
Tables of Dataset Management Module
"""
import uuid
from sqlalchemy import Column, String, BigInteger, Boolean, TIMESTAMP, Text, Integer, JSON, Date
from sqlalchemy.sql import func
from app.db.models.base_entity import Base, BaseEntity
class Dataset(BaseEntity):
"""数据集模型(支持医学影像、文本、问答等多种类型)"""
__tablename__ = "t_dm_datasets"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(255), nullable=False, comment="数据集名称")
description = Column(Text, nullable=True, comment="数据集描述")
dataset_type = Column(String(50), nullable=False, comment="数据集类型:IMAGE/TEXT/QA/MULTIMODAL/OTHER")
category = Column(String(100), nullable=True, comment="数据集分类:医学影像/问答/文献等")
path = Column(String(500), nullable=True, comment="数据存储路径")
format = Column(String(50), nullable=True, comment="数据格式:DCM/JPG/JSON/CSV等")
schema_info = Column(JSON, nullable=True, comment="数据结构信息")
size_bytes = Column(BigInteger, default=0, comment="数据大小(字节)")
file_count = Column(BigInteger, default=0, comment="文件数量")
record_count = Column(BigInteger, default=0, comment="记录数量")
retention_days = Column(Integer, default=0, comment="数据保留天数(0表示长期保留)")
tags = Column(JSON, nullable=True, comment="标签列表")
dataset_metadata = Column("metadata", JSON, nullable=True, comment="元数据信息")
status = Column(String(50), default='DRAFT', comment="状态:DRAFT/ACTIVE/ARCHIVED")
is_public = Column(Boolean, default=False, comment="是否公开")
is_featured = Column(Boolean, default=False, comment="是否推荐")
version = Column(BigInteger, nullable=False, default=0, comment="版本号")
def __repr__(self):
return f"<Dataset(id={self.id}, name={self.name}, type={self.dataset_type})>"
class DatasetTag(BaseEntity):
"""数据集标签关联模型"""
__tablename__ = "t_dm_dataset_tags"
dataset_id = Column(String(36), primary_key=True, comment="数据集ID(UUID)")
tag_id = Column(String(36), primary_key=True, comment="标签ID(UUID)")
def __repr__(self):
return f"<DatasetTag(dataset_id={self.dataset_id}, tag_id={self.tag_id})>"
class DatasetFiles(Base):
"""DM数据集文件模型"""
__tablename__ = "t_dm_dataset_files"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
dataset_id = Column(String(36), nullable=False, comment="所属数据集ID(UUID)")
file_name = Column(String(255), nullable=False, comment="文件名")
file_path = Column(String(1000), nullable=False, comment="文件路径")
file_type = Column(String(50), nullable=True, comment="文件格式:JPG/PNG/DCM/TXT等")
file_size = Column(BigInteger, default=0, comment="文件大小(字节)")
check_sum = Column(String(64), nullable=True, comment="文件校验和")
tags = Column(JSON, nullable=True, comment="文件标签信息")
tags_updated_at = Column(TIMESTAMP, nullable=True, comment="标签最后更新时间")
annotation = Column(JSON, nullable=True, comment="完整标注结果(原始JSON)")
dataset_filemetadata = Column("metadata", JSON, nullable=True, comment="文件元数据")
status = Column(String(50), default='ACTIVE', comment="文件状态:ACTIVE/DELETED/PROCESSING")
upload_time = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="上传时间")
last_access_time = Column(TIMESTAMP, nullable=True, comment="最后访问时间")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间")
def __repr__(self):
return f"<DatasetFiles(id={self.id}, dataset_id={self.dataset_id}, file_name={self.file_name})>"
class DatasetStatistics(Base):
"""数据集统计信息模型"""
__tablename__ = "t_dm_dataset_statistics"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
dataset_id = Column(String(36), nullable=False, comment="数据集ID(UUID)")
stat_date = Column(Date, nullable=False, comment="统计日期")
total_files = Column(BigInteger, default=0, comment="总文件数")
total_size = Column(BigInteger, default=0, comment="总大小(字节)")
processed_files = Column(BigInteger, default=0, comment="已处理文件数")
error_files = Column(BigInteger, default=0, comment="错误文件数")
download_count = Column(BigInteger, default=0, comment="下载次数")
view_count = Column(BigInteger, default=0, comment="查看次数")
quality_metrics = Column(JSON, nullable=True, comment="质量指标")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间")
def __repr__(self):
return f"<DatasetStatistics(id={self.id}, dataset_id={self.dataset_id}, date={self.stat_date})>"
class Tag(Base):
"""标签集合模型"""
__tablename__ = "t_dm_tags"
id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(100), nullable=False, unique=True, comment="标签名称")
description = Column(Text, nullable=True, comment="标签描述")
category = Column(String(50), nullable=True, comment="标签分类")
color = Column(String(7), nullable=True, comment="标签颜色(十六进制)")
usage_count = Column(BigInteger, default=0, comment="使用次数")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间")
def __repr__(self):
return f"<Tag(id={self.id}, name={self.name}, category={self.category})>"
@@ -0,0 +1,82 @@
"""
知识库(RAG)相关 ORM 模型
表: t_rag_knowledge_base, t_rag_file
与 Java 实体保持一致。
"""
from enum import Enum
from sqlalchemy import Column, String, Integer, JSON
from app.db.models.base_entity import BaseEntity
class RagType(str, Enum):
"""RAG 类型枚举
对应 Java: com.deepdata.rag.indexer.interfaces.dto.RagType
"""
DOCUMENT = "DOCUMENT" # 文档型 RAG(向量检索)
GRAPH = "GRAPH" # 知识图谱型 RAG(LightRAG)
class FileStatus(str, Enum):
"""文件状态枚举
对应 Java: com.deepdata.rag.indexer.domain.model.FileStatus
"""
UNPROCESSED = "UNPROCESSED" # 未处理
PROCESSING = "PROCESSING" # 处理中
PROCESSED = "PROCESSED" # 已处理
PROCESS_FAILED = "PROCESS_FAILED" # 处理失败
class KnowledgeBase(BaseEntity):
"""知识库实体
对应 Java: com.deepdata.rag.indexer.domain.model.KnowledgeBase
表名: t_rag_knowledge_base
"""
__tablename__ = "t_rag_knowledge_base"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, comment="知识库ID")
name = Column(String(255), nullable=False, unique=True, comment="知识库名称")
description = Column(String(512), nullable=True, comment="知识库描述")
type = Column(
String(50),
nullable=False,
default=RagType.DOCUMENT,
comment="RAG类型",
)
embedding_model = Column(String(255), nullable=False, comment="嵌入模型ID")
chat_model = Column(String(255), nullable=True, comment="聊天模型ID")
def __repr__(self):
return f"<KnowledgeBase(id={self.id}, name={self.name}, type={self.type})>"
class RagFile(BaseEntity):
"""RAG 文件实体
对应 Java: com.deepdata.rag.indexer.domain.model.RagFile
表名: t_rag_file
"""
__tablename__ = "t_rag_file"
__ignore_data_scope__ = True
id = Column(String(36), primary_key=True, comment="RAG文件ID")
knowledge_base_id = Column(String(36), nullable=False, index=True, comment="知识库ID")
file_name = Column(String(512), nullable=False, comment="文件名")
file_id = Column(String(36), nullable=False, comment="原始文件ID")
chunk_count = Column(Integer, nullable=True, comment="分块数量")
file_metadata = Column("metadata", JSON, nullable=True, comment="元数据(JSON格式)")
status = Column(
String(50),
nullable=False,
default=FileStatus.UNPROCESSED,
comment="处理状态",
)
err_msg = Column(String(2048), nullable=True, comment="错误信息")
progress = Column(Integer, default=0, nullable=False, comment="处理进度(0-100)")
def __repr__(self):
return f"<RagFile(id={self.id}, file_name={self.file_name}, status={self.status})>"
@@ -0,0 +1,46 @@
from sqlalchemy import Boolean, Column, String, TIMESTAMP
from app.db.models.base_entity import BaseEntity
class Models(BaseEntity):
"""模型配置表,对应表 t_models。模型为系统级配置,RAG/生成等按 ID 引用时不受数据权限过滤。
CREATE TABLE IF NOT EXISTS t_models (
id VARCHAR(36) PRIMARY KEY COMMENT '主键ID',
model_name VARCHAR(100) NOT NULL COMMENT '模型名称(如 qwen2)',
provider VARCHAR(50) NOT NULL COMMENT '模型提供商(如 Ollama、OpenAI、DeepSeek)',
base_url VARCHAR(255) NOT NULL COMMENT 'API 基础地址',
api_key VARCHAR(512) DEFAULT '' COMMENT 'API 密钥(无密钥则为空)',
type VARCHAR(50) NOT NULL COMMENT '模型类型(如 chat、embedding)',
is_enabled TINYINT DEFAULT 1 COMMENT '是否启用:1-启用,0-禁用',
is_default TINYINT DEFAULT 0 COMMENT '是否默认:1-默认,0-非默认',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
created_by VARCHAR(255) COMMENT '创建者',
updated_by VARCHAR(255) COMMENT '更新者',
UNIQUE KEY uk_model_provider (model_name, provider)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT ='模型配置表';
"""
__tablename__ = "t_models"
id = Column(String(36), primary_key=True, index=True, comment="主键ID")
model_name = Column(String(100), nullable=False, comment="模型名称(如 qwen2)")
provider = Column(String(50), nullable=False, comment="模型提供商(如 Ollama、OpenAI、DeepSeek)")
base_url = Column(String(255), nullable=False, comment="API 基础地址")
api_key = Column(String(512), nullable=False, default="", comment="API 密钥(无密钥则为空)")
type = Column(String(50), nullable=False, comment="模型类型(如 chat、embedding)")
is_enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
is_default = Column(Boolean, nullable=False, default=False, comment="是否默认")
is_deleted = Column(Boolean, nullable=False, default=False, comment="是否删除")
__table_args__ = (
# 与 DDL 中的 uk_model_provider 保持一致
{
"mysql_engine": "InnoDB",
"mysql_charset": "utf8mb4",
"comment": "模型配置表",
},
)
@@ -0,0 +1,86 @@
"""
Operator Market Data Models
算子市场数据模型
"""
from sqlalchemy import (
Column,
String,
Integer,
Boolean,
BigInteger,
Text,
JSON,
TIMESTAMP,
Index,
)
from sqlalchemy.sql import func
from app.db.models.base_entity import Base, BaseEntity
class Operator(BaseEntity):
"""算子实体"""
__tablename__ = "t_operator"
id = Column(String(36), primary_key=True, index=True, comment="算子ID")
name = Column(String(255), nullable=False, comment="算子名称")
description = Column(Text, nullable=True, comment="算子描述")
version = Column(String(50), nullable=False, comment="算子版本")
inputs = Column(Text, nullable=True, comment="输入定义(JSON)")
outputs = Column(Text, nullable=True, comment="输出定义(JSON)")
runtime = Column(Text, nullable=True, comment="运行时配置(JSON)")
settings = Column(Text, nullable=True, comment="算子设置(JSON)")
file_name = Column(String(255), nullable=True, comment="文件名")
file_size = Column(BigInteger, nullable=True, comment="文件大小(字节)")
metrics = Column(Text, nullable=True, comment="算子指标(JSON)")
usage_count = Column(Integer, default=0, nullable=False, comment="使用次数")
is_star = Column(Boolean, default=False, nullable=False, comment="是否收藏")
__table_args__ = (Index("idx_is_star", "is_star"),)
class Category(BaseEntity):
"""算子分类实体"""
__tablename__ = "t_operator_category"
id = Column(String(36), primary_key=True, index=True, comment="分类ID")
name = Column(String(255), nullable=False, comment="分类名称(中文)")
name_en = Column(String(255), nullable=True, comment="分类名称(英文)")
value = Column(String(255), nullable=True, comment="分类值")
type = Column(String(50), nullable=True, comment="分类类型")
parent_id = Column(String(36), nullable=False, default="0", comment="父分类ID")
class CategoryRelation(BaseEntity):
"""算子分类关系实体"""
__tablename__ = "t_operator_category_relation"
category_id = Column(String(36), primary_key=True, comment="分类ID")
operator_id = Column(String(36), primary_key=True, comment="算子ID")
__table_args__ = (
Index("idx_category_id", "category_id"),
Index("idx_operator_id", "operator_id"),
)
class OperatorRelease(BaseEntity):
"""算子发布版本实体"""
__tablename__ = "t_operator_release"
id = Column(String(36), primary_key=True, comment="算子ID")
version = Column(String(50), primary_key=True, comment="版本号")
release_date = Column(
TIMESTAMP, nullable=False, default=func.now(), comment="发布时间"
)
changelog = Column(JSON, nullable=True, comment="更新日志列表")
# Ignore data scope for operator models
for model in [Operator, Category, CategoryRelation, OperatorRelease]:
model.__ignore_data_scope__ = True
@@ -0,0 +1,62 @@
"""
Tables for Ratio (Data Synthesis Ratio) module
Derived from scripts/db/data-ratio-init.sql
- t_st_ratio_instances
- t_st_ratio_relations
"""
import uuid
from sqlalchemy import Column, String, Text, BigInteger, TIMESTAMP, JSON, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from app.db.models.base_entity import BaseEntity
class RatioInstance(BaseEntity):
"""配比实例表(UUID 主键) -> t_st_ratio_instances
Columns per data-ratio-init.sql:
id, name, description, target_dataset_id, ratio_method, ratio_parameters,
merge_method, status, totals, created_at, updated_at, created_by, updated_by
"""
__tablename__ = "t_st_ratio_instances"
id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
name = Column(String(64), nullable=True, comment="名称")
description = Column(Text, nullable=True, comment="描述")
target_dataset_id = Column(String(64), nullable=True, comment="模板数据集ID")
ratio_parameters = Column(JSON, nullable=True, comment="配比参数")
merge_method = Column(String(50), nullable=True, comment="合并方式")
status = Column(String(20), nullable=True, comment="状态")
totals = Column(BigInteger, nullable=True, comment="总数")
def __repr__(self) -> str:
return f"<RatioInstance(id={self.id}, name={self.name}, status={self.status})>"
class RatioRelation(BaseEntity):
"""配比关系表(UUID 主键) -> t_st_ratio_relations
Columns per data-ratio-init.sql:
id, ratio_instance_id, source_dataset_id, ratio_value, counts, filter_conditions,
created_at, updated_at, created_by, updated_by
"""
__tablename__ = "t_st_ratio_relations"
id = Column(String(64), primary_key=True, default=lambda: str(uuid.uuid4()), comment="UUID")
ratio_instance_id = Column(String(64), nullable=False, comment="配比实例ID")
source_dataset_id = Column(String(64), nullable=True, comment="源数据集ID")
ratio_value = Column(String(256), nullable=True)
counts = Column(BigInteger, nullable=True, comment="条数")
filter_conditions = Column(Text, nullable=True, comment="过滤条件")
def __repr__(self) -> str:
return (
f"<RatioRelation(id={self.id}, ratio_instance_id={self.ratio_instance_id}, "
f"source_dataset_id={self.source_dataset_id}, counts={self.counts})>"
)
@@ -0,0 +1,30 @@
"""
System Parameter Model
系统参数模型
"""
from sqlalchemy import Column, String, Boolean, Text
from app.db.models.base_entity import BaseEntity
class SysParam(BaseEntity):
"""系统参数实体"""
__tablename__ = "t_sys_param"
__ignore_data_scope__ = True # 系统参数不进行数据权限过滤
id = Column(String(100), primary_key=True, comment="参数ID")
param_value = Column(Text, nullable=False, comment="参数值")
param_type = Column(
String(50),
nullable=False,
default="string",
comment="参数类型(string、integer、boolean)",
)
option_list = Column(
Text, nullable=True, comment="选项列表(JSON格式,仅对enum类型有效)"
)
description = Column(String(255), nullable=True, default="", comment="参数描述")
is_built_in = Column(Boolean, nullable=False, default=False, comment="是否内置")
can_modify = Column(Boolean, nullable=False, default=True, comment="是否可修改")
is_enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
@@ -0,0 +1,29 @@
"""
Tables of User Management Module
"""
from sqlalchemy import Column, String, BigInteger, Boolean, TIMESTAMP
from sqlalchemy.sql import func
from app.db.models.base_entity import Base
class User(Base):
"""用户模型"""
__tablename__ = "users"
id = Column(BigInteger, primary_key=True, autoincrement=True, comment="用户ID")
username = Column(String(255), nullable=False, unique=True, comment="用户名")
email = Column(String(255), nullable=False, unique=True, comment="邮箱")
password_hash = Column(String(255), nullable=False, comment="密码哈希")
full_name = Column(String(255), nullable=True, comment="真实姓名")
avatar_url = Column(String(500), nullable=True, comment="头像URL")
role = Column(String(50), nullable=False, default='USER', comment="角色:ADMIN/USER")
organization = Column(String(255), nullable=True, comment="所属机构")
enabled = Column(Boolean, nullable=False, default=True, comment="是否启用")
last_login_at = Column(TIMESTAMP, nullable=True, comment="最后登录时间")
created_at = Column(TIMESTAMP, server_default=func.current_timestamp(), comment="创建时间")
updated_at = Column(TIMESTAMP, server_default=func.current_timestamp(), onupdate=func.current_timestamp(), comment="更新时间")
def __repr__(self):
return f"<User(id={self.id}, username={self.username}, role={self.role})>"
+94
View File
@@ -0,0 +1,94 @@
from datetime import datetime
from sqlalchemy import event, true
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy.orm import Session, declarative_base, with_loader_criteria
from sqlalchemy.orm.attributes import flag_modified
from app.core.config import settings
from app.core.logging import get_logger
from typing import AsyncGenerator
from app.db.datascope import DataScopeHandle, SYSTEM_USER
from app.db.models.base_entity import BaseEntity
logger = get_logger(__name__)
# 创建数据库引擎
engine = create_async_engine(
settings.database_url,
echo=False, # 关闭SQL调试日志以减少输出
future=True,
pool_pre_ping=True,
pool_recycle=3600
)
# 创建会话工厂
AsyncSessionLocal = async_sessionmaker(
engine,
class_=AsyncSession,
expire_on_commit=False
)
@event.listens_for(Session, "do_orm_execute")
def _apply_data_scope(orm_execute_state):
# only act on SELECT queries generated by ORM
if not orm_execute_state.is_select:
return
allowed = DataScopeHandle.allowed_users()
if not allowed or len(allowed) == 0:
return
# predicate builder: return None to skip for classes that opt-out
def criteria_fn(cls):
# skip if the mapped class explicitly disables data-scope
if getattr(cls, "__ignore_data_scope__", False):
return true()
# some classes may not have created_by column; guard dynamically
col = getattr(cls, "created_by", None)
if col is None:
return true()
return col.in_(allowed)
# apply loader-level criteria to all subclasses of BaseEntity
orm_execute_state.statement = orm_execute_state.statement.options(
with_loader_criteria(BaseEntity, criteria_fn, include_aliases=True)
)
@event.listens_for(Session, "before_flush")
def _audit_before_flush(session, flush_context, instances):
user = DataScopeHandle.get_user_info()
effective_user = (user or "").strip() or SYSTEM_USER
now = datetime.now()
# new -> set created_* and updated_*
for obj in list(session.new):
if isinstance(obj, BaseEntity):
if getattr(obj, "created_at", None) is None:
obj.created_at = now
if getattr(obj, "created_by", None) is None:
obj.created_by = effective_user
obj.updated_at = now
obj.updated_by = effective_user
# ensure SQLAlchemy sees changes
try:
flag_modified(obj, "created_by")
except Exception:
pass
# dirty -> set updated_*
for obj in list(session.dirty):
if isinstance(obj, BaseEntity):
obj.updated_at = now
obj.updated_by = effective_user
try:
flag_modified(obj, "updated_by")
except Exception:
pass
async def get_db() -> AsyncGenerator[AsyncSession, None]:
"""获取数据库会话"""
async with AsyncSessionLocal() as session:
try:
yield session
finally:
await session.close()