using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using ZhonTai.Admin.Services; using ZhonTai.DynamicApi; using ZhonTai.DynamicApi.Attributes; using NPP.SmartSchedue.Api.Contracts.Services.Integration; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Input; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Output; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Repositories.Work; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 智能调度编排服务 /// 职责:协调各个专业服务,实现智能任务整合的核心流程编排 /// 架构思考:采用编排器模式,专注于流程控制而非具体业务逻辑实现 /// [DynamicApi(Area = "app")] public class SmartScheduleOrchestratorService : BaseService, ISmartScheduleOrchestratorService, IDynamicApi { private readonly WorkOrderRepository _workOrderRepository; private readonly IGlobalPersonnelAllocationService _globalPersonnelAllocationService; private readonly IEquipmentAllocationService _equipmentAllocationService; private readonly IIntegrationRecordService _integrationRecordService; private readonly ITaskValidationService _taskValidationService; private readonly ILogger _logger; public SmartScheduleOrchestratorService( WorkOrderRepository workOrderRepository, IGlobalPersonnelAllocationService globalPersonnelAllocationService, IEquipmentAllocationService equipmentAllocationService, IIntegrationRecordService integrationRecordService, ITaskValidationService taskValidationService, ILogger logger) { _workOrderRepository = workOrderRepository; _globalPersonnelAllocationService = globalPersonnelAllocationService; _equipmentAllocationService = equipmentAllocationService; _integrationRecordService = integrationRecordService; _taskValidationService = taskValidationService; _logger = logger; } /// /// 执行智能任务整合主流程 /// 深度业务思考:编排器负责协调各服务,确保流程的一致性和完整性 /// 架构优化:增加分组处理、降级策略和智能重试机制 /// [HttpPost] public async Task ExecuteSmartIntegrationAsync(SmartIntegrationInput input) { var stopwatch = Stopwatch.StartNew(); var batchCode = GenerateIntegrationBatchCode(); _logger.LogInformation("==================== 开始执行智能任务整合主流程 ===================="); _logger.LogInformation("🎯 整合批次号: {BatchCode}", batchCode); _logger.LogInformation("📊 整合策略: 人员={PersonnelStrategy}, 设备={EquipmentStrategy}, 强制班次规则={EnforceShiftRules}", input.Strategy?.PersonnelStrategy, input.Strategy?.EquipmentStrategy, input.Strategy?.EnforceShiftRules); _logger.LogInformation("👤 操作员: {OperatorName} (ID: {OperatorUserId})", input.OperatorName, input.OperatorUserId); var result = new SmartIntegrationResult { IntegrationBatchCode = batchCode, CompletionTime = DateTime.Now }; try { // 0. 获取待整合的任务列表 _logger.LogInformation("🔍 步骤0: 开始获取待整合的任务列表..."); var taskStopwatch = Stopwatch.StartNew(); var tasksToIntegrate = await GetTasksForIntegrationAsync(input); taskStopwatch.Stop(); _logger.LogInformation("✅ 步骤0完成: 获取到 {TaskCount} 个待整合任务,耗时 {ElapsedMs}ms", tasksToIntegrate.Count, taskStopwatch.ElapsedMilliseconds); if (!tasksToIntegrate.Any()) { _logger.LogWarning("⚠️ 未找到需要整合的任务,流程结束"); result.IsSuccess = false; result.IntegrationDetails = "未找到需要整合的任务"; return result; } // 打印任务详细信息和日期分析 _logger.LogInformation("📋 待整合任务详情:"); var tasksByDate = GroupTasksByDate(tasksToIntegrate); foreach (var task in tasksToIntegrate) { var hasPreAssigned = task.AssignedPersonnelId.HasValue && task.AssignedPersonnelId.Value > 0; _logger.LogInformation(" 任务ID: {TaskId}, 编码: {TaskCode}, 状态: {Status}, 日期: {WorkDate}, 预分配: {PreAssigned}", task.Id, task.WorkOrderCode, task.Status, task.WorkOrderDate.ToString("yyyy-MM-dd"), hasPreAssigned ? $"人员{task.AssignedPersonnelId}" : "无"); } _logger.LogInformation("📅 任务日期分组: {DateGroups} 个日期组,跨度 {DateSpan} 天", tasksByDate.Count, tasksByDate.Keys.Any() ? (tasksByDate.Keys.Max() - tasksByDate.Keys.Min()).Days + 1 : 0); // 1. 任务需求汇总与验证 _logger.LogInformation("🔍 步骤1: 开始任务需求汇总与验证..."); var validationStopwatch = Stopwatch.StartNew(); _logger.LogInformation(" 1.1 分析任务需求..."); await _taskValidationService.AnalyzeTaskRequirementsAsync(tasksToIntegrate); _logger.LogInformation(" 1.2 验证任务数据完整性..."); await _taskValidationService.ValidateTaskDataIntegrityAsync(tasksToIntegrate); _logger.LogInformation(" 1.3 加载任务详细信息..."); await _taskValidationService.LoadTaskDetailedInfoAsync(tasksToIntegrate); validationStopwatch.Stop(); _logger.LogInformation("✅ 步骤1完成: 任务验证完成,耗时 {ElapsedMs}ms", validationStopwatch.ElapsedMilliseconds); // 2. 执行智能人员分配 - 分时段处理优化 _logger.LogInformation("🔍 步骤2: 开始执行智能人员分配..."); var personnelStopwatch = Stopwatch.StartNew(); // 检查是否需要分时段处理 bool useTimeSegmentedAllocation = tasksByDate.Count > 1 && !input.Strategy.EnforceShiftRules; if (useTimeSegmentedAllocation) { _logger.LogInformation(" 2.1 采用分时段人员分配策略处理 {DateCount} 个日期组", tasksByDate.Count); result.PersonnelAllocation = await ExecuteTimeSegmentedPersonnelAllocationAsync( tasksByDate, input.Strategy, input.OperatorUserId, input.OperatorName); } else { _logger.LogInformation(" 2.1 采用统一人员分配策略: {Strategy}, 强制班次规则: {EnforceShiftRules}", input.Strategy.PersonnelStrategy, input.Strategy.EnforceShiftRules); var globalAllocationInput = new GlobalAllocationInput { Tasks = tasksToIntegrate, ExcludedPersonnelIds = new List() }; var globalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(globalAllocationInput); result.PersonnelAllocation = ConvertToPersonnelAllocationResult(globalResult); } personnelStopwatch.Stop(); _logger.LogInformation("✅ 步骤2完成: 人员分配完成,成功 {SuccessCount} 个,失败 {FailedCount} 个,耗时 {ElapsedMs}ms", result.PersonnelAllocation?.SuccessfulMatches?.Count ?? 0, result.PersonnelAllocation?.FailedTasks?.Count ?? 0, personnelStopwatch.ElapsedMilliseconds); // 2.2 智能重试机制:如果失败任务过多,尝试降级策略 if (result.PersonnelAllocation?.FailedTasks?.Count > 0) { var failedCount = result.PersonnelAllocation.FailedTasks.Count; var totalCount = tasksToIntegrate.Count; var failureRate = (double)failedCount / totalCount; if (failureRate > 0.3) // 失败率超过30%时启动降级重试 { _logger.LogWarning("⚠️ 人员分配失败率 {FailureRate:P1},启动降级重试策略...", failureRate); await ExecuteFallbackPersonnelAllocationAsync(result, tasksToIntegrate, input.Strategy); } } // 3. 执行智能设备分配 _logger.LogInformation("🔍 步骤3: 开始执行智能设备分配..."); var equipmentStopwatch = Stopwatch.StartNew(); // 深度架构优化:直接传入Tasks对象,避免重复数据库查询 var equipmentAllocationInput = new EquipmentAllocationInput { Tasks = tasksToIntegrate, // 性能优化:直接使用已加载的task对象 TaskIds = tasksToIntegrate.Select(t => t.Id).ToList(), // 保持兼容性 Strategy = input.Strategy.EquipmentStrategy, TargetUtilization = input.Strategy.TargetEquipmentUtilization, ExcludedEquipmentIds = new List(), PreferredEquipmentIds = new List() }; _logger.LogInformation(" 3.1 设备分配策略: {Strategy}, 目标利用率: {TargetUtilization}%", input.Strategy.EquipmentStrategy, input.Strategy.TargetEquipmentUtilization); result.EquipmentAllocation = await _equipmentAllocationService.AllocateEquipmentSmartlyAsync(equipmentAllocationInput); equipmentStopwatch.Stop(); _logger.LogInformation("✅ 步骤3完成: 设备分配完成,成功 {SuccessCount} 个,失败 {FailedCount} 个,耗时 {ElapsedMs}ms", result.EquipmentAllocation?.SuccessfulMatches?.Count ?? 0, result.EquipmentAllocation?.FailedAllocations.Count ?? 0, equipmentStopwatch.ElapsedMilliseconds); // 4. 更新任务状态 await UpdateTaskStatusAsync(tasksToIntegrate, result); // 5. 生成整合记录 var integrationRecordInput = new IntegrationRecordInput { TaskIds = tasksToIntegrate.Select(t => t.Id).ToList(), PersonnelAllocation = result.PersonnelAllocation, EquipmentAllocation = result.EquipmentAllocation, Strategy = input.Strategy, OperatorUserId = input.OperatorUserId, OperatorName = input.OperatorName, Remarks = input.Remarks }; var integrationRecord = await _integrationRecordService.GenerateIntegrationRecordAsync(integrationRecordInput); result.IntegrationRecordId = integrationRecord.Id; result.IntegrationBatchCode = integrationRecord.IntegrationBatchCode; // 6. 最终结果汇总统计 GenerateFinalResultsSummary(result, tasksToIntegrate); } catch (Exception ex) { _logger.LogError(ex, "💥 智能整合过程中发生严重异常: {ExceptionMessage}", ex.Message); result.IsSuccess = false; result.IntegrationDetails = $"智能整合过程中发生异常: {ex.Message}"; // 详细的异常分析日志 _logger.LogError("🔍 异常详细信息:"); _logger.LogError(" 异常类型: {ExceptionType}", ex.GetType().Name); _logger.LogError(" 异常消息: {ExceptionMessage}", ex.Message); _logger.LogError(" 堆栈跟踪: {StackTrace}", ex.StackTrace); if (ex.InnerException != null) { _logger.LogError(" 内部异常: {InnerExceptionType} - {InnerExceptionMessage}", ex.InnerException.GetType().Name, ex.InnerException.Message); } result.FailedTasks.Add(new FailedTaskInfo { TaskId = 0, TaskCode = "系统错误", FailureReason = $"系统异常: {ex.Message}", FailureType = FailureType.SystemError, ConflictDetails = new List { $"异常类型: {ex.GetType().Name}", $"发生时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}", $"整合批次: {batchCode}" } }); } stopwatch.Stop(); result.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds; return result; } #region 私有辅助方法 /// /// 获取待整合的任务列表 /// private async Task> GetTasksForIntegrationAsync(SmartIntegrationInput input) { var query = _workOrderRepository.Select .Where(w => w.Status == (int)WorkOrderStatusEnum.PendingIntegration) .Include(w => w.ProcessEntity) .IncludeMany(w => w.WorkOrderFLPersonnels); if (input.SelectedTaskIds?.Any() == true) { query = query.Where(t => input.SelectedTaskIds.Contains(t.Id)); } if (input.SelectedProjectNumbers?.Any() == true) { query = query.Where(t => input.SelectedProjectNumbers.Contains(t.ProjectNumber)); } var allTasks = await query.ToListAsync(); var tasks = allTasks .OrderBy(w => w.Priority) .ThenBy(w => w.UrgencyLevel) .ThenBy(w => w.ComplexityLevel) .ThenBy(w => w.PlannedStartTime) .ToList(); // 项目分组优化 return tasks .GroupBy(t => t.ProjectNumber) .OrderBy(g => g.Min(t => t.Priority)) .SelectMany(g => g.OrderBy(t => t.PlannedStartTime)) .ToList(); } /// /// 更新任务状态 /// private async Task UpdateTaskStatusAsync(List tasks, SmartIntegrationResult result) { var successfulTaskIds = result.PersonnelAllocation.SuccessfulMatches.Select(m => m.TaskId).ToHashSet(); foreach (var task in tasks.Where(t => successfulTaskIds.Contains(t.Id))) { task.Status = (int)WorkOrderStatusEnum.Assigned; task.AssignedTime = DateTime.Now; task.LastModifiedTime = DateTime.Now; var personnelMatch = result.PersonnelAllocation.SuccessfulMatches .FirstOrDefault(m => m.TaskId == task.Id); var equipmentMatch = result.EquipmentAllocation.SuccessfulMatches .FirstOrDefault(m => m.TaskId == task.Id); var allocationNotes = new List(); if (personnelMatch != null) { task.AssignedPersonnelId = personnelMatch.PersonnelId; task.AssignedPersonnelName = personnelMatch.PersonnelName; allocationNotes.Add($"分配人员: {personnelMatch.PersonnelName ?? "未知"} (评分: {personnelMatch.MatchScore:F1})"); } if (equipmentMatch != null) { task.AssignedEquipmentId = equipmentMatch.EquipmentId; task.AssignedEquipmentName = equipmentMatch.EquipmentName; allocationNotes.Add($"分配设备: {equipmentMatch.EquipmentCode ?? equipmentMatch.EquipmentId.ToString()}"); } allocationNotes.Add($"智能分配完成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); task.Remarks = string.Join("; ", allocationNotes); } // 处理失败的任务 var failedTasks = tasks.Where(t => !successfulTaskIds.Contains(t.Id)).ToList(); foreach (var failedTask in failedTasks) { failedTask.Status = (int)WorkOrderStatusEnum.PendingIntegration; failedTask.Remarks = $"分配失败,等待重新分配 - {DateTime.Now:yyyy-MM-dd HH:mm}"; } if (tasks.Any()) { await _workOrderRepository.UpdateAsync(tasks); } } /// /// 最终结果汇总统计 /// 优化:提供更详细的分析报告和改进建议 /// private void GenerateFinalResultsSummary(SmartIntegrationResult result, List tasksToIntegrate) { var successCount = result.PersonnelAllocation?.SuccessfulMatches?.Count ?? 0; var totalTasks = tasksToIntegrate.Count; result.SuccessfulTaskCount = successCount; result.OverallSuccessRate = totalTasks > 0 ? (double)successCount / totalTasks : 0; result.FailedTasks = CollectFailedTasksComprehensive(result); var fairnessScore = result.PersonnelAllocation?.FairnessScore ?? 0; var equipmentUtilizationRate = result.EquipmentAllocation?.OverallUtilizationRate ?? 0; var equipmentSuccessCount = result.EquipmentAllocation?.SuccessfulMatches?.Count ?? 0; // 生成详细的统计报告 _logger.LogInformation("📊 === 智能整合最终统计报告 ==="); _logger.LogInformation("🎯 总体成功率: {SuccessRate:P1} ({SuccessCount}/{TotalCount})", result.OverallSuccessRate, successCount, totalTasks); _logger.LogInformation("👥 人员分配: 成功 {PersonnelSuccess} 个,公平性评分 {FairnessScore:F1}", successCount, fairnessScore); _logger.LogInformation("🔧 设备分配: 成功 {EquipmentSuccess} 个,利用率 {UtilizationRate:F1}%", equipmentSuccessCount, equipmentUtilizationRate); _logger.LogInformation("⏱️ 总耗时: {ElapsedTime}ms", result.ElapsedMilliseconds); // 按失败原因分类统计 if (result.FailedTasks?.Any() == true) { var failureStats = result.FailedTasks .GroupBy(f => f.FailureType) .Select(g => new { Type = g.Key, Count = g.Count() }) .OrderByDescending(x => x.Count); _logger.LogWarning("⚠️ 失败任务分析:"); foreach (var stat in failureStats) { _logger.LogWarning(" {FailureType}: {Count} 个任务", stat.Type, stat.Count); } } // 生成改进建议 var suggestions = GenerateImprovementSuggestions(result, tasksToIntegrate); if (suggestions.Any()) { _logger.LogInformation("💡 改进建议:"); foreach (var suggestion in suggestions) { _logger.LogInformation(" • {Suggestion}", suggestion); } } result.IntegrationDetails = $"智能整合完成。" + $"成功分配 {result.SuccessfulTaskCount} 个任务," + $"失败 {result.FailedTasks.Count} 个任务。" + $"人员分配公平性评分: {fairnessScore:F1}," + $"设备利用率: {equipmentUtilizationRate:F1}%。" + $"执行耗时: {result.ElapsedMilliseconds}ms。"; result.IsSuccess = result.SuccessfulTaskCount > 0; result.CompletionTime = DateTime.Now; if (string.IsNullOrEmpty(result.IntegrationBatchCode)) { result.IntegrationBatchCode = GenerateIntegrationBatchCode(); } } /// /// 生成改进建议 /// private List GenerateImprovementSuggestions(SmartIntegrationResult result, List tasksToIntegrate) { var suggestions = new List(); if (result.OverallSuccessRate < 0.7) { suggestions.Add("成功率偏低,建议增加人员池或调整任务优先级"); } if (result.FailedTasks?.Any(f => f.FailureType == FailureType.PersonnelAllocationFailed) == true) { suggestions.Add("存在人员不可用问题,建议优化人员排班计划"); } if (result.PersonnelAllocation?.FairnessScore < 70) { suggestions.Add("人员分配公平性有待提升,建议调整分配策略"); } var dateSpan = tasksToIntegrate.Any() ? (tasksToIntegrate.Max(t => t.WorkOrderDate) - tasksToIntegrate.Min(t => t.WorkOrderDate)).Days : 0; if (dateSpan > 7) { suggestions.Add("任务时间跨度较大,建议采用分时段处理策略"); } return suggestions; } /// /// 收集失败任务信息 /// private List CollectFailedTasksComprehensive(SmartIntegrationResult result) { var failedTasks = new List(); if (result.PersonnelAllocation?.FailedTasks != null) { failedTasks.AddRange(result.PersonnelAllocation.FailedTasks); } if (result.EquipmentAllocation?.FailedAllocations != null) { failedTasks.AddRange(result.EquipmentAllocation.FailedAllocations.Select(f => new FailedTaskInfo { TaskId = f.TaskId, TaskCode = f.TaskCode, FailureReason = f.FailureReason, FailureType = FailureType.EquipmentUnavailable, ConflictDetails = f.ConflictDetails ?? new List() })); } return failedTasks.GroupBy(f => f.TaskId).Select(g => g.First()).ToList(); } /// /// 生成整合批次编码 /// 格式:INT + yyyyMMddHHmmss + XXX,总长度23字符,符合数据库30字符限制 /// 业务思考:优化编码长度,保持唯一性的同时确保数据库兼容性 /// private string GenerateIntegrationBatchCode() { return $"INT{DateTime.Now:yyyyMMddHHmmss}{new Random().Next(100, 999)}"; } /// /// 按日期分组任务 /// 架构设计:将任务按工作日期分组,确保不同日期的任务可以分配给同一人员 /// 深度业务思考:这种分组方式解决了原算法中时间维度处理粗糙的问题 /// private Dictionary> GroupTasksByDate(List tasks) { return tasks .Where(t => t.WorkOrderDate != default(DateTime)) .GroupBy(t => t.WorkOrderDate.Date) .ToDictionary(g => g.Key, g => g.OrderBy(t => t.PlannedStartTime).ToList()); } /// /// 执行分时段人员分配 /// 核心算法优化:分别处理每个日期的任务,避免跨日期的时间冲突误判 /// 预分配优化:确保已指定人员的任务也进行最优性检测 /// 约束分离:独立处理时间约束与匹配算法 /// private async Task ExecuteTimeSegmentedPersonnelAllocationAsync( Dictionary> taskGroups, IntegrationStrategy strategy, long operatorUserId, string operatorName) { var allSuccessfulMatches = new List(); var allFailedTasks = new List(); var totalFairnessScore = 0.0; var processedDates = 0; foreach (var dateGroup in taskGroups.OrderBy(g => g.Key)) { var date = dateGroup.Key; var dailyTasks = dateGroup.Value; _logger.LogInformation("🗓️ 处理 {Date} 的 {TaskCount} 个任务", date.ToString("yyyy-MM-dd"), dailyTasks.Count); // 为当日任务执行人员分配 var dailyGlobalInput = new GlobalAllocationInput { Tasks = dailyTasks, ExcludedPersonnelIds = new List() }; // 执行当日分配,采用增强的预分配约束处理 var dailyGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(dailyGlobalInput); var dailyResult = ConvertToPersonnelAllocationResult(dailyGlobalResult); if (dailyResult?.SuccessfulMatches != null) { allSuccessfulMatches.AddRange(dailyResult.SuccessfulMatches); _logger.LogInformation(" ✅ {Date} 分配成功 {SuccessCount} 个任务", date.ToString("MM-dd"), dailyResult.SuccessfulMatches.Count); } if (dailyResult?.FailedTasks != null) { allFailedTasks.AddRange(dailyResult.FailedTasks); if (dailyResult.FailedTasks.Any()) { _logger.LogWarning(" ⚠️ {Date} 分配失败 {FailedCount} 个任务", date.ToString("MM-dd"), dailyResult.FailedTasks.Count); } } // 累计公平性评分 if (dailyResult?.FairnessScore > 0) { totalFairnessScore += dailyResult.FairnessScore; processedDates++; } // 记录详细分配信息用于调试 LogDailyAllocationDetails(date, dailyResult); } // 执行跨日期约束验证 var crossDateValidationResult = await ValidateCrossDateConstraintsAsync(allSuccessfulMatches, taskGroups); if (!crossDateValidationResult.IsValid) { _logger.LogWarning("⚠️ 跨日期约束验证失败: {ValidationDetails}", string.Join("; ", crossDateValidationResult.ValidationErrors)); // 将约束验证失败的任务移到失败列表 foreach (var failedTaskId in crossDateValidationResult.FailedTaskIds) { var failedMatch = allSuccessfulMatches.FirstOrDefault(m => m.TaskId == failedTaskId); if (failedMatch != null) { allSuccessfulMatches.Remove(failedMatch); allFailedTasks.Add(new FailedTaskInfo { TaskId = failedTaskId, TaskCode = failedMatch.TaskCode, FailureReason = "跨日期约束验证失败", FailureType = FailureType.TimeConflict, ConflictDetails = crossDateValidationResult.ValidationErrors }); } } } return new PersonnelAllocationResult { SuccessfulMatches = allSuccessfulMatches, FailedTasks = allFailedTasks, FairnessScore = (int)(processedDates > 0 ? totalFairnessScore / processedDates : 0), AllocationStrategy = strategy.PersonnelStrategy.ToString(), ProcessingDetails = $"处理了 {taskGroups.Count} 个日期分组,成功分配 {allSuccessfulMatches.Count} 个任务" }; } /// /// 验证跨日期约束 /// 约束分离设计:独立处理跨日期的人员工作负载、休息时间等约束 /// private async Task ValidateCrossDateConstraintsAsync( List allMatches, Dictionary> taskGroups) { var validationResult = new CrossDateValidationResult { IsValid = true }; var personnelWorkloadMap = new Dictionary>(); // 按人员分组已分配的任务 foreach (var match in allMatches) { if (!personnelWorkloadMap.ContainsKey(match.PersonnelId)) personnelWorkloadMap[match.PersonnelId] = new List(); personnelWorkloadMap[match.PersonnelId].Add(match); } // 检查每个人员的跨日期工作负载 foreach (var personnelGroup in personnelWorkloadMap) { var personnelId = personnelGroup.Key; var personnelTasks = personnelGroup.Value; // 检查连续工作天数限制(放宽为8天限制) var workDates = personnelTasks .Select(t => taskGroups.First(g => g.Value.Any(task => task.Id == t.TaskId)).Key) .Distinct() .OrderBy(d => d) .ToList(); // 采用更宽松的连续工作天数检查 if (HasExcessiveContinuousWorkDays(workDates, maxContinuousDays: 8)) { validationResult.IsValid = false; validationResult.ValidationErrors.Add( $"人员 {personnelId} 连续工作天数严重超过限制: {string.Join(", ", workDates.Select(d => d.ToString("MM-dd")))}"); // 仅将最后1天的任务标记为失败,而不是最后2天 var lastWorkDate = workDates.LastOrDefault(); var tasksToFail = personnelTasks .Where(t => taskGroups.Any(g => g.Key == lastWorkDate && g.Value.Any(task => task.Id == t.TaskId))) .Select(t => t.TaskId); validationResult.FailedTaskIds.AddRange(tasksToFail); _logger.LogWarning("⚠️ 人员 {PersonnelId} 连续工作天数过多,仅限制最后一天 {LastDate} 的任务", personnelId, lastWorkDate.ToString("MM-dd")); } else if (workDates.Count >= 6) { // 6-8天连续工作:记录警告但不阻止 _logger.LogInformation("ℹ️ 人员 {PersonnelId} 连续工作 {WorkDays} 天,在可接受范围内: {Dates}", personnelId, workDates.Count, string.Join(", ", workDates.Select(d => d.ToString("MM-dd")))); } // 检查每日工作时长限制 foreach (var date in workDates) { var dailyTasks = personnelTasks .Where(t => taskGroups[date].Any(task => task.Id == t.TaskId)) .ToList(); if (dailyTasks.Count > 0) { var dailyWorkHours = CalculateDailyWorkHours(dailyTasks, taskGroups[date]); if (dailyWorkHours > 12) // 假设每日工作时长不超过12小时 { validationResult.IsValid = false; validationResult.ValidationErrors.Add( $"人员 {personnelId} 在 {date:MM-dd} 的工作时长 {dailyWorkHours:F1} 小时超过限制"); // 将当日最后的任务标记为失败 var lastTask = dailyTasks.OrderByDescending(t => t.TaskId).First(); validationResult.FailedTaskIds.Add(lastTask.TaskId); } } } } await Task.CompletedTask; // 保持异步方法契约 return validationResult; } /// /// 检查是否有过度的连续工作天数 /// 优化策略:根据任务紧急程度和业务需求灵活调整限制 /// private bool HasExcessiveContinuousWorkDays(List workDates, int maxContinuousDays = 6) { // 如果工作日期少于限制天数,直接返回false if (workDates.Count <= maxContinuousDays) return false; // 排序工作日期 var sortedDates = workDates.OrderBy(d => d).ToList(); var consecutiveDays = 1; var maxConsecutiveFound = 1; for (int i = 1; i < sortedDates.Count; i++) { // 检查是否连续 if (sortedDates[i] == sortedDates[i - 1].AddDays(1)) { consecutiveDays++; maxConsecutiveFound = Math.Max(maxConsecutiveFound, consecutiveDays); // 如果超过限制,记录详细信息但不立即返回true if (consecutiveDays > maxContinuousDays) { _logger.LogWarning("🚨 发现连续工作 {ConsecutiveDays} 天,超过限制 {MaxDays} 天,日期范围: {StartDate} 到 {EndDate}", consecutiveDays, maxContinuousDays, sortedDates[i - consecutiveDays + 1].ToString("MM-dd"), sortedDates[i].ToString("MM-dd")); } } else { consecutiveDays = 1; } } // 基于业务优先级决定是否真的阻止分配 bool shouldBlock = maxConsecutiveFound > maxContinuousDays + 1; // 允许适度超出 if (shouldBlock) { _logger.LogError("❌ 连续工作天数 {MaxConsecutive} 严重超过限制 {MaxAllowed},阻止分配", maxConsecutiveFound, maxContinuousDays + 1); } else if (maxConsecutiveFound > maxContinuousDays) { _logger.LogWarning("⚠️ 连续工作天数 {MaxConsecutive} 超过建议限制 {MaxRecommended},但允许分配", maxConsecutiveFound, maxContinuousDays); } return shouldBlock; } /// /// 计算每日工作时长 /// private double CalculateDailyWorkHours(List dailyTasks, List dayTasks) { var totalMinutes = 0.0; foreach (var match in dailyTasks) { var task = dayTasks.FirstOrDefault(t => t.Id == match.TaskId); if (task != null) { var duration = (task.PlannedEndTime - task.PlannedStartTime).TotalMinutes; totalMinutes += duration; } } return totalMinutes / 60.0; } /// /// 记录每日分配详情(用于调试) /// private void LogDailyAllocationDetails(DateTime date, PersonnelAllocationResult? dailyResult) { if (dailyResult?.SuccessfulMatches != null) { _logger.LogDebug("📊 {Date} 分配详情:", date.ToString("yyyy-MM-dd")); foreach (var match in dailyResult.SuccessfulMatches) { _logger.LogDebug(" 👤 任务 {TaskCode} → 人员 {PersonnelName} (评分: {Score:F1})", match.TaskCode, match.PersonnelName, match.MatchScore); } } } #endregion #region 新增数据模型 /// /// 跨日期验证结果 /// private class CrossDateValidationResult { public bool IsValid { get; set; } public List ValidationErrors { get; set; } = new List(); public List FailedTaskIds { get; set; } = new List(); } /// /// 执行降级人员分配策略 /// 深度业务思考:当标准分配失败率过高时,采用更宽松的约束条件重新分配 /// private async Task ExecuteFallbackPersonnelAllocationAsync( SmartIntegrationResult result, List allTasks, IntegrationStrategy strategy) { var failedTaskIds = result.PersonnelAllocation.FailedTasks.Select(f => f.TaskId).ToHashSet(); var failedTasks = allTasks.Where(t => failedTaskIds.Contains(t.Id)).ToList(); _logger.LogInformation("🔄 降级重试: 尝试为 {FailedCount} 个失败任务重新分配", failedTasks.Count); // 降级策略1:放松时间约束 var fallbackGlobalInput = new GlobalAllocationInput { Tasks = failedTasks, ExcludedPersonnelIds = new List() // TODO: 未来版本支持更多降级策略 // AllowConstraintRelaxation = true, // MaxContinuousWorkDays = 10, // IgnoreDateConstraints = true }; var fallbackGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(fallbackGlobalInput); var fallbackResult = ConvertToPersonnelAllocationResult(fallbackGlobalResult); if (fallbackResult?.SuccessfulMatches?.Any() == true) { // 合并降级成功的分配结果 result.PersonnelAllocation.SuccessfulMatches.AddRange(fallbackResult.SuccessfulMatches); // 从失败列表中移除成功分配的任务 var recoveredTaskIds = fallbackResult.SuccessfulMatches.Select(m => m.TaskId).ToHashSet(); result.PersonnelAllocation.FailedTasks = result.PersonnelAllocation.FailedTasks .Where(f => !recoveredTaskIds.Contains(f.TaskId)) .ToList(); _logger.LogInformation("✅ 降级重试成功: 额外分配了 {RecoveredCount} 个任务", fallbackResult.SuccessfulMatches.Count); // 更新分配摘要 result.PersonnelAllocation.ProcessingDetails += $"; 降级重试恢复了{fallbackResult.SuccessfulMatches.Count}个任务"; } else { _logger.LogWarning("⚠️ 降级重试未能恢复任何任务"); } } /// /// 优化的分时段人员分配方法 /// 架构设计:增强错误处理和智能重分配机制 /// private async Task ExecuteOptimizedTimeSegmentedAllocationAsync( Dictionary> taskGroups, IntegrationStrategy strategy, long operatorUserId, string operatorName) { var allSuccessfulMatches = new List(); var allFailedTasks = new List(); var totalFairnessScore = 0.0; var processedDates = 0; // 按日期优先级排序:预分配任务较多的日期优先处理 var sortedDateGroups = taskGroups .OrderBy(g => g.Value.Count(t => !t.AssignedPersonnelId.HasValue || t.AssignedPersonnelId.Value == 0)) .ThenBy(g => g.Key); foreach (var dateGroup in sortedDateGroups) { var date = dateGroup.Key; var dailyTasks = dateGroup.Value; _logger.LogInformation("🗓️ 处理 {Date} 的 {TaskCount} 个任务 (预分配: {PreAssignedCount})", date.ToString("yyyy-MM-dd"), dailyTasks.Count, dailyTasks.Count(t => t.AssignedPersonnelId.HasValue && t.AssignedPersonnelId.Value > 0)); // 为当日任务执行人员分配,采用逐步放松约束的策略 var dailyResult = await ExecuteDailyAllocationWithProgressiveRelaxationAsync( dailyTasks, strategy, allSuccessfulMatches); if (dailyResult?.SuccessfulMatches != null) { allSuccessfulMatches.AddRange(dailyResult.SuccessfulMatches); _logger.LogInformation(" ✅ {Date} 分配成功 {SuccessCount} 个任务", date.ToString("MM-dd"), dailyResult.SuccessfulMatches.Count); } if (dailyResult?.FailedTasks != null) { allFailedTasks.AddRange(dailyResult.FailedTasks); if (dailyResult.FailedTasks.Any()) { _logger.LogWarning(" ⚠️ {Date} 分配失败 {FailedCount} 个任务", date.ToString("MM-dd"), dailyResult.FailedTasks.Count); } } // 累计公平性评分 if (dailyResult?.FairnessScore > 0) { totalFairnessScore += dailyResult.FairnessScore; processedDates++; } } return new PersonnelAllocationResult { SuccessfulMatches = allSuccessfulMatches, FailedTasks = allFailedTasks, FairnessScore = (int)(processedDates > 0 ? totalFairnessScore / processedDates : 0), AllocationStrategy = strategy.PersonnelStrategy.ToString(), ProcessingDetails = $"优化分时段处理: {taskGroups.Count}个日期组,成功分配{allSuccessfulMatches.Count}个任务" }; } /// /// 每日分配的渐进式约束放松策略 /// 深度业务思考:从严格约束开始,逐步放松直到找到可行解 /// private async Task ExecuteDailyAllocationWithProgressiveRelaxationAsync( List dailyTasks, IntegrationStrategy strategy, List existingMatches) { // 策略1:标准约束 var standardGlobalInput = new GlobalAllocationInput { Tasks = dailyTasks, ExcludedPersonnelIds = new List() // TODO: 未来版本支持现有分配考虑 // ExistingAllocations = existingMatches }; var standardGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(standardGlobalInput); var result = ConvertToPersonnelAllocationResult(standardGlobalResult); // 如果标准策略成功率较高,直接返回 if (result?.SuccessfulMatches?.Count > 0 && (result.FailedTasks?.Count ?? 0) <= dailyTasks.Count * 0.3) { return result; } // 策略2:放松班次规则 _logger.LogInformation(" 🔄 标准策略效果不佳,尝试放松班次规则..."); var relaxedGlobalInput = new GlobalAllocationInput { Tasks = dailyTasks, ExcludedPersonnelIds = new List() // TODO: 未来版本支持约束放松 // AllowConstraintRelaxation = true }; var relaxedGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(relaxedGlobalInput); var relaxedResult = ConvertToPersonnelAllocationResult(relaxedGlobalResult); if (relaxedResult?.SuccessfulMatches?.Count > result?.SuccessfulMatches?.Count) { return relaxedResult; } // 策略3:最宽松约束 _logger.LogInformation(" 🔄 尝试最宽松约束策略..."); var fallbackGlobalInput = new GlobalAllocationInput { Tasks = dailyTasks, ExcludedPersonnelIds = new List() // TODO: 未来版本支持更多宽松约束 // MaxContinuousWorkDays = 10, // IgnoreDateConstraints = true }; var fallbackGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(fallbackGlobalInput); var fallbackResult = ConvertToPersonnelAllocationResult(fallbackGlobalResult); // 返回效果最好的结果 return SelectBestResult(result, relaxedResult, fallbackResult); } /// /// 选择最佳分配结果 /// private PersonnelAllocationResult SelectBestResult(params PersonnelAllocationResult[] results) { return results .Where(r => r != null) .OrderByDescending(r => r.SuccessfulMatches?.Count ?? 0) .ThenBy(r => r.FailedTasks?.Count ?? int.MaxValue) .ThenByDescending(r => r.FairnessScore) .FirstOrDefault() ?? new PersonnelAllocationResult { SuccessfulMatches = new List(), FailedTasks = new List() }; } #endregion #region ISmartTaskIntegrationService 接口转发方法 /// /// 转发到全局人员分配服务 /// [HttpPost] public async Task AllocatePersonnelSmartlyAsync(PersonnelAllocationInput input) { // 将PersonnelAllocationInput转换为GlobalAllocationInput var globalInput = new GlobalAllocationInput { Tasks = input.Tasks, TaskIds = input.TaskIds, ExcludedPersonnelIds = input.ExcludedPersonnelIds }; var globalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(globalInput); return ConvertToPersonnelAllocationResult(globalResult); } /// /// 转发到设备分配服务 /// [HttpPost] public async Task AllocateEquipmentSmartlyAsync(EquipmentAllocationInput input) { return await _equipmentAllocationService.AllocateEquipmentSmartlyAsync(input); } /// /// 转发到整合记录服务 /// [HttpPost] public async Task GenerateIntegrationRecordAsync(IntegrationRecordInput input) { return await _integrationRecordService.GenerateIntegrationRecordAsync(input); } /// /// 获取整合历史记录 /// [HttpPost] public async Task> GetIntegrationRecordsAsync(IntegrationRecordGetListInput input) { return await _integrationRecordService.GetIntegrationRecordsAsync(input); } /// /// 获取整合记录详细信息 /// [HttpGet] public async Task GetIntegrationRecordDetailAsync(long integrationRecordId) { // 转发到整合记录服务或实现基本逻辑 return await _integrationRecordService.GetIntegrationRecordWithTasksAsync(integrationRecordId); } /// /// 发布任务 /// /// /// [HttpPost] public async Task PublishIntegrationRecordAsync(PublishIntegrationRecordInput input) { return await _integrationRecordService.PublishIntegrationRecordAsync(input); } /// /// 转换全局分配结果为人员分配结果 /// 业务逻辑:适配不同服务间的数据结构差异,确保接口兼容性 /// private PersonnelAllocationResult ConvertToPersonnelAllocationResult(GlobalAllocationResult globalResult) { return new PersonnelAllocationResult { IsSuccess = globalResult.IsSuccess, SuccessfulMatches = globalResult.SuccessfulMatches?.Select(gm => new TaskPersonnelMatch { TaskId = gm.TaskId, PersonnelId = gm.PersonnelId, PersonnelName = gm.PersonnelName, MatchScore = (int)gm.MatchScore, TaskCode = $"WO_{gm.TaskId}", MatchReason = gm.MatchReason }).ToList() ?? new List(), FailedTasks = globalResult.FailedAllocations?.Select(gf => new FailedTaskInfo { TaskId = gf.TaskId, TaskCode = gf.TaskCode, FailureReason = gf.FailureReason, FailureType = FailureType.PersonnelAllocationFailed, ConflictDetails = gf.ConflictDetails }).ToList() ?? new List(), FairnessScore = globalResult.FairnessAnalysis?.GiniCoefficient != null ? (int)((1.0 - globalResult.FairnessAnalysis.GiniCoefficient) * 100) : 85, AllocationStrategy = "GlobalOptimization", ProcessingDetails = globalResult.AllocationSummary ?? "全局优化分配完成" }; } #endregion } }