Files
loom/backend/app/models/skill.py
T
2026-04-24 17:46:03 +08:00

90 lines
3.6 KiB
Python

"""Skill:双执行器(龙虾 / 脚本)注册 + 参数 schema + 产品范围绑定。"""
from __future__ import annotations
from datetime import datetime
from sqlalchemy import (
JSON,
Column,
DateTime,
ForeignKey,
Integer,
String,
Table,
Text,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.db.base import Base, TimestampMixin
skill_product_scope = Table(
"skill_product_scope",
Base.metadata,
Column("skill_id", Integer, ForeignKey("skill.id", ondelete="CASCADE"), primary_key=True),
Column("product_id", Integer, ForeignKey("product.id", ondelete="CASCADE"), primary_key=True),
)
class Skill(Base, TimestampMixin):
__tablename__ = "skill"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(64), nullable=False, unique=True)
name: Mapped[str] = mapped_column(String(128), nullable=False)
category: Mapped[str | None] = mapped_column(String(32), nullable=True)
# LOBSTER / SCRIPT
executor_type: Mapped[str] = mapped_column(String(16), nullable=False, default="LOBSTER")
description: Mapped[str | None] = mapped_column(String(255), nullable=True)
# 输入参数 JSON Schema(展示给用户填写)
input_schema: Mapped[dict | None] = mapped_column(JSON, nullable=True)
# 参数映射表达式:{ "lobster_param_key": "{{input.field}}" } 之类
param_mapping: Mapped[dict | None] = mapped_column(JSON, nullable=True)
# 龙虾模板 ID / 脚本模板 ID (二选一,由 executor_type 决定)
lobster_template_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
script_template_id: Mapped[int | None] = mapped_column(
ForeignKey("script_template.id", ondelete="SET NULL"), nullable=True
)
status: Mapped[str] = mapped_column(String(16), default="ACTIVE", nullable=False)
# 执行示例 / 说明文档(可选)
doc: Mapped[str | None] = mapped_column(Text, nullable=True)
product_scope: Mapped[list["Product"]] = relationship( # type: ignore[name-defined] # noqa: F821
"Product", secondary=skill_product_scope, lazy="selectin"
)
class SkillCallLog(Base):
"""Skill 调用审计(由测试台 / 任务执行器写入)。"""
__tablename__ = "skill_call_log"
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
skill_id: Mapped[int] = mapped_column(
ForeignKey("skill.id", ondelete="CASCADE"), nullable=False, index=True
)
skill_code: Mapped[str] = mapped_column(String(64), nullable=False)
operator_id: Mapped[int | None] = mapped_column(
ForeignKey("user.id", ondelete="SET NULL"), nullable=True, index=True
)
operator_name: Mapped[str | None] = mapped_column(String(64), nullable=True)
executor_type: Mapped[str] = mapped_column(String(16), nullable=False)
product_id: Mapped[int | None] = mapped_column(
ForeignKey("product.id", ondelete="SET NULL"), nullable=True
)
input_data: Mapped[dict | None] = mapped_column(JSON, nullable=True)
output_data: Mapped[dict | None] = mapped_column(JSON, nullable=True)
# ok / failed
result: Mapped[str] = mapped_column(String(16), nullable=False, default="ok")
message: Mapped[str | None] = mapped_column(String(512), nullable=True)
cost_ms: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
# 是测试台调用还是真实任务调用
source: Mapped[str] = mapped_column(String(16), default="test", nullable=False)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), nullable=False, index=True
)