deepdata: core product code (backend, frontend, runtime, deepflow module)
This commit is contained in:
@@ -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})>"
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user