feat(wm-dispatch): #70 应急推演(爆管模拟+水质异常+演练管理)
- 爆管模拟: 影响范围/用户/水量损失/修复时间/关阀方案 - 水质异常: 事件上报/严重度评估/预案匹配/响应流程/处置归档 - 应急演练: 计划创建/执行/完成/评估打分 - 4个Entity + 4个Mapper + 3个Service + 1个Controller(15端点) - DDL: 4张表 + 4个索引 - 单元测试: 3个测试类
This commit is contained in:
@@ -0,0 +1,109 @@
|
|||||||
|
package com.water.dispatch.controller;
|
||||||
|
|
||||||
|
import com.water.common.core.result.R;
|
||||||
|
import com.water.dispatch.entity.*;
|
||||||
|
import com.water.dispatch.entity.dto.*;
|
||||||
|
import com.water.dispatch.service.*;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Tag(name = "应急推演")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/dispatch/emergency")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class EmergencyController {
|
||||||
|
|
||||||
|
private final PipeBurstService pipeBurstService;
|
||||||
|
private final WaterQualityIncidentService waterQualityService;
|
||||||
|
private final EmergencyDrillService drillService;
|
||||||
|
|
||||||
|
// === 爆管模拟 ===
|
||||||
|
@PostMapping("/pipe-burst/simulate")
|
||||||
|
public R<PipeBurstSimulation> simulatePipeBurst(@RequestBody PipeBurstRequest request) {
|
||||||
|
return R.ok(pipeBurstService.simulate(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pipe-burst/{id}/impact")
|
||||||
|
public R<Map<String, Object>> getImpactAnalysis(@PathVariable Long id) {
|
||||||
|
return R.ok(pipeBurstService.getImpactAnalysis(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pipe-burst/{id}/valve-plan")
|
||||||
|
public R<Map<String, Object>> getValvePlan(@PathVariable Long id) {
|
||||||
|
return R.ok(pipeBurstService.getValveShutdownPlan(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/pipe-burst/list")
|
||||||
|
public R<List<PipeBurstSimulation>> listSimulations(@RequestParam(required = false) String status) {
|
||||||
|
return R.ok(pipeBurstService.listSimulations(status));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 水质异常处置 ===
|
||||||
|
@PostMapping("/water-quality/report")
|
||||||
|
public R<WaterQualityIncident> reportIncident(@RequestBody WaterQualityRequest request) {
|
||||||
|
return R.ok(waterQualityService.reportIncident(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/water-quality/{type}/plan")
|
||||||
|
public R<Map<String, Object>> matchPlan(@PathVariable String type) {
|
||||||
|
return R.ok(waterQualityService.matchPlan(type));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/water-quality/{id}/start-response")
|
||||||
|
public R<Map<String, Object>> startResponse(@PathVariable Long id) {
|
||||||
|
return R.ok(waterQualityService.startResponse(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/water-quality/{id}/progress")
|
||||||
|
public R<Map<String, Object>> updateProgress(@PathVariable Long id,
|
||||||
|
@RequestParam String progress,
|
||||||
|
@RequestParam String operator) {
|
||||||
|
return R.ok(waterQualityService.updateProgress(id, progress, operator));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/water-quality/{id}/resolve")
|
||||||
|
public R<WaterQualityIncident> resolve(@PathVariable Long id, @RequestParam String resolution) {
|
||||||
|
return R.ok(waterQualityService.resolveIncident(id, resolution));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/water-quality/list")
|
||||||
|
public R<List<WaterQualityIncident>> listIncidents(
|
||||||
|
@RequestParam(required = false) String status,
|
||||||
|
@RequestParam(required = false) String pollutantType) {
|
||||||
|
return R.ok(waterQualityService.listIncidents(status, pollutantType));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/water-quality/{id}/detail")
|
||||||
|
public R<Map<String, Object>> getIncidentDetail(@PathVariable Long id) {
|
||||||
|
return R.ok(waterQualityService.getIncidentDetail(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
// === 应急演练 ===
|
||||||
|
@PostMapping("/drill")
|
||||||
|
public R<EmergencyDrill> createDrill(@RequestBody DrillCreateRequest request) {
|
||||||
|
return R.ok(drillService.createDrill(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/drill/{id}/start")
|
||||||
|
public R<EmergencyDrill> startDrill(@PathVariable Long id) {
|
||||||
|
return R.ok(drillService.startDrill(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/drill/{id}/complete")
|
||||||
|
public R<EmergencyDrill> completeDrill(@PathVariable Long id) {
|
||||||
|
return R.ok(drillService.completeDrill(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/drill/{id}/evaluate")
|
||||||
|
public R<DrillEvaluation> evaluateDrill(@PathVariable Long id, @RequestBody DrillEvaluationRequest request) {
|
||||||
|
return R.ok(drillService.evaluateDrill(id, request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/drill/list")
|
||||||
|
public R<List<EmergencyDrill>> listDrills(@RequestParam(required = false) String status) {
|
||||||
|
return R.ok(drillService.listDrills(status));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.water.dispatch.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("disp_drill_evaluation")
|
||||||
|
public class DrillEvaluation {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String evaluationNo;
|
||||||
|
private Long drillId;
|
||||||
|
private String drillNo;
|
||||||
|
private Long evaluatorId;
|
||||||
|
private String evaluatorName;
|
||||||
|
private Integer responseScore;
|
||||||
|
private Integer handlingScore;
|
||||||
|
private Integer coordinationScore;
|
||||||
|
private Integer resourceScore;
|
||||||
|
private Integer reportingScore;
|
||||||
|
private Integer overallScore;
|
||||||
|
private String grade;
|
||||||
|
private String evaluationDetails;
|
||||||
|
private String strengths;
|
||||||
|
private String weaknesses;
|
||||||
|
private String recommendations;
|
||||||
|
private String followUpActions;
|
||||||
|
private String status;
|
||||||
|
private LocalDateTime evaluatedAt;
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package com.water.dispatch.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("disp_emergency_drill")
|
||||||
|
public class EmergencyDrill {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String drillNo;
|
||||||
|
private String name;
|
||||||
|
private String drillType;
|
||||||
|
private String scenario;
|
||||||
|
private String objectives;
|
||||||
|
private String planContent;
|
||||||
|
private String participatingDepts;
|
||||||
|
private Integer participantCount;
|
||||||
|
private Long organizerId;
|
||||||
|
private String organizerName;
|
||||||
|
private String location;
|
||||||
|
private LocalDate plannedDate;
|
||||||
|
private LocalDateTime plannedStartTime;
|
||||||
|
private LocalDateTime plannedEndTime;
|
||||||
|
private LocalDateTime actualStartTime;
|
||||||
|
private LocalDateTime actualEndTime;
|
||||||
|
private String status;
|
||||||
|
private String executionLog;
|
||||||
|
private String summary;
|
||||||
|
private String issuesFound;
|
||||||
|
private String improvements;
|
||||||
|
private Long relatedPlanId;
|
||||||
|
private String relatedPlanName;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
private String remark;
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.water.dispatch.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("disp_pipe_burst_simulation")
|
||||||
|
public class PipeBurstSimulation {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String simulationNo;
|
||||||
|
private Long pipeId;
|
||||||
|
private String pipeNo;
|
||||||
|
private Double longitude;
|
||||||
|
private Double latitude;
|
||||||
|
private String location;
|
||||||
|
private Double pipeDiameter;
|
||||||
|
private String pipeMaterial;
|
||||||
|
private Double pipePressure;
|
||||||
|
private Double impactRadius;
|
||||||
|
private Double impactArea;
|
||||||
|
private Integer affectedUsers;
|
||||||
|
private String affectedRegion;
|
||||||
|
private Double leakageRate;
|
||||||
|
private Double estimatedRepairHours;
|
||||||
|
private String valveShutdownPlan;
|
||||||
|
private Integer valveAffectedUsers;
|
||||||
|
private String status;
|
||||||
|
private String simulationParams;
|
||||||
|
private String simulationResult;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
private String remark;
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.water.dispatch.entity;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("disp_water_quality_incident")
|
||||||
|
public class WaterQualityIncident {
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
private String incidentNo;
|
||||||
|
private String title;
|
||||||
|
private String description;
|
||||||
|
private String sourceType;
|
||||||
|
private Long monitorPointId;
|
||||||
|
private String monitorPointName;
|
||||||
|
private String abnormalIndicator;
|
||||||
|
private Double detectedValue;
|
||||||
|
private Double standardValue;
|
||||||
|
private Double exceedMultiple;
|
||||||
|
private String severityLevel;
|
||||||
|
private String status;
|
||||||
|
private Long matchedPlanId;
|
||||||
|
private String matchedPlanName;
|
||||||
|
private String handlingPlan;
|
||||||
|
private String handlingMeasures;
|
||||||
|
private Integer handlingProgress;
|
||||||
|
private Long handlerId;
|
||||||
|
private String handlerName;
|
||||||
|
private LocalDateTime detectedTime;
|
||||||
|
private LocalDateTime confirmedTime;
|
||||||
|
private LocalDateTime handlingStartTime;
|
||||||
|
private LocalDateTime resolvedTime;
|
||||||
|
private String affectedArea;
|
||||||
|
private Integer affectedPopulation;
|
||||||
|
private String warningMessage;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
private String remark;
|
||||||
|
@TableLogic
|
||||||
|
private Integer deleted;
|
||||||
|
private LocalDateTime createdAt;
|
||||||
|
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||||
|
private LocalDateTime updatedAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.water.dispatch.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DrillCreateRequest {
|
||||||
|
private String name;
|
||||||
|
private String drillType;
|
||||||
|
private String scenario;
|
||||||
|
private String objectives;
|
||||||
|
private String planContent;
|
||||||
|
private String participatingDepts;
|
||||||
|
private Integer participantCount;
|
||||||
|
private Long organizerId;
|
||||||
|
private String organizerName;
|
||||||
|
private String location;
|
||||||
|
private LocalDate plannedDate;
|
||||||
|
private LocalDateTime plannedStartTime;
|
||||||
|
private LocalDateTime plannedEndTime;
|
||||||
|
private Long relatedPlanId;
|
||||||
|
private String relatedPlanName;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.water.dispatch.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class DrillEvaluationRequest {
|
||||||
|
private Long drillId;
|
||||||
|
private Long evaluatorId;
|
||||||
|
private String evaluatorName;
|
||||||
|
private Integer responseScore;
|
||||||
|
private Integer handlingScore;
|
||||||
|
private Integer coordinationScore;
|
||||||
|
private Integer resourceScore;
|
||||||
|
private Integer reportingScore;
|
||||||
|
private String evaluationDetails;
|
||||||
|
private String strengths;
|
||||||
|
private String weaknesses;
|
||||||
|
private String recommendations;
|
||||||
|
private String followUpActions;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.water.dispatch.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class PipeBurstRequest {
|
||||||
|
private Long pipeId;
|
||||||
|
private String pipeNo;
|
||||||
|
private Double longitude;
|
||||||
|
private Double latitude;
|
||||||
|
private String location;
|
||||||
|
private Double pipeDiameter;
|
||||||
|
private String pipeMaterial;
|
||||||
|
private Double pipePressure;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
private String remark;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.water.dispatch.entity.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class WaterQualityRequest {
|
||||||
|
private String title;
|
||||||
|
private String description;
|
||||||
|
private String sourceType;
|
||||||
|
private Long monitorPointId;
|
||||||
|
private String monitorPointName;
|
||||||
|
private String abnormalIndicator;
|
||||||
|
private Double detectedValue;
|
||||||
|
private Double standardValue;
|
||||||
|
private LocalDateTime detectedTime;
|
||||||
|
private String affectedArea;
|
||||||
|
private Integer affectedPopulation;
|
||||||
|
private Long creatorId;
|
||||||
|
private String creatorName;
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.water.dispatch.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.water.dispatch.entity.DrillEvaluation;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface DrillEvaluationMapper extends BaseMapper<DrillEvaluation> {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.water.dispatch.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.water.dispatch.entity.EmergencyDrill;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface EmergencyDrillMapper extends BaseMapper<EmergencyDrill> {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.water.dispatch.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.water.dispatch.entity.PipeBurstSimulation;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface PipeBurstSimulationMapper extends BaseMapper<PipeBurstSimulation> {}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
package com.water.dispatch.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.water.dispatch.entity.WaterQualityIncident;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface WaterQualityIncidentMapper extends BaseMapper<WaterQualityIncident> {}
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.water.common.core.exception.BusinessException;
|
||||||
|
import com.water.dispatch.entity.DrillEvaluation;
|
||||||
|
import com.water.dispatch.entity.EmergencyDrill;
|
||||||
|
import com.water.dispatch.entity.dto.DrillCreateRequest;
|
||||||
|
import com.water.dispatch.entity.dto.DrillEvaluationRequest;
|
||||||
|
import com.water.dispatch.mapper.DrillEvaluationMapper;
|
||||||
|
import com.water.dispatch.mapper.EmergencyDrillMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 应急演练服务 - 演练计划/执行/评估全流程管理
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class EmergencyDrillService {
|
||||||
|
|
||||||
|
private final EmergencyDrillMapper drillMapper;
|
||||||
|
private final DrillEvaluationMapper evaluationMapper;
|
||||||
|
|
||||||
|
// ============ 演练计划 ============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建演练计划
|
||||||
|
*/
|
||||||
|
public EmergencyDrill createDrill(DrillCreateRequest request) {
|
||||||
|
if (request.getName() == null || request.getName().isBlank()) {
|
||||||
|
throw new BusinessException("演练名称不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
EmergencyDrill drill = new EmergencyDrill();
|
||||||
|
drill.setDrillNo("DRILL-" + System.currentTimeMillis());
|
||||||
|
drill.setName(request.getName());
|
||||||
|
drill.setDrillType(request.getDrillType() != null ? request.getDrillType() : "OTHER");
|
||||||
|
drill.setScenario(request.getScenario());
|
||||||
|
drill.setObjectives(request.getObjectives());
|
||||||
|
drill.setPlanContent(request.getPlanContent());
|
||||||
|
drill.setParticipatingDepts(request.getParticipatingDepts());
|
||||||
|
drill.setParticipantCount(request.getParticipantCount());
|
||||||
|
drill.setOrganizerId(request.getOrganizerId());
|
||||||
|
drill.setOrganizerName(request.getOrganizerName());
|
||||||
|
drill.setLocation(request.getLocation());
|
||||||
|
drill.setPlannedDate(request.getPlannedDate());
|
||||||
|
drill.setPlannedStartTime(request.getPlannedStartTime());
|
||||||
|
drill.setPlannedEndTime(request.getPlannedEndTime());
|
||||||
|
drill.setRelatedPlanId(request.getRelatedPlanId());
|
||||||
|
drill.setRelatedPlanName(request.getRelatedPlanName());
|
||||||
|
drill.setCreatorId(request.getCreatorId());
|
||||||
|
drill.setCreatorName(request.getCreatorName());
|
||||||
|
drill.setStatus("PLANNED");
|
||||||
|
|
||||||
|
drillMapper.insert(drill);
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询演练详情
|
||||||
|
*/
|
||||||
|
public EmergencyDrill getDrillById(Long id) {
|
||||||
|
EmergencyDrill drill = drillMapper.selectById(id);
|
||||||
|
if (drill == null) throw new BusinessException("演练记录不存在");
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询演练列表
|
||||||
|
*/
|
||||||
|
public List<EmergencyDrill> listDrills(String status, String drillType) {
|
||||||
|
return drillMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<EmergencyDrill>()
|
||||||
|
.eq(status != null, EmergencyDrill::getStatus, status)
|
||||||
|
.eq(drillType != null, EmergencyDrill::getDrillType, drillType)
|
||||||
|
.orderByDesc(EmergencyDrill::getCreatedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 演练执行 ============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动演练
|
||||||
|
*/
|
||||||
|
public EmergencyDrill startDrill(Long id) {
|
||||||
|
EmergencyDrill drill = getDrillById(id);
|
||||||
|
if (!"PLANNED".equals(drill.getStatus())) {
|
||||||
|
throw new BusinessException("仅已计划状态的演练可启动");
|
||||||
|
}
|
||||||
|
drill.setStatus("IN_PROGRESS");
|
||||||
|
drill.setActualStartTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// 初始化执行日志
|
||||||
|
List<Map<String, Object>> logs = new ArrayList<>();
|
||||||
|
addExecutionLog(logs, "演练启动", "演练正式开始", LocalDateTime.now());
|
||||||
|
drill.setExecutionLog(logs.toString());
|
||||||
|
|
||||||
|
drillMapper.updateById(drill);
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 记录演练执行过程
|
||||||
|
*/
|
||||||
|
public EmergencyDrill logExecution(Long id, String stage, String content, String operator) {
|
||||||
|
EmergencyDrill drill = getDrillById(id);
|
||||||
|
if (!"IN_PROGRESS".equals(drill.getStatus())) {
|
||||||
|
throw new BusinessException("演练未在进行中");
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Map<String, Object>> logs = parseExecutionLog(drill.getExecutionLog());
|
||||||
|
addExecutionLog(logs, stage, content + " (操作人: " + operator + ")", LocalDateTime.now());
|
||||||
|
drill.setExecutionLog(logs.toString());
|
||||||
|
|
||||||
|
drillMapper.updateById(drill);
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 完成演练
|
||||||
|
*/
|
||||||
|
public EmergencyDrill completeDrill(Long id, String summary, String issuesFound, String improvements) {
|
||||||
|
EmergencyDrill drill = getDrillById(id);
|
||||||
|
if (!"IN_PROGRESS".equals(drill.getStatus())) {
|
||||||
|
throw new BusinessException("演练未在进行中,无法完成");
|
||||||
|
}
|
||||||
|
drill.setStatus("COMPLETED");
|
||||||
|
drill.setActualEndTime(LocalDateTime.now());
|
||||||
|
drill.setSummary(summary);
|
||||||
|
drill.setIssuesFound(issuesFound);
|
||||||
|
drill.setImprovements(improvements);
|
||||||
|
|
||||||
|
// 追加完成日志
|
||||||
|
List<Map<String, Object>> logs = parseExecutionLog(drill.getExecutionLog());
|
||||||
|
addExecutionLog(logs, "演练结束", "演练完成,进入评估阶段", LocalDateTime.now());
|
||||||
|
drill.setExecutionLog(logs.toString());
|
||||||
|
|
||||||
|
drillMapper.updateById(drill);
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消演练
|
||||||
|
*/
|
||||||
|
public EmergencyDrill cancelDrill(Long id, String reason) {
|
||||||
|
EmergencyDrill drill = getDrillById(id);
|
||||||
|
if ("COMPLETED".equals(drill.getStatus()) || "EVALUATED".equals(drill.getStatus())) {
|
||||||
|
throw new BusinessException("已完成/已评估的演练不可取消");
|
||||||
|
}
|
||||||
|
drill.setStatus("CANCELLED");
|
||||||
|
drill.setRemark(reason);
|
||||||
|
drillMapper.updateById(drill);
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 演练评估 ============
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建演练评估
|
||||||
|
*/
|
||||||
|
public DrillEvaluation evaluate(DrillEvaluationRequest request) {
|
||||||
|
if (request.getDrillId() == null) {
|
||||||
|
throw new BusinessException("演练ID不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
EmergencyDrill drill = getDrillById(request.getDrillId());
|
||||||
|
if (!"COMPLETED".equals(drill.getStatus()) && !"EVALUATED".equals(drill.getStatus())) {
|
||||||
|
throw new BusinessException("仅已完成状态的演练可评估");
|
||||||
|
}
|
||||||
|
|
||||||
|
DrillEvaluation eval = new DrillEvaluation();
|
||||||
|
eval.setEvaluationNo("EVAL-" + System.currentTimeMillis());
|
||||||
|
eval.setDrillId(request.getDrillId());
|
||||||
|
eval.setDrillNo(drill.getDrillNo());
|
||||||
|
eval.setEvaluatorId(request.getEvaluatorId());
|
||||||
|
eval.setEvaluatorName(request.getEvaluatorName());
|
||||||
|
eval.setResponseScore(request.getResponseScore());
|
||||||
|
eval.setHandlingScore(request.getHandlingScore());
|
||||||
|
eval.setCoordinationScore(request.getCoordinationScore());
|
||||||
|
eval.setResourceScore(request.getResourceScore());
|
||||||
|
eval.setReportingScore(request.getReportingScore());
|
||||||
|
eval.setEvaluationDetails(request.getEvaluationDetails());
|
||||||
|
eval.setStrengths(request.getStrengths());
|
||||||
|
eval.setWeaknesses(request.getWeaknesses());
|
||||||
|
eval.setRecommendations(request.getRecommendations());
|
||||||
|
eval.setFollowUpActions(request.getFollowUpActions());
|
||||||
|
eval.setEvaluatedAt(LocalDateTime.now());
|
||||||
|
eval.setStatus("SUBMITTED");
|
||||||
|
|
||||||
|
// 计算综合评分(五维度加权平均)
|
||||||
|
int overallScore = calculateOverallScore(
|
||||||
|
request.getResponseScore(),
|
||||||
|
request.getHandlingScore(),
|
||||||
|
request.getCoordinationScore(),
|
||||||
|
request.getResourceScore(),
|
||||||
|
request.getReportingScore());
|
||||||
|
eval.setOverallScore(overallScore);
|
||||||
|
|
||||||
|
// 评估等级
|
||||||
|
eval.setGrade(calculateGrade(overallScore));
|
||||||
|
|
||||||
|
evaluationMapper.insert(eval);
|
||||||
|
|
||||||
|
// 更新演练状态为已评估
|
||||||
|
drill.setStatus("EVALUATED");
|
||||||
|
drillMapper.updateById(drill);
|
||||||
|
|
||||||
|
return eval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询演练的评估列表
|
||||||
|
*/
|
||||||
|
public List<DrillEvaluation> getEvaluations(Long drillId) {
|
||||||
|
return evaluationMapper.selectList(
|
||||||
|
new LambdaQueryWrapper<DrillEvaluation>()
|
||||||
|
.eq(DrillEvaluation::getDrillId, drillId)
|
||||||
|
.orderByDesc(DrillEvaluation::getCreatedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询评估详情
|
||||||
|
*/
|
||||||
|
public DrillEvaluation getEvaluationById(Long id) {
|
||||||
|
DrillEvaluation eval = evaluationMapper.selectById(id);
|
||||||
|
if (eval == null) throw new BusinessException("评估记录不存在");
|
||||||
|
return eval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取演练统计
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getDrillStatistics() {
|
||||||
|
Map<String, Object> stats = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
long totalDrills = drillMapper.selectCount(null);
|
||||||
|
long completedDrills = drillMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<EmergencyDrill>().eq(EmergencyDrill::getStatus, "COMPLETED"));
|
||||||
|
long evaluatedDrills = drillMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<EmergencyDrill>().eq(EmergencyDrill::getStatus, "EVALUATED"));
|
||||||
|
long plannedDrills = drillMapper.selectCount(
|
||||||
|
new LambdaQueryWrapper<EmergencyDrill>().eq(EmergencyDrill::getStatus, "PLANNED"));
|
||||||
|
|
||||||
|
stats.put("totalDrills", totalDrills);
|
||||||
|
stats.put("completedDrills", completedDrills);
|
||||||
|
stats.put("evaluatedDrills", evaluatedDrills);
|
||||||
|
stats.put("plannedDrills", plannedDrills);
|
||||||
|
|
||||||
|
// 平均评分
|
||||||
|
List<DrillEvaluation> allEvals = evaluationMapper.selectList(null);
|
||||||
|
if (!allEvals.isEmpty()) {
|
||||||
|
double avgScore = allEvals.stream()
|
||||||
|
.filter(e -> e.getOverallScore() != null)
|
||||||
|
.mapToInt(DrillEvaluation::getOverallScore)
|
||||||
|
.average()
|
||||||
|
.orElse(0.0);
|
||||||
|
stats.put("averageScore", Math.round(avgScore * 10.0) / 10.0);
|
||||||
|
} else {
|
||||||
|
stats.put("averageScore", 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============ 私有方法 ============
|
||||||
|
|
||||||
|
private void addExecutionLog(List<Map<String, Object>> logs, String stage, String content, LocalDateTime time) {
|
||||||
|
Map<String, Object> log = new LinkedHashMap<>();
|
||||||
|
log.put("stage", stage);
|
||||||
|
log.put("content", content);
|
||||||
|
log.put("time", time);
|
||||||
|
logs.add(log);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, Object>> parseExecutionLog(String executionLog) {
|
||||||
|
if (executionLog == null || executionLog.isBlank()) {
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
// 简单解析:返回新list,通过addExecutionLog追加
|
||||||
|
return new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int calculateOverallScore(Integer response, Integer handling, Integer coordination,
|
||||||
|
Integer resource, Integer reporting) {
|
||||||
|
int r = response != null ? response : 0;
|
||||||
|
int h = handling != null ? handling : 0;
|
||||||
|
int c = coordination != null ? coordination : 0;
|
||||||
|
int re = resource != null ? resource : 0;
|
||||||
|
int rp = reporting != null ? reporting : 0;
|
||||||
|
// 加权: 响应25% + 处置30% + 协调20% + 资源15% + 信息10%
|
||||||
|
return (int) Math.round(r * 0.25 + h * 0.30 + c * 0.20 + re * 0.15 + rp * 0.10);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String calculateGrade(int score) {
|
||||||
|
if (score >= 90) return "EXCELLENT";
|
||||||
|
if (score >= 75) return "GOOD";
|
||||||
|
if (score >= 60) return "PASS";
|
||||||
|
return "FAIL";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.water.dispatch.entity.PipeBurstSimulation;
|
||||||
|
import com.water.dispatch.entity.dto.PipeBurstRequest;
|
||||||
|
import com.water.dispatch.mapper.PipeBurstSimulationMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class PipeBurstService {
|
||||||
|
|
||||||
|
private final PipeBurstSimulationMapper simulationMapper;
|
||||||
|
|
||||||
|
public PipeBurstSimulation simulate(PipeBurstRequest request) {
|
||||||
|
PipeBurstSimulation sim = new PipeBurstSimulation();
|
||||||
|
sim.setSimulationNo("PB-" + System.currentTimeMillis());
|
||||||
|
sim.setLocation(request.getLocation());
|
||||||
|
sim.setLng(request.getLng());
|
||||||
|
sim.setLat(request.getLat());
|
||||||
|
sim.setPipeDiameter(request.getPipeDiameter());
|
||||||
|
sim.setPipeMaterial(request.getPipeMaterial());
|
||||||
|
|
||||||
|
// Simulate impact based on diameter
|
||||||
|
double diameter = request.getPipeDiameter() != null ? request.getPipeDiameter() : 200.0;
|
||||||
|
int affectedRadius = (int)(diameter * 2.5); // meters
|
||||||
|
int affectedUsers = (int)(diameter * 0.6);
|
||||||
|
double waterLoss = diameter * 0.15; // m³/h
|
||||||
|
int repairHours = (int)(diameter / 50.0) + 2;
|
||||||
|
|
||||||
|
sim.setAffectedRadius(affectedRadius);
|
||||||
|
sim.setAffectedUsers(affectedUsers);
|
||||||
|
sim.setEstimatedWaterLoss(waterLoss);
|
||||||
|
sim.setEstimatedRepairHours(repairHours);
|
||||||
|
sim.setPressureDrop(diameter * 0.02);
|
||||||
|
|
||||||
|
// Generate affected valves
|
||||||
|
List<String> valves = new ArrayList<>();
|
||||||
|
valves.add("V-" + (int)(Math.random() * 1000));
|
||||||
|
valves.add("V-" + (int)(Math.random() * 1000));
|
||||||
|
sim.setAffectedValves(String.join(",", valves));
|
||||||
|
|
||||||
|
sim.setStatus("COMPLETED");
|
||||||
|
sim.setCreatedTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
simulationMapper.insert(sim);
|
||||||
|
return sim;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getImpactAnalysis(Long id) {
|
||||||
|
PipeBurstSimulation sim = simulationMapper.selectById(id);
|
||||||
|
if (sim == null) throw new RuntimeException("模拟记录不存在");
|
||||||
|
|
||||||
|
Map<String, Object> analysis = new LinkedHashMap<>();
|
||||||
|
analysis.put("simulation", sim);
|
||||||
|
analysis.put("affectedArea", sim.getAffectedRadius() * sim.getAffectedRadius() * Math.PI / 10000 + " 公顷");
|
||||||
|
analysis.put("estimatedCost", sim.getAffectedUsers() * 150.0 + " 元");
|
||||||
|
analysis.put("priority", sim.getPipeDiameter() > 300 ? "紧急" : sim.getPipeDiameter() > 150 ? "重要" : "一般");
|
||||||
|
|
||||||
|
// Suggested isolation plan
|
||||||
|
Map<String, Object> plan = new LinkedHashMap<>();
|
||||||
|
plan.put("closeValves", sim.getAffectedValves() != null ? sim.getAffectedValves().split(",") : new String[]{});
|
||||||
|
plan.put("notifyUsers", sim.getAffectedUsers());
|
||||||
|
plan.put("dispatchTeam", sim.getPipeDiameter() > 300 ? "应急抢修一队" : "常规维修组");
|
||||||
|
plan.put("estimatedArrival", sim.getPipeDiameter() > 300 ? "30分钟" : "60分钟");
|
||||||
|
analysis.put("isolationPlan", plan);
|
||||||
|
|
||||||
|
return analysis;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PipeBurstSimulation> listSimulations(String status) {
|
||||||
|
LambdaQueryWrapper<PipeBurstSimulation> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (status != null && !status.isBlank()) {
|
||||||
|
wrapper.eq(PipeBurstSimulation::getStatus, status);
|
||||||
|
}
|
||||||
|
return simulationMapper.selectList(wrapper.orderByDesc(PipeBurstSimulation::getCreatedTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getValveShutdownPlan(Long simulationId) {
|
||||||
|
PipeBurstSimulation sim = simulationMapper.selectById(simulationId);
|
||||||
|
if (sim == null) throw new RuntimeException("模拟记录不存在");
|
||||||
|
|
||||||
|
Map<String, Object> plan = new LinkedHashMap<>();
|
||||||
|
plan.put("simulationNo", sim.getSimulationNo());
|
||||||
|
plan.put("burstLocation", sim.getLocation());
|
||||||
|
plan.put("valvesToClose", sim.getAffectedValves() != null ?
|
||||||
|
Arrays.asList(sim.getAffectedValves().split(",")) : Collections.emptyList());
|
||||||
|
plan.put("alternativeSupply", "附近消防栓临时供水");
|
||||||
|
plan.put("estimatedShutdownTime", "15分钟");
|
||||||
|
plan.put("estimatedRestoreTime", sim.getEstimatedRepairHours() + 2 + "小时");
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.water.dispatch.entity.WaterQualityIncident;
|
||||||
|
import com.water.dispatch.entity.dto.WaterQualityRequest;
|
||||||
|
import com.water.dispatch.mapper.WaterQualityIncidentMapper;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class WaterQualityIncidentService {
|
||||||
|
|
||||||
|
private final WaterQualityIncidentMapper incidentMapper;
|
||||||
|
|
||||||
|
// Predefined response plans
|
||||||
|
private static final Map<String, Map<String, Object>> RESPONSE_PLANS = Map.of(
|
||||||
|
"TURBIDITY", Map.of("planName", "浊度异常处置预案", "steps", List.of(
|
||||||
|
"1. 立即停止取水", "2. 启动备用水源", "3. 加密水质检测频次(每30分钟)",
|
||||||
|
"4. 排查上游污染源", "5. 调整水厂处理工艺", "6. 水质恢复后逐步恢复供水"
|
||||||
|
)),
|
||||||
|
"CHLORINE", Map.of("planName", "余氯异常处置预案", "steps", List.of(
|
||||||
|
"1. 检查加氯设备", "2. 调整加氯量", "3. 末梢水采样检测",
|
||||||
|
"4. 必要时冲洗管网", "5. 通知用户注意事项"
|
||||||
|
)),
|
||||||
|
"PH", Map.of("planName", "pH异常处置预案", "steps", List.of(
|
||||||
|
"1. 停止供水", "2. 排查酸碱污染源", "3. 中和处理",
|
||||||
|
"4. 管网冲洗", "5. 连续监测直至达标"
|
||||||
|
)),
|
||||||
|
"HEAVY_METAL", Map.of("planName", "重金属超标处置预案", "steps", List.of(
|
||||||
|
"1. 立即停止供水并上报", "2. 启动应急供水", "3. 排查工业污染源",
|
||||||
|
"4. 联系环保部门", "5. 管网彻底冲洗消毒", "6. 连续72小时监测"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
public WaterQualityIncident reportIncident(WaterQualityRequest request) {
|
||||||
|
WaterQualityIncident incident = new WaterQualityIncident();
|
||||||
|
incident.setIncidentNo("WQI-" + System.currentTimeMillis());
|
||||||
|
incident.setLocation(request.getLocation());
|
||||||
|
incident.setPollutantType(request.getPollutantType());
|
||||||
|
incident.setDetectedValue(request.getDetectedValue());
|
||||||
|
incident.setStandardValue(request.getStandardValue());
|
||||||
|
incident.setSeverity(calculateSeverity(request.getPollutantType(),
|
||||||
|
request.getDetectedValue(), request.getStandardValue()));
|
||||||
|
incident.setStatus("DETECTED");
|
||||||
|
incident.setDetectedTime(LocalDateTime.now());
|
||||||
|
incident.setCreatedTime(LocalDateTime.now());
|
||||||
|
|
||||||
|
// Auto-match response plan
|
||||||
|
Map<String, Object> plan = matchPlan(request.getPollutantType());
|
||||||
|
if (plan != null) {
|
||||||
|
incident.setMatchedPlan((String) plan.get("planName"));
|
||||||
|
}
|
||||||
|
|
||||||
|
incidentMapper.insert(incident);
|
||||||
|
return incident;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> matchPlan(String pollutantType) {
|
||||||
|
return RESPONSE_PLANS.getOrDefault(pollutantType,
|
||||||
|
Map.of("planName", "通用水质异常处置预案", "steps", List.of(
|
||||||
|
"1. 采样复检", "2. 分析异常原因", "3. 采取对应措施", "4. 持续监测"
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> startResponse(Long incidentId) {
|
||||||
|
WaterQualityIncident incident = incidentMapper.selectById(incidentId);
|
||||||
|
if (incident == null) throw new RuntimeException("水质事件不存在");
|
||||||
|
|
||||||
|
incident.setStatus("RESPONDING");
|
||||||
|
incident.setResponseStartTime(LocalDateTime.now());
|
||||||
|
incidentMapper.updateById(incident);
|
||||||
|
|
||||||
|
Map<String, Object> result = new LinkedHashMap<>();
|
||||||
|
result.put("incident", incident);
|
||||||
|
result.put("plan", matchPlan(incident.getPollutantType()));
|
||||||
|
result.put("responseTeam", "水质应急小组");
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> updateProgress(Long incidentId, String progress, String operator) {
|
||||||
|
WaterQualityIncident incident = incidentMapper.selectById(incidentId);
|
||||||
|
if (incident == null) throw new RuntimeException("水质事件不存在");
|
||||||
|
|
||||||
|
String currentLog = incident.getResponseLog() != null ? incident.getResponseLog() : "";
|
||||||
|
incident.setResponseLog(currentLog + "[" + LocalDateTime.now() + "] " + operator + ": " + progress + "\n");
|
||||||
|
incidentMapper.updateById(incident);
|
||||||
|
|
||||||
|
return Map.of("incidentId", incidentId, "status", incident.getStatus(), "logUpdated", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WaterQualityIncident resolveIncident(Long incidentId, String resolution) {
|
||||||
|
WaterQualityIncident incident = incidentMapper.selectById(incidentId);
|
||||||
|
if (incident == null) throw new RuntimeException("水质事件不存在");
|
||||||
|
|
||||||
|
incident.setStatus("RESOLVED");
|
||||||
|
incident.setResolvedTime(LocalDateTime.now());
|
||||||
|
incident.setResolution(resolution);
|
||||||
|
incidentMapper.updateById(incident);
|
||||||
|
return incident;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<WaterQualityIncident> listIncidents(String status, String pollutantType) {
|
||||||
|
LambdaQueryWrapper<WaterQualityIncident> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
if (status != null && !status.isBlank()) wrapper.eq(WaterQualityIncident::getStatus, status);
|
||||||
|
if (pollutantType != null && !pollutantType.isBlank()) wrapper.eq(WaterQualityIncident::getPollutantType, pollutantType);
|
||||||
|
return incidentMapper.selectList(wrapper.orderByDesc(WaterQualityIncident::getCreatedTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getIncidentDetail(Long id) {
|
||||||
|
WaterQualityIncident incident = incidentMapper.selectById(id);
|
||||||
|
if (incident == null) throw new RuntimeException("水质事件不存在");
|
||||||
|
|
||||||
|
Map<String, Object> detail = new LinkedHashMap<>();
|
||||||
|
detail.put("incident", incident);
|
||||||
|
detail.put("plan", matchPlan(incident.getPollutantType()));
|
||||||
|
detail.put("exceedRate", incident.getStandardValue() > 0 ?
|
||||||
|
String.format("%.1f%%", (incident.getDetectedValue() / incident.getStandardValue() - 1) * 100) : "N/A");
|
||||||
|
return detail;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String calculateSeverity(String type, Double detected, Double standard) {
|
||||||
|
if (standard == null || standard <= 0) return "MEDIUM";
|
||||||
|
double ratio = detected / standard;
|
||||||
|
if (ratio > 3.0 || "HEAVY_METAL".equals(type)) return "CRITICAL";
|
||||||
|
if (ratio > 2.0) return "HIGH";
|
||||||
|
if (ratio > 1.0) return "MEDIUM";
|
||||||
|
return "LOW";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
-- Emergency Drill & Response DDL
|
||||||
|
CREATE TABLE IF NOT EXISTS disp_pipe_burst_simulation (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
simulation_no VARCHAR(50) UNIQUE,
|
||||||
|
location VARCHAR(200),
|
||||||
|
lng DOUBLE PRECISION,
|
||||||
|
lat DOUBLE PRECISION,
|
||||||
|
pipe_diameter DOUBLE PRECISION,
|
||||||
|
pipe_material VARCHAR(50),
|
||||||
|
affected_radius INT,
|
||||||
|
affected_users INT,
|
||||||
|
estimated_water_loss DOUBLE PRECISION,
|
||||||
|
estimated_repair_hours INT,
|
||||||
|
pressure_drop DOUBLE PRECISION,
|
||||||
|
affected_valves TEXT,
|
||||||
|
status VARCHAR(20) DEFAULT 'COMPLETED',
|
||||||
|
created_time TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS disp_water_quality_incident (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
incident_no VARCHAR(50) UNIQUE,
|
||||||
|
location VARCHAR(200),
|
||||||
|
pollutant_type VARCHAR(50),
|
||||||
|
detected_value DOUBLE PRECISION,
|
||||||
|
standard_value DOUBLE PRECISION,
|
||||||
|
severity VARCHAR(20),
|
||||||
|
status VARCHAR(20) DEFAULT 'DETECTED',
|
||||||
|
matched_plan VARCHAR(200),
|
||||||
|
detected_time TIMESTAMP,
|
||||||
|
response_start_time TIMESTAMP,
|
||||||
|
resolved_time TIMESTAMP,
|
||||||
|
response_log TEXT,
|
||||||
|
resolution TEXT,
|
||||||
|
created_time TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS disp_emergency_drill (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
drill_no VARCHAR(50) UNIQUE,
|
||||||
|
name VARCHAR(200),
|
||||||
|
drill_type VARCHAR(30),
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(20) DEFAULT 'PLANNED',
|
||||||
|
planned_time TIMESTAMP,
|
||||||
|
started_time TIMESTAMP,
|
||||||
|
completed_time TIMESTAMP,
|
||||||
|
created_time TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS disp_drill_evaluation (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
drill_id BIGINT REFERENCES disp_emergency_drill(id),
|
||||||
|
score INT,
|
||||||
|
response_time_score INT,
|
||||||
|
coordination_score INT,
|
||||||
|
overall_rating VARCHAR(20),
|
||||||
|
findings TEXT,
|
||||||
|
recommendations TEXT,
|
||||||
|
evaluator VARCHAR(50),
|
||||||
|
created_time TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_pbs_status ON disp_pipe_burst_simulation(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_wqi_status ON disp_water_quality_incident(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_wqi_type ON disp_water_quality_incident(pollutant_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ed_status ON disp_emergency_drill(status);
|
||||||
@@ -0,0 +1,253 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.water.common.core.exception.BusinessException;
|
||||||
|
import com.water.dispatch.entity.DrillEvaluation;
|
||||||
|
import com.water.dispatch.entity.EmergencyDrill;
|
||||||
|
import com.water.dispatch.entity.dto.DrillCreateRequest;
|
||||||
|
import com.water.dispatch.entity.dto.DrillEvaluationRequest;
|
||||||
|
import com.water.dispatch.mapper.DrillEvaluationMapper;
|
||||||
|
import com.water.dispatch.mapper.EmergencyDrillMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class EmergencyDrillServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EmergencyDrillMapper drillMapper;
|
||||||
|
@Mock
|
||||||
|
private DrillEvaluationMapper evaluationMapper;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private EmergencyDrillService emergencyDrillService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateDrill() {
|
||||||
|
when(drillMapper.insert(any())).thenAnswer(invocation -> {
|
||||||
|
EmergencyDrill drill = invocation.getArgument(0);
|
||||||
|
drill.setId(1L);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
DrillCreateRequest request = new DrillCreateRequest();
|
||||||
|
request.setName("2024年度爆管抢修演练");
|
||||||
|
request.setDrillType("PIPE_BURST");
|
||||||
|
request.setScenario("模拟DN600主干管爆裂");
|
||||||
|
request.setObjectives("检验应急响应速度和关阀操作");
|
||||||
|
request.setOrganizerId(1L);
|
||||||
|
request.setOrganizerName("张三");
|
||||||
|
request.setLocation("城南水厂");
|
||||||
|
request.setPlannedDate(LocalDate.of(2024, 6, 15));
|
||||||
|
request.setParticipantCount(50);
|
||||||
|
|
||||||
|
EmergencyDrill result = emergencyDrillService.createDrill(request);
|
||||||
|
|
||||||
|
assertNotNull(result.getDrillNo());
|
||||||
|
assertTrue(result.getDrillNo().startsWith("DRILL-"));
|
||||||
|
assertEquals("PLANNED", result.getStatus());
|
||||||
|
assertEquals("PIPE_BURST", result.getDrillType());
|
||||||
|
assertEquals("2024年度爆管抢修演练", result.getName());
|
||||||
|
|
||||||
|
verify(drillMapper).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCreateDrillMissingName() {
|
||||||
|
DrillCreateRequest request = new DrillCreateRequest();
|
||||||
|
assertThrows(BusinessException.class, () -> emergencyDrillService.createDrill(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStartDrill() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "PLANNED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
when(drillMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
EmergencyDrill result = emergencyDrillService.startDrill(1L);
|
||||||
|
|
||||||
|
assertEquals("IN_PROGRESS", result.getStatus());
|
||||||
|
assertNotNull(result.getActualStartTime());
|
||||||
|
assertNotNull(result.getExecutionLog());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStartDrillWrongStatus() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> emergencyDrillService.startDrill(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLogExecution() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
|
||||||
|
drill.setExecutionLog("[]");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
when(drillMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
EmergencyDrill result = emergencyDrillService.logExecution(1L, "关阀操作", "完成上游阀门关闭", "李四");
|
||||||
|
|
||||||
|
assertEquals("IN_PROGRESS", result.getStatus());
|
||||||
|
assertNotNull(result.getExecutionLog());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testLogExecutionWrongStatus() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "PLANNED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class,
|
||||||
|
() -> emergencyDrillService.logExecution(1L, "stage", "content", "operator"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompleteDrill() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "IN_PROGRESS");
|
||||||
|
drill.setExecutionLog("[]");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
when(drillMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
EmergencyDrill result = emergencyDrillService.completeDrill(1L,
|
||||||
|
"演练顺利完成", "发现通信设备不足", "建议增配对讲机");
|
||||||
|
|
||||||
|
assertEquals("COMPLETED", result.getStatus());
|
||||||
|
assertNotNull(result.getActualEndTime());
|
||||||
|
assertEquals("演练顺利完成", result.getSummary());
|
||||||
|
assertEquals("发现通信设备不足", result.getIssuesFound());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCompleteDrillWrongStatus() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "PLANNED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class,
|
||||||
|
() -> emergencyDrillService.completeDrill(1L, "summary", "issues", "improvements"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCancelDrill() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "PLANNED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
when(drillMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
EmergencyDrill result = emergencyDrillService.cancelDrill(1L, "天气原因取消");
|
||||||
|
|
||||||
|
assertEquals("CANCELLED", result.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testCancelCompletedDrill() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "COMPLETED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> emergencyDrillService.cancelDrill(1L, "reason"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEvaluateDrill() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "COMPLETED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
when(drillMapper.updateById(any())).thenReturn(1);
|
||||||
|
when(evaluationMapper.insert(any())).thenAnswer(invocation -> {
|
||||||
|
DrillEvaluation eval = invocation.getArgument(0);
|
||||||
|
eval.setId(1L);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
DrillEvaluationRequest request = new DrillEvaluationRequest();
|
||||||
|
request.setDrillId(1L);
|
||||||
|
request.setEvaluatorId(1L);
|
||||||
|
request.setEvaluatorName("王五");
|
||||||
|
request.setResponseScore(85);
|
||||||
|
request.setHandlingScore(90);
|
||||||
|
request.setCoordinationScore(80);
|
||||||
|
request.setResourceScore(75);
|
||||||
|
request.setReportingScore(88);
|
||||||
|
request.setStrengths("响应迅速");
|
||||||
|
request.setWeaknesses("资源调配待加强");
|
||||||
|
request.setRecommendations("增加备品备件储备");
|
||||||
|
|
||||||
|
DrillEvaluation result = emergencyDrillService.evaluate(request);
|
||||||
|
|
||||||
|
assertNotNull(result.getEvaluationNo());
|
||||||
|
assertTrue(result.getEvaluationNo().startsWith("EVAL-"));
|
||||||
|
assertNotNull(result.getOverallScore());
|
||||||
|
assertTrue(result.getOverallScore() > 0);
|
||||||
|
assertNotNull(result.getGrade());
|
||||||
|
assertEquals("SUBMITTED", result.getStatus());
|
||||||
|
|
||||||
|
// 验证综合评分计算
|
||||||
|
// 85*0.25 + 90*0.30 + 80*0.20 + 75*0.15 + 88*0.10 = 21.25+27+16+11.25+8.8 = 84.3 ≈ 84
|
||||||
|
assertEquals(84, result.getOverallScore());
|
||||||
|
assertEquals("GOOD", result.getGrade());
|
||||||
|
|
||||||
|
verify(drillMapper).updateById(any()); // 更新演练状态为 EVALUATED
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testEvaluateWrongStatus() {
|
||||||
|
EmergencyDrill drill = buildDrill(1L, "PLANNED");
|
||||||
|
when(drillMapper.selectById(1L)).thenReturn(drill);
|
||||||
|
|
||||||
|
DrillEvaluationRequest request = new DrillEvaluationRequest();
|
||||||
|
request.setDrillId(1L);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> emergencyDrillService.evaluate(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetDrillStatistics() {
|
||||||
|
when(drillMapper.selectCount(any())).thenReturn(10L, 5L, 3L, 2L);
|
||||||
|
when(evaluationMapper.selectList(any())).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
Map<String, Object> stats = emergencyDrillService.getDrillStatistics();
|
||||||
|
|
||||||
|
assertNotNull(stats);
|
||||||
|
assertEquals(10L, stats.get("totalDrills"));
|
||||||
|
assertEquals(5L, stats.get("completedDrills"));
|
||||||
|
assertEquals(3L, stats.get("evaluatedDrills"));
|
||||||
|
assertEquals(2L, stats.get("plannedDrills"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetDrillStatisticsWithEvaluations() {
|
||||||
|
when(drillMapper.selectCount(any())).thenReturn(5L, 3L, 2L, 0L);
|
||||||
|
|
||||||
|
DrillEvaluation eval1 = new DrillEvaluation();
|
||||||
|
eval1.setOverallScore(85);
|
||||||
|
DrillEvaluation eval2 = new DrillEvaluation();
|
||||||
|
eval2.setOverallScore(75);
|
||||||
|
when(evaluationMapper.selectList(any())).thenReturn(List.of(eval1, eval2));
|
||||||
|
|
||||||
|
Map<String, Object> stats = emergencyDrillService.getDrillStatistics();
|
||||||
|
|
||||||
|
assertEquals(80.0, stats.get("averageScore"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private EmergencyDrill buildDrill(Long id, String status) {
|
||||||
|
EmergencyDrill drill = new EmergencyDrill();
|
||||||
|
drill.setId(id);
|
||||||
|
drill.setDrillNo("DRILL-TEST-001");
|
||||||
|
drill.setName("测试演练");
|
||||||
|
drill.setDrillType("PIPE_BURST");
|
||||||
|
drill.setScenario("测试场景");
|
||||||
|
drill.setStatus(status);
|
||||||
|
drill.setOrganizerId(1L);
|
||||||
|
drill.setOrganizerName("张三");
|
||||||
|
return drill;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,164 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.water.common.core.exception.BusinessException;
|
||||||
|
import com.water.dispatch.entity.PipeBurstSimulation;
|
||||||
|
import com.water.dispatch.entity.dto.PipeBurstRequest;
|
||||||
|
import com.water.dispatch.mapper.PipeBurstSimulationMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class PipeBurstServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private PipeBurstSimulationMapper simulationMapper;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private PipeBurstService pipeBurstService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimulatePipeBurst() {
|
||||||
|
when(simulationMapper.insert(any())).thenAnswer(invocation -> {
|
||||||
|
PipeBurstSimulation sim = invocation.getArgument(0);
|
||||||
|
sim.setId(1L);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
when(simulationMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
PipeBurstRequest request = new PipeBurstRequest();
|
||||||
|
request.setLongitude(116.404);
|
||||||
|
request.setLatitude(39.915);
|
||||||
|
request.setLocation("北京市朝阳区建国路100号");
|
||||||
|
request.setPipeDiameter(400.0);
|
||||||
|
request.setPipeMaterial("球墨铸铁");
|
||||||
|
request.setPipePressure(0.35);
|
||||||
|
request.setCreatorId(1L);
|
||||||
|
request.setCreatorName("张三");
|
||||||
|
|
||||||
|
PipeBurstSimulation result = pipeBurstService.simulate(request);
|
||||||
|
|
||||||
|
assertNotNull(result.getSimulationNo());
|
||||||
|
assertTrue(result.getSimulationNo().startsWith("PBS-"));
|
||||||
|
assertEquals("COMPLETED", result.getStatus());
|
||||||
|
assertNotNull(result.getImpactRadius());
|
||||||
|
assertTrue(result.getImpactRadius() > 0);
|
||||||
|
assertNotNull(result.getAffectedUsers());
|
||||||
|
assertTrue(result.getAffectedUsers() > 0);
|
||||||
|
assertNotNull(result.getEstimatedRepairHours());
|
||||||
|
assertTrue(result.getEstimatedRepairHours() > 0);
|
||||||
|
assertNotNull(result.getValveShutdownPlan());
|
||||||
|
assertNotNull(result.getLeakageRate());
|
||||||
|
|
||||||
|
verify(simulationMapper).insert(any());
|
||||||
|
verify(simulationMapper, atLeastOnce()).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimulateMissingCoordinates() {
|
||||||
|
PipeBurstRequest request = new PipeBurstRequest();
|
||||||
|
request.setLocation("测试位置");
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> pipeBurstService.simulate(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testSimulateDefaultValues() {
|
||||||
|
when(simulationMapper.insert(any())).thenReturn(1);
|
||||||
|
when(simulationMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
PipeBurstRequest request = new PipeBurstRequest();
|
||||||
|
request.setLongitude(120.0);
|
||||||
|
request.setLatitude(30.0);
|
||||||
|
// 不设置pipeDiameter和pipePressure,使用默认值
|
||||||
|
|
||||||
|
PipeBurstSimulation result = pipeBurstService.simulate(request);
|
||||||
|
|
||||||
|
assertNotNull(result);
|
||||||
|
assertEquals("COMPLETED", result.getStatus());
|
||||||
|
assertNotNull(result.getImpactRadius());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetImpactAnalysis() {
|
||||||
|
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
|
||||||
|
when(simulationMapper.selectById(1L)).thenReturn(sim);
|
||||||
|
|
||||||
|
Map<String, Object> analysis = pipeBurstService.getImpactAnalysis(1L);
|
||||||
|
|
||||||
|
assertNotNull(analysis);
|
||||||
|
assertEquals("PBS-TEST-001", analysis.get("simulationNo"));
|
||||||
|
assertTrue(analysis.containsKey("impactRadius"));
|
||||||
|
assertTrue(analysis.containsKey("affectedUsers"));
|
||||||
|
assertTrue(analysis.containsKey("valveShutdownPlan"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetValvePlan() {
|
||||||
|
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
|
||||||
|
when(simulationMapper.selectById(1L)).thenReturn(sim);
|
||||||
|
|
||||||
|
Map<String, Object> plan = pipeBurstService.getValvePlan(1L);
|
||||||
|
|
||||||
|
assertNotNull(plan);
|
||||||
|
assertEquals("PBS-TEST-001", plan.get("pipeNo"));
|
||||||
|
assertTrue(plan.containsKey("valveShutdownPlan"));
|
||||||
|
assertTrue(plan.containsKey("recommendation"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testArchiveSimulation() {
|
||||||
|
PipeBurstSimulation sim = buildSimulation(1L, "COMPLETED");
|
||||||
|
when(simulationMapper.selectById(1L)).thenReturn(sim);
|
||||||
|
when(simulationMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
PipeBurstSimulation result = pipeBurstService.archive(1L);
|
||||||
|
|
||||||
|
assertEquals("ARCHIVED", result.getStatus());
|
||||||
|
verify(simulationMapper).updateById(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testArchiveNonCompletedSimulation() {
|
||||||
|
PipeBurstSimulation sim = buildSimulation(1L, "RUNNING");
|
||||||
|
when(simulationMapper.selectById(1L)).thenReturn(sim);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> pipeBurstService.archive(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetByIdNotFound() {
|
||||||
|
when(simulationMapper.selectById(999L)).thenReturn(null);
|
||||||
|
assertThrows(BusinessException.class, () -> pipeBurstService.getById(999L));
|
||||||
|
}
|
||||||
|
|
||||||
|
private PipeBurstSimulation buildSimulation(Long id, String status) {
|
||||||
|
PipeBurstSimulation sim = new PipeBurstSimulation();
|
||||||
|
sim.setId(id);
|
||||||
|
sim.setSimulationNo("PBS-TEST-001");
|
||||||
|
sim.setPipeNo("PIPE-001");
|
||||||
|
sim.setLongitude(116.404);
|
||||||
|
sim.setLatitude(39.915);
|
||||||
|
sim.setLocation("测试位置");
|
||||||
|
sim.setPipeDiameter(300.0);
|
||||||
|
sim.setPipePressure(0.3);
|
||||||
|
sim.setImpactRadius(150.0);
|
||||||
|
sim.setImpactArea(70685.83);
|
||||||
|
sim.setAffectedUsers(354);
|
||||||
|
sim.setAffectedRegion("测试区域");
|
||||||
|
sim.setLeakageRate(100.0);
|
||||||
|
sim.setEstimatedRepairHours(6.0);
|
||||||
|
sim.setValveShutdownPlan("[{valveId: V-001}]");
|
||||||
|
sim.setValveAffectedUsers(637);
|
||||||
|
sim.setStatus(status);
|
||||||
|
return sim;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
package com.water.dispatch.service;
|
||||||
|
|
||||||
|
import com.water.common.core.exception.BusinessException;
|
||||||
|
import com.water.dispatch.entity.WaterQualityIncident;
|
||||||
|
import com.water.dispatch.entity.dto.WaterQualityRequest;
|
||||||
|
import com.water.dispatch.mapper.EmergencyPlanMapper;
|
||||||
|
import com.water.dispatch.mapper.WaterQualityIncidentMapper;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class WaterQualityServiceTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private WaterQualityIncidentMapper incidentMapper;
|
||||||
|
@Mock
|
||||||
|
private EmergencyPlanMapper planMapper;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
private WaterQualityService waterQualityService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReportIncident() {
|
||||||
|
when(incidentMapper.insert(any())).thenAnswer(invocation -> {
|
||||||
|
WaterQualityIncident inc = invocation.getArgument(0);
|
||||||
|
inc.setId(1L);
|
||||||
|
return 1;
|
||||||
|
});
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
when(planMapper.selectList(any())).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
WaterQualityRequest request = new WaterQualityRequest();
|
||||||
|
request.setTitle("末梢水浊度超标");
|
||||||
|
request.setDescription("某小区末梢水浊度异常");
|
||||||
|
request.setSourceType("END");
|
||||||
|
request.setAbnormalIndicator("TURBIDITY");
|
||||||
|
request.setDetectedValue(3.5);
|
||||||
|
request.setStandardValue(1.0);
|
||||||
|
request.setDetectedTime(LocalDateTime.now());
|
||||||
|
request.setCreatorId(1L);
|
||||||
|
request.setCreatorName("张三");
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.report(request);
|
||||||
|
|
||||||
|
assertNotNull(result.getIncidentNo());
|
||||||
|
assertTrue(result.getIncidentNo().startsWith("WQI-"));
|
||||||
|
assertEquals("DETECTED", result.getStatus());
|
||||||
|
assertEquals(3.5, result.getExceedMultiple());
|
||||||
|
assertNotNull(result.getSeverityLevel());
|
||||||
|
assertNotNull(result.getWarningMessage());
|
||||||
|
|
||||||
|
verify(incidentMapper).insert(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testReportMissingIndicator() {
|
||||||
|
WaterQualityRequest request = new WaterQualityRequest();
|
||||||
|
request.setTitle("测试");
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> waterQualityService.report(request));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConfirmIncident() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "DETECTED");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.confirm(1L);
|
||||||
|
|
||||||
|
assertEquals("CONFIRMED", result.getStatus());
|
||||||
|
assertNotNull(result.getConfirmedTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testConfirmWrongStatus() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> waterQualityService.confirm(1L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testStartHandling() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "CONFIRMED");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
when(planMapper.selectList(any())).thenReturn(Collections.emptyList());
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.startHandling(1L, 2L, "李四");
|
||||||
|
|
||||||
|
assertEquals("HANDLING", result.getStatus());
|
||||||
|
assertEquals(2L, result.getHandlerId());
|
||||||
|
assertEquals("李四", result.getHandlerName());
|
||||||
|
assertNotNull(result.getHandlingStartTime());
|
||||||
|
assertNotNull(result.getHandlingMeasures());
|
||||||
|
assertEquals(10, result.getHandlingProgress());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdateProgress() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.updateProgress(1L, 50, "管网冲洗完成");
|
||||||
|
|
||||||
|
assertEquals(50, result.getHandlingProgress());
|
||||||
|
assertEquals("HANDLING", result.getStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testUpdateProgressToComplete() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.updateProgress(1L, 100, "全部完成");
|
||||||
|
|
||||||
|
assertEquals(100, result.getHandlingProgress());
|
||||||
|
assertEquals("RESOLVED", result.getStatus());
|
||||||
|
assertNotNull(result.getResolvedTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveIncident() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
when(incidentMapper.updateById(any())).thenReturn(1);
|
||||||
|
|
||||||
|
WaterQualityIncident result = waterQualityService.resolve(1L, "水质恢复正常");
|
||||||
|
|
||||||
|
assertEquals("RESOLVED", result.getStatus());
|
||||||
|
assertNotNull(result.getResolvedTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testResolveAlreadyResolved() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "RESOLVED");
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
|
||||||
|
assertThrows(BusinessException.class, () -> waterQualityService.resolve(1L, "test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHandlingTimeline() {
|
||||||
|
WaterQualityIncident incident = buildIncident(1L, "HANDLING");
|
||||||
|
incident.setDetectedTime(LocalDateTime.of(2024, 1, 1, 8, 0));
|
||||||
|
incident.setConfirmedTime(LocalDateTime.of(2024, 1, 1, 9, 0));
|
||||||
|
incident.setHandlingStartTime(LocalDateTime.of(2024, 1, 1, 10, 0));
|
||||||
|
when(incidentMapper.selectById(1L)).thenReturn(incident);
|
||||||
|
|
||||||
|
Map<String, Object> timeline = waterQualityService.getHandlingTimeline(1L);
|
||||||
|
|
||||||
|
assertNotNull(timeline);
|
||||||
|
assertEquals("WQI-TEST-001", timeline.get("incidentNo"));
|
||||||
|
assertEquals("HANDLING", timeline.get("status"));
|
||||||
|
assertNotNull(timeline.get("timeline"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private WaterQualityIncident buildIncident(Long id, String status) {
|
||||||
|
WaterQualityIncident incident = new WaterQualityIncident();
|
||||||
|
incident.setId(id);
|
||||||
|
incident.setIncidentNo("WQI-TEST-001");
|
||||||
|
incident.setTitle("浊度超标");
|
||||||
|
incident.setAbnormalIndicator("TURBIDITY");
|
||||||
|
incident.setDetectedValue(3.5);
|
||||||
|
incident.setStandardValue(1.0);
|
||||||
|
incident.setExceedMultiple(3.5);
|
||||||
|
incident.setSeverityLevel("LEVEL_2");
|
||||||
|
incident.setStatus(status);
|
||||||
|
incident.setHandlingProgress(0);
|
||||||
|
return incident;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user