aps-agent/server/db/models.py

363 lines
17 KiB
Python
Raw Normal View History

# ============================================================
# 主数据表模型(moduleId: db-models, 可重生 ✅)
# 设计:projects 多项目隔离;master_records 宽表承载 world 主数据键
# (payload=完整记录 JSON,投影零损耗;code/name 建索引供查询);
# routing_templates 行业工艺路线模板库(平台级,跨项目共享)。
# ============================================================
from __future__ import annotations
from sqlalchemy import (
JSON,
BigInteger,
Boolean,
Float,
ForeignKey,
Index,
Integer,
String,
Text,
UniqueConstraint,
)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
pass
class TenantAuditMixin:
tenant_uuid: Mapped[str] = mapped_column(String(32), nullable=False, index=True, default="platform")
creator_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
updater_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
updated_at: Mapped[str] = mapped_column(String(32), default="")
deleted: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0)
class Project(TenantAuditMixin, Base):
"""项目/租户:康尼、演示厂……主数据按项目隔离。"""
__tablename__ = "projects"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(64), index=True)
name: Mapped[str] = mapped_column(String(128), default="")
industry: Mapped[str] = mapped_column(String(64), default="machining")
active: Mapped[bool] = mapped_column(Boolean, default=False)
owner_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
data_version: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
UniqueConstraint("tenant_uuid", "code", "deleted", name="uk_projects_tenant_code"),
)
class MasterRecord(TenantAuditMixin, Base):
"""主数据宽表:一行 = world 某主数据表的一条记录(payload 全量 JSON)。
table_key 对齐 world 键名(factories/routingSteps/flexRoutings/salesOrders…),
投影时按 (project_id, table_key, seq) 还原顺序,保证与导入时零差异。
"""
__tablename__ = "master_records"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), index=True)
table_key: Mapped[str] = mapped_column(String(64), index=True)
seq: Mapped[int] = mapped_column(Integer, default=0)
rec_id: Mapped[str] = mapped_column(String(64), default="") # 原记录 id/code(辅助定位)
code: Mapped[str] = mapped_column(String(128), default="", index=True)
name: Mapped[str] = mapped_column(String(256), default="")
payload: Mapped[dict] = mapped_column(JSON)
__table_args__ = (
Index("ix_master_tenant_project_table", "tenant_uuid", "project_id", "table_key"),
)
class ProjectSetting(TenantAuditMixin, Base):
"""项目级字典配置:scheduleParams / constraintProfile / flexParams。"""
__tablename__ = "project_settings"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
project_id: Mapped[int] = mapped_column(ForeignKey("projects.id"), index=True)
key: Mapped[str] = mapped_column(String(64), index=True)
payload: Mapped[dict] = mapped_column(JSON)
__table_args__ = (
Index("ix_settings_tenant_project_key", "tenant_uuid", "project_id", "key", unique=True),
)
class RoutingTemplate(TenantAuditMixin, Base):
"""行业工艺路线模板(平台级):机加工车/铣/钻/磨/装配等生成模式的结构化形态。"""
__tablename__ = "routing_templates"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
code: Mapped[str] = mapped_column(String(64), index=True)
name: Mapped[str] = mapped_column(String(128))
industry: Mapped[str] = mapped_column(String(64), default="machining")
category: Mapped[str] = mapped_column(String(64), default="") # 车削/铣削/装配…
keywords: Mapped[str] = mapped_column(Text, default="") # 逗号分隔,检索匹配
description: Mapped[str] = mapped_column(Text, default="")
asset_id: Mapped[str] = mapped_column(String(32), default="") # 关联知识资产(RAG 出处)
source: Mapped[str] = mapped_column(String(128), default="builtin")
__table_args__ = (
UniqueConstraint("tenant_uuid", "code", "deleted", name="uk_routing_template_tenant_code"),
)
class RoutingTemplateStep(TenantAuditMixin, Base):
"""模板工步:含推荐工时区间与设备类型(供小工厂一键生成路线)。"""
__tablename__ = "routing_template_steps"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
template_id: Mapped[int] = mapped_column(ForeignKey("routing_templates.id"), index=True)
seq: Mapped[int] = mapped_column(Integer)
operation_code: Mapped[str] = mapped_column(String(64))
operation_name: Mapped[str] = mapped_column(String(128))
equipment_type: Mapped[str] = mapped_column(String(128), default="")
std_min_low: Mapped[float] = mapped_column(Float, default=0) # 推荐单件工时下限(分钟)
std_min_high: Mapped[float] = mapped_column(Float, default=0) # 上限
setup_min: Mapped[float] = mapped_column(Float, default=10)
changeover_min: Mapped[float] = mapped_column(Float, default=10)
note: Mapped[str] = mapped_column(Text, default="")
class WorkspaceProject(TenantAuditMixin, Base):
__tablename__ = "aps_workspace_projects"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
name: Mapped[str] = mapped_column(String(128), default="")
scope_label: Mapped[str] = mapped_column(String(128), default="未设定作用域")
owner_user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
work_dir: Mapped[str] = mapped_column(String(512), default="")
archived: Mapped[bool] = mapped_column(Boolean, default=False)
data_version: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
Index("ix_workspace_projects_tenant_owner", "tenant_uuid", "owner_user_id", "deleted"),
)
class ProjectMember(TenantAuditMixin, Base):
__tablename__ = "aps_project_members"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
project_id: Mapped[str] = mapped_column(ForeignKey("aps_workspace_projects.id"), index=True)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
role: Mapped[str] = mapped_column(String(16), nullable=False, default="viewer")
status: Mapped[str] = mapped_column(String(16), nullable=False, default="active")
invited_by: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
UniqueConstraint("tenant_uuid", "project_id", "user_id", "deleted", name="uk_project_member"),
Index("ix_project_member_access", "tenant_uuid", "user_id", "status", "deleted"),
)
class UserWorkspace(TenantAuditMixin, Base):
__tablename__ = "aps_user_workspaces"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
active_project_id: Mapped[str] = mapped_column(String(64), default="__personal__")
active_session_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
settings: Mapped[dict] = mapped_column(JSON, default=dict)
version: Mapped[int] = mapped_column(Integer, default=0)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
UniqueConstraint("tenant_uuid", "user_id", "deleted", name="uk_user_workspace"),
)
class ChatSessionRecord(TenantAuditMixin, Base):
__tablename__ = "aps_chat_sessions"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
owner_user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
scope: Mapped[str] = mapped_column(String(16), nullable=False, default="personal")
title: Mapped[str] = mapped_column(String(200), default="新话题")
status: Mapped[str] = mapped_column(String(16), default="running")
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
Index("ix_chat_session_personal", "tenant_uuid", "owner_user_id", "scope", "deleted"),
Index("ix_chat_session_project", "tenant_uuid", "project_id", "scope", "deleted"),
)
class ChatMessageRecord(TenantAuditMixin, Base):
__tablename__ = "aps_chat_messages"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
session_id: Mapped[str] = mapped_column(ForeignKey("aps_chat_sessions.id"), index=True)
sender_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
seq: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
payload: Mapped[dict] = mapped_column(JSON)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
Index("ix_chat_message_tenant_session_seq", "tenant_uuid", "session_id", "seq", unique=True),
)
class ProjectFileRecord(TenantAuditMixin, Base):
__tablename__ = "aps_project_files"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("aps_workspace_projects.id"), index=True)
name: Mapped[str] = mapped_column(String(256), default="")
kind: Mapped[str] = mapped_column(String(32), default="other")
note: Mapped[str] = mapped_column(Text, default="")
storage_key: Mapped[str] = mapped_column(String(512), default="")
created_at: Mapped[str] = mapped_column(String(32), default="")
class ScheduleRun(TenantAuditMixin, Base):
__tablename__ = "aps_schedule_runs"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("aps_workspace_projects.id"), index=True)
triggered_by: Mapped[int] = mapped_column(BigInteger, nullable=False)
input_version: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
status: Mapped[str] = mapped_column(String(24), nullable=False, default="pending")
started_at: Mapped[str] = mapped_column(String(32), default="")
finished_at: Mapped[str] = mapped_column(String(32), default="")
error: Mapped[str] = mapped_column(Text, default="")
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
Index("ix_schedule_run_project_status", "tenant_uuid", "project_id", "status"),
)
class ScheduleResult(TenantAuditMixin, Base):
__tablename__ = "aps_schedule_results"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
project_id: Mapped[str] = mapped_column(ForeignKey("aps_workspace_projects.id"), index=True)
run_id: Mapped[str] = mapped_column(ForeignKey("aps_schedule_runs.id"), index=True)
input_version: Mapped[int] = mapped_column(Integer, nullable=False)
result_version: Mapped[str] = mapped_column(String(64), nullable=False)
payload: Mapped[dict] = mapped_column(JSON)
created_at: Mapped[str] = mapped_column(String(32), default="")
class AuditEventRecord(TenantAuditMixin, Base):
__tablename__ = "aps_audit_events"
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
actor_user_id: Mapped[int] = mapped_column(BigInteger, nullable=False)
project_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
action: Mapped[str] = mapped_column(String(128), nullable=False)
resource_type: Mapped[str] = mapped_column(String(64), nullable=False)
resource_id: Mapped[str] = mapped_column(String(128), default="")
detail: Mapped[dict] = mapped_column(JSON, default=dict)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
Index("ix_audit_tenant_actor_created", "tenant_uuid", "actor_user_id", "created_at"),
)
class ApprovalRequestRecord(Base):
"""Mutable approval state; transitions are guarded by ``revision`` CAS."""
__tablename__ = "aps_approval_requests"
confirm_id: Mapped[str] = mapped_column(String(64), primary_key=True)
tenant_uuid: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
project_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
owner_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
action: Mapped[str] = mapped_column(String(128), nullable=False)
power: Mapped[str] = mapped_column(String(8), nullable=False)
params_hash: Mapped[str] = mapped_column(String(64), nullable=False)
status: Mapped[str] = mapped_column(String(24), nullable=False, default="PENDING")
approval_step: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
required_approvals: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
created_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
expires_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
revision: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
payload: Mapped[dict] = mapped_column(JSON, nullable=False)
__table_args__ = (
Index(
"ix_approval_request_scope_status_expiry",
"tenant_uuid",
"project_id",
"status",
"expires_at_epoch",
),
)
class ApprovalGrantRecord(Base):
"""One-time P3 execution grant. Only the token digest is persisted."""
__tablename__ = "aps_approval_grants"
token_hash: Mapped[str] = mapped_column(String(64), primary_key=True)
confirm_id: Mapped[str] = mapped_column(
ForeignKey("aps_approval_requests.confirm_id"), nullable=False, unique=True, index=True
)
tenant_uuid: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
project_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
owner_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
action: Mapped[str] = mapped_column(String(128), nullable=False)
params_hash: Mapped[str] = mapped_column(String(64), nullable=False)
status: Mapped[str] = mapped_column(String(24), nullable=False, default="ACTIVE")
created_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
expires_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
consumed_at_epoch: Mapped[float | None] = mapped_column(Float, nullable=True)
revision: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
payload: Mapped[dict] = mapped_column(JSON, nullable=False)
__table_args__ = (
Index(
"ix_approval_grant_scope_status_expiry",
"tenant_uuid",
"project_id",
"status",
"expires_at_epoch",
),
)
class ApprovalEventRecord(Base):
"""Append-only approval history projection."""
__tablename__ = "aps_approval_events"
id: Mapped[int] = mapped_column(
BigInteger().with_variant(Integer, "sqlite"), primary_key=True, autoincrement=True
)
confirm_id: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
tenant_uuid: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
project_id: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
owner_user_id: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
action: Mapped[str] = mapped_column(String(128), nullable=False)
power: Mapped[str] = mapped_column(String(8), nullable=False)
status: Mapped[str] = mapped_column(String(32), nullable=False)
decided_at_epoch: Mapped[float] = mapped_column(Float, nullable=False)
payload: Mapped[dict] = mapped_column(JSON, nullable=False)
__table_args__ = (
Index(
"ix_approval_event_scope_order",
"tenant_uuid",
"project_id",
"id",
),
)
class LicenseActivation(TenantAuditMixin, Base):
"""A server-validated desktop entitlement bound to one installation."""
__tablename__ = "aps_license_activations"
id: Mapped[str] = mapped_column(String(64), primary_key=True)
code_hash: Mapped[str] = mapped_column(String(64), nullable=False)
code_hint: Mapped[str] = mapped_column(String(32), default="")
device_hash: Mapped[str] = mapped_column(String(64), nullable=False, index=True)
user_id: Mapped[int] = mapped_column(BigInteger, nullable=False, index=True)
license_type: Mapped[str] = mapped_column(String(16), nullable=False)
status: Mapped[str] = mapped_column(String(16), nullable=False, default="active")
activated_at: Mapped[int] = mapped_column(BigInteger, nullable=False)
expires_at: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
last_seen_at: Mapped[int] = mapped_column(BigInteger, nullable=False)
revoked_at: Mapped[int | None] = mapped_column(BigInteger, nullable=True)
created_at: Mapped[str] = mapped_column(String(32), default="")
__table_args__ = (
UniqueConstraint("code_hash", "deleted", name="uk_license_activation_code"),
Index("ix_license_device_status", "device_hash", "status", "deleted"),
Index("ix_license_tenant_user", "tenant_uuid", "user_id", "deleted"),
)