From 607270c15894d06b8ddd59045cf607e76b76cfc2 Mon Sep 17 00:00:00 2001 From: Your Name Date: Tue, 13 Jan 2026 16:49:50 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E5=B8=88=E6=89=A3=E5=88=86=E6=98=8E=E7=BB=86=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E5=8A=9F=E8=83=BD=EF=BC=8C=E7=82=B9=E5=87=BB=E6=89=A3=E5=88=86?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E5=8F=AF=E6=9F=A5=E7=9C=8B=E8=AF=A6=E7=BB=86?= =?UTF-8?q?=E6=89=A3=E5=88=86=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/AnalyticsController.java | 13 + .../designhub/service/AnalyticsService.java | 166 ++++++++++++ .../com/designhub/vo/DeductionDetailVO.java | 71 +++++ frontend/src/api/analytics.ts | 57 ++++ frontend/src/views/Analytics.vue | 256 +++++++++++++++++- 扣分逻辑说明.md | 140 ++++++++++ 6 files changed, 701 insertions(+), 2 deletions(-) create mode 100644 backend/src/main/java/com/designhub/vo/DeductionDetailVO.java create mode 100644 扣分逻辑说明.md diff --git a/backend/src/main/java/com/designhub/controller/AnalyticsController.java b/backend/src/main/java/com/designhub/controller/AnalyticsController.java index 2d43ea4..2200bbc 100644 --- a/backend/src/main/java/com/designhub/controller/AnalyticsController.java +++ b/backend/src/main/java/com/designhub/controller/AnalyticsController.java @@ -3,6 +3,7 @@ package com.designhub.controller; import com.designhub.common.Result; import com.designhub.service.AnalyticsService; import com.designhub.util.JwtUtil; +import com.designhub.vo.DeductionDetailVO; import com.designhub.vo.DemandStatisticsVO; import com.designhub.vo.DesignerPerformanceVO; import com.designhub.vo.ProcessEfficiencyVO; @@ -88,5 +89,17 @@ public class AnalyticsController { return Result.success(analyticsService.getWarningTasks(currentUserId)); } + + @Operation(summary = "获取设计师扣分明细") + @GetMapping("/designer-deduction-details") + public Result getDesignerDeductionDetails( + @RequestHeader("Authorization") String authHeader, + @RequestParam Long userId, + @RequestParam(required = false) String month + ) { + log.info("获取设计师扣分明细: userId={}, month={}", userId, month); + + return Result.success(analyticsService.getDesignerDeductionDetails(userId, month)); + } } diff --git a/backend/src/main/java/com/designhub/service/AnalyticsService.java b/backend/src/main/java/com/designhub/service/AnalyticsService.java index e193f09..374b2ff 100644 --- a/backend/src/main/java/com/designhub/service/AnalyticsService.java +++ b/backend/src/main/java/com/designhub/service/AnalyticsService.java @@ -3,6 +3,7 @@ package com.designhub.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.designhub.entity.*; import com.designhub.mapper.*; +import com.designhub.vo.DeductionDetailVO; import com.designhub.vo.DemandStatisticsVO; import com.designhub.vo.DesignerPerformanceVO; import com.designhub.vo.ProcessEfficiencyVO; @@ -1277,5 +1278,170 @@ public class AnalyticsService { return null; } + + /** + * 获取设计师扣分明细 + * @param userId 设计师ID + * @param month 月份 yyyy-MM + * @return 扣分明细 + */ + public DeductionDetailVO getDesignerDeductionDetails(Long userId, String month) { + log.info("获取设计师扣分明细: userId={}, month={}", userId, month); + + // 计算时间范围 + LocalDateTime startTime; + LocalDateTime endTime; + String currentMonth; + + if (month != null && !month.isEmpty()) { + currentMonth = month; + try { + LocalDate monthDate = LocalDate.parse(month + "-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")); + startTime = LocalDateTime.of(monthDate, LocalTime.MIN); + endTime = LocalDateTime.of(monthDate.with(TemporalAdjusters.lastDayOfMonth()), LocalTime.MAX); + } catch (Exception e) { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); + } + } else { + LocalDate now = LocalDate.now(); + startTime = LocalDateTime.of(now.withDayOfMonth(1), LocalTime.MIN); + endTime = LocalDateTime.now(); + currentMonth = now.format(DateTimeFormatter.ofPattern("yyyy-MM")); + } + + // 获取用户信息 + User designer = userMapper.selectById(userId); + if (designer == null) { + log.warn("设计师不存在: userId={}", userId); + return DeductionDetailVO.builder() + .totalDeduction(BigDecimal.ZERO) + .delayDeductions(new ArrayList<>()) + .progressDeductions(new ArrayList<>()) + .build(); + } + + // 获取时间范围内的已完成任务 + List completedTasks = taskMapper.selectList( + new LambdaQueryWrapper() + .eq(Task::getReceiverId, userId) + .eq(Task::getStatus, "completed") + .ge(Task::getCreateTime, startTime) + .le(Task::getCreateTime, endTime) + ); + + // 获取扣分比例配置 + BigDecimal deductionRate = getDeductionRate(designer.getDeptId(), currentMonth); + + // 计算延误扣分明细 + List delayDeductions = new ArrayList<>(); + BigDecimal totalDelayDeduction = BigDecimal.ZERO; + + for (Task task : completedTasks) { + if (task.getDeadline() != null && task.getActualEndTime() != null + && task.getActualEndTime().isAfter(task.getDeadline())) { + + // 计算任务积分 + BigDecimal taskScore = BigDecimal.ZERO; + if (task.getScoreReward() != null && task.getScoreReward().compareTo(BigDecimal.ZERO) > 0) { + taskScore = task.getScoreReward(); + } else if (task.getScore() != null) { + taskScore = BigDecimal.valueOf(task.getScore()); + } + + if (taskScore.compareTo(BigDecimal.ZERO) > 0) { + // 计算延误小时数 + long overdueHours = Duration.between(task.getDeadline(), task.getActualEndTime()).toHours(); + + if (overdueHours > 0) { + // 计算扣分 + BigDecimal taskDeduction = taskScore + .multiply(BigDecimal.valueOf(overdueHours)) + .multiply(deductionRate) + .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); + + // 扣分不能超过任务积分 + if (taskDeduction.compareTo(taskScore) > 0) { + taskDeduction = taskScore; + } + + totalDelayDeduction = totalDelayDeduction.add(taskDeduction); + + delayDeductions.add(DeductionDetailVO.DelayDeductionItem.builder() + .taskId(task.getId()) + .taskNo(task.getTaskNo()) + .taskTitle(task.getTitle()) + .taskScore(taskScore) + .deadline(task.getDeadline()) + .actualEndTime(task.getActualEndTime()) + .overdueHours(overdueHours) + .deductionScore(taskDeduction) + .deductionReason("任务延误 " + overdueHours + " 小时") + .build()); + } + } + } + } + + // 获取进度未更新扣分明细 + List progressDeductions = new ArrayList<>(); + BigDecimal totalProgressDeduction = BigDecimal.ZERO; + + try { + if (progressDeductionLogMapper != null) { + // 解析月份 + String[] monthParts = currentMonth.split("-"); + if (monthParts.length == 2) { + int yearNum = Integer.parseInt(monthParts[0]); + int monthNum = Integer.parseInt(monthParts[1]); + + // 查询该月的所有进度未更新扣分记录 + LocalDate monthStartDate = LocalDate.of(yearNum, monthNum, 1); + LocalDate monthEndDate = monthStartDate.with(TemporalAdjusters.lastDayOfMonth()); + + List progressLogs = progressDeductionLogMapper.selectList( + new LambdaQueryWrapper() + .eq(ProgressDeductionLog::getUserId, userId) + .ge(ProgressDeductionLog::getDeductionDate, monthStartDate) + .le(ProgressDeductionLog::getDeductionDate, monthEndDate) + .isNull(ProgressDeductionLog::getTaskItemId) // 只查询主任务的扣分 + .orderByDesc(ProgressDeductionLog::getDeductionDate) + ); + + for (ProgressDeductionLog log : progressLogs) { + if (log.getDeductionScore() != null && log.getDeductionScore().compareTo(BigDecimal.ZERO) > 0) { + totalProgressDeduction = totalProgressDeduction.add(log.getDeductionScore()); + + // 获取任务信息 + Task task = taskMapper.selectById(log.getTaskId()); + String taskNo = task != null ? task.getTaskNo() : ""; + String taskTitle = task != null ? task.getTitle() : "任务已删除"; + + progressDeductions.add(DeductionDetailVO.ProgressDeductionItem.builder() + .taskId(log.getTaskId()) + .taskNo(taskNo) + .taskTitle(taskTitle) + .deductionDate(log.getDeductionDate()) + .deductionScore(log.getDeductionScore()) + .reason(log.getReason() != null ? log.getReason() : "当日未更新任务进度") + .build()); + } + } + } + } + } catch (Exception e) { + log.warn("获取进度未更新扣分明细失败: {}", e.getMessage()); + } + + BigDecimal totalDeduction = totalDelayDeduction.add(totalProgressDeduction); + + return DeductionDetailVO.builder() + .totalDeduction(totalDeduction) + .delayDeductions(delayDeductions) + .progressDeductions(progressDeductions) + .build(); + } } diff --git a/backend/src/main/java/com/designhub/vo/DeductionDetailVO.java b/backend/src/main/java/com/designhub/vo/DeductionDetailVO.java new file mode 100644 index 0000000..def1bb0 --- /dev/null +++ b/backend/src/main/java/com/designhub/vo/DeductionDetailVO.java @@ -0,0 +1,71 @@ +package com.designhub.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.List; + +/** + * 扣分明细VO + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class DeductionDetailVO { + + /** + * 总扣分 + */ + private BigDecimal totalDeduction; + + /** + * 延误扣分明细列表 + */ + private List delayDeductions; + + /** + * 进度未更新扣分明细列表 + */ + private List progressDeductions; + + /** + * 延误扣分明细项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class DelayDeductionItem { + private Long taskId; + private String taskNo; + private String taskTitle; + private BigDecimal taskScore; + private LocalDateTime deadline; + private LocalDateTime actualEndTime; + private Long overdueHours; + private BigDecimal deductionScore; + private String deductionReason; + } + + /** + * 进度未更新扣分明细项 + */ + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + public static class ProgressDeductionItem { + private Long taskId; + private String taskNo; + private String taskTitle; + private java.time.LocalDate deductionDate; + private BigDecimal deductionScore; + private String reason; + } +} + diff --git a/frontend/src/api/analytics.ts b/frontend/src/api/analytics.ts index c008d46..e3d6d3f 100644 --- a/frontend/src/api/analytics.ts +++ b/frontend/src/api/analytics.ts @@ -351,3 +351,60 @@ export const getWarningTasks = async (): Promise => { return []; }; +// ==================== 扣分明细类型 ==================== + +export interface DelayDeductionItem { + taskId: number; + taskNo: string; + taskTitle: string; + taskScore: number; + deadline: string; + actualEndTime: string; + overdueHours: number; + deductionScore: number; + deductionReason: string; +} + +export interface ProgressDeductionItem { + taskId: number; + taskNo: string; + taskTitle: string; + deductionDate: string; + deductionScore: number; + reason: string; +} + +export interface DeductionDetail { + totalDeduction: number; + delayDeductions: DelayDeductionItem[]; + progressDeductions: ProgressDeductionItem[]; +} + +/** + * 获取设计师扣分明细 + */ +export const getDesignerDeductionDetails = async ( + userId: number, + month?: string +): Promise => { + const params: any = { userId }; + if (month) params.month = month; + + const response = await request.get('/analytics/designer-deduction-details', { params }); + + if (response && typeof response === 'object') { + if ('data' in response && response.data !== undefined && response.data !== null) { + return response.data as DeductionDetail; + } + if ('totalDeduction' in response || 'delayDeductions' in response) { + return response as unknown as DeductionDetail; + } + } + + return { + totalDeduction: 0, + delayDeductions: [], + progressDeductions: [] + }; +}; + diff --git a/frontend/src/views/Analytics.vue b/frontend/src/views/Analytics.vue index f7feca3..61a127f 100644 --- a/frontend/src/views/Analytics.vue +++ b/frontend/src/views/Analytics.vue @@ -251,7 +251,13 @@ {{ record.totalScore || 0 }} @@ -429,10 +508,14 @@ import { getDesignerPerformance, getProcessEfficiency, getWarningTasks, + getDesignerDeductionDetails, type DemandStatistics, type DesignerPerformance, type ProcessEfficiency, - type WarningTask + type WarningTask, + type DeductionDetail, + type DelayDeductionItem, + type ProgressDeductionItem } from '@/api/analytics' import { message } from 'ant-design-vue' import * as echarts from 'echarts' @@ -524,6 +607,16 @@ const warningFilter = ref('all') const selectedDeptId = ref(undefined) const departmentList = ref([]) +// 扣分明细弹窗 +const deductionDetailVisible = ref(false) +const deductionDetailLoading = ref(false) +const currentDesigner = ref(null) +const deductionDetail = ref({ + totalDeduction: 0, + delayDeductions: [], + progressDeductions: [] +}) + // 图表引用 const trendChartRef = ref() const typeChartRef = ref() @@ -652,6 +745,26 @@ const warningColumns = [ { title: '操作', key: 'action', width: 140, align: 'center' } ] +// 扣分明细表格列定义 +const delayDeductionColumns = [ + { title: '任务编号', dataIndex: 'taskNo', key: 'taskNo', width: 120 }, + { title: '任务名称', dataIndex: 'taskTitle', key: 'taskTitle', ellipsis: true }, + { title: '任务积分', dataIndex: 'taskScore', key: 'taskScore', width: 100, align: 'center' }, + { title: '截止时间', key: 'deadline', width: 150 }, + { title: '实际完成时间', key: 'actualEndTime', width: 150 }, + { title: '延误时长', key: 'overdueHours', width: 100, align: 'center' }, + { title: '扣分', key: 'deductionScore', width: 100, align: 'center' }, + { title: '扣分原因', dataIndex: 'deductionReason', key: 'deductionReason', width: 150 } +] + +const progressDeductionColumns = [ + { title: '任务编号', dataIndex: 'taskNo', key: 'taskNo', width: 120 }, + { title: '任务名称', dataIndex: 'taskTitle', key: 'taskTitle', ellipsis: true }, + { title: '扣分日期', key: 'deductionDate', width: 120 }, + { title: '扣分', key: 'deductionScore', width: 100, align: 'center' }, + { title: '扣分原因', dataIndex: 'reason', key: 'reason' } +] + // 方法 const handleMonthChange = (value: string | null) => { selectedMonth.value = value @@ -973,6 +1086,40 @@ const handleDeptChange = (deptId: number | undefined) => { loadDesignerData() } +// 显示扣分明细 +const showDeductionDetails = async (record: any) => { + if (!record.deductionScore || record.deductionScore <= 0) { + return // 没有扣分,不显示弹窗 + } + + currentDesigner.value = record + deductionDetailVisible.value = true + deductionDetailLoading.value = true + + try { + const month = selectedMonth.value || undefined + const detail = await getDesignerDeductionDetails(record.userId, month) + deductionDetail.value = detail + } catch (error) { + message.error('加载扣分明细失败') + console.error('加载扣分明细失败:', error) + } finally { + deductionDetailLoading.value = false + } +} + +// 格式化时间 +const formatDateTime = (time: string | undefined) => { + if (!time) return '--' + return dayjs(time).format('YYYY-MM-DD HH:mm') +} + +// 格式化日期 +const formatDateOnly = (date: string | undefined) => { + if (!date) return '--' + return dayjs(date).format('YYYY-MM-DD') +} + // 单独加载设计师绩效数据(用于部门筛选) const loadDesignerData = async () => { loading.value = true @@ -1500,6 +1647,17 @@ onMounted(() => { font-weight: 500; color: #ef4444; font-size: 14px; + + &.clickable { + text-decoration: underline; + text-decoration-style: dashed; + text-underline-offset: 2px; + + &:hover { + color: #ff6b6b; + text-decoration-color: #ff6b6b; + } + } } // 净积分样式 @@ -1676,4 +1834,98 @@ onMounted(() => { } } } + +// 扣分明细弹窗样式 +.deduction-detail-modal { + :deep(.ant-modal-content) { + background: #1f1f1f; + border: 1px solid #303030; + } + + :deep(.ant-modal-header) { + background: #1f1f1f; + border-bottom: 1px solid #303030; + + .ant-modal-title { + color: #ffffff; + } + } + + :deep(.ant-modal-body) { + background: #1f1f1f; + padding: 24px; + } +} + +.deduction-detail-content { + .designer-info { + margin-bottom: 24px; + padding-bottom: 16px; + border-bottom: 1px solid #303030; + + h4 { + color: #ffffff; + font-size: 18px; + font-weight: 600; + margin: 0 0 8px 0; + } + + .designer-dept { + color: rgba(255, 255, 255, 0.65); + font-size: 14px; + margin: 0 0 8px 0; + } + + .total-deduction { + color: rgba(255, 255, 255, 0.85); + font-size: 16px; + margin: 0; + + .deduction-number { + color: #ef4444; + font-weight: 600; + font-size: 18px; + } + } + } + + .deduction-section { + margin-bottom: 24px; + + h5 { + color: #ffffff; + font-size: 16px; + font-weight: 600; + margin: 0 0 12px 0; + } + } + + .deduction-table { + :deep(.ant-table) { + background: #141414; + + .ant-table-thead > tr > th { + background: rgba(0, 0, 0, 0.4); + border-bottom: 1px solid #303030; + color: rgba(255, 255, 255, 0.85); + } + + .ant-table-tbody > tr > td { + background: #141414; + border-bottom: 1px solid #262626; + color: rgba(255, 255, 255, 0.85); + } + + .ant-table-tbody > tr:hover > td { + background: #1a1a1a; + } + } + } + + .no-data { + text-align: center; + padding: 40px 0; + color: rgba(255, 255, 255, 0.45); + } +} diff --git a/扣分逻辑说明.md b/扣分逻辑说明.md new file mode 100644 index 0000000..f5e9c99 --- /dev/null +++ b/扣分逻辑说明.md @@ -0,0 +1,140 @@ +# 设计师绩效排行榜扣分逻辑说明 + +## 一、当前实现逻辑(数据分析页面) + +数据分析页面的设计师绩效排行榜使用的是 `AnalyticsService.getDesignerPerformance` 方法,扣分逻辑如下: + +### 1. 延误扣分计算公式 + +``` +扣分 = 任务积分 × 延误小时数 × 扣分比例(默认1%) +``` + +**限制条件:** +- 扣分不能超过任务积分 +- 如果计算结果超过任务积分,则扣分 = 任务积分 + +**扣分比例:** +- 默认值:1%(即 `BigDecimal.ONE`) +- 可以从部门配置中获取(`DesignerTargetScore.deductionRate`) +- 三级部门继承二级部门的配置 + +### 2. 进度未更新扣分 + +除了延误扣分,还会加上进度未更新扣分: +- 从 `progress_deduction_log` 表中查询当月的进度未更新扣分总额 +- 进度未更新扣分 + 延误扣分 = 总扣分 + +### 3. 净积分计算 + +``` +净积分 = 获得积分 - 总扣分 +如果净积分 < 0,则净积分 = 0 +``` + +## 二、需求文档中的扣分规则(DelayPenaltyService) + +需求文档中定义的延误扣分规则(在 `DelayPenaltyService` 中实现,但**当前数据分析页面未使用**): + +| 延误程度 | 延误时长 | 扣分规则 | 示例 | +|---------|---------|---------|------| +| 轻微延误 | < 2小时 | 每小时扣2分 | 延误1.5小时 → 扣3分 | +| 一般延误 | 2-8小时 | 每小时扣3分 | 延误5小时 → 扣15分 | +| 严重延误 | 8-24小时 | 每小时扣5分 | 延误10小时 → 扣50分 | +| 重大延误 | > 24小时 | 基础50分+每天10分 | 延误2天 → 扣70分 | + +## 三、"若兰"被扣10分的情况分析 + +从图片数据看: +- **获得积分**:10分 +- **延误扣分**:10分 +- **净积分**:0分 + +### 可能的原因: + +1. **情况A:延误扣分达到上限** + - 如果任务积分是10分,延误时间较长 + - 按照公式:`10分 × 延误小时数 × 1% = 10分` + - 或者计算结果超过10分,被限制为10分 + +2. **情况B:延误扣分 + 进度未更新扣分 = 10分** + - 延误扣分 + 进度未更新扣分 = 10分 + - 例如:延误扣分5分 + 进度未更新扣分5分 = 10分 + +## 四、代码位置 + +### 当前使用的扣分逻辑: +- 文件:`backend/src/main/java/com/designhub/service/AnalyticsService.java` +- 方法:`getDesignerPerformance`(第535行) +- 扣分计算:第680-698行(延误扣分)+ 第701-718行(进度未更新扣分) +- 扣分比例获取:`getDeductionRate`(第1146行) + +### 需求文档中的扣分逻辑(未在数据分析页面使用): +- 文件:`backend/src/main/java/com/designhub/service/DelayPenaltyService.java` +- 方法:`calculateDelayPenalty`(第36行) +- 规则:按延误等级和固定扣分规则 + +## 五、建议复核的问题 + +1. **数据分析页面应该使用哪套扣分规则?** + - 当前实现:按百分比扣分(任务积分 × 延误小时数 × 扣分比例) + - 需求文档:按延误等级固定扣分(轻微/一般/严重/重大) + +2. **扣分比例默认值是否合理?** + - 当前默认:1% + - 如果任务积分100分,延误10小时,扣分 = 100 × 10 × 1% = 10分 + - 如果任务积分10分,延误100小时,扣分 = min(10 × 100 × 1%, 10) = 10分 + +3. **进度未更新扣分是否应该计入?** + - 当前实现:计入总扣分 + - 是否需要单独显示? + +4. **是否需要使用 DelayPenaltyRecord 表的数据?** + - 当前实现:直接从任务表计算,不使用 DelayPenaltyRecord + - 需求文档中有 DelayPenaltyRecord 表的设计,但数据分析页面未使用 + +## 六、相关代码片段 + +### 当前扣分计算逻辑(AnalyticsService.java 第680-698行): +```java +// 计算延误扣分(如果超时完成) +if (task.getDeadline() != null && task.getActualEndTime() != null + && task.getActualEndTime().isAfter(task.getDeadline())) { + // 计算延误小时数 + long overdueHours = Duration.between(task.getDeadline(), task.getActualEndTime()).toHours(); + if (overdueHours > 0 && taskScore.compareTo(BigDecimal.ZERO) > 0) { + // 扣分 = 任务积分 * 延误小时数 * 扣分比例 + BigDecimal taskDeduction = taskScore + .multiply(BigDecimal.valueOf(overdueHours)) + .multiply(deductionRate) + .divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP); + // 扣分不能超过任务积分 + if (taskDeduction.compareTo(taskScore) > 0) { + taskDeduction = taskScore; + } + deductionScore = deductionScore.add(taskDeduction); + } +} +``` + +### 需求文档中的扣分规则(DelayPenaltyService.java 第56-72行): +```java +if (delayHours < 2) { + // 轻微延误:每小时扣2分 + delayLevel = "light"; + penaltyScore = (int) Math.ceil(delayHours * 2.0); +} else if (delayHours < 8) { + // 一般延误:每小时扣3分 + delayLevel = "general"; + penaltyScore = (int) Math.ceil(delayHours * 3.0); +} else if (delayHours < 24) { + // 严重延误:每小时扣5分 + delayLevel = "severe"; + penaltyScore = (int) Math.ceil(delayHours * 5.0); +} else { + // 重大延误:基础50分+每天10分 + delayLevel = "major"; + penaltyScore = 50 + (int) delayDays * 10; +} +``` +