feat: 新增员工行为画像算法与费用风险标签体系

后端新增员工行为画像算法模块,支持标签规则引擎和评分计算,
完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流
和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费
用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台
样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
caoxiaozhu
2026-05-28 12:09:49 +08:00
parent 04cd6d0f81
commit 8a4a777be7
96 changed files with 9835 additions and 704 deletions

View File

@@ -5,6 +5,7 @@ from app.models.approval import ApprovalRecord
from app.models.audit_log import AuditLog
from app.models.budget import BudgetAllocation, BudgetReservation, BudgetTransaction
from app.models.employee_change_log import EmployeeChangeLog
from app.models.employee_behavior_profile import EmployeeBehaviorProfileSnapshot
from app.models.employee import Employee
from app.models.financial_record import (
AccountsPayableRecord,
@@ -37,6 +38,7 @@ __all__ = [
"BudgetReservation",
"BudgetTransaction",
"Employee",
"EmployeeBehaviorProfileSnapshot",
"EmployeeChangeLog",
"ExpenseClaim",
"ExpenseClaimItem",

View File

@@ -32,6 +32,9 @@ class Employee(Base):
grade: Mapped[str] = mapped_column(String(20), default="P3", index=True)
cost_center: Mapped[str | None] = mapped_column(String(50), nullable=True)
finance_owner_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
bank_name: Mapped[str | None] = mapped_column(String(120), nullable=True)
bank_account_no: Mapped[str | None] = mapped_column(String(80), nullable=True)
bank_account_name: Mapped[str | None] = mapped_column(String(100), nullable=True)
password_hash: Mapped[str | None] = mapped_column(String(255), nullable=True)
employment_status: Mapped[str] = mapped_column(String(30), default="在职", index=True)
sync_state: Mapped[str] = mapped_column(String(30), default="已同步")

View File

@@ -0,0 +1,62 @@
from __future__ import annotations
import uuid
from datetime import datetime
from typing import Any
from sqlalchemy import DateTime, ForeignKey, Index, Integer, String, func
from sqlalchemy.orm import Mapped, mapped_column, relationship
from sqlalchemy.types import JSON
from app.db.base_class import Base
class EmployeeBehaviorProfileSnapshot(Base):
__tablename__ = "employee_behavior_profile_snapshots"
__table_args__ = (
Index(
"ix_employee_behavior_profile_latest",
"subject_id",
"profile_type",
"window_days",
"expense_type_scope",
"calculated_at",
),
)
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
subject_type: Mapped[str] = mapped_column(String(30), default="employee", index=True)
subject_id: Mapped[str] = mapped_column(String(100), index=True)
subject_name: Mapped[str] = mapped_column(String(100), index=True)
department_id: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
department_name: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True)
position: Mapped[str | None] = mapped_column(String(100), nullable=True)
grade: Mapped[str | None] = mapped_column(String(30), nullable=True, index=True)
profile_type: Mapped[str] = mapped_column(String(50), index=True)
window_days: Mapped[int] = mapped_column(Integer, index=True)
expense_type_scope: Mapped[str] = mapped_column(String(50), default="overall", index=True)
peer_group_key: Mapped[str] = mapped_column(String(255), default="")
peer_group_fallback_level: Mapped[int] = mapped_column(Integer, default=0)
profile_score: Mapped[int] = mapped_column(Integer, default=0)
profile_level: Mapped[str] = mapped_column(String(30), default="normal", index=True)
metrics_json: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict)
basis_codes_json: Mapped[list[Any]] = mapped_column(JSON, default=list)
source_task_type: Mapped[str] = mapped_column(
String(80), default="employee_behavior_profile_scan"
)
source_task_log_id: Mapped[str | None] = mapped_column(
ForeignKey("hermes_task_execution_logs.id"),
nullable=True,
index=True,
)
algorithm_version: Mapped[str] = mapped_column(
String(80), default="employee_behavior_profile.v1"
)
calculated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), index=True
)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now())
source_task_log = relationship("HermesTaskExecutionLog")