```
feat(ai_agent): 完善AI适配器和服务功能 - 添加OpenClaw和Hermes健康检查接口 - 实现OpenClaw工具调用功能 - 重构AI适配器使用常量定义 - 增加AI技能系统支持 - 更新配置文件中的默认模型提供者设置 refactor(scheduler): 使用常量替换硬编码值 - 将硬编码的actor值替换为ActorValue常量 - 将receive_id_type替换为FeishuReceiveIdType枚举 refactor(audit): 统一审计日志常量使用 - 将硬编码的actor、source、risk_level等值替换为对应常量 - 更新审核服务中的状态和操作常量引用 refactor(approvals): 标准化审批模块常量使用 - 将applicant默认值替换为ActorValue.API常量 - 使用ApprovalStatus常量替代硬编码状态值 - 更新审核操作常量引用 ```
This commit is contained in:
@@ -5,14 +5,28 @@ from typing import Any
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.constants import ActorValue
|
||||
from app.modules.audit.constants import (
|
||||
AuditAction,
|
||||
AuditRiskLevel,
|
||||
AuditSource,
|
||||
AuditTargetType,
|
||||
)
|
||||
from app.modules.audit.schemas import AuditLogCreate
|
||||
from app.modules.audit.service import AuditService
|
||||
from app.modules.business.constants import (
|
||||
CLOSED_RISK_STATUSES,
|
||||
DONE_STATUSES,
|
||||
PROJECT_CLOSED_STATUSES,
|
||||
SUPPLIER_RISK_LEVELS,
|
||||
BusinessDomain,
|
||||
RiskEventType,
|
||||
RiskLevel,
|
||||
StatusValue,
|
||||
)
|
||||
from app.modules.business.models import FundAccount, Project, RiskEvent, Supplier, WorkTask
|
||||
from app.modules.business.service import serialize_model
|
||||
|
||||
DONE_STATUSES = {"完成", "已完成", "关闭", "done", "completed", "closed"}
|
||||
CLOSED_RISK_STATUSES = {"closed", "resolved"}
|
||||
|
||||
|
||||
class RiskService:
|
||||
"""Evaluate rule-based business risk signals from internal ledgers."""
|
||||
@@ -32,7 +46,7 @@ class RiskService:
|
||||
stmt = select(Project).where(
|
||||
Project.due_date.is_not(None),
|
||||
Project.due_date < date.today(),
|
||||
Project.status.notin_(["验收", "已完成", "复盘", "归档", "关闭", "closed"]),
|
||||
Project.status.notin_(PROJECT_CLOSED_STATUSES),
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
@@ -49,8 +63,8 @@ class RiskService:
|
||||
|
||||
def supplier_risks(self) -> list[dict[str, Any]]:
|
||||
stmt = select(Supplier).where(
|
||||
(Supplier.blacklist_status != "normal")
|
||||
| Supplier.risk_level.in_(["medium", "high"])
|
||||
(Supplier.blacklist_status != StatusValue.NORMAL)
|
||||
| Supplier.risk_level.in_(SUPPLIER_RISK_LEVELS)
|
||||
)
|
||||
return [serialize_model(item) for item in self.db.execute(stmt).scalars()]
|
||||
|
||||
@@ -75,7 +89,7 @@ class RiskService:
|
||||
over_budget_projects = self.over_budget_projects()
|
||||
fund_risks = self.fund_risks()
|
||||
supplier_risks = self.supplier_risks()
|
||||
open_events = self.list_events(status_filter="open")
|
||||
open_events = self.list_events(status_filter=StatusValue.OPEN)
|
||||
risk_score = (
|
||||
len(overdue_tasks) * 1
|
||||
+ len(delayed_projects) * 3
|
||||
@@ -85,11 +99,11 @@ class RiskService:
|
||||
+ len(open_events) * 2
|
||||
)
|
||||
if risk_score >= 15:
|
||||
level = "high"
|
||||
level = RiskLevel.HIGH
|
||||
elif risk_score >= 5:
|
||||
level = "medium"
|
||||
level = RiskLevel.MEDIUM
|
||||
else:
|
||||
level = "low"
|
||||
level = RiskLevel.LOW
|
||||
return {
|
||||
"risk_level": level,
|
||||
"risk_score": Decimal(risk_score),
|
||||
@@ -101,7 +115,7 @@ class RiskService:
|
||||
"open_events": open_events,
|
||||
}
|
||||
|
||||
def generate_events(self, actor: str = "api") -> dict[str, Any]:
|
||||
def generate_events(self, actor: str = ActorValue.API) -> dict[str, Any]:
|
||||
"""Generate or refresh risk-event ledger entries from current signals."""
|
||||
|
||||
payloads = self._build_event_payloads()
|
||||
@@ -136,10 +150,10 @@ class RiskService:
|
||||
AuditService(self.db).log(
|
||||
AuditLogCreate(
|
||||
actor=actor,
|
||||
source="risk",
|
||||
action="generate_events",
|
||||
target_type="risk-events",
|
||||
risk_level="medium",
|
||||
source=AuditSource.RISK,
|
||||
action=AuditAction.GENERATE_EVENTS,
|
||||
target_type=AuditTargetType.RISK_EVENTS,
|
||||
risk_level=AuditRiskLevel.MEDIUM,
|
||||
response_payload={
|
||||
"created": created,
|
||||
"updated": updated,
|
||||
@@ -170,17 +184,19 @@ class RiskService:
|
||||
{
|
||||
"code": f"RISK-TASK-OVERDUE-{task.id}",
|
||||
"title": f"任务逾期:{task.title}",
|
||||
"risk_type": "overdue_task",
|
||||
"risk_level": "medium",
|
||||
"status": "open",
|
||||
"source_domain": "tasks",
|
||||
"risk_type": RiskEventType.OVERDUE_TASK,
|
||||
"risk_level": RiskLevel.MEDIUM,
|
||||
"status": StatusValue.OPEN,
|
||||
"source_domain": BusinessDomain.TASKS,
|
||||
"source_record_id": str(task.id),
|
||||
"project_code": task.project_code,
|
||||
"owner": task.owner,
|
||||
"due_date": task.due_date,
|
||||
"detected_at": datetime.utcnow(),
|
||||
"description": "任务已超过截止日期且未完成。",
|
||||
"mitigation": "请负责人更新进度、明确阻塞项并给出新的完成时间。",
|
||||
"mitigation": (
|
||||
"请负责人更新进度、明确阻塞项并给出新的完成时间。"
|
||||
),
|
||||
"evidence": serialize_model(task),
|
||||
}
|
||||
)
|
||||
@@ -190,26 +206,28 @@ class RiskService:
|
||||
stmt = select(Project).where(
|
||||
Project.due_date.is_not(None),
|
||||
Project.due_date < date.today(),
|
||||
Project.status.notin_(["验收", "已完成", "复盘", "归档", "关闭", "closed"]),
|
||||
Project.status.notin_(PROJECT_CLOSED_STATUSES),
|
||||
)
|
||||
payloads = []
|
||||
for project in self.db.execute(stmt).scalars():
|
||||
level = "high" if project.progress_percent < 80 else "medium"
|
||||
level = RiskLevel.HIGH if project.progress_percent < 80 else RiskLevel.MEDIUM
|
||||
payloads.append(
|
||||
{
|
||||
"code": f"RISK-PROJECT-DELAY-{project.id}",
|
||||
"title": f"项目延期:{project.name}",
|
||||
"risk_type": "delayed_project",
|
||||
"risk_type": RiskEventType.DELAYED_PROJECT,
|
||||
"risk_level": level,
|
||||
"status": "open",
|
||||
"source_domain": "projects",
|
||||
"status": StatusValue.OPEN,
|
||||
"source_domain": BusinessDomain.PROJECTS,
|
||||
"source_record_id": str(project.id),
|
||||
"project_code": project.code,
|
||||
"owner": project.owner,
|
||||
"due_date": project.due_date,
|
||||
"detected_at": datetime.utcnow(),
|
||||
"description": "项目已超过计划截止日期且未进入完成状态。",
|
||||
"mitigation": "请项目负责人提交延期原因、资源需求和纠偏计划。",
|
||||
"mitigation": (
|
||||
"请项目负责人提交延期原因、资源需求和纠偏计划。"
|
||||
),
|
||||
"evidence": serialize_model(project),
|
||||
}
|
||||
)
|
||||
@@ -226,17 +244,19 @@ class RiskService:
|
||||
{
|
||||
"code": f"RISK-PROJECT-BUDGET-{project.id}",
|
||||
"title": f"项目超预算:{project.name}",
|
||||
"risk_type": "over_budget_project",
|
||||
"risk_level": "high",
|
||||
"status": "open",
|
||||
"source_domain": "projects",
|
||||
"risk_type": RiskEventType.OVER_BUDGET_PROJECT,
|
||||
"risk_level": RiskLevel.HIGH,
|
||||
"status": StatusValue.OPEN,
|
||||
"source_domain": BusinessDomain.PROJECTS,
|
||||
"source_record_id": str(project.id),
|
||||
"project_code": project.code,
|
||||
"owner": project.owner,
|
||||
"due_date": project.due_date,
|
||||
"detected_at": datetime.utcnow(),
|
||||
"description": "项目实际成本已超过预算。",
|
||||
"mitigation": "请复核预算科目、冻结非必要采购并补充审批依据。",
|
||||
"mitigation": (
|
||||
"请复核预算科目、冻结非必要采购并补充审批依据。"
|
||||
),
|
||||
"evidence": serialize_model(project),
|
||||
}
|
||||
)
|
||||
@@ -250,15 +270,18 @@ class RiskService:
|
||||
{
|
||||
"code": f"RISK-FUND-{account.id}",
|
||||
"title": f"资金低于安全线:{account.name}",
|
||||
"risk_type": "fund_safety_line",
|
||||
"risk_level": "high",
|
||||
"status": "open",
|
||||
"source_domain": "fund-accounts",
|
||||
"risk_type": RiskEventType.FUND_SAFETY_LINE,
|
||||
"risk_level": RiskLevel.HIGH,
|
||||
"status": StatusValue.OPEN,
|
||||
"source_domain": BusinessDomain.FUND_ACCOUNTS,
|
||||
"source_record_id": str(account.id),
|
||||
"owner": None,
|
||||
"detected_at": datetime.utcnow(),
|
||||
"description": "账户当前余额低于设置的安全线。",
|
||||
"mitigation": "请财务确认收付款计划,并优先处理关键项目资金安排。",
|
||||
"mitigation": (
|
||||
"请财务确认收付款计划,"
|
||||
"并优先处理关键项目资金安排。"
|
||||
),
|
||||
"evidence": serialize_model(account),
|
||||
}
|
||||
)
|
||||
@@ -266,25 +289,31 @@ class RiskService:
|
||||
|
||||
def _supplier_risk_payloads(self) -> list[dict[str, Any]]:
|
||||
stmt = select(Supplier).where(
|
||||
(Supplier.blacklist_status != "normal")
|
||||
| Supplier.risk_level.in_(["medium", "high"])
|
||||
(Supplier.blacklist_status != StatusValue.NORMAL)
|
||||
| Supplier.risk_level.in_(SUPPLIER_RISK_LEVELS)
|
||||
)
|
||||
payloads = []
|
||||
for supplier in self.db.execute(stmt).scalars():
|
||||
level = "high" if supplier.blacklist_status != "normal" else supplier.risk_level
|
||||
level = (
|
||||
RiskLevel.HIGH
|
||||
if supplier.blacklist_status != StatusValue.NORMAL
|
||||
else supplier.risk_level
|
||||
)
|
||||
payloads.append(
|
||||
{
|
||||
"code": f"RISK-SUPPLIER-{supplier.id}",
|
||||
"title": f"供应商风险:{supplier.name}",
|
||||
"risk_type": "supplier_risk",
|
||||
"risk_type": RiskEventType.SUPPLIER_RISK,
|
||||
"risk_level": level,
|
||||
"status": "open",
|
||||
"source_domain": "suppliers",
|
||||
"status": StatusValue.OPEN,
|
||||
"source_domain": BusinessDomain.SUPPLIERS,
|
||||
"source_record_id": str(supplier.id),
|
||||
"owner": supplier.contact,
|
||||
"detected_at": datetime.utcnow(),
|
||||
"description": "供应商风险等级或黑名单状态需要关注。",
|
||||
"mitigation": "请采购负责人复核供应商准入、履约和替代方案。",
|
||||
"mitigation": (
|
||||
"请采购负责人复核供应商准入、履约和替代方案。"
|
||||
),
|
||||
"evidence": serialize_model(supplier),
|
||||
}
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user