feat(risk): 实现AI项目风险评估功能
- 新增RiskAssessmentResult DTO,定义AI风险评估结果结构
- 在RiskService接口和实现中添加assessProjectRisk方法
- RiskServiceImpl中集成ChatClient调用AI进行全面风险评估
- 构建项目数据上下文包含基本信息、任务统计、团队成员、风险数量、进度和预算等
- 解析AI返回结果,存储识别风险至数据库并更新项目风险等级
- RiskController新增接口 /assess/{projectId} 提供REST风评估入口
- 完善日志记录,捕获异常并返回友好错误信息
This commit is contained in:
@@ -6,6 +6,7 @@ import cn.yinlihupo.common.util.ResultUtils;
|
||||
import cn.yinlihupo.common.util.SecurityUtils;
|
||||
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||
import cn.yinlihupo.domain.vo.RiskAssessmentResult;
|
||||
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||
import cn.yinlihupo.domain.vo.RiskVO;
|
||||
import cn.yinlihupo.service.risk.RiskService;
|
||||
@@ -196,4 +197,27 @@ public class RiskController {
|
||||
return ResultUtils.error("更新失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== AI 风险评估接口 ====================
|
||||
|
||||
/**
|
||||
* AI风险评估
|
||||
* 使用AI能力对项目整体的进度、人员、资金等会影响项目开展的所有因素进行风险评估
|
||||
* 评估完成后自动将识别的风险入库
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 风险评估结果
|
||||
*/
|
||||
@PostMapping("/assess/{projectId}")
|
||||
public BaseResponse<RiskAssessmentResult> assessProjectRisk(@PathVariable Long projectId) {
|
||||
log.info("AI风险评估, projectId: {}", projectId);
|
||||
|
||||
try {
|
||||
RiskAssessmentResult result = riskService.assessProjectRisk(projectId);
|
||||
return ResultUtils.success("风险评估完成", result);
|
||||
} catch (Exception e) {
|
||||
log.error("AI风险评估失败: {}", e.getMessage(), e);
|
||||
return ResultUtils.error("风险评估失败: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
249
src/main/java/cn/yinlihupo/domain/vo/RiskAssessmentResult.java
Normal file
249
src/main/java/cn/yinlihupo/domain/vo/RiskAssessmentResult.java
Normal file
@@ -0,0 +1,249 @@
|
||||
package cn.yinlihupo.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* AI项目风险评估结果DTO
|
||||
* 使用AI对项目整体进行风险评估,分析进度、人员、资金等影响项目开展的因素
|
||||
*/
|
||||
@Data
|
||||
public class RiskAssessmentResult {
|
||||
|
||||
/**
|
||||
* 项目ID
|
||||
*/
|
||||
private Long projectId;
|
||||
|
||||
/**
|
||||
* 项目名称
|
||||
*/
|
||||
private String projectName;
|
||||
|
||||
/**
|
||||
* 整体风险评估等级: critical-严重, high-高, medium-中, low-低
|
||||
*/
|
||||
@JsonProperty("overall_risk_level")
|
||||
private String overallRiskLevel;
|
||||
|
||||
/**
|
||||
* 整体风险得分(0-100)
|
||||
*/
|
||||
@JsonProperty("overall_risk_score")
|
||||
private BigDecimal overallRiskScore;
|
||||
|
||||
/**
|
||||
* 风险评估摘要
|
||||
*/
|
||||
@JsonProperty("assessment_summary")
|
||||
private String assessmentSummary;
|
||||
|
||||
/**
|
||||
* 关键风险领域
|
||||
*/
|
||||
@JsonProperty("risk_areas")
|
||||
private List<RiskArea> riskAreas;
|
||||
|
||||
/**
|
||||
* 识别的风险列表
|
||||
*/
|
||||
@JsonProperty("identified_risks")
|
||||
private List<IdentifiedRisk> identifiedRisks;
|
||||
|
||||
/**
|
||||
* 风险应对建议
|
||||
*/
|
||||
private List<RiskRecommendation> recommendations;
|
||||
|
||||
/**
|
||||
* 评估时间
|
||||
*/
|
||||
private LocalDate assessmentDate;
|
||||
|
||||
// ==================== 内部类定义 ====================
|
||||
|
||||
/**
|
||||
* 风险领域评估
|
||||
*/
|
||||
@Data
|
||||
public static class RiskArea {
|
||||
/**
|
||||
* 领域名称: schedule-进度, resource-资源, budget-预算, quality-质量, personnel-人员, external-外部
|
||||
*/
|
||||
@JsonProperty("area_name")
|
||||
private String areaName;
|
||||
|
||||
/**
|
||||
* 风险等级
|
||||
*/
|
||||
@JsonProperty("risk_level")
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* 风险得分(0-100)
|
||||
*/
|
||||
@JsonProperty("risk_score")
|
||||
private Integer riskScore;
|
||||
|
||||
/**
|
||||
* 风险描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 关键指标
|
||||
*/
|
||||
@JsonProperty("key_indicators")
|
||||
private List<KeyIndicator> keyIndicators;
|
||||
}
|
||||
|
||||
/**
|
||||
* 关键指标
|
||||
*/
|
||||
@Data
|
||||
public static class KeyIndicator {
|
||||
/**
|
||||
* 指标名称
|
||||
*/
|
||||
@JsonProperty("indicator_name")
|
||||
private String indicatorName;
|
||||
|
||||
/**
|
||||
* 当前值
|
||||
*/
|
||||
@JsonProperty("current_value")
|
||||
private String currentValue;
|
||||
|
||||
/**
|
||||
* 目标值/预期值
|
||||
*/
|
||||
@JsonProperty("target_value")
|
||||
private String targetValue;
|
||||
|
||||
/**
|
||||
* 状态: normal-正常, warning-警告, critical-严重
|
||||
*/
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 说明
|
||||
*/
|
||||
private String description;
|
||||
}
|
||||
|
||||
/**
|
||||
* 识别的风险
|
||||
*/
|
||||
@Data
|
||||
public static class IdentifiedRisk {
|
||||
/**
|
||||
* 风险名称
|
||||
*/
|
||||
@JsonProperty("risk_name")
|
||||
private String riskName;
|
||||
|
||||
/**
|
||||
* 风险分类: technical-技术风险, schedule-进度风险, cost-成本风险, quality-质量风险, resource-资源风险, external-外部风险, personnel-人员风险
|
||||
*/
|
||||
private String category;
|
||||
|
||||
/**
|
||||
* 风险描述
|
||||
*/
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 发生概率(0-100%)
|
||||
*/
|
||||
private Integer probability;
|
||||
|
||||
/**
|
||||
* 影响程度(1-5)
|
||||
*/
|
||||
private Integer impact;
|
||||
|
||||
/**
|
||||
* 风险等级
|
||||
*/
|
||||
@JsonProperty("risk_level")
|
||||
private String riskLevel;
|
||||
|
||||
/**
|
||||
* 影响范围
|
||||
*/
|
||||
@JsonProperty("impact_scope")
|
||||
private String impactScope;
|
||||
|
||||
/**
|
||||
* 触发条件
|
||||
*/
|
||||
@JsonProperty("trigger_condition")
|
||||
private String triggerCondition;
|
||||
|
||||
/**
|
||||
* 缓解措施
|
||||
*/
|
||||
@JsonProperty("mitigation_plan")
|
||||
private String mitigationPlan;
|
||||
|
||||
/**
|
||||
* 应急计划
|
||||
*/
|
||||
@JsonProperty("contingency_plan")
|
||||
private String contingencyPlan;
|
||||
|
||||
/**
|
||||
* 建议优先级: critical-紧急, high-高, medium-中, low-低
|
||||
*/
|
||||
private String priority;
|
||||
}
|
||||
|
||||
/**
|
||||
* 风险应对建议
|
||||
*/
|
||||
@Data
|
||||
public static class RiskRecommendation {
|
||||
/**
|
||||
* 建议标题
|
||||
*/
|
||||
private String title;
|
||||
|
||||
/**
|
||||
* 建议类型: preventive-预防性, mitigative-缓解性, contingent-应急性
|
||||
*/
|
||||
@JsonProperty("recommendation_type")
|
||||
private String recommendationType;
|
||||
|
||||
/**
|
||||
* 建议内容
|
||||
*/
|
||||
private String content;
|
||||
|
||||
/**
|
||||
* 优先级
|
||||
*/
|
||||
private String priority;
|
||||
|
||||
/**
|
||||
* 预期效果
|
||||
*/
|
||||
@JsonProperty("expected_effect")
|
||||
private String expectedEffect;
|
||||
|
||||
/**
|
||||
* 实施时间建议
|
||||
*/
|
||||
@JsonProperty("suggested_timeline")
|
||||
private String suggestedTimeline;
|
||||
|
||||
/**
|
||||
* 责任方建议
|
||||
*/
|
||||
@JsonProperty("responsible_party")
|
||||
private String responsibleParty;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package cn.yinlihupo.service.risk;
|
||||
import cn.yinlihupo.common.page.TableDataInfo;
|
||||
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||
import cn.yinlihupo.domain.vo.RiskAssessmentResult;
|
||||
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||
import cn.yinlihupo.domain.vo.RiskVO;
|
||||
|
||||
@@ -84,4 +85,15 @@ public interface RiskService {
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean batchUpdateStatus(java.util.List<Long> riskIds, String status);
|
||||
|
||||
// ==================== AI 风险评估相关接口 ====================
|
||||
|
||||
/**
|
||||
* 对项目进行AI风险评估并入库
|
||||
* 使用AI能力对项目整体的进度、人员、资金等会影响项目开展的所有因素进行风险评估
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @return 风险评估结果(已入库的风险ID列表)
|
||||
*/
|
||||
RiskAssessmentResult assessProjectRisk(Long projectId);
|
||||
}
|
||||
|
||||
@@ -6,14 +6,19 @@ import cn.yinlihupo.common.util.SecurityUtils;
|
||||
import cn.yinlihupo.domain.dto.CreateRiskRequest;
|
||||
import cn.yinlihupo.domain.dto.CreateWorkOrderRequest;
|
||||
import cn.yinlihupo.domain.entity.Project;
|
||||
import cn.yinlihupo.domain.entity.ProjectMember;
|
||||
import cn.yinlihupo.domain.entity.Risk;
|
||||
import cn.yinlihupo.domain.entity.SysUser;
|
||||
import cn.yinlihupo.domain.entity.Task;
|
||||
import cn.yinlihupo.domain.entity.WorkOrder;
|
||||
import cn.yinlihupo.domain.vo.RiskAssessmentResult;
|
||||
import cn.yinlihupo.domain.vo.RiskStatisticsVO;
|
||||
import cn.yinlihupo.domain.vo.RiskVO;
|
||||
import cn.yinlihupo.mapper.ProjectMapper;
|
||||
import cn.yinlihupo.mapper.ProjectMemberMapper;
|
||||
import cn.yinlihupo.mapper.RiskMapper;
|
||||
import cn.yinlihupo.mapper.SysUserMapper;
|
||||
import cn.yinlihupo.mapper.TaskMapper;
|
||||
import cn.yinlihupo.mapper.WorkOrderMapper;
|
||||
import cn.yinlihupo.service.risk.RiskService;
|
||||
import cn.yinlihupo.service.risk.WorkOrderService;
|
||||
@@ -21,6 +26,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.ai.chat.client.ChatClient;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -41,9 +47,95 @@ public class RiskServiceImpl implements RiskService {
|
||||
|
||||
private final RiskMapper riskMapper;
|
||||
private final ProjectMapper projectMapper;
|
||||
private final ProjectMemberMapper projectMemberMapper;
|
||||
private final TaskMapper taskMapper;
|
||||
private final SysUserMapper sysUserMapper;
|
||||
private final WorkOrderMapper workOrderMapper;
|
||||
private final WorkOrderService workOrderService;
|
||||
private final ChatClient chatClient;
|
||||
|
||||
/**
|
||||
* AI风险评估系统提示词模板
|
||||
*/
|
||||
private static final String RISK_ASSESSMENT_SYSTEM_PROMPT = """
|
||||
# 角色
|
||||
|
||||
你是一个专业的项目风险管理专家,擅长对项目进行全面的风险评估和分析。
|
||||
|
||||
# 任务
|
||||
|
||||
根据提供的项目数据,对项目整体进行风险评估,分析以下方面的风险因素:
|
||||
1. 进度风险 - 项目时间节点、里程碑、任务完成情况
|
||||
2. 资源风险 - 人力、物料、设备等资源分配情况
|
||||
3. 预算风险 - 成本控制、预算执行情况
|
||||
4. 质量风险 - 交付物质量、验收标准
|
||||
5. 人员风险 - 团队配置、人员能力、人员流动
|
||||
6. 外部风险 - 政策变化、市场环境、供应商等
|
||||
|
||||
# 输出格式
|
||||
|
||||
请严格按照以下JSON格式输出:
|
||||
|
||||
```json
|
||||
{
|
||||
"overall_risk_level": "critical/high/medium/low",
|
||||
"overall_risk_score": 75,
|
||||
"assessment_summary": "整体风险评估摘要,说明主要风险点和建议",
|
||||
"risk_areas": [
|
||||
{
|
||||
"area_name": "schedule/resource/budget/quality/personnel/external",
|
||||
"risk_level": "critical/high/medium/low",
|
||||
"risk_score": 80,
|
||||
"description": "该领域的风险描述",
|
||||
"key_indicators": [
|
||||
{
|
||||
"indicator_name": "指标名称",
|
||||
"current_value": "当前值",
|
||||
"target_value": "目标值",
|
||||
"status": "normal/warning/critical",
|
||||
"description": "指标说明"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"identified_risks": [
|
||||
{
|
||||
"risk_name": "风险名称",
|
||||
"category": "technical/schedule/cost/quality/resource/external/personnel",
|
||||
"description": "风险详细描述",
|
||||
"probability": 60,
|
||||
"impact": 4,
|
||||
"risk_level": "high",
|
||||
"impact_scope": "影响范围描述",
|
||||
"trigger_condition": "触发条件",
|
||||
"mitigation_plan": "缓解措施",
|
||||
"contingency_plan": "应急计划",
|
||||
"priority": "critical/high/medium/low"
|
||||
}
|
||||
],
|
||||
"recommendations": [
|
||||
{
|
||||
"title": "建议标题",
|
||||
"recommendation_type": "preventive/mitigative/contingent",
|
||||
"content": "具体建议内容",
|
||||
"priority": "high",
|
||||
"expected_effect": "预期效果",
|
||||
"suggested_timeline": "建议实施时间",
|
||||
"responsible_party": "责任方"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
# 注意事项
|
||||
|
||||
1. probability(发生概率) 范围 0-100
|
||||
2. impact(影响程度) 范围 1-5
|
||||
3. 风险得分计算:probability * impact / 5 * 100,得出0-100的分数
|
||||
4. 风险等级判定:score>=80为critical,60<=score<80为high,40<=score<60为medium,score<40为low
|
||||
5. 识别的风险应该具体且可操作,不要泛泛而谈
|
||||
6. 建议要结合项目实际情况,有针对性
|
||||
""";
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@@ -199,12 +291,8 @@ public class RiskServiceImpl implements RiskService {
|
||||
.like(Risk::getDescription, keyword));
|
||||
}
|
||||
|
||||
// 按风险等级和得分排序
|
||||
wrapper.orderByAsc(
|
||||
r -> "critical".equals(r.getRiskLevel()) ? 1 :
|
||||
"high".equals(r.getRiskLevel()) ? 2 :
|
||||
"medium".equals(r.getRiskLevel()) ? 3 : 4
|
||||
).orderByDesc(Risk::getRiskScore);
|
||||
// 按风险等级和得分排序(使用原生SQL避免Lambda表达式问题)
|
||||
wrapper.last("ORDER BY CASE risk_level WHEN 'critical' THEN 1 WHEN 'high' THEN 2 WHEN 'medium' THEN 3 ELSE 4 END ASC, risk_score DESC");
|
||||
|
||||
Page<Risk> riskPage = riskMapper.selectPage(page, wrapper);
|
||||
|
||||
@@ -413,4 +501,217 @@ public class RiskServiceImpl implements RiskService {
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
// ==================== AI 风险评估相关实现 ====================
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public RiskAssessmentResult assessProjectRisk(Long projectId) {
|
||||
log.info("开始AI风险评估, projectId: {}", projectId);
|
||||
|
||||
// 1. 验证项目存在
|
||||
Project project = projectMapper.selectById(projectId);
|
||||
if (project == null || project.getDeleted() == 1) {
|
||||
throw new RuntimeException("项目不存在");
|
||||
}
|
||||
|
||||
// 2. 构建项目数据上下文
|
||||
String projectDataContext = buildProjectDataContext(projectId);
|
||||
|
||||
// 3. 构建用户提示词
|
||||
String userPrompt = """
|
||||
请根据以下项目数据,进行全面的风险评估分析:
|
||||
|
||||
%s
|
||||
|
||||
请严格按照系统提示词中的JSON格式输出评估结果,确保识别的风险具体且可操作。
|
||||
""".formatted(projectDataContext);
|
||||
|
||||
// 4. 调用AI进行风险评估
|
||||
log.info("调用AI进行风险评估...");
|
||||
RiskAssessmentResult result = chatClient.prompt()
|
||||
.system(RISK_ASSESSMENT_SYSTEM_PROMPT)
|
||||
.user(userPrompt)
|
||||
.call()
|
||||
.entity(RiskAssessmentResult.class);
|
||||
|
||||
// 5. 设置项目信息
|
||||
result.setProjectId(projectId);
|
||||
result.setProjectName(project.getProjectName());
|
||||
result.setAssessmentDate(LocalDate.now());
|
||||
|
||||
// 6. 将识别的风险入库
|
||||
if (result.getIdentifiedRisks() != null && !result.getIdentifiedRisks().isEmpty()) {
|
||||
List<Long> savedRiskIds = saveIdentifiedRisks(result.getIdentifiedRisks(), projectId);
|
||||
log.info("已保存 {} 个识别的风险", savedRiskIds.size());
|
||||
}
|
||||
|
||||
// 7. 更新项目的风险等级
|
||||
if (result.getOverallRiskLevel() != null) {
|
||||
project.setRiskLevel(result.getOverallRiskLevel());
|
||||
projectMapper.updateById(project);
|
||||
}
|
||||
|
||||
log.info("AI风险评估完成, overallRiskLevel: {}", result.getOverallRiskLevel());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建项目数据上下文
|
||||
*/
|
||||
private String buildProjectDataContext(Long projectId) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
|
||||
// 项目基本信息
|
||||
Project project = projectMapper.selectById(projectId);
|
||||
sb.append("## 项目基本信息\n");
|
||||
sb.append(String.format("- 项目名称: %s\n", project.getProjectName()));
|
||||
sb.append(String.format("- 项目类型: %s\n", project.getProjectType()));
|
||||
sb.append(String.format("- 项目状态: %s\n", project.getStatus()));
|
||||
sb.append(String.format("- 计划开始日期: %s\n", project.getPlanStartDate()));
|
||||
sb.append(String.format("- 计划结束日期: %s\n", project.getPlanEndDate()));
|
||||
sb.append(String.format("- 实际开始日期: %s\n", project.getActualStartDate()));
|
||||
sb.append(String.format("- 当前进度: %s%%\n", project.getProgress()));
|
||||
sb.append(String.format("- 项目预算: %s %s\n", project.getBudget(), project.getCurrency()));
|
||||
sb.append(String.format("- 已花费成本: %s %s\n", project.getCost(), project.getCurrency()));
|
||||
sb.append(String.format("- 优先级: %s\n", project.getPriority()));
|
||||
sb.append("\n");
|
||||
|
||||
// 使用 MyBatis 查询关联数据
|
||||
// 查询任务统计
|
||||
long taskCount = taskMapper.selectCount(
|
||||
new LambdaQueryWrapper<Task>()
|
||||
.eq(Task::getProjectId, projectId)
|
||||
.eq(Task::getDeleted, 0)
|
||||
);
|
||||
long completedTaskCount = taskMapper.selectCount(
|
||||
new LambdaQueryWrapper<Task>()
|
||||
.eq(Task::getProjectId, projectId)
|
||||
.eq(Task::getDeleted, 0)
|
||||
.eq(Task::getStatus, "completed")
|
||||
);
|
||||
sb.append("## 任务统计\n");
|
||||
sb.append(String.format("- 任务总数: %d\n", taskCount));
|
||||
sb.append(String.format("- 已完成任务: %d\n", completedTaskCount));
|
||||
sb.append(String.format("- 任务完成率: %.1f%%\n", taskCount > 0 ? (completedTaskCount * 100.0 / taskCount) : 0));
|
||||
sb.append("\n");
|
||||
|
||||
// 查询成员数量
|
||||
long memberCount = projectMemberMapper.selectCount(
|
||||
new LambdaQueryWrapper<ProjectMember>()
|
||||
.eq(ProjectMember::getProjectId, projectId)
|
||||
.eq(ProjectMember::getDeleted, 0)
|
||||
);
|
||||
sb.append("## 团队成员\n");
|
||||
sb.append(String.format("- 团队人数: %d\n", memberCount));
|
||||
sb.append("\n");
|
||||
|
||||
// 查询已识别风险数量
|
||||
long existingRiskCount = riskMapper.selectCount(
|
||||
new LambdaQueryWrapper<Risk>()
|
||||
.eq(Risk::getProjectId, projectId)
|
||||
.eq(Risk::getDeleted, 0)
|
||||
);
|
||||
long highRiskCount = riskMapper.selectCount(
|
||||
new LambdaQueryWrapper<Risk>()
|
||||
.eq(Risk::getProjectId, projectId)
|
||||
.eq(Risk::getDeleted, 0)
|
||||
.in(Risk::getRiskLevel, "critical", "high")
|
||||
);
|
||||
sb.append("## 已识别风险\n");
|
||||
sb.append(String.format("- 已识别风险数: %d\n", existingRiskCount));
|
||||
sb.append(String.format("- 高风险数量: %d\n", highRiskCount));
|
||||
sb.append("\n");
|
||||
|
||||
// 计算项目进度偏差
|
||||
if (project.getPlanEndDate() != null && project.getPlanStartDate() != null) {
|
||||
LocalDate now = LocalDate.now();
|
||||
long totalDays = java.time.temporal.ChronoUnit.DAYS.between(project.getPlanStartDate(), project.getPlanEndDate());
|
||||
long elapsedDays = java.time.temporal.ChronoUnit.DAYS.between(project.getPlanStartDate(), now);
|
||||
elapsedDays = Math.max(0, Math.min(elapsedDays, totalDays));
|
||||
double expectedProgress = totalDays > 0 ? (elapsedDays * 100.0 / totalDays) : 0;
|
||||
int progressDeviation = project.getProgress() != null ? (int)(project.getProgress() - expectedProgress) : 0;
|
||||
|
||||
sb.append("## 进度分析\n");
|
||||
sb.append(String.format("- 计划工期: %d 天\n", totalDays));
|
||||
sb.append(String.format("- 已过时间: %d 天\n", elapsedDays));
|
||||
sb.append(String.format("- 预期进度: %.1f%%\n", expectedProgress));
|
||||
sb.append(String.format("- 实际进度: %d%%\n", project.getProgress() != null ? project.getProgress() : 0));
|
||||
sb.append(String.format("- 进度偏差: %+.1f%%\n", (double) progressDeviation));
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
// 预算使用情况
|
||||
if (project.getBudget() != null && project.getBudget().compareTo(BigDecimal.ZERO) > 0) {
|
||||
BigDecimal cost = project.getCost() != null ? project.getCost() : BigDecimal.ZERO;
|
||||
double budgetUsage = cost.multiply(BigDecimal.valueOf(100))
|
||||
.divide(project.getBudget(), 2, RoundingMode.HALF_UP).doubleValue();
|
||||
sb.append("## 预算使用\n");
|
||||
sb.append(String.format("- 预算使用率: %.1f%%\n", budgetUsage));
|
||||
sb.append(String.format("- 剩余预算: %s %s\n",
|
||||
project.getBudget().subtract(cost), project.getCurrency()));
|
||||
sb.append("\n");
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存识别的风险到数据库
|
||||
*/
|
||||
private List<Long> saveIdentifiedRisks(List<RiskAssessmentResult.IdentifiedRisk> risks, Long projectId) {
|
||||
List<Long> savedIds = new ArrayList<>();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
|
||||
for (RiskAssessmentResult.IdentifiedRisk riskInfo : risks) {
|
||||
Risk risk = new Risk();
|
||||
risk.setProjectId(projectId);
|
||||
risk.setRiskCode(generateRiskCode());
|
||||
risk.setRiskName(riskInfo.getRiskName());
|
||||
risk.setCategory(riskInfo.getCategory() != null ? riskInfo.getCategory() : "other");
|
||||
risk.setDescription(riskInfo.getDescription());
|
||||
risk.setRiskSource("ai_detection");
|
||||
|
||||
if (riskInfo.getProbability() != null) {
|
||||
risk.setProbability(BigDecimal.valueOf(riskInfo.getProbability()));
|
||||
}
|
||||
if (riskInfo.getImpact() != null) {
|
||||
risk.setImpact(BigDecimal.valueOf(riskInfo.getImpact()));
|
||||
}
|
||||
|
||||
// 计算风险得分
|
||||
if (risk.getProbability() != null && risk.getImpact() != null) {
|
||||
BigDecimal score = risk.getProbability()
|
||||
.multiply(risk.getImpact())
|
||||
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP);
|
||||
risk.setRiskScore(score);
|
||||
}
|
||||
|
||||
// 设置风险等级
|
||||
if (riskInfo.getRiskLevel() != null) {
|
||||
risk.setRiskLevel(riskInfo.getRiskLevel());
|
||||
} else {
|
||||
risk.setRiskLevel(calculateRiskLevel(riskInfo.getProbability(), riskInfo.getImpact()));
|
||||
}
|
||||
|
||||
risk.setMitigationPlan(riskInfo.getMitigationPlan());
|
||||
risk.setContingencyPlan(riskInfo.getContingencyPlan());
|
||||
risk.setTriggerCondition(riskInfo.getTriggerCondition());
|
||||
risk.setStatus("identified");
|
||||
risk.setDiscoverTime(now);
|
||||
|
||||
// 存储 AI 分析结果
|
||||
Map<String, Object> aiAnalysis = new HashMap<>();
|
||||
aiAnalysis.put("impact_scope", riskInfo.getImpactScope());
|
||||
aiAnalysis.put("priority", riskInfo.getPriority());
|
||||
aiAnalysis.put("assessment_date", LocalDate.now().toString());
|
||||
risk.setAiAnalysis(aiAnalysis);
|
||||
|
||||
riskMapper.insert(risk);
|
||||
savedIds.add(risk.getId());
|
||||
log.debug("保存风险: {}, ID: {}", risk.getRiskName(), risk.getId());
|
||||
}
|
||||
|
||||
return savedIds;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user