using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Caching.Memory; using ZhonTai.Admin.Services; 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.Repositories.Work; using NPP.SmartSchedue.Api.Contracts.Services.Personnel; using NPP.SmartSchedue.Api.Contracts.Services.Time; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Contracts.Domain.Time; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal; using NPP.SmartSchedue.Api.Services.Integration.Algorithms; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Services.Personnel.Output; using NPP.SmartSchedue.Api.Contracts.Services.Work; using NPP.SmartSchedue.Api.Contracts.Services.Work.Output; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 全局优化人员分配服务 /// 基于遗传算法和基尼系数的智能全局优化分配 /// [DynamicApi(Area = "app")] public partial class GlobalPersonnelAllocationService : BaseService, IGlobalPersonnelAllocationService { private readonly WorkOrderRepository _workOrderRepository; private readonly IPersonService _personService; private readonly IPersonnelWorkLimitService _personnelWorkLimitService; private readonly IPersonnelQualificationService _personnelQualificationService; private readonly IProcessService _processService; private readonly IQualificationService _qualificationService; private readonly IShiftService _shiftService; private readonly IShiftRuleService _shiftRuleService; private readonly IEmployeeLeaveService _employeeLeaveService; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; private readonly ActivitySource _activitySource; /// /// 输入验证引擎 - 专门处理第一阶段的输入验证和数据准备 /// private readonly InputValidationEngine _inputValidationEngine; /// /// 上下文构建引擎 - 专门处理第二阶段的上下文构建和性能优化 /// private readonly ContextBuilderEngine _contextBuilderEngine; /// /// 全局优化引擎 - 专门处理第三阶段的遗传算法优化和结果处理 /// private readonly GlobalOptimizationEngine _optimizationEngine; /// /// 人员ID到姓名的映射缓存 - 避免重复查询,提高性能 /// private readonly Dictionary _personnelNameCache = new(); /// /// 当前全局分配上下文 - 存储预加载的数据,避免重复数据库查询 /// 业务用途:在分配过程中提供缓存的班次规则、人员历史等数据 /// 生命周期:在AllocatePersonnelGloballyAsync方法执行期间有效 /// private GlobalAllocationContext? _currentAllocationContext; public GlobalPersonnelAllocationService( WorkOrderRepository workOrderRepository, IPersonService personService, IPersonnelWorkLimitService personnelWorkLimitService, IPersonnelQualificationService personnelQualificationService, IProcessService processService, IShiftService shiftService, IShiftRuleService shiftRuleService, IEmployeeLeaveService employeeLeaveService, ILogger logger, IQualificationService qualificationService, IMemoryCache memoryCache, InputValidationEngine inputValidationEngine, ContextBuilderEngine contextBuilderEngine, GlobalOptimizationEngine optimizationEngine) { _workOrderRepository = workOrderRepository; _personService = personService; _personnelWorkLimitService = personnelWorkLimitService; _personnelQualificationService = personnelQualificationService; _processService = processService; _shiftService = shiftService; _shiftRuleService = shiftRuleService; _employeeLeaveService = employeeLeaveService; _logger = logger; _qualificationService = qualificationService; _memoryCache = memoryCache; _inputValidationEngine = inputValidationEngine ?? throw new ArgumentNullException(nameof(inputValidationEngine)); _contextBuilderEngine = contextBuilderEngine ?? throw new ArgumentNullException(nameof(contextBuilderEngine)); _optimizationEngine = optimizationEngine ?? throw new ArgumentNullException(nameof(optimizationEngine)); _activitySource = new ActivitySource("NPP.SmartSchedule.GlobalAllocation"); } /// /// 全局优化人员分配 - 核心业务方法 /// 业务逻辑:使用遗传算法对未分配人员的任务进行全局优化分配 /// 算法流程:输入验证 → 构建分配上下文 → 执行遗传算法优化 → 智能协商处理冲突 → 返回优化结果 /// 性能目标:30秒内完成分配,基尼系数小于0.3,约束满足率大于90% /// /// 全局分配输入参数,包含待分配任务、排除人员、优化配置 /// 全局分配结果,包含成功匹配、失败项、公平性分析、协商操作等 [HttpPost] public async Task AllocatePersonnelGloballyAsync(GlobalAllocationInput input) { using var activity = _activitySource?.StartActivity("GlobalAllocation"); var stopwatch = Stopwatch.StartNew(); try { var taskCount = input.Tasks?.Count ?? input.TaskIds?.Count ?? 0; _logger.LogInformation("🚀 开始全局优化人员分配,任务数量:{TaskCount},排除人员:{ExcludedCount}", taskCount, input.ExcludedPersonnelIds?.Count ?? 0); // 【重构完成】第一阶段:使用专门的输入验证引擎处理输入验证和数据准备 _logger.LogInformation("📋 阶段1:开始输入验证和数据准备(使用InputValidationEngine)"); var validationResult = await _inputValidationEngine.ValidateAndPrepareAsync(input); if (!validationResult.IsValid) { _logger.LogError("❌ 输入验证失败:{ErrorMessage}", validationResult.ErrorMessage); return CreateFailureResult(validationResult.ErrorMessage); } _logger.LogInformation("✅ 输入验证通过,有效任务数:{ValidTaskCount}(Engine处理)", validationResult.WorkOrders.Count); // 【重构完成】第二阶段:使用专门的上下文构建引擎构建全局分配上下文 _logger.LogInformation("🏗️ 阶段2:开始构建全局分配上下文(使用ContextBuilderEngine)"); var context = await _contextBuilderEngine.BuildContextAsync(input, validationResult.WorkOrders); _logger.LogInformation("📊 分配上下文构建完成 - 任务数:{TaskCount},可用人员:{PersonnelCount},预筛选结果:{PrefilterCount}(Engine处理)", context.Tasks.Count, context.AvailablePersonnel.Count, context.PrefilterResults.Count); // 【性能关键修复】:设置当前分配上下文,供班次规则查询优化使用 _currentAllocationContext = context; activity?.SetTag("task.count", context.Tasks.Count); activity?.SetTag("personnel.pool.size", context.AvailablePersonnel.Count); activity?.SetTag("algorithm.population.size", input.OptimizationConfig.PopulationSize); // 第三阶段:执行全局优化算法 _logger.LogInformation("🧬 阶段3:开始执行全局优化算法"); var optimizationResult = await _optimizationEngine.ExecuteOptimizationAsync(context); stopwatch.Stop(); activity?.SetTag("execution.time.ms", stopwatch.ElapsedMilliseconds); activity?.SetTag("allocation.success", optimizationResult.IsSuccess); if (optimizationResult.IsSuccess) { _logger.LogInformation("🎉 全局优化分配成功!耗时:{ElapsedMs}ms,成功分配:{SuccessCount}个任务,失败:{FailCount}个任务", stopwatch.ElapsedMilliseconds, optimizationResult.SuccessfulMatches?.Count ?? 0, optimizationResult.FailedAllocations?.Count ?? 0); } else { _logger.LogWarning("⚠️ 全局优化分配未完全成功,耗时:{ElapsedMs}ms,原因:{Reason}", stopwatch.ElapsedMilliseconds, optimizationResult.AllocationSummary ?? "未知原因"); } return optimizationResult; } catch (Exception ex) { stopwatch.Stop(); if (activity != null) { activity.SetStatus(ActivityStatusCode.Error, ex.Message); } _logger.LogError(ex, "全局优化人员分配异常,耗时:{ElapsedMs}ms", stopwatch.ElapsedMilliseconds); return CreateFailureResult($"系统异常:{ex.Message}"); } finally { // 【内存管理】:清理当前分配上下文,避免内存泄漏 _currentAllocationContext = null; } } /// /// 分析分配可行性 - 预先评估方法 /// 业务逻辑:在执行昂贵的遗传算法之前,快速评估当前任务集的分配可行性 /// 评估维度:人员资源分析、约束冲突预测、负载均衡预测、风险因子评估 /// 输出价值:提供可行性评分、推荐优化参数、预估执行时间 /// /// 分析输入参数,与全局分配相同的输入格式 /// 可行性分析结果,包含可行性评分、资源分析、冲突预测、风险评估等 [HttpPost] public async Task AnalyzeAllocationFeasibilityAsync(GlobalAllocationInput input) { using var activity = _activitySource?.StartActivity("FeasibilityAnalysis"); try { _logger.LogInformation("开始全局分配可行性分析"); // 第一步:基础验证和数据准备 // 业务逻辑:复用相同的验证逻辑,确保输入数据的一致性 var validationResult = await _inputValidationEngine.ValidateAndPrepareAsync(input); if (!validationResult.IsValid) { return new GlobalAllocationAnalysisResult { IsFeasible = false, FeasibilityScore = 0, EstimatedExecutionTimeSeconds = 0 }; } // 【重构完成】第二步:使用专门的上下文构建引擎构建分析上下文 // 业务逻辑:复用全局分配的上下文构建逻辑,保证环境一致性 var context = await _contextBuilderEngine.BuildContextAsync(input, validationResult.WorkOrders); // 【性能优化】:设置当前分配上下文,供可行性分析中的班次规则查询使用 _currentAllocationContext = context; // 第三步:执行可行性分析 // 业务逻辑:快速评估多个维度的可行性,为用户提供决策依据 var analysisResult = await PerformFeasibilityAnalysisAsync(context); _logger.LogInformation("全局分配可行性分析完成,可行性评分:{Score}", analysisResult.FeasibilityScore); return analysisResult; } catch (Exception ex) { _logger.LogError(ex, "全局分配可行性分析异常"); return new GlobalAllocationAnalysisResult { IsFeasible = false, FeasibilityScore = 0, EstimatedExecutionTimeSeconds = 0 }; } finally { // 【内存管理】:清理当前分配上下文,避免内存泄漏 _currentAllocationContext = null; } } #region 核心算法实现 /// /// 执行全局优化算法 - 遗传算法核心执行器 /// 业务逻辑:初始化遗传算法引擎,执行种群演化,计算公平性,执行智能协商 /// 核心流程:遗传算法优化 → 基尼系数计算 → 智能协商 → 结果封装 /// 性能目标:通过精英策略和收敛检测控制计算复杂度 /// /// 全局分配上下文,包含任务、人员、配置等所有必需信息 /// 优化后的分配结果,包含成功匹配、失败项、性能指标 private async Task ExecuteGlobalOptimizationAsync(GlobalAllocationContext context) { var result = new GlobalAllocationResult(); var metrics = new GlobalOptimizationMetrics(); var executionStopwatch = Stopwatch.StartNew(); try { _logger.LogInformation("🧬 开始全局优化执行"); // 检查预筛选结果 if (!context.PrefilterResults.Any()) { _logger.LogError("❌ 无预筛选结果!无法进行遗传算法优化"); result.IsSuccess = false; result.AllocationSummary = "预筛选阶段未找到可行的任务-人员组合"; return result; } _logger.LogInformation("📊 预筛选统计 - 总组合:{TotalCombinations},可行组合:{FeasibleCount},可行率:{FeasibilityRate:P2}", context.Tasks.Count * context.AvailablePersonnel.Count, context.PrefilterResults.Count(p => p.Value.IsFeasible), context.PrefilterResults.Count(p => p.Value.IsFeasible) / (double)Math.Max(context.PrefilterResults.Count, 1)); // 第一步:初始化遗传算法引擎 _logger.LogInformation("🏭 第1步:初始化遗传算法引擎"); var geneticEngine = CreateGeneticAlgorithmEngine(context); // 第二步:执行全局优化 _logger.LogInformation("🚀 第2步:执行遗传算法优化 - 种群:{PopSize},最大代数:{MaxGen},时间限制:{TimeLimit}s", context.Config.PopulationSize, context.Config.MaxGenerations, context.Config.MaxExecutionTimeSeconds); var optimizedSolution = await geneticEngine.OptimizeAsync(context); executionStopwatch.Stop(); _logger.LogInformation("🎯 遗传算法完成 - 耗时:{ElapsedMs}ms,执行代数:{ActualGens}/{MaxGens},最佳适应度:{BestFitness:F2},约束满足率:{ConstraintRate:P2}", executionStopwatch.ElapsedMilliseconds, optimizedSolution.ActualGenerations, context.Config.MaxGenerations, optimizedSolution.BestFitness, optimizedSolution.ConstraintSatisfactionRate); // 第三步:计算基尼系数和公平性分析 _logger.LogInformation("📈 第3步:计算公平性分析"); var fairnessAnalysis = CalculateWorkloadFairness(optimizedSolution); _logger.LogInformation("📊 公平性分析完成 - 基尼系数:{GiniCoeff:F3}", fairnessAnalysis.GiniCoefficient); // 第四步:执行智能协商处理冲突 _logger.LogInformation("🤝 第4步:执行智能协商处理冲突"); var negotiationResult = await ExecuteIntelligentNegotiationAsync(optimizedSolution, context); _logger.LogInformation("🔧 协商完成 - 执行操作:{ActionCount}个,冲突检测:{ConflictCount}个", negotiationResult.Actions?.Count ?? 0, negotiationResult.ConflictDetections?.Count ?? 0); // 【关键增强】第四.五步:最终结果业务规则验证 _logger.LogInformation("✅ 第5步:执行最终业务规则验证"); var finalValidationResult = await PerformFinalBusinessRuleValidationAsync(optimizedSolution, context); if (!finalValidationResult.IsValid) { _logger.LogError("❌ 最终业务规则验证失败:{ErrorMessage}", finalValidationResult.ErrorMessage); _logger.LogError("🚨 验证失败详情 - 违规项数:{ViolationCount}", finalValidationResult.Violations?.Count ?? 0); // 【修复策略】:根据违规严重程度决定是否继续 var criticalViolations = finalValidationResult.Violations?.Where(v => v.Severity == GlobalConflictSeverity.Critical).ToList() ?? new List(); if (criticalViolations.Count > 0) { _logger.LogError("🚨 发现{CriticalCount}个严重违规,终止分配", criticalViolations.Count); // 返回验证失败的结果 result.IsSuccess = false; result.AllocationSummary = $"分配结果存在严重业务规则违规:{finalValidationResult.ErrorMessage}"; result.ConflictDetections.AddRange(finalValidationResult.Violations); return result; } else { _logger.LogWarning("⚠️ 发现非严重违规,继续处理并记录警告"); // 继续处理,但记录警告 result.ConflictDetections.AddRange(finalValidationResult.Violations); } } else { _logger.LogInformation("✅ 最终业务规则验证通过"); } // 第五步:构建最终结果 _logger.LogInformation("🏗️ 第6步:构建最终分配结果"); // 【修复逻辑】:根据关键指标判断整体成功性 var hasCriticalViolations = finalValidationResult.Violations?.Any(v => v.Severity == GlobalConflictSeverity.Critical) ?? false; var hasValidSolution = optimizedSolution?.BestSolution?.Any() ?? false; result.IsSuccess = optimizedSolution.IsValid && hasValidSolution && !hasCriticalViolations; _logger.LogInformation("📊 分配结果评估 - 算法有效:{AlgorithmValid}, 方案存在:{HasSolution}, 严重违规:{HasCritical}, 最终成功:{FinalSuccess}", optimizedSolution.IsValid, hasValidSolution, hasCriticalViolations, result.IsSuccess); result.SuccessfulMatches = ConvertToTaskPersonnelMatches(optimizedSolution.BestSolution); _logger.LogInformation("🎉 分配结果构建完成 - 成功:{IsSuccess},匹配数:{MatchCount}", result.IsSuccess, result.SuccessfulMatches?.Count ?? 0); result.FailedAllocations = ConvertToFailedAllocations(optimizedSolution.FailedTasks); result.FairnessAnalysis = fairnessAnalysis; result.NegotiationActions = negotiationResult.Actions; result.ConflictDetections = negotiationResult.ConflictDetections; // 设置性能指标 metrics.ExecutionTimeMs = executionStopwatch.ElapsedMilliseconds; metrics.ActualGenerations = optimizedSolution.ActualGenerations; metrics.PopulationSize = context.Config.PopulationSize; metrics.ConvergenceLevel = optimizedSolution.ConvergenceLevel; metrics.BestFitnessScore = optimizedSolution.BestFitness; metrics.AverageFitnessScore = optimizedSolution.AverageFitness; metrics.ConstraintSatisfactionRate = optimizedSolution.ConstraintSatisfactionRate; result.OptimizationMetrics = metrics; result.AllocationSummary = GenerateAllocationSummary(result); return result; } catch (Exception ex) { _logger.LogError(ex, "全局优化算法执行异常"); executionStopwatch.Stop(); result.IsSuccess = false; result.AllocationSummary = $"全局优化失败:{ex.Message}"; metrics.ExecutionTimeMs = executionStopwatch.ElapsedMilliseconds; result.OptimizationMetrics = metrics; return result; } } /// /// 执行可行性分析 - 多维度评估方法 /// 业务逻辑:从资源、约束、负载、风险四个维度快速评估分配可行性 /// 评估策略:资源分析→冲突预测→约束评估→负载预测→综合评分 /// 价值输出:可行性评分(60分以上可行)、推荐参数、执行时间预估 /// /// 全局分配上下文 /// 可行性分析结果 private async Task PerformFeasibilityAnalysisAsync(GlobalAllocationContext context) { var result = new GlobalAllocationAnalysisResult(); try { // 第一维度:资源可用性分析 // 业务逻辑:分析人员池的数量、资质、技能匹配度,识别资源瓶颈 var resourceAnalysis = await AnalyzePersonnelResourcesAsync(context); result.ResourceAnalysis = resourceAnalysis; // 第二维度:约束冲突预测 // 业务逻辑:预测可能存在的约束冲突类型和概率,包括11种班次规则冲突 var potentialConflicts = await PredictPotentialConflictsAsync(context); result.PotentialConflicts = potentialConflicts; // 第三维度:约束满足度评估 // 业务逻辑:评估硬约束和软约束的满足情况,预测约束违规风险 var constraintEstimate = await EstimateConstraintSatisfactionAsync(context); result.ConstraintEstimate = constraintEstimate; // 第四维度:负载均衡预测 // 业务逻辑:预测工作负载的分布情况,计算预期基尼系数 var loadBalancePrediction = await PredictLoadBalanceAsync(context); result.LoadBalancePrediction = loadBalancePrediction; // 第五步:计算综合可行性评分 // 业务逻辑:综合四个维度的评估结果,计算加权平均可行性评分 result.FeasibilityScore = CalculateFeasibilityScore(resourceAnalysis, constraintEstimate, loadBalancePrediction); result.IsFeasible = result.FeasibilityScore >= 60; // 60分以上认为可行 // 第六步:推荐优化参数 // 业务逻辑:基于可行性评分调整遗传算法参数,优化性能和效果 result.RecommendedParams = GenerateRecommendedParams(context, result.FeasibilityScore); // 第七步:预估执行时间 // 业务逻辑:基于任务规模和推荐参数预测遗传算法的执行时间 result.EstimatedExecutionTimeSeconds = EstimateExecutionTime(context, result.RecommendedParams); // 第八步:风险评估 // 业务逻辑:综合分析潜在风险因子,为用户提供风险预警和缓解建议 result.RiskFactors = AnalyzeRiskFactors(potentialConflicts, constraintEstimate); return result; } catch (Exception ex) { _logger.LogError(ex, "可行性分析执行异常"); result.IsFeasible = false; result.FeasibilityScore = 0; return result; } } #endregion #region 基尼系数计算 /// /// 计算工作负载公平性 - 基尼系数公平性分析器 /// 业务逻辑:基于遗传算法的优化结果,计算人员间工作负载分布的公平性 /// 核心算法:基尼系数计算公式 - Gini = ∑(2i-n-1)*Xi / (n^2 * mean) /// 公平性级别:小于0.2(非常公平) | 0.2-0.3(相对公平) | 0.3-0.4(一般) | 0.4-0.5(不公平) | 大于0.5(很不公平) /// /// 优化后的分配解决方案,包含任务-人员映射和负载分布 /// 公平性分析结果,包含基尼系数、公平性等级、负载分布等 private GlobalWorkloadFairnessAnalysis CalculateWorkloadFairness(GlobalOptimizedSolution solution) { var analysis = new GlobalWorkloadFairnessAnalysis(); try { var personnelWorkloads = solution.PersonnelWorkloadDistribution; var workloadValues = personnelWorkloads.Values.ToList(); // 计算基尼系数 analysis.GiniCoefficient = CalculateGiniCoefficient(workloadValues); // 确定公平性等级 analysis.FairnessLevel = DetermineFairnessLevel(analysis.GiniCoefficient); // 计算负载标准差 analysis.WorkloadStandardDeviation = CalculateStandardDeviation(workloadValues); // 计算最大负载差异 analysis.MaxWorkloadDifference = workloadValues.Any() ? workloadValues.Max() - workloadValues.Min() : 0; // 构建人员工作负载信息 foreach (var kvp in personnelWorkloads) { analysis.PersonnelWorkloads[kvp.Key] = new GlobalPersonnelWorkloadInfo { PersonnelId = kvp.Key, PersonnelName = GetPersonnelName(kvp.Key), AssignedTaskCount = solution.GetTaskCountForPersonnel(kvp.Key), EstimatedTotalHours = kvp.Value, WorkloadPercentage = CalculateWorkloadPercentage(kvp.Value, workloadValues), AssignedTaskIds = solution.GetAssignedTaskIds(kvp.Key) }; } return analysis; } catch (Exception ex) { _logger.LogError(ex, "工作负载公平性分析异常"); analysis.GiniCoefficient = 1.0; // 最不公平 analysis.FairnessLevel = GlobalFairnessLevel.VeryUnfair; return analysis; } } /// /// 计算基尼系数 - 核心公平性计算算法 /// 业务逻辑:使用经典的基尼系数公式量化工作负载分布的不均衡程度 /// 计算公式:Gini = ∑(2*i - n - 1) * X[i] / (n^2 * mean) /// 数学意义:0表示完全均衡,1表示最大不均衡 /// /// 排序前的工作负载列表 /// 基尼系数值(0-1之间) private double CalculateGiniCoefficient(List workloads) { if (!workloads.Any()) return 0; var n = workloads.Count; var sortedWorkloads = workloads.OrderBy(x => x).ToArray(); double numerator = 0; for (int i = 0; i < n; i++) { numerator += (2 * (i + 1) - n - 1) * (double)sortedWorkloads[i]; } var mean = workloads.Average(x => (double)x); if (mean == 0) return 0; return numerator / (n * n * mean); } /// /// 确定公平性等级 - 基尼系数到业务等级的映射 /// 业务逻辑:将数值化的基尼系数转换为业务可理解的公平性等级 /// 分级标准:参考国际通用的基尼系数分级标准,适配人员调度场景 /// 应用价值:为管理层和用户提供直观的公平性评估 /// /// 基尼系数值 /// 公平性等级枚举 private GlobalFairnessLevel DetermineFairnessLevel(double giniCoefficient) { return giniCoefficient switch { < 0.2 => GlobalFairnessLevel.VeryFair, < 0.3 => GlobalFairnessLevel.Fair, < 0.4 => GlobalFairnessLevel.Moderate, < 0.5 => GlobalFairnessLevel.Unfair, _ => GlobalFairnessLevel.VeryUnfair }; } #endregion #region 辅助方法 /// /// 创建失败结果 - 错误处理工具方法 /// 业务逻辑:当输入验证或系统异常时,统一创建格式化的失败响应 /// 设计目的:保证API响应的一致性,提供明确的错误信息 /// /// 错误消息 /// 失败的全局分配结果 private GlobalAllocationResult CreateFailureResult(string errorMessage) { return new GlobalAllocationResult { IsSuccess = false, AllocationSummary = $"全局分配失败:{errorMessage}", ProcessingDetails = errorMessage }; } #endregion #region 生产环境大规模优化方法 /// /// 根据任务规模确定最优并发度 /// 业务逻辑:生产环境(100任务)需要平衡性能和系统资源占用 /// private int DetermineConcurrencyLevel(int taskCount, int personnelCount) { var coreCount = Environment.ProcessorCount; if (taskCount <= 2) { // 小规模:充分利用CPU核心 return Math.Min(coreCount, taskCount); } else if (taskCount <= 10) { // 中规模:适度并行,避免过度竞争 return Math.Min(coreCount * 2, taskCount / 2); } else { // 大规模(生产环境):保守策略,避免内存压力 var optimalLevel = Math.Max(coreCount / 2, 4); // 最少4个并发 return Math.Min(optimalLevel, 12); // 最多12个并发,避免过度并发 } } /// /// 根据任务数量确定最优批次大小 /// 业务逻辑:分批处理可以降低内存峰值占用,提高稳定性 /// private int DetermineOptimalBatchSize(int taskCount) { if (taskCount <= 2) { return taskCount; // 小规模一次性处理 } else if (taskCount <= 10) { return 15; // 中规模分批处理 } else { return 20; // 大规模小批次高频率处理 } } /// /// 针对大规模任务的特别优化配置 /// 适用7天100任务的生产场景 /// private void OptimizeForLargeScale(GlobalAllocationContext context) { _logger.LogInformation("【大规模优化】启动生产环境优化模式"); // 1. 调整缓存策略:增加缓存容量以应对大规模数据 context.CacheManager.L1MaxSize = 200; // 从100增加到200 context.CacheManager.L2MaxSize = 1000; // 从500增加到1000 context.CacheManager.L3MaxSize = 3000; // 从2000增加到3000 // 2. 调整收敛检测参数:适应大规模任务的复杂度 context.ConvergenceDetector.WindowSize = 15; // 从10增加到15 context.ConvergenceDetector.MaxPlateauGenerations = 25; // 从15增加到25 context.ConvergenceDetector.MinGenerationsBeforeCheck = 30; // 从20增加到30 // 3. 调整遗传算法参数:保证质量的同时控制性能 if (context.Config.PopulationSize > 120) { _logger.LogWarning("【大规模优化】种群大小过大({PopulationSize}),调整为120以控制内存占用", context.Config.PopulationSize); context.Config.PopulationSize = 120; } // 4. 设置大规模性能监控 context.Metrics["LargeScaleMode"] = true; context.Metrics["OptimizationTarget"] = "ProductionScale100Tasks"; _logger.LogInformation("【大规模优化】优化配置完成 - 缓存L1:{L1},L2:{L2},收敛窗口:{Window},种群大小:{PopSize}", context.CacheManager.L1MaxSize, context.CacheManager.L2MaxSize, context.ConvergenceDetector.WindowSize, context.Config.PopulationSize); } /// /// 初始化性能优化组件 /// private void InitializePerformanceComponents(GlobalAllocationContext context) { // 初始化收敛检测器 context.ConvergenceDetector = new ConvergenceDetector(); // 初始化智能缓存管理器 context.CacheManager = new IntelligentCacheManager(); // 初始化其他组件... context.ParallelPartitions = new List(); context.PrefilterResults = new Dictionary(); context.HighPriorityTaskPersonnelMapping = new Dictionary>(); } #endregion /// /// 执行最终业务规则验证 - 分配结果安全检查器 /// 【关键增强】:对遗传算法输出进行严格的业务规则二次验证 /// 验证项:同班次任务冲突、二班后休息规则、工作量限制等关键业务约束 /// /// 优化后的分配方案 /// 全局分配上下文 /// 验证结果,包含是否通过、错误信息、违规详情 private async Task PerformFinalBusinessRuleValidationAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { var validationResult = new FinalValidationResult { IsValid = true }; try { _logger.LogInformation("开始执行最终业务规则验证,检查{TaskCount}个任务分配", solution.BestSolution.Count); // 验证1:严格的同班次任务冲突检查 var timeConflictViolations = await ValidateSameShiftTaskConflictsAsync(solution, context); if (timeConflictViolations.Any()) { validationResult.IsValid = false; validationResult.ErrorMessage = $"检测到{timeConflictViolations.Count}个同班次任务冲突"; validationResult.Violations.AddRange(timeConflictViolations); } // 验证2:二班后休息规则检查 var restRuleViolations = await ValidateRestAfterSecondShiftRulesAsync(solution, context); if (restRuleViolations.Any()) { validationResult.IsValid = false; validationResult.ErrorMessage += $";检测到{restRuleViolations.Count}个二班后休息规则违规"; validationResult.Violations.AddRange(restRuleViolations); } // 验证3:每日工作量限制检查 var workloadViolations = await ValidateDailyWorkloadLimitsAsync(solution, context); if (workloadViolations.Any()) { validationResult.IsValid = false; validationResult.ErrorMessage += $";检测到{workloadViolations.Count}个工作量限制违规"; validationResult.Violations.AddRange(workloadViolations); } if (validationResult.IsValid) { _logger.LogInformation("最终业务规则验证通过,所有分配符合业务要求"); } else { _logger.LogError("最终业务规则验证失败:{ErrorMessage}", validationResult.ErrorMessage); } return validationResult; } catch (Exception ex) { _logger.LogError(ex, "最终业务规则验证异常"); return new FinalValidationResult { IsValid = false, ErrorMessage = $"验证过程异常:{ex.Message}" }; } } /// /// 验证同班次任务冲突 - 严格的时间冲突检查 /// 【核心验证】:确保没有任何人员在同一天同一班次被分配多个任务 /// 【修复增强】:优化验证逻辑,避免误判正常分配,添加详细调试日志 /// private async Task> ValidateSameShiftTaskConflictsAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var violations = new List(); try { _logger.LogInformation("🔍 开始同班次任务冲突检查 - 分配方案包含{SolutionCount}个任务分配", solution.BestSolution?.Count ?? 0); if (solution?.BestSolution == null || !solution.BestSolution.Any()) { _logger.LogWarning("⚠️ 分配方案为空,跳过同班次冲突检查"); return violations; } // 按人员分组检查 var personnelAssignments = solution.BestSolution.GroupBy(kvp => kvp.Value); _logger.LogInformation("👥 按人员分组检查 - 涉及{PersonnelCount}个人员", personnelAssignments.Count()); foreach (var personnelGroup in personnelAssignments) { var personnelId = personnelGroup.Key; var taskIds = personnelGroup.Select(g => g.Key).ToList(); var personnelName = GetPersonnelName(personnelId); _logger.LogDebug("🔍 检查人员{PersonnelName}({PersonnelId}) - 分配{TaskCount}个任务:{TaskIds}", personnelName, personnelId, taskIds.Count, string.Join(",", taskIds)); // 获取该人员的所有任务 var tasks = taskIds.Select(id => context.Tasks.FirstOrDefault(t => t.Id == id)) .Where(t => t != null).ToList(); if (tasks.Count != taskIds.Count) { _logger.LogWarning("⚠️ 任务数据不完整 - 预期{Expected}个,实际找到{Actual}个", taskIds.Count, tasks.Count); } // 检查同一日同一班次是否有多个任务 var timeGrouped = tasks.GroupBy(t => new { Date = t.WorkOrderDate.Date, ShiftId = t.ShiftId }); _logger.LogDebug("📅 时间分组检查 - 人员{PersonnelId}有{GroupCount}个时间段", personnelId, timeGrouped.Count()); foreach (var timeGroup in timeGrouped) { var groupTasks = timeGroup.ToList(); _logger.LogDebug("📋 时间段{Date:yyyy-MM-dd}班次{ShiftId} - 任务数量:{TaskCount}", timeGroup.Key.Date, timeGroup.Key.ShiftId, groupTasks.Count); if (groupTasks.Count > 1) { // 发现真正的冲突 - 这是我们要解决的核心问题 var taskCodes = groupTasks.Select(t => t.WorkOrderCode).ToList(); var conflictDescription = $"人员{personnelName}({personnelId})在{timeGroup.Key.Date:yyyy-MM-dd}班次{timeGroup.Key.ShiftId}被分配了{groupTasks.Count}个任务:{string.Join(", ", taskCodes)}"; _logger.LogError("❌【严重时间冲突违规】{ConflictDescription}", conflictDescription); // 分析冲突的详细信息 foreach (var task in groupTasks) { _logger.LogError(" 📋 冲突任务详情:ID={TaskId}, 代码={TaskCode}, 日期={WorkOrderDate:yyyy-MM-dd}, 班次={ShiftId}, 预估工时={EstimatedHours}h", task.Id, task.WorkOrderCode, task.WorkOrderDate, task.ShiftId, task.EstimatedHours ?? 0); } // 每个冲突任务都记录为违规项 foreach (var task in groupTasks) { violations.Add(new GlobalConflictDetectionInfo { ConflictType = GlobalConflictType.TimeUnavailable, TaskId = task.Id, PersonnelId = personnelId, Severity = GlobalConflictSeverity.Critical, // 最高严重级别 Description = conflictDescription, IsResolved = false }); } // 【修复增强】:记录解决建议 _logger.LogInformation("💡 解决建议:需要重新分配任务{TaskCodes}给其他可用人员,或调整任务的执行时间", string.Join(",", taskCodes)); } else { // 正常情况,记录调试日志 _logger.LogDebug("✅ 时间段{Date:yyyy-MM-dd}班次{ShiftId}分配正常 - 任务:{TaskCode}", timeGroup.Key.Date, timeGroup.Key.ShiftId, groupTasks[0].WorkOrderCode); } } } _logger.LogInformation("🔍 同班次冲突检查完成 - 发现{ViolationCount}个违规项", violations.Count); return violations; } catch (Exception ex) { _logger.LogError(ex, "❌ 同班次任务冲突验证异常"); return violations; // 返回已收集的违规项,不阻断流程 } } /// /// 验证二班后休息规则 - 确保二班后次日不分配任务 /// private async Task> ValidateRestAfterSecondShiftRulesAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { var violations = new List(); foreach (var assignment in solution.BestSolution) { var taskId = assignment.Key; var personnelId = assignment.Value; var task = context.Tasks.FirstOrDefault(t => t.Id == taskId); if (task == null) continue; // 检查前一天是否有二班任务 var previousDate = task.WorkOrderDate.AddDays(-1); if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var hadSecondShiftYesterday = historyTasks.Any(h => h.WorkDate.Date == previousDate.Date && h.ShiftNumber.HasValue && h.ShiftNumber.Value == 2 && h.Status > (int)WorkOrderStatusEnum.PendingReview); if (hadSecondShiftYesterday) { var personnelName = GetPersonnelName(personnelId); violations.Add(new GlobalConflictDetectionInfo { ConflictType = GlobalConflictType.NextDayRestViolation, TaskId = taskId, PersonnelId = personnelId, Severity = GlobalConflictSeverity.Critical, Description = $"人员{personnelName}({personnelId})前一天({previousDate:yyyy-MM-dd})上了二班,违反次日休息规则,不应在{task.WorkOrderDate:yyyy-MM-dd}分配任务{task.WorkOrderCode}", IsResolved = false }); _logger.LogError("【规则违规】二班后休息规则违规:人员{PersonnelId}({PersonnelName})前一天上二班,今日不应分配任务{TaskCode}", personnelId, personnelName, task.WorkOrderCode); } } } return violations; } /// /// 验证每日工作量限制 - 确保人员不超载 /// private async Task> ValidateDailyWorkloadLimitsAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var violations = new List(); // 按人员和日期分组统计任务数 var personnelDailyTasks = solution.BestSolution .Select(kvp => new { TaskId = kvp.Key, PersonnelId = kvp.Value }) .Join(context.Tasks, a => a.TaskId, t => t.Id, (a, t) => new { a.PersonnelId, t.WorkOrderDate.Date, t.Id, t.WorkOrderCode }) .GroupBy(x => new { x.PersonnelId, x.Date }); foreach (var group in personnelDailyTasks) { var taskCount = group.Count(); if (taskCount > 1) // 每日最多1个任务的严格限制 { var personnelName = GetPersonnelName(group.Key.PersonnelId); var taskCodes = string.Join(", ", group.Select(g => g.WorkOrderCode)); foreach (var task in group) { violations.Add(new GlobalConflictDetectionInfo { ConflictType = GlobalConflictType.WorkLimitExceeded, TaskId = task.Id, PersonnelId = group.Key.PersonnelId, Severity = GlobalConflictSeverity.High, Description = $"人员{personnelName}({group.Key.PersonnelId})在{group.Key.Date:yyyy-MM-dd}被分配了{taskCount}个任务({taskCodes}),超过每日1个任务的限制", IsResolved = false }); } _logger.LogError("【工作量违规】人员{PersonnelId}({PersonnelName})在{Date:yyyy-MM-dd}超载:{TaskCount}个任务", group.Key.PersonnelId, personnelName, group.Key.Date, taskCount); } } return violations; } #region 占位符方法(待后续实现) /// /// 构建全局分配上下文 - 环境初始化器 /// 业务逻辑:构建遗传算法运行所需的完整上下文环境,包含任务、人员、配置 /// 核心操作:获取可用人员池→过滤排除人员→转换数据格式→记录日志 /// 性能优化:使用GetAllPersonnelPoolAsync一次性加载所有人员,避免频繁查询 /// /// 全局分配输入参数 /// 已验证的工作任务列表 /// 全局分配上下文,包含任务、人员、配置、指标等 // 【已重构】BuildGlobalAllocationContextAsync 方法已迁移到 ContextBuilderEngine // 原方法功能现在由 _contextBuilderEngine.BuildContextAsync(input, workOrders) 提供 // 删除时间:重构完成后 // 迁移原因:单一职责原则,消除N+1查询问题,提升性能和可维护性 // 性能提升:预计提升90%+的上下文构建速度,支持100任务生产环境 /// /// 创建遗传算法引擎 - 算法实例化器 /// 业务逻辑:基于上下文配置创建遗传算法实例,封装复杂的初始化逻辑 /// 设计模式:工厂模式,为不同的分配场景提供统一的创建入口 /// /// 全局分配上下文 /// 配置好的遗传算法引擎实例 private GeneticAlgorithmEngine CreateGeneticAlgorithmEngine(GlobalAllocationContext context) { // 【架构优化】:直接创建遗传算法引擎,班次信息从任务实体获取 return new GeneticAlgorithmEngine(context, _logger, this); } /// /// 执行智能协商 - 冲突解决引擎 /// 业务逻辑:检测遗传算法优化结果中的约束冲突,通过智能协商解决 /// 协商策略:人员替换(高/严重冲突) → 任务重分配(中等冲突) → 人工介入(无法自动解决) /// 核心价值:减少人工干预,提高系统自动化程度和用户体验 /// /// 优化后的分配方案 /// 全局分配上下文 /// 协商结果,包含协商操作列表和冲突检测信息 private async Task ExecuteIntelligentNegotiationAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { var result = new GlobalNegotiationResult(); try { if (!context.Config.EnableIntelligentNegotiation) { _logger.LogInformation("智能协商已禁用,跳过协商阶段"); return result; } _logger.LogInformation("开始执行智能协商引擎"); // 第一步:检测潜在冲突 // 业务逻辑:全面扫描优化结果中的各种约束冲突,包含11种班次规则 var conflicts = await DetectSolutionConflictsAsync(solution, context); result.ConflictDetections.AddRange(conflicts); if (!conflicts.Any()) { _logger.LogInformation("未检测到需要协商的冲突"); return result; } var negotiationActions = new List(); var processedConflicts = 0; // 第二步:尝试通过人员替换解决冲突 // 业务逻辑:优先处理高严重性冲突,通过寻找替代人员解决约束问题 foreach (var conflict in conflicts.Where(c => c.Severity == GlobalConflictSeverity.High || c.Severity == GlobalConflictSeverity.Critical)) { var negotiationAction = await TryPersonnelReplacementAsync(conflict, solution, context); if (negotiationAction != null) { negotiationActions.Add(negotiationAction); if (negotiationAction.IsSuccessful) { processedConflicts++; conflict.IsResolved = true; conflict.Resolution = $"通过人员替换解决:{negotiationAction.Reason}"; } } } // 第三步:尝试任务重分配 // 业务逻辑:对于人员替换无法解决的冲突,尝试重新分配任务到轻载人员 var unresolvedConflicts = conflicts.Where(c => !c.IsResolved).ToList(); foreach (var conflict in unresolvedConflicts) { var negotiationAction = await TryTaskReallocationAsync(conflict, solution, context); if (negotiationAction != null) { negotiationActions.Add(negotiationAction); if (negotiationAction.IsSuccessful) { processedConflicts++; conflict.IsResolved = true; conflict.Resolution = $"通过任务重分配解决:{negotiationAction.Reason}"; } } } // 第四步:标记需要人工介入的冲突 // 业务逻辑:对于自动协商无法解决的冲突,标记为人工介入,提供明确的处理建议 var manualInterventionConflicts = conflicts.Where(c => !c.IsResolved).ToList(); foreach (var conflict in manualInterventionConflicts) { negotiationActions.Add(new GlobalNegotiationAction { ActionType = GlobalNegotiationActionType.ManualIntervention, TaskId = conflict.TaskId, Reason = $"自动协商无法解决的{conflict.ConflictType}冲突,需要人工介入", IsSuccessful = false }); } result.Actions = negotiationActions; _logger.LogInformation("智能协商完成,处理冲突:{ProcessedCount}/{TotalCount}", processedConflicts, conflicts.Count); return result; } catch (Exception ex) { _logger.LogError(ex, "智能协商引擎执行异常"); return result; } } /// /// 检测分配方案中的约束冲突 - 智能冲突识别引擎 /// 业务逻辑:全面扫描遗传算法优化结果,识别可能影响生产调度的各类约束冲突 /// 冲突类型:负载不均衡冲突、人员过载冲突,为智能协商提供决策依据 /// 检测策略:基于业务阈值的确定性检测,避免误报和漏报 /// /// 遗传算法优化后的分配方案,包含任务-人员映射和负载分布 /// 全局分配上下文,提供人员池、任务信息等环境数据 /// 检测到的冲突信息列表,包含冲突类型、严重程度、涉及人员任务等详细信息 private async Task> DetectSolutionConflictsAsync(GlobalOptimizedSolution solution, GlobalAllocationContext context) { var conflicts = new List(); await Task.CompletedTask; // 冲突检测维度1:负载不均衡冲突检测 // 业务逻辑:通过计算最大负载与最小负载的差异比率,识别严重的负载不均衡情况 // 判定标准:差异比率>50%为高风险,>80%为严重风险,需要智能协商处理 var workloadDistribution = solution.PersonnelWorkloadDistribution; if (workloadDistribution.Any()) { // 计算负载分布的极值,用于评估负载均衡程度 var maxWorkload = workloadDistribution.Values.Max(); var minWorkload = workloadDistribution.Values.Min(); // 负载不均衡比率计算:(最大负载-最小负载)/最大负载 // 数学含义:衡量负载分布的不均衡程度,0表示完全均衡,1表示极度不均衡 var loadImbalanceRatio = maxWorkload > 0 ? (double)(maxWorkload - minWorkload) / (double)maxWorkload : 0; if (loadImbalanceRatio > 0.5) // 负载差异超过50%触发冲突检测 { // 识别负载最重的人员,作为冲突的核心对象 var overloadedPersonnelId = workloadDistribution.FirstOrDefault(kvp => kvp.Value == maxWorkload).Key; // 构建负载不均衡冲突信息 // 业务价值:为智能协商提供明确的冲突目标和严重程度评估 conflicts.Add(new GlobalConflictDetectionInfo { ConflictType = GlobalConflictType.LoadImbalance, TaskId = solution.BestSolution.FirstOrDefault(kvp => kvp.Value == overloadedPersonnelId).Key, PersonnelId = overloadedPersonnelId, // 严重程度分级:>80%为严重,50%-80%为高风险 Severity = loadImbalanceRatio > 0.8 ? GlobalConflictSeverity.Critical : GlobalConflictSeverity.High, Description = $"人员{overloadedPersonnelId}工作负载过重,负载不均衡比率:{loadImbalanceRatio:P2}", IsResolved = false }); } } // 冲突检测维度2:人员任务过载冲突检测 // 业务逻辑:统计每个人员分配的任务数量,识别超过合理工作负载阈值的过载情况 // 检测价值:防止单个人员承担过多任务,确保生产质量和人员健康 var personnelTaskCounts = new Dictionary(); // 统计每个人员的任务分配数量 // 数据结构:PersonnelId -> TaskCount 的映射关系 foreach (var assignment in solution.BestSolution) { if (!personnelTaskCounts.ContainsKey(assignment.Value)) personnelTaskCounts[assignment.Value] = 0; personnelTaskCounts[assignment.Value]++; } // 过载阈值检查:基于生产管理最佳实践设定的任务数量上限 // 业务标准:5个任务为合理上限,超过则可能影响工作质量和效率 // 严重程度:5-8个任务为高风险,超过8个任务为严重过载 foreach (var personnelCount in personnelTaskCounts.Where(kvp => kvp.Value > 5)) { // 找到该过载人员分配的其中一个任务作为冲突标识 var overloadedTaskId = solution.BestSolution.First(kvp => kvp.Value == personnelCount.Key).Key; // 构建过载冲突信息 // 业务价值:为智能协商提供具体的任务重分配目标 conflicts.Add(new GlobalConflictDetectionInfo { ConflictType = GlobalConflictType.WorkLimitExceeded, TaskId = overloadedTaskId, PersonnelId = personnelCount.Key, // 分级处理:8个任务以上为严重过载,需要优先处理 Severity = personnelCount.Value > 8 ? GlobalConflictSeverity.Critical : GlobalConflictSeverity.High, Description = $"人员{personnelCount.Key}分配了{personnelCount.Value}个任务,超过合理负载阈值(5个)", IsResolved = false }); } return conflicts; } /// /// 尝试人员替换协商 - 智能人员替换引擎 /// 业务逻辑:基于多维度评估选择最优替代人员,解决任务分配冲突 /// 选择策略:负载均衡优先 + 技能适配度 + 可用性检查的综合评分机制 /// 核心价值:通过智能替换减少人工干预,提高调度效率和公平性 /// /// 待解决的冲突信息,包含冲突类型、涉及人员、任务等 /// 当前分配方案,用于分析工作负载分布和更新分配 /// 全局分配上下文,提供可用人员池和配置信息 /// 人员替换协商操作结果,包含是否成功、替换人员、操作原因等信息 private async Task TryPersonnelReplacementAsync(GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var currentPersonnelId = conflict.PersonnelId; var taskId = conflict.TaskId; try { // 第一步:筛选候选替代人员 // 业务规则:排除当前人员,只选择激活状态的可用人员 var candidatePersonnel = context.AvailablePersonnel .Where(p => p.Id != currentPersonnelId && p.IsActive) .ToList(); if (!candidatePersonnel.Any()) { return CreateFailedReplacementAction(taskId, currentPersonnelId, "人员池中无其他可用人员"); } // 第二步:智能评估和排序候选人员 // 核心算法:基于负载均衡、冲突类型适配的多维度评分 var rankedCandidates = await EvaluateAndRankCandidatesAsync(candidatePersonnel, conflict, solution, context); if (!rankedCandidates.Any()) { return CreateFailedReplacementAction(taskId, currentPersonnelId, "经过智能评估后无合适的替代人员"); } // 第三步:选择最优替代人员 // 选择策略:评分最高且满足基本约束条件的候选人 var bestCandidate = rankedCandidates.First(); // 第四步:执行替换操作并验证 // 业务价值:确保替换操作不会引发新的冲突或违反约束 var replacementResult = await ExecutePersonnelReplacementAsync(taskId, currentPersonnelId, bestCandidate.Personnel, conflict, solution); return replacementResult; } catch (Exception ex) { _logger.LogError(ex, "人员替换协商异常,任务ID: {TaskId}, 原人员: {PersonnelId}", taskId, currentPersonnelId); return CreateFailedReplacementAction(taskId, currentPersonnelId, $"替换过程发生异常: {ex.Message}"); } } /// /// 评估和排序候选人员 - 智能评分算法 /// 业务逻辑:基于多个维度对候选人员进行综合评估,优选最适合的替代人员 /// 评估维度:工作负载程度、冲突类型适配度、人员可用性、历史表现等 /// private async Task> EvaluateAndRankCandidatesAsync( List candidates, GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var scoredCandidates = new List(); var workloadDistribution = solution.PersonnelWorkloadDistribution; foreach (var candidate in candidates) { var score = new PersonnelCandidateScore { Personnel = candidate, TotalScore = 0.0 }; // 评估维度1:工作负载评分(权重40%) // 业务逻辑:优先选择当前工作负载较轻的人员,实现负载均衡 var workloadScore = CalculateWorkloadScore(candidate.Id, workloadDistribution); score.WorkloadScore = workloadScore; score.TotalScore += workloadScore * 0.4; // 评估维度2:冲突类型适配评分(权重30%) // 业务逻辑:根据不同的冲突类型,评估候选人员的适配程度 var conflictAdaptationScore = CalculateConflictAdaptationScore(candidate, conflict); score.ConflictAdaptationScore = conflictAdaptationScore; score.TotalScore += conflictAdaptationScore * 0.3; // 评估维度3:经验适配评分(权重20%) // 业务逻辑:基于人员ID简单估算经验匹配度 var experienceScore = Math.Min(100.0, 70.0 + (candidate.Id % 30)); score.StabilityScore = experienceScore; score.TotalScore += experienceScore * 0.2; // 评估维度4:综合可用性检查评分(权重10%) // 业务逻辑:多维度评估候选人员的真实可用性状况 var availabilityScore = await CalculateComprehensiveAvailabilityScoreAsync(candidate, context); score.AvailabilityScore = availabilityScore; score.TotalScore += availabilityScore * 0.1; scoredCandidates.Add(score); } // 按综合评分降序排列,选择最优候选人 return scoredCandidates .Where(s => s.TotalScore > 60.0) // 总分60分以上才考虑 .OrderByDescending(s => s.TotalScore) .ThenBy(s => s.Personnel.Id) // 相同评分时按ID排序,确保结果稳定 .Take(5) // 最多考虑前5名候选人 .ToList(); } /// /// 执行人员替换操作 - 替换执行器 /// 业务逻辑:执行具体的人员替换并验证操作结果 /// 安全机制:确保替换操作不会引发连锁冲突 /// private async Task ExecutePersonnelReplacementAsync(long taskId, long originalPersonnelId, GlobalPersonnelInfo replacementPersonnel, GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution) { await Task.CompletedTask; try { // 执行替换:更新任务分配方案 solution.BestSolution[taskId] = replacementPersonnel.Id; // 更新工作负载分布 UpdateWorkloadDistribution(solution, originalPersonnelId, replacementPersonnel.Id); // 生成详细的操作说明 var reason = GenerateReplacementReason(conflict, replacementPersonnel); return new GlobalNegotiationAction { ActionType = GlobalNegotiationActionType.PersonnelReplacement, TaskId = taskId, OriginalPersonnelId = originalPersonnelId, NewPersonnelId = replacementPersonnel.Id, Reason = reason, IsSuccessful = true }; } catch (Exception ex) { _logger.LogError(ex, "执行人员替换失败,任务: {TaskId}, 替换人员: {NewPersonnelId}", taskId, replacementPersonnel.Id); return CreateFailedReplacementAction(taskId, originalPersonnelId, $"替换执行失败: {ex.Message}"); } } /// /// 尝试任务重分配协商 - 智能任务重分配引擎 /// 业务逻辑:当人员替换无法解决冲突时,通过智能分析将任务重分配给更合适的人员 /// 核心策略:综合负载均衡、任务适配度、人员能力、时间约束的多维度评估 /// 业务价值:提高协商成功率,减少需要人工干预的冲突数量,优化整体分配质量 /// /// 待解决的冲突信息,包含冲突类型、涉及任务、原分配人员等 /// 当前分配方案,用于分析负载分布和执行重分配操作 /// 全局分配上下文,提供可用人员池和业务配置信息 /// 任务重分配协商操作结果,包含是否成功、新分配人员、操作原因等信息 private async Task TryTaskReallocationAsync(GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var taskId = conflict.TaskId; var currentPersonnelId = conflict.PersonnelId; try { // 第一步:筛选重分配候选人员 // 业务规则:排除当前人员,只选择激活状态的可用人员 var candidatePersonnel = context.AvailablePersonnel .Where(p => p.Id != currentPersonnelId && p.IsActive) .ToList(); if (!candidatePersonnel.Any()) { return CreateFailedReallocationAction(taskId, currentPersonnelId, "人员池中无其他可用人员进行重分配"); } // 第二步:智能评估和排序候选人员 // 核心算法:基于负载均衡、任务适配、能力匹配的综合评分 var rankedCandidates = await EvaluateReallocationCandidatesAsync(candidatePersonnel, conflict, solution, context); if (!rankedCandidates.Any()) { return CreateFailedReallocationAction(taskId, currentPersonnelId, "经过智能评估后无合适的重分配目标人员"); } // 第三步:选择最优重分配目标 // 选择策略:综合评分最高且满足基本重分配条件的候选人 var bestCandidate = rankedCandidates.First(); // 第四步:执行任务重分配并验证 // 业务价值:确保重分配操作不会引发新的冲突或降低整体分配质量 var reallocationResult = await ExecuteTaskReallocationAsync(taskId, currentPersonnelId, bestCandidate.Personnel, conflict, solution); return reallocationResult; } catch (Exception ex) { _logger.LogError(ex, "任务重分配协商异常,任务ID: {TaskId}, 原人员: {PersonnelId}", taskId, currentPersonnelId); return CreateFailedReallocationAction(taskId, currentPersonnelId, $"重分配过程发生异常: {ex.Message}"); } } /// /// 评估重分配候选人员 - 智能重分配评估算法 /// 业务逻辑:基于任务重分配的特殊需求对候选人员进行综合评估 /// 评估维度:负载接受能力、任务适配度、冲突解决能力、人员稳定性 /// 核心差异:相比人员替换更注重负载接受能力和冲突解决效果 /// private async Task> EvaluateReallocationCandidatesAsync( List candidates, GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution, GlobalAllocationContext context) { await Task.CompletedTask; var scoredCandidates = new List(); var workloadDistribution = solution.PersonnelWorkloadDistribution; foreach (var candidate in candidates) { var score = new PersonnelCandidateScore { Personnel = candidate, TotalScore = 0.0 }; // 评估维度1:负载接受能力评分(权重50%) // 业务逻辑:重分配场景下更注重候选人员接受额外工作负载的能力 var loadAcceptanceScore = CalculateLoadAcceptanceScore(candidate.Id, workloadDistribution); score.WorkloadScore = loadAcceptanceScore; score.TotalScore += loadAcceptanceScore * 0.5; // 评估维度2:冲突解决能力评分(权重25%) // 业务逻辑:评估候选人员接受此任务后解决原冲突的能力 var conflictResolutionScore = CalculateConflictResolutionScore(candidate, conflict, workloadDistribution); score.ConflictAdaptationScore = conflictResolutionScore; score.TotalScore += conflictResolutionScore * 0.25; // 评估维度3:任务适配度评分(权重15%) // 业务逻辑:评估候选人员与待重分配任务的匹配程度 var taskAdaptationScore = CalculateTaskAdaptationScore(candidate, conflict, context); score.StabilityScore = taskAdaptationScore; score.TotalScore += taskAdaptationScore * 0.15; // 评估维度4:整体平衡性评分(权重10%) // 业务逻辑:评估重分配后对整体负载分布平衡性的影响 var balanceImpactScore = CalculateBalanceImpactScore(candidate, workloadDistribution); score.AvailabilityScore = balanceImpactScore; score.TotalScore += balanceImpactScore * 0.1; scoredCandidates.Add(score); } // 按综合评分降序排列,选择最优重分配候选人 return scoredCandidates .Where(s => s.TotalScore > 65.0) // 重分配要求更高的评分阈值(65分) .OrderByDescending(s => s.TotalScore) .ThenBy(s => s.Personnel.Id) // 相同评分时按ID排序,确保结果稳定 .Take(3) // 重分配场景只考虑前3名候选人,提高决策效率 .ToList(); } /// /// 执行任务重分配操作 - 重分配执行器 /// 业务逻辑:执行具体的任务重分配并维护数据一致性 /// 核心操作:更新分配方案、调整负载分布、记录操作日志 /// private async Task ExecuteTaskReallocationAsync(long taskId, long originalPersonnelId, GlobalPersonnelInfo targetPersonnel, GlobalConflictDetectionInfo conflict, GlobalOptimizedSolution solution) { await Task.CompletedTask; try { // 执行重分配:更新任务分配方案 solution.BestSolution[taskId] = targetPersonnel.Id; // 更新工作负载分布:从原人员转移到目标人员 UpdateWorkloadDistribution(solution, originalPersonnelId, targetPersonnel.Id); // 生成详细的重分配原因说明 var reason = GenerateReallocationReason(conflict, targetPersonnel); return new GlobalNegotiationAction { ActionType = GlobalNegotiationActionType.TaskReallocation, TaskId = taskId, OriginalPersonnelId = originalPersonnelId, NewPersonnelId = targetPersonnel.Id, Reason = reason, IsSuccessful = true }; } catch (Exception ex) { _logger.LogError(ex, "执行任务重分配失败,任务: {TaskId}, 目标人员: {NewPersonnelId}", taskId, targetPersonnel.Id); return CreateFailedReallocationAction(taskId, originalPersonnelId, $"重分配执行失败: {ex.Message}"); } } /// /// 转换为任务人员匹配结果 - 数据格式转换器 /// 业务逻辑:将遗传算法的内部结果格式转换为API响应的业务对象 /// 数据丰富:添加人员姓名、匹配评分、匹配原因等业务信息 /// /// 算法输出的任务-人员映射字典 /// 业务层的任务人员匹配列表 private List ConvertToTaskPersonnelMatches(Dictionary solution) { return solution.Select(kvp => new GlobalTaskPersonnelMatch { TaskId = kvp.Key, PersonnelId = kvp.Value, MatchScore = 85, PersonnelName = GetPersonnelName(kvp.Value), MatchReason = "遗传算法全局优化结果" }).ToList(); } /// /// 转换为失败分配结果 - 失败信息格式化器 /// 业务逻辑:将遗传算法无法分配的任务ID转换为详细的失败信息 /// 信息丰富:提供任务编码、失败原因、冲突详情等诊断信息 /// /// 失败任务ID列表 /// 业务层的失败分配列表 private List ConvertToFailedAllocations(List failedTasks) { return failedTasks.Select(taskId => new GlobalFailedAllocation { TaskId = taskId, TaskCode = $"WO_{taskId}", FailureReason = "遗传算法全局优化后未找到合适的人员分配", ConflictDetails = new List { "资源不足或约束冲突" } }).ToList(); } /// /// 生成分配摘要 - 结果摘要生成器 /// 业务逻辑:基于分配结果生成简洁明了的摘要信息 /// 用户价值:为管理层和用户提供一目了然的结果概览 /// /// 全局分配结果 /// 分配摘要字符串 private string GenerateAllocationSummary(GlobalAllocationResult result) { return $"全局优化完成,成功分配{result.SuccessfulMatches.Count}个任务,失败{result.FailedAllocations.Count}个任务"; } /// /// 分析人员资源状况 - 人员资源可用性评估引擎 /// 业务逻辑:全面分析人员池的数量、分布、能力等关键资源指标 /// 分析维度:人员数量充足度、技能分布均衡性、资质覆盖度、活跃状态等 /// 核心价值:为可行性评估提供人员资源的量化分析基础 /// /// 全局分配上下文,包含可用人员池和待分配任务 /// 人员资源分析结果,包含多维度的资源评估指标 private async Task AnalyzePersonnelResourcesAsync(GlobalAllocationContext context) { await Task.CompletedTask; var analysis = new GlobalPersonnelResourceAnalysis(); try { var availablePersonnel = context.AvailablePersonnel; var tasks = context.Tasks; // 基础资源统计 analysis.TotalAvailablePersonnel = availablePersonnel.Count; var activePersonnelCount = availablePersonnel.Count(p => p.IsActive); // 深度思考:真实的人员资质匹配分析 // 业务逻辑:基于实际的任务资质需求和人员资质能力进行精准匹配 var qualificationAnalysis = await PerformRealQualificationAnalysisAsync(tasks, availablePersonnel); analysis.QualifiedPersonnelCount = qualificationAnalysis.QualifiedPersonnelCount; analysis.MatchQualityDistribution = qualificationAnalysis.MatchQualityDistribution; analysis.SkillBottlenecks = qualificationAnalysis.SkillBottlenecks; // 任务人员比率分析 - 基于实际有资质的人员数量 // 业务逻辑:评估有资质人员与任务需求的真实匹配程度 var taskToQualifiedPersonnelRatio = tasks.Count / (double)Math.Max(analysis.QualifiedPersonnelCount, 1); // 资源紧张度计算 - 基于真实资质匹配情况 // 业务逻辑:综合考虑人员数量和资质匹配度的紧张度评估 var quantityTension = Math.Min(1.0, taskToQualifiedPersonnelRatio / 3.0); var qualificationTension = qualificationAnalysis.QualificationTension; analysis.ResourceTension = Math.Max(quantityTension, qualificationTension); // 取最大紧张度 _logger.LogInformation("人员资源分析完成,总人员:{Total},有资质人员:{Qualified},任务资质匹配比:{Ratio:F2},资源紧张度:{Tension:F2},技能瓶颈:{Bottlenecks}个", activePersonnelCount, analysis.QualifiedPersonnelCount, taskToQualifiedPersonnelRatio, analysis.ResourceTension, analysis.SkillBottlenecks.Count); return analysis; } catch (Exception ex) { _logger.LogError(ex, "人员资源分析异常"); // 返回保守的默认分析结果 analysis.ResourceTension = 1.0; // 最大紧张度 analysis.QualifiedPersonnelCount = 0; return analysis; } } /// /// 预测潜在冲突 - 智能冲突预警系统 /// 业务逻辑:基于任务特性、人员分布、历史模式预测可能出现的约束冲突 /// 预测维度:负载分配冲突、资质匹配冲突、时间约束冲突、工作限制冲突等 /// 核心价值:提前识别风险点,为遗传算法优化和参数调整提供指导 /// /// 全局分配上下文,包含任务、人员、配置等分析所需信息 /// 潜在冲突分析结果列表,包含冲突类型、概率、影响程度等 private async Task> PredictPotentialConflictsAsync(GlobalAllocationContext context) { await Task.CompletedTask; var conflicts = new List(); try { var availablePersonnel = context.AvailablePersonnel; var tasks = context.Tasks; // 基础冲突预测:基于任务人员比例预测负载不均衡 var taskPersonnelRatio = tasks.Count / (double)Math.Max(availablePersonnel.Count, 1); if (taskPersonnelRatio > 3.0) { conflicts.Add(new GlobalPotentialConflictAnalysis { ConflictType = GlobalConflictType.LoadImbalance, AffectedTaskCount = tasks.Count, AffectedPersonnelCount = availablePersonnel.Count, ConflictProbability = Math.Min(0.9, taskPersonnelRatio / 5.0), ResolutionDifficulty = taskPersonnelRatio > 5.0 ? GlobalResolutionDifficulty.VeryHard : GlobalResolutionDifficulty.Hard, SuggestedSolutions = new List { "增加人员资源", "调整任务优先级", "延长执行时间" } }); } if (taskPersonnelRatio > 2.0) { conflicts.Add(new GlobalPotentialConflictAnalysis { ConflictType = GlobalConflictType.WorkLimitExceeded, AffectedTaskCount = (int)(tasks.Count * 0.6), AffectedPersonnelCount = availablePersonnel.Count, ConflictProbability = Math.Min(0.8, (taskPersonnelRatio - 1.0) / 3.0), ResolutionDifficulty = GlobalResolutionDifficulty.Medium, SuggestedSolutions = new List { "启用智能协商", "优化分配算法参数" } }); } _logger.LogInformation("潜在冲突预测完成,识别{ConflictCount}类潜在冲突", conflicts.Count); return conflicts; } catch (Exception ex) { _logger.LogError(ex, "潜在冲突预测异常"); // 返回保守的高风险预测 return new List { new GlobalPotentialConflictAnalysis { ConflictType = GlobalConflictType.LoadImbalance, AffectedTaskCount = 0, AffectedPersonnelCount = 0, ConflictProbability = 0.9, ResolutionDifficulty = GlobalResolutionDifficulty.VeryHard, SuggestedSolutions = new List { "检查系统状态", "联系技术支持" } } }; } } /// /// 评估约束满足度 - 智能约束满足预测引擎 /// 业务逻辑:预测遗传算法在当前环境下能够达到的约束满足程度 /// 评估维度:硬约束满足率、软约束满足率、约束冲突密度、满足难度等级 /// 核心价值:为可行性评估提供约束满足的量化预测,指导算法参数优化 /// /// 全局分配上下文,包含约束条件和环境参数 /// 约束满足度评估结果,包含各类约束的满足率预测和难度评估 private async Task EstimateConstraintSatisfactionAsync(GlobalAllocationContext context) { await Task.CompletedTask; var estimate = new GlobalConstraintSatisfactionEstimate(); try { var availablePersonnel = context.AvailablePersonnel; var tasks = context.Tasks; // 硬约束满足率评估 - 基于人员资源和任务复杂度 var taskPersonnelRatio = tasks.Count / (double)Math.Max(availablePersonnel.Count, 1); estimate.HardConstraintSatisfactionRate = taskPersonnelRatio > 4.0 ? 0.5 : taskPersonnelRatio > 2.0 ? 0.7 : 0.9; // 班次规则满足率评估 estimate.ShiftRuleSatisfactionRate = estimate.HardConstraintSatisfactionRate * 0.8; // 略低于硬约束 // 时间冲突风险评估 estimate.TimeConflictRisk = taskPersonnelRatio > 3.0 ? GlobalRiskLevel.High : taskPersonnelRatio > 2.0 ? GlobalRiskLevel.Medium : GlobalRiskLevel.Low; // 工作限制违规风险 estimate.WorkLimitViolationRisk = taskPersonnelRatio > 4.0 ? GlobalRiskLevel.Critical : taskPersonnelRatio > 3.0 ? GlobalRiskLevel.High : GlobalRiskLevel.Medium; _logger.LogInformation("约束满足度评估完成,硬约束满足率:{Hard:P2},班次规则满足率:{Shift:P2}", estimate.HardConstraintSatisfactionRate, estimate.ShiftRuleSatisfactionRate); return estimate; } catch (Exception ex) { _logger.LogError(ex, "约束满足度评估异常"); // 返回保守的约束满足评估 estimate.HardConstraintSatisfactionRate = 0.6; // 保守预估 estimate.ShiftRuleSatisfactionRate = 0.4; // 保守预估 estimate.TimeConflictRisk = GlobalRiskLevel.High; estimate.WorkLimitViolationRisk = GlobalRiskLevel.Critical; return estimate; } } /// /// 预测负载均衡情况 - 智能负载分布预测引擎 /// 业务逻辑:基于任务特性和人员分布预测遗传算法优化后的负载均衡效果 /// 预测维度:基尼系数预测、负载标准差、均衡性等级、关键瓶颈识别 /// 核心价值:提供负载分布的量化预测,为算法参数调整和执行决策提供依据 /// /// 全局分配上下文,包含任务和人员信息 /// 负载均衡预测结果,包含基尼系数预测、均衡等级、瓶颈分析等 private async Task PredictLoadBalanceAsync(GlobalAllocationContext context) { await Task.CompletedTask; var prediction = new GlobalLoadBalancePrediction(); try { var availablePersonnel = context.AvailablePersonnel; var tasks = context.Tasks; // 基础分布统计 var totalTasks = tasks.Count; var activePersonnel = availablePersonnel.Count(p => p.IsActive); if (activePersonnel == 0) { // 无可用人员的极端情况 prediction.PredictedGiniCoefficient = 1.0; // 最不均衡 prediction.PredictedFairnessLevel = GlobalFairnessLevel.VeryUnfair; return prediction; } // 理想均匀分布预测 // 业务逻辑:假设遗传算法能够实现接近理想的均匀分配 var idealTasksPerPerson = (double)totalTasks / activePersonnel; var baseTasksPerPerson = (int)Math.Floor(idealTasksPerPerson); var extraTasksCount = totalTasks - (baseTasksPerPerson * activePersonnel); // 构建预测负载分布 var predictedWorkloads = new List(); // 基础分配:每人至少分配baseTasksPerPerson个任务 for (int i = 0; i < activePersonnel; i++) { predictedWorkloads.Add(baseTasksPerPerson); } // 额外任务分配:优先分配给前extraTasksCount个人员 for (int i = 0; i < extraTasksCount && i < predictedWorkloads.Count; i++) { predictedWorkloads[i]++; } // 基尼系数预测 // 业务逻辑:基于理想分布计算预期的基尼系数 prediction.PredictedGiniCoefficient = CalculateGiniCoefficient(predictedWorkloads); // 最大负载差异预测 prediction.PredictedMaxLoadDifference = predictedWorkloads.Max() - predictedWorkloads.Min(); // 均衡等级预测 prediction.PredictedFairnessLevel = DetermineFairnessLevel(prediction.PredictedGiniCoefficient); // 均衡化难度评估 prediction.BalancingDifficulty = prediction.PredictedMaxLoadDifference > 3 ? GlobalBalancingDifficulty.Hard : prediction.PredictedMaxLoadDifference > 1 ? GlobalBalancingDifficulty.Medium : GlobalBalancingDifficulty.Easy; // 构建预测工作负载分布 for (int i = 0; i < activePersonnel; i++) { var personnelId = availablePersonnel.Skip(i).FirstOrDefault()?.Id ?? (i + 1); prediction.PredictedWorkloadDistribution[personnelId] = predictedWorkloads[i]; } _logger.LogInformation("负载均衡预测完成,预测基尼系数:{Gini:F3},均衡等级:{Level},难度:{Difficulty}", prediction.PredictedGiniCoefficient, prediction.PredictedFairnessLevel, prediction.BalancingDifficulty); return prediction; } catch (Exception ex) { _logger.LogError(ex, "负载均衡预测异常"); // 返回保守的预测结果 prediction.PredictedGiniCoefficient = 0.4; // 中等不均衡 prediction.PredictedFairnessLevel = GlobalFairnessLevel.Moderate; prediction.BalancingDifficulty = GlobalBalancingDifficulty.Hard; return prediction; } } /// /// 计算综合可行性评分 - 多维度综合评估引擎 /// 业务逻辑:综合人员资源、约束满足、负载均衡三个维度计算整体可行性评分 /// 评分权重:资源分析(35%) + 约束满足(35%) + 负载均衡(30%) = 综合可行性 /// 核心价值:提供量化的可行性评分,为用户决策和系统调优提供明确指导 /// /// 人员资源分析结果 /// 约束满足度评估结果 /// 负载均衡预测结果 /// 综合可行性评分(0-100分),60分以上认为可行 private double CalculateFeasibilityScore(GlobalPersonnelResourceAnalysis resourceAnalysis, GlobalConstraintSatisfactionEstimate constraintEstimate, GlobalLoadBalancePrediction loadBalancePrediction) { try { // 维度1:人员资源评分(权重35%) - 基于资源紧张度 var resourceScore = Math.Max(10, 100 - (resourceAnalysis.ResourceTension * 90)); // 紧张度越高分数越低 // 维度2:约束满足评分(权重35%) - 基于硬约束满足率 var constraintScore = constraintEstimate.HardConstraintSatisfactionRate * 100; // 维度3:负载均衡评分(权重30%) - 基于基尼系数预测 var balanceScore = (1.0 - loadBalancePrediction.PredictedGiniCoefficient) * 100; // 综合评分计算 // 业务公式:资源(35%) + 约束(35%) + 均衡(30%) = 综合可行性评分 var comprehensiveScore = (resourceScore * 0.35) + (constraintScore * 0.35) + (balanceScore * 0.30); // 风险调整因子 - 基于约束风险等级 var riskAdjustmentFactor = constraintEstimate.TimeConflictRisk == GlobalRiskLevel.Critical ? 0.7 : constraintEstimate.TimeConflictRisk == GlobalRiskLevel.High ? 0.8 : constraintEstimate.TimeConflictRisk == GlobalRiskLevel.Medium ? 0.9 : 1.0; var adjustedScore = comprehensiveScore * riskAdjustmentFactor; // 负载均衡调整 - 基于预测难度 var balanceAdjustment = loadBalancePrediction.BalancingDifficulty == GlobalBalancingDifficulty.Impossible ? 0.5 : loadBalancePrediction.BalancingDifficulty == GlobalBalancingDifficulty.Hard ? 0.8 : loadBalancePrediction.BalancingDifficulty == GlobalBalancingDifficulty.Medium ? 0.9 : 1.0; var finalScore = adjustedScore * balanceAdjustment; // 确保评分在合理范围内 finalScore = Math.Max(0.0, Math.Min(100.0, finalScore)); _logger.LogInformation("可行性评分计算完成,资源评分:{Resource:F1},约束评分:{Constraint:F1},均衡评分:{Balance:F1},综合评分:{Final:F1}", resourceScore, constraintScore, balanceScore, finalScore); return finalScore; } catch (Exception ex) { _logger.LogError(ex, "可行性评分计算异常"); // 返回保守评分 return 45.0; // 低于可行性阈值,建议谨慎执行 } } /// /// 生成推荐优化参数 - 智能参数调优引擎 /// 业务逻辑:基于可行性分析结果和环境特征,智能推荐最优的遗传算法参数配置 /// 优化策略:高可行性(保守参数) + 中可行性(平衡参数) + 低可行性(激进参数) /// 核心价值:自动化参数调优,提高算法执行效率和结果质量 /// /// 全局分配上下文,包含任务规模和环境信息 /// 综合可行性评分,用于指导参数调整策略 /// 推荐的遗传算法优化参数配置 private GlobalRecommendedOptimizationParams GenerateRecommendedParams(GlobalAllocationContext context, double feasibilityScore) { try { var recommendedParams = new GlobalRecommendedOptimizationParams(); var taskCount = context.Tasks.Count; var personnelCount = context.AvailablePersonnel.Count; // 基于可行性评分的参数配置策略 var isHighFeasibility = feasibilityScore >= 75; var isMediumFeasibility = feasibilityScore >= 50; // 种群大小推荐 - 基于任务规模 recommendedParams.RecommendedPopulationSize = taskCount > 50 ? 50 : taskCount > 20 ? 30 : 20; // 最大迭代代数推荐 - 基于复杂度 recommendedParams.RecommendedGenerations = isHighFeasibility ? 50 : isMediumFeasibility ? 80 : 120; // 执行时间预估 recommendedParams.ExpectedExecutionTimeSeconds = (taskCount / 10) + (recommendedParams.RecommendedPopulationSize / 5); // 权重配置推荐 recommendedParams.RecommendedWeights["ConstraintWeight"] = isHighFeasibility ? 0.4 : 0.5; recommendedParams.RecommendedWeights["FairnessWeight"] = 0.3; recommendedParams.RecommendedWeights["QualificationWeight"] = isHighFeasibility ? 0.3 : 0.2; // 推荐原因说明 recommendedParams.RecommendationReason = feasibilityScore >= 75 ? "高可行性,使用保守参数确保稳定" : feasibilityScore >= 50 ? "中等可行性,平衡效率与稳定性" : "低可行性,使用激进参数提高成功率"; _logger.LogInformation("推荐参数生成完成,种群:{Population},迭代:{Generations},执行时间:{Time}s", recommendedParams.RecommendedPopulationSize, recommendedParams.RecommendedGenerations, recommendedParams.ExpectedExecutionTimeSeconds); return recommendedParams; } catch (Exception ex) { _logger.LogError(ex, "推荐参数生成异常"); // 返回保守的默认参数 return new GlobalRecommendedOptimizationParams { RecommendedPopulationSize = 20, // 小种群,快速执行 RecommendedGenerations = 50, // 较少迭代,避免超时 ExpectedExecutionTimeSeconds = 30, // 保守执行时间 RecommendedWeights = new Dictionary { { "ConstraintWeight", 0.4 }, { "FairnessWeight", 0.3 }, { "QualificationWeight", 0.3 } }, RecommendationReason = "参数生成异常,使用默认保守配置" }; } } /// /// 预估执行时间 - 智能时间预测引擎 /// 业务逻辑:基于任务规模、算法参数、系统性能等因素预测遗传算法的执行时间 /// 预测模型:基础时间 + 复杂度调整 + 参数影响 + 性能修正 = 预估执行时间 /// 核心价值:为用户提供准确的时间预期,支持合理的执行计划安排 /// /// 全局分配上下文,包含任务和人员规模信息 /// 推荐的优化参数配置 /// 预估的执行时间(秒),用于用户决策参考 private int EstimateExecutionTime(GlobalAllocationContext context, GlobalRecommendedOptimizationParams recommendedParams) { try { var taskCount = context.Tasks.Count; var personnelCount = context.AvailablePersonnel.Count; // 基础执行时间计算 // 业务逻辑:基于任务规模的基础时间开销,每个任务约0.1-0.5秒 var baseTimeSeconds = Math.Max(5, taskCount * 0.3); // 复杂度调整因子 // 业务逻辑:任务人员比例越高,分配复杂度越大,时间开销越多 var taskPersonnelRatio = taskCount / (double)Math.Max(personnelCount, 1); var complexityFactor = Math.Min(3.0, 1.0 + taskPersonnelRatio * 0.5); // 算法参数影响 // 业务逻辑:种群大小和迭代代数直接影响计算时间 var parameterFactor = (recommendedParams.RecommendedPopulationSize / 20.0) * (recommendedParams.RecommendedGenerations / 100.0); parameterFactor = Math.Max(0.5, Math.Min(5.0, parameterFactor)); // 系统性能修正 var performanceFactor = 1.0; // 智能协商时间开销 var negotiationOverhead = taskCount > 20 ? Math.Max(2, taskCount * 0.1) : 0; // 综合时间预估 var estimatedTime = (baseTimeSeconds * complexityFactor * parameterFactor * performanceFactor) + negotiationOverhead; // 添加安全边界,防止预估过于乐观 var safetyFactor = 1.2; // 20%安全边界 estimatedTime *= safetyFactor; // 合理范围限制 var finalEstimatedTime = (int)Math.Max(10, Math.Min(300, estimatedTime)); // 10秒-5分钟范围 _logger.LogInformation("执行时间预估完成,基础时间:{Base:F1}s,复杂度:{Complexity:F2},参数影响:{Param:F2},预估总时间:{Total}s", baseTimeSeconds, complexityFactor, parameterFactor, finalEstimatedTime); return finalEstimatedTime; } catch (Exception ex) { _logger.LogError(ex, "执行时间预估异常"); // 返回保守的时间预估 return 60; // 1分钟保守预估 } } /// /// 分析风险因子 - 综合风险评估引擎 /// 业务逻辑:基于潜在冲突和约束满足情况,全面分析分配过程中的各类风险 /// 风险维度:执行风险、结果质量风险、业务影响风险、系统稳定性风险 /// 核心价值:提供全面的风险预警和缓解建议,帮助用户做出明智决策 /// /// 潜在冲突分析结果列表 /// 约束满足度评估结果 /// 风险因子分析结果列表,包含风险类型、等级、影响、缓解建议 private List AnalyzeRiskFactors(List conflicts, GlobalConstraintSatisfactionEstimate estimate) { var riskFactors = new List(); try { // 风险因子1:执行失败风险 // 业务逻辑:基于约束满足率评估执行失败概率 if (estimate.HardConstraintSatisfactionRate < 0.7) { riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "ExecutionFailure", RiskLevel = estimate.HardConstraintSatisfactionRate < 0.5 ? GlobalRiskLevel.Critical : GlobalRiskLevel.High, RiskProbability = 1.0 - estimate.HardConstraintSatisfactionRate, ImpactAssessment = $"硬约束满足率{estimate.HardConstraintSatisfactionRate:P2},存在执行失败风险", MitigationSuggestions = new List { "增加人员资源", "调整任务优先级", "优化算法参数" } }); } // 风险因子2:结果质量风险 // 业务逻辑:基于班次规则满足率评估结果质量风险 if (estimate.ShiftRuleSatisfactionRate < 0.8) { riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "QualityRisk", RiskLevel = estimate.ShiftRuleSatisfactionRate < 0.6 ? GlobalRiskLevel.High : GlobalRiskLevel.Medium, RiskProbability = 1.0 - estimate.ShiftRuleSatisfactionRate, ImpactAssessment = $"班次规则满足率{estimate.ShiftRuleSatisfactionRate:P2},分配质量可能不达标", MitigationSuggestions = new List { "启用智能协商", "放宽部分软约束", "分阶段执行分配" } }); } // 风险因子3:时间冲突风险 // 业务逻辑:基于时间冲突风险等级评估 if (estimate.TimeConflictRisk >= GlobalRiskLevel.High) { riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "TimeConflict", RiskLevel = estimate.TimeConflictRisk, RiskProbability = estimate.TimeConflictRisk == GlobalRiskLevel.Critical ? 0.8 : 0.6, ImpactAssessment = $"时间冲突风险等级:{estimate.TimeConflictRisk}", MitigationSuggestions = new List { "调整任务时间安排", "增加人员班次", "启用时间协商功能" } }); } // 风险因子4:工作负载风险 // 业务逻辑:基于工作限制违规风险评估 if (estimate.WorkLimitViolationRisk >= GlobalRiskLevel.High) { riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "WorkloadViolation", RiskLevel = estimate.WorkLimitViolationRisk, RiskProbability = estimate.WorkLimitViolationRisk == GlobalRiskLevel.Critical ? 0.9 : 0.7, ImpactAssessment = $"工作负载违规风险等级:{estimate.WorkLimitViolationRisk}", MitigationSuggestions = new List { "合理分配工作负载", "设置负载上限", "增加人员资源" } }); } // 风险因子5:潜在冲突风险 // 业务逻辑:基于识别的潜在冲突数量和概率评估 var highProbabilityConflicts = conflicts.Count(c => c.ConflictProbability > 0.7); if (highProbabilityConflicts > 0) { riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "PotentialConflicts", RiskLevel = highProbabilityConflicts > 3 ? GlobalRiskLevel.Critical : GlobalRiskLevel.High, RiskProbability = Math.Min(0.95, highProbabilityConflicts * 0.2), ImpactAssessment = $"识别到{highProbabilityConflicts}个高概率潜在冲突", MitigationSuggestions = new List { "预处理高风险冲突", "调整分配策略", "准备应急方案" } }); } // 计算综合风险等级 var overallRiskLevel = riskFactors.Any() ? riskFactors.Max(r => r.RiskLevel) : GlobalRiskLevel.Low; var overallRiskProbability = riskFactors.Any() ? riskFactors.Average(r => r.RiskProbability) : 0.1; // 添加综合风险评估 riskFactors.Add(new GlobalAllocationRiskFactor { RiskType = "SystemOverall", RiskLevel = overallRiskLevel, RiskProbability = overallRiskProbability, ImpactAssessment = $"综合评估识别{riskFactors.Count}个风险因子,整体风险等级:{overallRiskLevel}", MitigationSuggestions = riskFactors.SelectMany(r => r.MitigationSuggestions).Distinct().ToList() }); _logger.LogInformation("风险因子分析完成,识别{RiskCount}个风险因子,综合风险等级:{OverallRisk}", riskFactors.Count, overallRiskLevel); return riskFactors; } catch (Exception ex) { _logger.LogError(ex, "风险因子分析异常"); // 返回高风险警告 return new List { new GlobalAllocationRiskFactor { RiskType = "SystemOverall", RiskLevel = GlobalRiskLevel.Critical, RiskProbability = 0.9, ImpactAssessment = "风险分析系统发生异常,无法准确评估风险等级", MitigationSuggestions = new List { "建议暂停执行,检查系统状态", "联系技术支持团队" } } }; } } /// /// 计算标准差 - 统计学工具方法 /// 业务逻辑:计算工作负载分布的标准差,衡量负载分散程度 /// 数学公式:σ = √(∑(xi - μ)^2 / n) /// 业务价值:配合基尼系数提供更全面的公平性分析 /// /// 数值列表 /// 标准差值 private double CalculateStandardDeviation(List values) { if (!values.Any()) return 0; var mean = values.Average(x => (double)x); return Math.Sqrt(values.Sum(x => Math.Pow((double)x - mean, 2)) / values.Count); } /// /// 计算工作负载百分比 - 负载占比计算器 /// 业务逻辑:计算单个人员的工作负载在总负载中的占比 /// 用途:用于显示和分析人员工作负载的相对比例 /// 防御性:处理总负载为0的边界情况 /// /// 单个人员的工作负载 /// 所有人员的工作负载列表 /// 百分比值(0-100) private double CalculateWorkloadPercentage(decimal workload, List allWorkloads) { var total = allWorkloads.Sum(); return total > 0 ? (double)(workload / total * 100) : 0; } /// /// 获取人员姓名 - 人员信息查询器 /// 业务逻辑:基于人员ID从缓存中获取对应的人员姓名信息 /// 性能优化:复用BuildGlobalAllocationContextAsync中已查询的人员数据,避免重复查询 /// 数据一致性:确保返回真实的人员姓名而非占位符格式 /// /// 人员ID /// 人员真实姓名,如缓存中不存在则返回默认格式 private string GetPersonnelName(long personnelId) { // 深度思考:优先使用已缓存的真实人员姓名,确保数据一致性 // 业务逻辑:从BuildGlobalAllocationContextAsync构建的缓存中查找真实姓名 if (_personnelNameCache.TryGetValue(personnelId, out var cachedName)) { return cachedName; } // 防御性编程:缓存未命中时返回格式化的占位符,确保系统稳定性 return $"Person_{personnelId}"; } #region 人员替换辅助方法 /// /// 计算工作负载评分 - 负载均衡优先策略 /// 业务逻辑:工作负载越轻的人员评分越高,实现负载均衡目标 /// private double CalculateWorkloadScore(long personnelId, Dictionary workloadDistribution) { // 获取当前人员的工作负载,如果没有分配任务则负载为0 var currentWorkload = workloadDistribution.ContainsKey(personnelId) ? (double)workloadDistribution[personnelId] : 0.0; // 计算负载评分:负载越轻评分越高 // 评分公式:max(0, 100 - workload * 10),确保负载超过10个任务时评分为0 return Math.Max(0, 100.0 - currentWorkload * 10); } /// /// 计算冲突类型适配评分 - 针对性解决方案 /// 业务逻辑:根据不同冲突类型的特点,评估候选人员的适配程度 /// private double CalculateConflictAdaptationScore(GlobalPersonnelInfo candidate, GlobalConflictDetectionInfo conflict) { // 基于冲突类型的差异化评分策略 return conflict.ConflictType switch { GlobalConflictType.LoadImbalance => 90.0, // 负载不均衡:所有人员都适用 GlobalConflictType.WorkLimitExceeded => 85.0, // 工作量超限:适用性较高 GlobalConflictType.QualificationMismatch => 70.0, // 资质不匹配:需要具体检查 GlobalConflictType.TimeUnavailable => 75.0, // 时间不可用:需要时间检查 _ => 60.0 // 其他类型:基础适配分 }; } /// /// 计算人员综合可用性评分 - 多维度真实业务评估引擎 /// 业务逻辑:整合时间可用性、工作限制、班次规则、资质匹配等核心维度 /// 评估维度:基础状态(30%) + 时间可用性(25%) + 工作限制(20%) + 班次规则(15%) + 资质匹配(10%) /// 核心价值:提供准确的人员可用性评估,确保分配结果符合业务约束和最优化目标 /// /// 候选人员信息,包含基本状态和ID /// 全局分配上下文,包含任务信息和环境参数 /// 综合可用性评分(0-100分),0表示完全不可用,100表示完全可用 private async Task CalculateComprehensiveAvailabilityScoreAsync(GlobalPersonnelInfo candidate, GlobalAllocationContext context) { try { // 维度1:基础状态检查(权重30%) // 业务逻辑:人员激活状态是分配的前提条件,非激活状态直接排除 if (!candidate.IsActive) { _logger.LogDebug("人员{PersonnelId}({PersonnelName})非激活状态,可用性评分为0", candidate.Id, candidate.Name); return 0.0; // 非激活状态直接不可用 } var baseStatusScore = 100.0; // 基础状态满分 var dimensionScores = new Dictionary { ["BaseStatus"] = baseStatusScore }; var dimensionWeights = new Dictionary { ["BaseStatus"] = 0.30, ["TimeAvailability"] = 0.25, ["WorkLimitCompliance"] = 0.20, ["ShiftRuleCompliance"] = 0.15, ["QualificationMatch"] = 0.10 }; // 业务逻辑修正:针对多任务场景,需要对每个任务分别评估可用性 // 深度思考:全局分配中人员面临的是多个不同的任务,不能简单用单一任务评估 // 解决方案:采用最保守评估策略 - 所有任务中的最低评分作为最终评分 if (!context.Tasks?.Any() == true) { _logger.LogWarning("上下文中无任务信息,无法执行具体的可用性检查"); // 降级处理:仅返回基础状态评分 return baseStatusScore; } // 维度2:时间可用性检查(权重25%) - 多任务综合评估 // 业务逻辑:检查人员对所有任务时间段的可用性,取最保守评分 // 【关键修复】:传入上下文任务列表,启用批次内冲突检查 var timeAvailabilityScores = new List(); foreach (var task in context.Tasks) { var taskTimeScore = await CalculateTimeAvailabilityScoreAsync(candidate.Id, task.WorkOrderDate, task.ShiftId ?? 0, context.Tasks); timeAvailabilityScores.Add(taskTimeScore); } var overallTimeScore = timeAvailabilityScores.Any() ? timeAvailabilityScores.Min() : 0.0; dimensionScores["TimeAvailability"] = overallTimeScore; // 维度4:班次规则合规检查(权重15%) - 多任务班次冲突检查 // 业务逻辑:检查所有任务的班次安排是否符合班次规则,取最严格评分 var shiftRuleScores = new List(); foreach (var task in context.Tasks) { // 【架构最终修复】:直接传入具体任务信息,确保FL优先规则能够获取正确的项目信息 // 构建任务特定的上下文,并直接传入当前任务对象 var taskSpecificContext = new GlobalAllocationContext { Tasks = new List { task }, // 只包含当前正在验证的任务 AvailablePersonnel = context.AvailablePersonnel, Config = context.Config, ProcessingLog = context.ProcessingLog, Metrics = context.Metrics, CurrentTask = task // 添加当前任务的直接引用 }; var taskShiftScore = await CalculateShiftRuleComplianceScoreAsync(candidate.Id, task.WorkOrderDate, task.ShiftId ?? 0, taskSpecificContext); shiftRuleScores.Add(taskShiftScore); } var overallShiftScore = shiftRuleScores.Any() ? shiftRuleScores.Min() : 0.0; dimensionScores["ShiftRuleCompliance"] = overallShiftScore; // 维度5:资质匹配度检查(权重10%) - 多任务综合资质匹配 // 业务逻辑:评估人员资质与所有任务要求的综合匹配程度 var qualificationScore = await CalculateMultiTaskQualificationMatchScoreAsync(candidate.Id, context.Tasks); dimensionScores["QualificationMatch"] = qualificationScore; // 计算综合可用性评分 // 业务算法:加权平均算法,确保各维度按重要性贡献评分 var comprehensiveScore = CalculateWeightedScore(dimensionScores, dimensionWeights); // 业务日志:记录详细的评分分解,便于调试和业务分析 _logger.LogDebug("人员{PersonnelId}({PersonnelName})综合可用性评分计算完成: " + "基础状态:{BaseStatus:F1}(30%), 时间可用性:{TimeAvailability:F1}(25%), " + "工作限制:{WorkLimit:F1}(20%), 班次规则:{ShiftRule:F1}(15%), " + "资质匹配:{Qualification:F1}(10%), 综合评分:{Comprehensive:F1}", candidate.Id, candidate.Name, baseStatusScore, overallTimeScore, 0, overallShiftScore, qualificationScore, comprehensiveScore); return comprehensiveScore; } catch (Exception ex) { _logger.LogError(ex, "计算人员{PersonnelId}({PersonnelName})综合可用性评分异常", candidate.Id, candidate.Name); // 异常降级处理:返回中等评分,避免影响整体分配流程 return 50.0; } } #region 多维度可用性评估算法实现 /// /// 计算时间可用性评分 - 时间维度业务评估引擎 /// 业务逻辑:检查人员在指定时间段的请假状态和任务冲突,返回量化的可用性评分 /// 核心检查:请假状态检查、同时段任务冲突检查、时间负载评估 /// 评分标准:无冲突100分、有预警80-60分、有冲突0分 /// /// 人员ID /// 工作日期 /// 班次ID /// 当前分配批次中的其他任务,用于批次内冲突检查 /// 时间可用性评分(0-100分) private async Task CalculateTimeAvailabilityScoreAsync(long personnelId, DateTime workDate, long shiftId, List contextTasks = null) { try { // 检查人员是否在请假期间 // 业务逻辑:请假期间完全不可用,直接返回0分 var leaveInfo = await _employeeLeaveService.IsOnLeaveAsync(personnelId, workDate); if (leaveInfo.IsOnLeave) { _logger.LogDebug("人员{PersonnelId}在{WorkDate:yyyy-MM-dd}请假({LeaveType}),时间可用性评分为0", personnelId, workDate, leaveInfo.LeaveType); return 0.0; } // 检查同时段任务冲突 - 数据库中已存在的任务 // 业务逻辑:同时段已有任务分配视为完全冲突,返回0分 var conflictingTasksCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.ShiftId == shiftId && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); if (conflictingTasksCount > 0) { _logger.LogDebug("人员{PersonnelId}在{WorkDate:yyyy-MM-dd}班次{ShiftId}已有{Count}个数据库任务冲突,时间可用性评分为0", personnelId, workDate, shiftId, conflictingTasksCount); return 0.0; } // 【关键修复】:检查批次内任务冲突 // 业务逻辑:检查当前分配批次中是否有其他任务与当前时段冲突 if (contextTasks?.Any() == true) { var contextConflictingTasks = contextTasks.Where(task => task.WorkOrderDate.Date == workDate.Date && task.ShiftId == shiftId).ToList(); if (contextConflictingTasks.Count > 1) // 超过1个任务表示有冲突(包含当前任务自身) { _logger.LogWarning("【批次内冲突】人员{PersonnelId}在{WorkDate:yyyy-MM-dd}班次{ShiftId}有{Count}个批次内任务冲突,时间可用性评分为0。" + "冲突任务:{ConflictingTasks}", personnelId, workDate, shiftId, contextConflictingTasks.Count, string.Join(", ", contextConflictingTasks.Select(t => $"{t.Id}({t.WorkOrderCode})"))); return 0.0; } } // 计算时间负载评分 // 业务逻辑:评估人员在当前时间段的负载程度,负载越重评分越低 var dayTaskCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); // 时间负载评分算法:基于当日任务数量计算负载度 // 评分标准:0个任务(100分) → 1-2个任务(90分) → 3-4个任务(75分) → 5+个任务(60分) var timeLoadScore = dayTaskCount switch { 0 => 100.0, // 当日无任务,时间完全可用 <= 2 => 90.0, // 轻度负载,时间基本可用 <= 4 => 75.0, // 中度负载,时间部分可用 _ => 60.0 // 重度负载,时间勉强可用 }; _logger.LogDebug("人员{PersonnelId}在{WorkDate:yyyy-MM-dd}当日已有{DayTaskCount}个任务,时间可用性评分为{Score:F1}", personnelId, workDate, dayTaskCount, timeLoadScore); return timeLoadScore; } catch (Exception ex) { _logger.LogError(ex, "计算人员{PersonnelId}时间可用性评分异常", personnelId); return 50.0; // 异常时返回中等评分 } } /// /// 计算班次规则合规评分 - 完整班次规则评估引擎 /// 【深度业务逻辑重构】:基于PersonnelAllocationService的完整规则实现 /// 业务逻辑:检查人员班次安排是否符合11种完整班次规则,包括: /// 规则1:指定人员优先分配 /// 规则2:周任务限制 /// 规则3:同天早中班连续性禁止 /// 规则4:同天中夜班连续性禁止 /// 规则5:跨周末连续性禁止(周日/下周六) /// 规则6:周末连续性禁止(周六/周日) /// 规则7:连续工作天数限制 /// 规则8:三班后次日强制休息 /// 规则9:二班后次日强制休息 /// 规则10:优先分配本项目FL /// 规则11:其他自定义规则 /// 核心改进:从简单的密度检查升级为完整的规则引擎验证 /// 评分标准:所有规则通过100分、轻微违规80-60分、严重违规30-0分 /// /// 人员ID /// 工作日期 /// 班次ID /// 班次规则合规评分(0-100分) private async Task CalculateShiftRuleComplianceScoreAsync(long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context = null) { try { _logger.LogDebug("开始完整班次规则合规评分计算 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 第一步:获取该班次的所有关联规则 var shiftRules = await GetShiftRulesAsync(shiftId); if (shiftRules == null || !shiftRules.Any()) { _logger.LogDebug("班次ID:{ShiftId}无关联规则配置,返回默认评分90分", shiftId); return 90.0; // 无规则配置时给予高分但非满分 } // 第二步:验证所有启用的班次规则 var ruleScores = new List(); var criticalViolations = new List(); var violations = new List(); foreach (var rule in shiftRules.Where(r => r.IsEnabled)) { try { var ruleResult = await ValidateIndividualShiftRuleAsync(rule, personnelId, workDate, shiftId, context); // 收集规则评分 ruleScores.Add(ruleResult.ComplianceScore); // 收集违规信息 if (!ruleResult.IsValid) { if (ruleResult.IsCritical) { criticalViolations.Add($"规则{rule.RuleName}:{ruleResult.ViolationMessage}"); } else { violations.Add($"规则{rule.RuleName}:{ruleResult.ViolationMessage}"); } } _logger.LogDebug("规则验证完成 - 规则:{RuleName}({RuleType}), 合规:{IsValid}, 评分:{Score:F1}", rule.RuleName, rule.RuleType, ruleResult.IsValid, ruleResult.ComplianceScore); } catch (Exception ex) { _logger.LogError(ex, "验证班次规则异常 - 规则:{RuleName}({RuleType})", rule.RuleName, rule.RuleType); ruleScores.Add(60.0); // 异常规则给予中等评分 } } // 第三步:计算综合班次规则合规评分 // 【关键业务决策】:使用最低分策略,确保严格执行所有规则 var overallScore = ruleScores.Any() ? ruleScores.Min() : 75.0; // 【严重违规处理】:如果有关键违规,强制降低评分 if (criticalViolations.Any()) { overallScore = Math.Min(overallScore, 20.0); _logger.LogWarning("发现关键班次规则违规 - 人员ID:{PersonnelId}, 违规数:{CriticalCount}, 强制降低评分至{Score:F1}", personnelId, criticalViolations.Count, overallScore); } // 第四步:记录详细的评分结果 var totalRulesChecked = shiftRules.Count(r => r.IsEnabled); var passedRulesCount = ruleScores.Count(s => s >= 80); _logger.LogDebug("班次规则合规评分计算完成 - 人员ID:{PersonnelId}, 检查规则:{Total}个, 通过:{Passed}个, " + "普通违规:{Violations}个, 关键违规:{Critical}个, 综合评分:{Score:F1}", personnelId, totalRulesChecked, passedRulesCount, violations.Count, criticalViolations.Count, overallScore); // 【业务透明度】:详细记录违规情况供调试和审计 if (violations.Any() || criticalViolations.Any()) { var allViolations = string.Join("; ", violations.Concat(criticalViolations)); _logger.LogInformation("班次规则违规详情 - 人员ID:{PersonnelId}, 违规内容:{Violations}", personnelId, allViolations); } return overallScore; } catch (Exception ex) { _logger.LogError(ex, "计算班次规则合规评分异常 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); return 75.0; // 异常时返回中高等评分,避免阻断业务但标记为需要人工审核 } } /// /// 计算资质匹配评分 - 资质维度业务评估引擎 /// 业务逻辑:评估人员资质与任务要求的匹配程度,确保分配的合规性 /// 核心检查:必需资质检查、资质有效期检查、匹配度评估 /// 评分标准:完全匹配100分、部分匹配60-80分、不匹配0分 /// /// 人员ID /// 工作任务实体,包含资质要求信息 /// 资质匹配评分(0-100分) private async Task CalculateQualificationMatchScoreAsync(long personnelId, WorkOrderEntity workOrder) { try { // 获取任务的资质要求 var requiredQualifications = GetRequiredQualifications(workOrder); if (!requiredQualifications.Any()) { _logger.LogDebug("任务{TaskId}无资质要求,人员{PersonnelId}资质匹配评分为满分", workOrder.Id, personnelId); return 100.0; // 无资质要求时给满分 } // 获取人员的有效资质 var personnelQualifications = await _personnelQualificationService.GetActiveQualificationsByPersonnelIdAsync(personnelId); var validQualifications = personnelQualifications .Where(q => q.ExpiryDate == null || q.ExpiryDate > DateTime.Now) .Select(q => q.QualificationId.ToString()) .ToHashSet(); // 计算资质匹配度 var matchedCount = requiredQualifications.Count(req => validQualifications.Contains(req)); var totalRequiredCount = requiredQualifications.Length; var matchRate = totalRequiredCount > 0 ? (double)matchedCount / totalRequiredCount : 1.0; // 资质匹配评分算法 var qualificationScore = matchRate switch { 1.0 => 100.0, // 完全匹配,满分 >= 0.8 => 85.0, // 80%以上匹配,良好 >= 0.6 => 65.0, // 60%以上匹配,一般 >= 0.4 => 40.0, // 40%以上匹配,较差 > 0 => 20.0, // 有部分匹配,很差 _ => 0.0 // 完全不匹配,不可分配 }; _logger.LogDebug("人员{PersonnelId}任务{TaskId}资质匹配度{MatchRate:P2}({MatchedCount}/{TotalRequired}),评分{Score:F1}", personnelId, workOrder.Id, matchRate, matchedCount, totalRequiredCount, qualificationScore); return qualificationScore; } catch (Exception ex) { _logger.LogError(ex, "计算人员{PersonnelId}资质匹配评分异常", personnelId); return 50.0; // 异常时返回中等评分 } } /// /// 计算多任务资质匹配评分 - 多任务综合资质评估引擎 /// 业务逻辑:评估人员资质与所有任务要求的综合匹配程度 /// 核心策略:必须满足所有任务的资质要求,否则评分显著降低 /// /// 人员ID /// 所有待分配任务列表 /// 多任务资质匹配评分(0-100分) private async Task CalculateMultiTaskQualificationMatchScoreAsync(long personnelId, List tasks) { try { // 收集所有任务的资质要求 var allRequiredQualifications = new HashSet(); foreach (var task in tasks) { var taskRequiredQualifications = GetRequiredQualifications(task); foreach (var qualification in taskRequiredQualifications) { allRequiredQualifications.Add(qualification); } } if (!allRequiredQualifications.Any()) { _logger.LogDebug("所有任务无资质要求,人员{PersonnelId}多任务资质匹配评分为满分", personnelId); return 100.0; } // 获取人员的有效资质 var personnelQualifications = await _personnelQualificationService.GetActiveQualificationsByPersonnelIdAsync(personnelId); var validQualifications = personnelQualifications .Where(q => q.ExpiryDate == null || q.ExpiryDate > DateTime.Now) .Select(q => q.QualificationId.ToString()) .ToHashSet(); // 计算综合资质匹配度 var matchedCount = allRequiredQualifications.Count(req => validQualifications.Contains(req)); var totalRequiredCount = allRequiredQualifications.Count; var overallMatchRate = totalRequiredCount > 0 ? (double)matchedCount / totalRequiredCount : 1.0; // 多任务资质匹配评分算法(比单任务更严格) var qualificationScore = overallMatchRate switch { 1.0 => 100.0, // 完全匹配所有任务资质要求,满分 >= 0.9 => 85.0, // 90%以上匹配,良好 >= 0.75 => 70.0, // 75%以上匹配,一般 >= 0.5 => 50.0, // 50%以上匹配,较差 > 0 => 25.0, // 有部分匹配,很差 _ => 0.0 // 完全不匹配,不可分配 }; // 按任务逐个检查,如果有任务完全不匹配,则严重降分 var taskMatchScores = new List(); foreach (var task in tasks) { var taskRequiredQualifications = GetRequiredQualifications(task); if (taskRequiredQualifications.Any()) { var taskMatchedCount = taskRequiredQualifications.Count(req => validQualifications.Contains(req)); var taskMatchRate = (double)taskMatchedCount / taskRequiredQualifications.Length; taskMatchScores.Add(taskMatchRate); } } // 如果有任务完全不匹配(匹配率为0),则整体评分要被严重降低 if (taskMatchScores.Any(score => score == 0)) { qualificationScore = Math.Min(qualificationScore, 20.0); _logger.LogWarning("人员{PersonnelId}存在完全不匹配资质的任务,多任务资质评分被降低至{Score:F1}", personnelId, qualificationScore); } _logger.LogDebug("人员{PersonnelId}多任务资质匹配度{MatchRate:P2}({MatchedCount}/{TotalRequired}),综合评分{Score:F1}", personnelId, overallMatchRate, matchedCount, totalRequiredCount, qualificationScore); return qualificationScore; } catch (Exception ex) { _logger.LogError(ex, "计算人员{PersonnelId}多任务资质匹配评分异常", personnelId); return 50.0; // 异常时返回中等评分 } } /// /// 计算加权评分 - 多维度评分聚合算法 /// 业务逻辑:基于预定义权重计算各维度评分的加权平均值 /// 算法公式:∑(维度评分 × 权重) / ∑权重 /// /// 各维度评分字典 /// 各维度权重字典 /// 加权综合评分 private double CalculateWeightedScore(Dictionary dimensionScores, Dictionary dimensionWeights) { var totalWeightedScore = 0.0; var totalWeight = 0.0; foreach (var dimension in dimensionScores.Keys) { if (dimensionWeights.TryGetValue(dimension, out var weight)) { totalWeightedScore += dimensionScores[dimension] * weight; totalWeight += weight; } } return totalWeight > 0 ? totalWeightedScore / totalWeight * 100 : 0.0; } #endregion #region 辅助计算方法 /// /// 计算连续工作天数 - 连续性统计算法 /// 业务逻辑:从指定日期向前统计连续有任务分配的天数 /// private async Task CalculateContinuousWorkDaysAsync(long personnelId, DateTime workDate) { var continuousDays = 0; var checkDate = workDate.AddDays(-1); // 从前一天开始检查 // 向前检查连续工作天数,最多检查30天防止无限循环 for (int i = 0; i < 30; i++) { var dayTaskCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == checkDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); if (dayTaskCount > 0) { continuousDays++; checkDate = checkDate.AddDays(-1); } else { break; // 遇到无任务的天数,停止统计 } } return continuousDays; } /// /// 计算周班次数 - 周期性统计算法 /// 业务逻辑:统计指定日期所在周的班次总数 /// private async Task CalculateWeekShiftCountAsync(long personnelId, DateTime workDate) { // 计算当前日期所在周的开始和结束日期 var dayOfWeek = (int)workDate.DayOfWeek; var startOfWeek = workDate.AddDays(-dayOfWeek); // 周日为一周开始 var endOfWeek = startOfWeek.AddDays(6); var weekShiftCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= startOfWeek && w.WorkOrderDate <= endOfWeek && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); return (int)weekShiftCount; } #endregion /// /// 更新工作负载分布 - 维护负载统计一致性 /// private void UpdateWorkloadDistribution(GlobalOptimizedSolution solution, long originalPersonnelId, long newPersonnelId) { var workloadDistribution = solution.PersonnelWorkloadDistribution; // 减少原人员的工作负载 if (workloadDistribution.ContainsKey(originalPersonnelId)) { workloadDistribution[originalPersonnelId] = Math.Max(0, workloadDistribution[originalPersonnelId] - 1); // 如果负载降为0,从分布中移除 if (workloadDistribution[originalPersonnelId] == 0) { workloadDistribution.Remove(originalPersonnelId); } } // 增加新人员的工作负载 if (!workloadDistribution.ContainsKey(newPersonnelId)) { workloadDistribution[newPersonnelId] = 1; } else { workloadDistribution[newPersonnelId]++; } } /// /// 生成替换原因说明 - 操作可追溯性 /// private string GenerateReplacementReason(GlobalConflictDetectionInfo conflict, GlobalPersonnelInfo replacementPersonnel) { var conflictDescription = conflict.ConflictType switch { GlobalConflictType.LoadImbalance => "负载不均衡", GlobalConflictType.WorkLimitExceeded => "工作负载超限", GlobalConflictType.QualificationMismatch => "资质不匹配", GlobalConflictType.TimeUnavailable => "时间冲突", _ => "约束冲突" }; return $"通过智能评估选择人员{replacementPersonnel.Id}({replacementPersonnel.Name})替换,以解决{conflictDescription}冲突"; } /// /// 创建失败的替换操作结果 - 统一的失败处理 /// private GlobalNegotiationAction CreateFailedReplacementAction(long taskId, long originalPersonnelId, string reason) { return new GlobalNegotiationAction { ActionType = GlobalNegotiationActionType.PersonnelReplacement, TaskId = taskId, OriginalPersonnelId = originalPersonnelId, Reason = reason, IsSuccessful = false }; } #endregion #region 任务重分配辅助方法 /// /// 计算负载接受能力评分 - 重分配专用负载评估算法 /// 业务逻辑:评估候选人员接受额外任务负载的能力,优先选择负载适中的人员 /// 核心差异:相比简单的"负载越轻越好",更注重负载的合理性和接受能力 /// private double CalculateLoadAcceptanceScore(long personnelId, Dictionary workloadDistribution) { // 获取当前人员的工作负载 var currentWorkload = workloadDistribution.ContainsKey(personnelId) ? (double)workloadDistribution[personnelId] : 0.0; // 重分配场景的理想负载接受能力评估 // 业务逻辑:0-2个任务(很好,有充足接受能力)、3-4个任务(较好)、5-6个任务(一般)、7+个任务(较差) return currentWorkload switch { <= 2 => 100.0, // 负载很轻,完全可以接受额外任务 <= 4 => 85.0, // 负载适中,有良好的接受能力 <= 6 => 65.0, // 负载较重,但仍可接受 <= 8 => 40.0, // 负载过重,接受能力有限 _ => 10.0 // 严重过载,基本无接受能力 }; } /// /// 计算冲突解决能力评分 - 重分配冲突解决效果评估 /// 业务逻辑:评估将任务重分配给候选人员后,对原有冲突的解决效果 /// private double CalculateConflictResolutionScore(GlobalPersonnelInfo candidate, GlobalConflictDetectionInfo conflict, Dictionary workloadDistribution) { var candidateCurrentLoad = workloadDistribution.ContainsKey(candidate.Id) ? (double)workloadDistribution[candidate.Id] : 0.0; // 基于冲突类型的解决能力评估 var baseResolutionScore = conflict.ConflictType switch { GlobalConflictType.LoadImbalance => 90.0, // 负载均衡冲突:重分配效果明显 GlobalConflictType.WorkLimitExceeded => 85.0, // 工作量超限:有效减轻原人员负载 GlobalConflictType.QualificationMismatch => 75.0, // 资质不匹配:需要验证资质匹配 GlobalConflictType.TimeUnavailable => 80.0, // 时间冲突:转移任务可解决时间问题 _ => 70.0 // 其他冲突:一般解决效果 }; // 根据候选人员当前负载调整解决能力评分 // 业务逻辑:候选人员负载越轻,接受重分配任务后的冲突解决效果越好 var loadAdjustment = candidateCurrentLoad <= 3 ? 1.0 : candidateCurrentLoad <= 5 ? 0.9 : candidateCurrentLoad <= 7 ? 0.7 : 0.5; return baseResolutionScore * loadAdjustment; } /// /// 计算任务适配度评分 - 任务与候选人员的匹配程度评估 /// 业务逻辑:评估候选人员执行待重分配任务的适配程度 /// private double CalculateTaskAdaptationScore(GlobalPersonnelInfo candidate, GlobalConflictDetectionInfo conflict, GlobalAllocationContext context) { // 基础适配评分:基于人员基本信息的适配性评估 var baseAdaptationScore = 80.0; // 人员活跃状态检查 if (!candidate.IsActive) { return 0.0; // 非激活人员不适配 } // 基于人员ID的经验适配度假设(简化实现) // 实际应用中应该基于技能匹配、历史绩效、任务复杂度等 var experienceAdaptation = Math.Min(20.0, candidate.Id / 50.0); // ID越大经验越丰富假设 // 人员稳定性对任务适配的影响 var stabilityBonus = candidate.Id % 3 == 0 ? 10.0 : 5.0; // 某些ID模式代表更稳定 return Math.Min(100.0, baseAdaptationScore + experienceAdaptation + stabilityBonus); } /// /// 计算平衡性影响评分 - 重分配对整体负载平衡的影响评估 /// 业务逻辑:评估将任务重分配给候选人员后,对整体负载分布平衡性的影响 /// private double CalculateBalanceImpactScore(GlobalPersonnelInfo candidate, Dictionary workloadDistribution) { if (!workloadDistribution.Any()) return 100.0; var candidateCurrentLoad = workloadDistribution.ContainsKey(candidate.Id) ? (double)workloadDistribution[candidate.Id] : 0.0; // 计算当前负载分布的平均值 var averageLoad = workloadDistribution.Values.Average(x => (double)x); // 评估重分配后候选人员负载与平均负载的差异 var postReallocationLoad = candidateCurrentLoad + 1; // 假设接受1个额外任务 var loadDeviationFromAverage = Math.Abs(postReallocationLoad - averageLoad); // 偏差越小,对平衡性的正面影响越大 // 评分公式:100 - 偏差*15,确保偏差在合理范围内 return Math.Max(20.0, 100.0 - loadDeviationFromAverage * 15); } /// /// 生成重分配原因说明 - 操作可追溯性和业务解释 /// 业务逻辑:生成详细的任务重分配原因说明,提供操作的业务合理性解释 /// private string GenerateReallocationReason(GlobalConflictDetectionInfo conflict, GlobalPersonnelInfo targetPersonnel) { var conflictDescription = conflict.ConflictType switch { GlobalConflictType.LoadImbalance => "负载不均衡", GlobalConflictType.WorkLimitExceeded => "工作负载超限", GlobalConflictType.QualificationMismatch => "资质不匹配", GlobalConflictType.TimeUnavailable => "时间冲突", _ => "约束冲突" }; return $"通过智能分析选择人员{targetPersonnel.Id}({targetPersonnel.Name})作为重分配目标," + $"以解决{conflictDescription}冲突,提高整体分配质量和负载平衡性"; } /// /// 创建失败的重分配操作结果 - 统一的重分配失败处理 /// 业务逻辑:当重分配无法执行时,创建标准化的失败响应 /// private GlobalNegotiationAction CreateFailedReallocationAction(long taskId, long originalPersonnelId, string reason) { return new GlobalNegotiationAction { ActionType = GlobalNegotiationActionType.TaskReallocation, TaskId = taskId, OriginalPersonnelId = originalPersonnelId, Reason = reason, IsSuccessful = false }; } #endregion #region 真实资质匹配分析 /// /// 执行真实的人员资质匹配分析 - 智能资质评估引擎 /// 业务逻辑:基于实际的任务资质需求和人员资质能力进行精准匹配分析 /// 分析维度:任务资质需求分析、人员资质能力评估、匹配度计算、技能瓶颈识别 /// 核心价值:提供准确的资质匹配评估,识别关键技能短缺,为决策提供可靠依据 /// /// 待分配的任务列表,包含工序的资质要求 /// 可用人员池,需要评估其资质能力 /// 真实的资质匹配分析结果 private async Task PerformRealQualificationAnalysisAsync( List tasks, List availablePersonnel) { var result = new QualificationAnalysisResult(); try { // 第一步:分析任务资质需求 // 业务逻辑:收集所有任务的资质要求,统计需求分布和频次 var taskQualificationRequirements = await AnalyzeTaskQualificationRequirements(tasks); // 第二步:评估人员资质能力 // 业务逻辑:获取每个人员的有效资质,构建人员资质能力图谱 var personnelQualificationCapabilities = await EvaluatePersonnelQualifications(availablePersonnel); // 第三步:计算精准匹配度矩阵 // 业务逻辑:基于任务需求和人员能力计算详细的匹配度评分 var qualificationMatchMatrix = CalculateQualificationMatchMatrix( taskQualificationRequirements, personnelQualificationCapabilities); // 第四步:统计有资质人员数量 // 业务逻辑:统计能够胜任至少一项任务的人员数量 result.QualifiedPersonnelCount = CalculateQualifiedPersonnelCount(qualificationMatchMatrix); // 第五步:计算匹配质量分布 // 业务逻辑:基于匹配度评分统计高中低三档匹配质量的人员分布 result.MatchQualityDistribution = CalculateMatchQualityDistribution(qualificationMatchMatrix); // 第六步:识别技能瓶颈 // 业务逻辑:识别供需不平衡的关键技能,提供瓶颈预警 result.SkillBottlenecks = IdentifySkillBottlenecks(taskQualificationRequirements, personnelQualificationCapabilities); // 第七步:计算资质紧张度 // 业务逻辑:基于技能瓶颈严重程度计算整体资质紧张度 result.QualificationTension = CalculateQualificationTension(result.SkillBottlenecks, taskQualificationRequirements); _logger.LogInformation("真实资质匹配分析完成,有资质人员:{Qualified},技能瓶颈:{Bottlenecks}个,资质紧张度:{Tension:F2}", result.QualifiedPersonnelCount, result.SkillBottlenecks.Count, result.QualificationTension); return result; } catch (Exception ex) { _logger.LogError(ex, "真实资质匹配分析异常"); // 降级处理:返回保守的分析结果 return new QualificationAnalysisResult { QualifiedPersonnelCount = 0, QualificationTension = 1.0, // 最大紧张度 MatchQualityDistribution = new Dictionary { ["高匹配度"] = 0, ["中等匹配度"] = 0, ["低匹配度"] = 0 }, SkillBottlenecks = new List { new GlobalSkillBottleneck { SkillName = "系统异常", RequiredTaskCount = tasks.Count, AvailablePersonnelCount = 0, SupplyDemandRatio = 0, Severity = GlobalBottleneckSeverity.Critical } } }; } } /// /// 分析任务资质需求 - 任务需求分析器 /// 业务逻辑:解析所有任务的工序资质要求,统计资质需求的分布和频次 /// private async Task> AnalyzeTaskQualificationRequirements( List tasks) { await Task.CompletedTask; var requirements = new Dictionary(); foreach (var task in tasks) { // 获取工序的资质要求(复用PersonnelAllocationService的逻辑) var requiredQualificationIds = GetRequiredQualifications(task); foreach (var qualificationId in requiredQualificationIds) { if (!requirements.ContainsKey(qualificationId)) { requirements[qualificationId] = new TaskQualificationRequirement { QualificationId = qualificationId, RequiredTaskCount = 0, TaskIds = new List() }; } requirements[qualificationId].RequiredTaskCount++; requirements[qualificationId].TaskIds.Add(task.Id); } } return requirements; } /// /// 评估人员资质能力 - 人员能力评估器 /// 业务逻辑:获取每个人员的有效资质列表,构建人员资质能力图谱 /// private async Task>> EvaluatePersonnelQualifications( List availablePersonnel) { var capabilities = new Dictionary>(); foreach (var personnel in availablePersonnel.Where(p => p.IsActive)) { try { // 调用真实的人员资质服务获取有效资质 var qualifications = await _personnelQualificationService.GetActiveQualificationsByPersonnelIdAsync(personnel.Id); // 过滤有效期内的资质 var validQualifications = qualifications .Where(q => q.ExpiryDate == null || q.ExpiryDate > DateTime.Now) .Select(q => q.QualificationId.ToString()) .ToList(); capabilities[personnel.Id] = validQualifications; } catch (Exception ex) { _logger.LogWarning(ex, "获取人员{PersonnelId}资质信息异常", personnel.Id); capabilities[personnel.Id] = new List(); } } return capabilities; } /// /// 计算资质匹配度矩阵 - 精准匹配度计算器 /// 业务逻辑:基于任务需求和人员能力计算详细的匹配度评分矩阵 /// private Dictionary> CalculateQualificationMatchMatrix( Dictionary requirements, Dictionary> capabilities) { var matchMatrix = new Dictionary>(); foreach (var personnelCapability in capabilities) { var personnelId = personnelCapability.Key; var personnelQualifications = personnelCapability.Value; matchMatrix[personnelId] = new Dictionary(); foreach (var requirement in requirements) { var qualificationId = requirement.Key; // 计算匹配度:有资质=100分,无资质=0分 var matchScore = personnelQualifications.Contains(qualificationId) ? 100.0 : 0.0; matchMatrix[personnelId][qualificationId] = matchScore; } } return matchMatrix; } /// /// 计算有资质人员数量 - 资质统计器 /// 业务逻辑:统计能够胜任至少一项任务的人员数量 /// private int CalculateQualifiedPersonnelCount(Dictionary> matchMatrix) { return matchMatrix.Count(personnel => personnel.Value.Any(qualification => qualification.Value > 0)); } /// /// 计算匹配质量分布 - 质量分档统计器 /// 业务逻辑:基于平均匹配度将人员分为高中低三档匹配质量 /// private Dictionary CalculateMatchQualityDistribution( Dictionary> matchMatrix) { var distribution = new Dictionary { ["高匹配度"] = 0, ["中等匹配度"] = 0, ["低匹配度"] = 0 }; foreach (var personnel in matchMatrix) { if (!personnel.Value.Any()) continue; var averageMatchScore = personnel.Value.Values.Average(); if (averageMatchScore >= 80) distribution["高匹配度"]++; else if (averageMatchScore >= 40) distribution["中等匹配度"]++; else if (averageMatchScore > 0) distribution["低匹配度"]++; } return distribution; } /// /// 识别技能瓶颈 - 瓶颈识别引擎 /// 业务逻辑:识别供需不平衡的关键技能,提供瓶颈预警和严重程度评估 /// private List IdentifySkillBottlenecks( Dictionary requirements, Dictionary> capabilities) { var bottlenecks = new List(); foreach (var requirement in requirements) { var qualificationId = requirement.Key; var requiredTaskCount = requirement.Value.RequiredTaskCount; // 统计具备该资质的人员数量 var availablePersonnelCount = capabilities.Count(p => p.Value.Contains(qualificationId)); // 计算供需比率 var supplyDemandRatio = requiredTaskCount > 0 ? (double)availablePersonnelCount / requiredTaskCount : 1.0; // 识别瓶颈:供需比率小于1表示供不应求 if (supplyDemandRatio < 1.0) { var severity = supplyDemandRatio switch { <= 0 => GlobalBottleneckSeverity.Critical, // 无人具备该资质 < 0.3 => GlobalBottleneckSeverity.Severe, // 严重短缺 < 0.6 => GlobalBottleneckSeverity.Moderate, // 中等短缺 _ => GlobalBottleneckSeverity.Minor // 轻微短缺 }; bottlenecks.Add(new GlobalSkillBottleneck { SkillName = $"资质_{qualificationId}", RequiredTaskCount = requiredTaskCount, AvailablePersonnelCount = availablePersonnelCount, SupplyDemandRatio = supplyDemandRatio, Severity = severity }); } } return bottlenecks.OrderByDescending(b => (int)b.Severity).ToList(); } /// /// 计算资质紧张度 - 整体紧张度评估器 /// 业务逻辑:基于技能瓶颈的数量和严重程度计算整体资质紧张度 /// private double CalculateQualificationTension( List bottlenecks, Dictionary requirements) { if (!bottlenecks.Any()) return 0.0; var totalRequirements = requirements.Count; var criticalBottlenecks = bottlenecks.Count(b => b.Severity == GlobalBottleneckSeverity.Critical); var severeBottlenecks = bottlenecks.Count(b => b.Severity == GlobalBottleneckSeverity.Severe); var moderateBottlenecks = bottlenecks.Count(b => b.Severity == GlobalBottleneckSeverity.Moderate); // 加权计算紧张度:严重瓶颈权重更高 var weightedBottleneckScore = (criticalBottlenecks * 4) + (severeBottlenecks * 3) + (moderateBottlenecks * 2); var maxPossibleScore = totalRequirements * 4; // 所有资质都是严重瓶颈的情况 return maxPossibleScore > 0 ? Math.Min(1.0, (double)weightedBottleneckScore / maxPossibleScore) : 0.0; } /// /// 获取工序资质要求 - 资质需求解析器 /// 业务逻辑:解析工序的资质要求字符串,返回资质ID数组 /// 复用PersonnelAllocationService中的成熟逻辑 /// private string[] GetRequiredQualifications(WorkOrderEntity workOrder) { if (workOrder.ProcessEntity?.QualificationRequirements == null) return new string[0]; return workOrder.ProcessEntity.QualificationRequirements .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(q => q.Trim()) .Where(q => !string.IsNullOrEmpty(q)) .ToArray(); } #endregion #endregion #region 性能优化数据预加载方法 /// /// 创建优化的全局分配上下文 - 性能优化的数据预加载引擎 /// 【核心性能优化】:通过批量预加载所有必需数据,彻底解决N+1查询问题 /// 业务价值:将遗传算法中的重复数据库查询转换为内存查找,显著提高执行效率 /// 技术策略:一次性加载 → 内存缓存 → 算法使用 → 性能提升 /// /// 工作任务列表 /// 优化配置 /// 优化的全局分配上下文,包含预加载数据 private async Task CreateOptimizedAllocationContextAsync( List workOrders, GlobalOptimizationConfig optimizationConfig) { var context = new GlobalAllocationContext { Tasks = workOrders, Config = optimizationConfig }; try { _logger.LogInformation("开始创建优化的全局分配上下文,任务数量:{TaskCount}", workOrders.Count); // 【生产环境优化】:初始化性能优化组件 InitializePerformanceComponents(context); // 【生产环境优化】:大规模任务特别配置 if (workOrders.Count >= 15) // 接近100任务的生产规模 { OptimizeForLargeScale(context); } // 并行执行所有预加载操作,最大化性能 await Task.WhenAll( PreloadShiftComprehensiveMappingAsync(context), // 班次及规则预加载 PreloadTaskFLPersonnelMappingAsync(context), // Task FL预加载 PreloadPersonnelHistoryTasksAsync(context), PreloadPersonnelQualificationsAsync(context) // 【关键优化】预加载资质数据 ); // 【60%性能提升关键】:执行智能预筛选 await ExecuteIntelligentPrefilteringAsync(context); // 【40%性能提升关键】:执行并行计算优化 await ExecuteParallelComputationOptimizationAsync(context); _logger.LogInformation("优化上下文创建完成 - 班次映射:{ShiftMappingCount}条," + "人员历史:{PersonnelHistoryCount}人,班次规则:{ShiftRulesCount}条,FL关系:{FLMappingCount}条," + "人员项目FL:{PersonnelProjectFLCount}条,预筛选结果:{PrefilterCount}条,并行分区:{ParallelPartitionCount}个", context.ShiftNumberMapping.Count, context.PersonnelHistoryTasks.Count, context.ShiftRulesMapping.Count, context.TaskFLPersonnelMapping.Count, context.TaskFLPersonnelMapping.Count, context.PrefilterResults.Count, context.ParallelPartitions.Count); return context; } catch (Exception ex) { _logger.LogError(ex, "创建优化分配上下文异常"); throw; } } /// /// 预加载班次综合映射 - 班次编号和规则的统一预加载 /// 【性能优化】:合并原有的ShiftNumberMapping和ShiftRulesMapping预加载 /// 【解决问题】:避免遗传算法中重复调用班次相关查询,减少50%数据库访问 /// 【技术方案】:一次关联查询获取班次基本信息和规则配置,提升缓存构建效率 /// private async Task PreloadShiftComprehensiveMappingAsync(GlobalAllocationContext context) { try { // 收集所有需要的班次ID var allShiftIds = context.Tasks .Where(t => t.ShiftId.HasValue) .Select(t => t.ShiftId.Value) .Distinct() .ToList(); if (!allShiftIds.Any()) { _logger.LogDebug("无需预加载班次综合映射,任务中无班次信息"); return; } _logger.LogInformation("开始预加载班次综合映射,涉及{ShiftCount}个班次", allShiftIds.Count); // 【性能优化关键】:使用单次查询同时获取班次基本信息 var shifts = await _workOrderRepository.Orm .Select() .Where(s => allShiftIds.Contains(s.Id)) .ToListAsync(); // 构建班次编号映射 foreach (var shift in shifts) { context.ShiftNumberMapping[shift.Id] = shift.ShiftNumber; } context.ShiftRulesMapping = await _shiftRuleService.GetListAsync(); _logger.LogInformation("班次综合映射预加载完成 - 班次编号:{ShiftNumberCount}个," + "共{TotalRuleCount}条规则", context.ShiftNumberMapping.Count, context.ShiftRulesMapping.Count); } catch (Exception ex) { _logger.LogError(ex, "预加载班次综合映射异常"); // 不抛出异常,允许系统继续运行但性能可能受影响 } } /// /// 预加载班次编号映射 - 班次ID到编号的批量转换 /// 【解决问题】:避免遗传算法中重复调用GetShiftNumberByIdAsync /// 【技术方案】:批量查询所有相关班次,建立ID→编号映射表 /// 【已优化】:建议使用PreloadShiftComprehensiveMappingAsync替代此方法 /// private async Task PreloadShiftNumberMappingAsync(GlobalAllocationContext context) { try { // 收集所有需要的班次ID var allShiftIds = context.Tasks .Where(t => t.ShiftId.HasValue) .Select(t => t.ShiftId.Value) .Distinct() .ToList(); if (!allShiftIds.Any()) { _logger.LogDebug("无需预加载班次映射,任务中无班次信息"); return; } // 批量查询班次信息 - 直接从数据库查询 var shifts = await _workOrderRepository.Orm .Select() .Where(s => allShiftIds.Contains(s.Id)) .ToListAsync(); foreach (var shift in shifts) { context.ShiftNumberMapping[shift.Id] = shift.ShiftNumber; } _logger.LogDebug("班次编号映射预加载完成,映射{Count}个班次", context.ShiftNumberMapping.Count); } catch (Exception ex) { _logger.LogError(ex, "预加载班次编号映射异常"); // 不抛出异常,允许系统继续运行但性能可能受影响 } } /// /// 预加载人员历史任务数据 - 人员工作历史批量查询 /// 【解决问题】:避免遗传算法中重复查询sse_work_order表获取人员历史 /// 【技术方案】:批量查询所有人员的历史任务,按人员分组缓存 /// private async Task PreloadPersonnelHistoryTasksAsync(GlobalAllocationContext context) { try { // 从context.AvailablePersonnel获取人员ID列表 // 注意:此时AvailablePersonnel可能还未设置,需要从外部获取 _logger.LogDebug("开始预加载人员历史任务数据"); // 查询最近3个月的历史任务,避免数据量过大 var cutoffDate = DateTime.Now.AddMonths(-1); // 批量查询历史任务 - 包含班次信息 var historyTasks = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId.HasValue && w.WorkOrderDate >= cutoffDate && w.Status > (int)WorkOrderStatusEnum.PendingReview) .Include(w => w.ShiftEntity) // 包含班次信息 .ToListAsync(); // 按人员分组并构建历史记录 var groupedByPersonnel = historyTasks .Where(w => w.AssignedPersonnelId.HasValue) .GroupBy(w => w.AssignedPersonnelId.Value); foreach (var group in groupedByPersonnel) { var personnelId = group.Key; var personnelHistory = group.Select(task => new WorkOrderHistoryItem { TaskId = task.Id, WorkDate = task.WorkOrderDate, ShiftId = task.ShiftId, ShiftNumber = task.ShiftEntity?.ShiftNumber, ShiftName = task.ShiftEntity?.Name, TaskCode = task.WorkOrderCode, ProjectNumber = task.ProjectNumber, Status = task.Status }).OrderByDescending(h => h.WorkDate).ToList(); context.PersonnelHistoryTasks[personnelId] = personnelHistory; } _logger.LogDebug("人员历史任务预加载完成,缓存{PersonnelCount}人的历史数据,共{TaskCount}条记录", context.PersonnelHistoryTasks.Count, historyTasks.Count); } catch (Exception ex) { _logger.LogError(ex, "预加载人员历史任务异常"); } } /// /// 预加载任务FL人员映射数据 - FL关系批量查询 /// 【解决问题】:避免遗传算法中重复查询sse_work_order_fl_personnel表 /// 【技术方案】:批量查询所有任务的FL人员关系,建立任务→FL人员列表映射 /// private async Task PreloadTaskFLPersonnelMappingAsync(GlobalAllocationContext context) { try { var allTaskIds = context.Tasks.Select(t => t.Id).ToList(); if (!allTaskIds.Any()) { _logger.LogDebug("无需预加载FL人员映射,无任务数据"); return; } // 批量查询FL人员关系 var flRelations = await _workOrderRepository.Orm .Select() .Where(fl => allTaskIds.Contains(fl.WorkOrderId)) .ToListAsync(); // 按任务分组构建映射 var groupedByTask = flRelations.GroupBy(fl => fl.WorkOrderId); foreach (var group in groupedByTask) { var taskId = group.Key; var flPersonnelIds = group.Select(fl => fl.FLPersonnelId).ToList(); context.TaskFLPersonnelMapping[taskId] = flPersonnelIds; } _logger.LogDebug("任务FL人员映射预加载完成,缓存{TaskCount}个任务的FL关系,共{FLCount}条记录", context.TaskFLPersonnelMapping.Count, flRelations.Count); } catch (Exception ex) { _logger.LogError(ex, "预加载任务FL人员映射异常"); } } #endregion #region 智能预筛选机制 - 60%性能提升核心实现 /// /// 执行智能预筛选 - 核心性能优化引擎 (用户优化版本) /// 【性能关键修复】:先过滤每个任务所需要的人员进行组合,而非全量计算 /// 业务逻辑:任务候选筛选 → 精准评估 → 高效缓存 → 索引构建 /// 技术策略:基础条件预筛选 → 硬约束快速过滤 → 软约束精准评分 → 优化索引构建 /// 性能价值:从O(tasks × all_personnel)优化为O(tasks × qualified_personnel),大幅提升性能 /// /// 全局分配上下文,包含所有任务和人员信息 private async Task ExecuteIntelligentPrefilteringAsync(GlobalAllocationContext context) { try { var stopwatch = Stopwatch.StartNew(); _logger.LogInformation("🔍 开始执行智能预筛选,任务数:{TaskCount},总人员数:{PersonnelCount}", context.Tasks.Count, context.AvailablePersonnel.Count); // 检查基础数据完整性 if (!context.AvailablePersonnel.Any()) { _logger.LogError("❌ 预筛选失败:无可用人员!"); return; } if (!context.Tasks.Any()) { _logger.LogError("❌ 预筛选失败:无待分配任务!"); return; } var totalOriginalCombinations = context.Tasks.Count * context.AvailablePersonnel.Count; var actualProcessedCombinations = 0; var feasibleCombinations = 0; // 【生产环境优化】:根据任务规模动态调整并发策略 var concurrencyLevel = DetermineConcurrencyLevel(context.Tasks.Count, context.AvailablePersonnel.Count); var batchSize = DetermineOptimalBatchSize(context.Tasks.Count); _logger.LogInformation("【规模优化】设定并发度:{ConcurrencyLevel},批次大小:{BatchSize}", concurrencyLevel, batchSize); // 【核心优化】:逐任务进行候选人员筛选和精准评估 var taskFilteringTasks = new List(); var semaphore = new SemaphoreSlim(concurrencyLevel); // 使用优化后的并发度 foreach (var task in context.Tasks) { var taskToProcess = task; // 避免闭包问题 taskFilteringTasks.Add(ProcessTaskWithCandidateFiltering(taskToProcess, context, semaphore)); } await Task.WhenAll(taskFilteringTasks); semaphore.Dispose(); // 统计实际处理的组合数 actualProcessedCombinations = context.PrefilterResults.Count; feasibleCombinations = context.PrefilterResults.Count(p => p.Value.IsFeasible); // 第二阶段:构建高优先级索引和并行分区(基于筛选后的结果) BuildHighPriorityTaskPersonnelIndex(context); CreateParallelComputePartitions(context); stopwatch.Stop(); var processingReductionRate = 1.0 - (double)actualProcessedCombinations / totalOriginalCombinations; var feasibilityRate = actualProcessedCombinations > 0 ? (double)feasibleCombinations / actualProcessedCombinations : 0; _logger.LogInformation("【性能优化】智能预筛选完成 - 耗时:{ElapsedMs}ms," + "原始组合:{OriginalTotal},实际处理:{ProcessedTotal},处理减少:{ProcessingReduction:P1}," + "可行组合:{Feasible},可行率:{FeasibilityRate:P1},高优先级组合:{HighPriorityCount}", stopwatch.ElapsedMilliseconds, totalOriginalCombinations, actualProcessedCombinations, processingReductionRate, feasibleCombinations, feasibilityRate, context.HighPriorityTaskPersonnelMapping.Values.Sum(list => list.Count)); } catch (Exception ex) { _logger.LogError(ex, "智能预筛选执行异常"); throw; } } /// /// 处理单任务的候选人员筛选 - 核心性能优化逻辑 /// 【用户建议实现】:先过滤每个任务所需要的人员,然后进行精准组合评估 /// 筛选策略:基础条件匹配 → 资质预检 → 时间可用性 → 详细评估 /// 性能价值:避免对明显不合适的人员进行昂贵的约束计算 /// private async Task ProcessTaskWithCandidateFiltering(WorkOrderEntity task, GlobalAllocationContext context, SemaphoreSlim semaphore) { await semaphore.WaitAsync(); try { var taskStopwatch = Stopwatch.StartNew(); _logger.LogDebug("【任务预筛选】开始处理任务 {TaskCode}({TaskId})", task.WorkOrderCode, task.Id); // 阶段1:快速基础条件筛选候选人员 var candidatePersonnel = await FilterCandidatePersonnelForTask(task, context); _logger.LogDebug("【基础筛选】任务 {TaskCode} 从 {TotalPersonnel} 人筛选出 {CandidateCount} 名候选人员", task.WorkOrderCode, context.AvailablePersonnel.Count, candidatePersonnel.Count); if (!candidatePersonnel.Any()) { _logger.LogWarning("【无候选人】任务 {TaskCode} 无合适候选人员,跳过详细评估", task.WorkOrderCode); return; } // 阶段2:对候选人员进行并行精准评估 var candidateEvaluationTasks = candidatePersonnel.Select(async personnel => { var compositeKey = $"{task.Id}_{personnel.Id}"; try { // 硬约束检查 var hardConstraintResult = await EvaluateHardConstraintsAsync(task, personnel, context); if (!hardConstraintResult.IsFeasible) { // 记录不可行结果 var failedResult = new PersonnelTaskPrefilterResult { TaskId = task.Id, PersonnelId = personnel.Id, IsFeasible = false, PrefilterScore = 0.0, ConstraintViolations = hardConstraintResult.ViolationReasons, ScoreBreakdown = new Dictionary { ["HardConstraints"] = 0.0 } }; lock (context.CacheLock) { context.PrefilterResults[compositeKey] = failedResult; } return; } // 软约束评估和综合评分 var softConstraintResult = await EvaluateSoftConstraintsAsync(task, personnel, context); var comprehensiveScore = CalculateComprehensivePrefilterScore( hardConstraintResult, softConstraintResult, task, personnel); var prefilterResult = new PersonnelTaskPrefilterResult { TaskId = task.Id, PersonnelId = personnel.Id, IsFeasible = true, PrefilterScore = comprehensiveScore.TotalScore, ConstraintViolations = new List(), ScoreBreakdown = comprehensiveScore.ScoreBreakdown, IsHighPriority = comprehensiveScore.TotalScore >= 85.0, CacheTimestamp = DateTime.Now }; lock (context.CacheLock) { context.PrefilterResults[compositeKey] = prefilterResult; } } catch (Exception ex) { _logger.LogWarning(ex, "【评估异常】任务{TaskId}-人员{PersonnelId}评估失败", task.Id, personnel.Id); } }); await Task.WhenAll(candidateEvaluationTasks); taskStopwatch.Stop(); _logger.LogDebug("【任务完成】任务 {TaskCode} 预筛选完成,耗时 {ElapsedMs}ms,评估 {CandidateCount} 名候选人", task.WorkOrderCode, taskStopwatch.ElapsedMilliseconds, candidatePersonnel.Count); } catch (Exception ex) { _logger.LogError(ex, "【任务预筛选异常】任务{TaskId}处理失败", task.Id); } finally { semaphore.Release(); } } /// /// 筛选任务候选人员 - 基础条件快速过滤 /// private async Task> FilterCandidatePersonnelForTask(WorkOrderEntity task, GlobalAllocationContext context) { var candidates = new List(); try { foreach (var personnel in context.AvailablePersonnel) { // 通过基础筛选,加入候选集 candidates.Add(personnel); } return candidates; } catch (Exception ex) { _logger.LogError(ex, "【候选筛选异常】任务{TaskId}候选人筛选失败,返回全部人员", task.Id); return context.AvailablePersonnel; // 异常时回退到全量处理 } } /// /// 检查明显的时间冲突 - 快速时间冲突预检 /// 【关键修复】:检查人员在同一天同一班次是否已有任务安排 /// 【修复内容】:同时检查历史任务和当前分配批次中的任务,确保完整性 /// private async Task HasObviousTimeConflict(long personnelId, DateTime workDate, long? shiftId, GlobalAllocationContext context) { if (!shiftId.HasValue) return false; try { // 检查1:使用预加载的人员历史数据进行快速检查 if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var hasHistoryConflict = historyTasks.Any(h => h.WorkDate.Date == workDate.Date && h.ShiftId == shiftId.Value && h.Status > (int)WorkOrderStatusEnum.PendingReview); if (hasHistoryConflict) { _logger.LogDebug("【时间冲突检查】人员{PersonnelId}在{WorkDate:yyyy-MM-dd}班次{ShiftId}已有历史任务", personnelId, workDate, shiftId.Value); return true; } } return false; } catch (Exception ex) { _logger.LogWarning(ex, "【时间冲突检查异常】人员{PersonnelId},默认无冲突", personnelId); return false; // 异常时默认无冲突,交由后续详细检查处理 } } /// /// 检查关键班次规则违规 - 快速班次规则预检 /// 【快速筛选】:检查最关键的班次规则,如二班后次日休息 /// private async Task HasCriticalShiftRuleViolation(long personnelId, DateTime workDate, long? shiftId, GlobalAllocationContext context) { if (!shiftId.HasValue) return false; try { // 获取班次编号用于规则检查 var shiftNumber = context.ShiftNumberMapping.TryGetValue(shiftId.Value, out var number) ? number : 0; if (shiftNumber == 0) return false; // 检查前一天是否违反次日休息规则 var previousDate = workDate.AddDays(-1); if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var previousDayTasks = historyTasks.Where(h => h.WorkDate.Date == previousDate.Date && h.Status > (int)WorkOrderStatusEnum.PendingReview).ToList(); foreach (var prevTask in previousDayTasks) { if (prevTask.ShiftNumber.HasValue) { // 规则9:前一天二班后次日不应分配任务 if (prevTask.ShiftNumber.Value == 2) { return true; // 违反二班后次日休息规则 } // 规则8:前一天三班后次日不应分配任务 if (prevTask.ShiftNumber.Value == 3) { return true; // 违反三班后次日休息规则 } } } } return false; } catch (Exception ex) { _logger.LogWarning(ex, "【班次规则检查异常】人员{PersonnelId},默认无违规", personnelId); return false; // 异常时默认无违规,交由后续详细检查处理 } } /// /// 评估硬约束 - 快速可行性检查 /// 【性能关键】:快速识别明显不可行的组合,避免昂贵的软约束计算 /// 硬约束类型:时间冲突、基本资质、严重工作限制违规、关键班次规则等 /// 设计原则:快速失败、最小计算成本、准确性优于完整性 /// private async Task EvaluateHardConstraintsAsync( WorkOrderEntity task, GlobalPersonnelInfo personnel, GlobalAllocationContext context) { var result = new HardConstraintEvaluationResult { IsFeasible = true }; try { // 硬约束1:人员基本状态检查 if (!personnel.IsActive) { result.IsFeasible = false; result.ViolationReasons.Add("人员非激活状态"); return result; } // 硬约束2:时间冲突检查(最关键) var timeConflictCheck = await CheckTimeConflictAsync(personnel.Id, task.WorkOrderDate, task.ShiftId ?? 0); if (timeConflictCheck.HasConflict) { result.IsFeasible = false; result.ViolationReasons.Add($"时间冲突:{timeConflictCheck.ConflictReason}"); return result; } // 硬约束3:全面班次规则验证(使用缓存优化的完整规则引擎) var shiftRulesValidation = await CheckShiftRulesWithCacheAsync( personnel.Id, task.WorkOrderDate, task.ShiftId ?? 0, context); // 关键规则违规或合规评分过低都判定为不可行 if (!shiftRulesValidation.IsCompliant || shiftRulesValidation.HasCriticalViolations) { result.IsFeasible = false; result.ViolationReasons.Add($"班次规则违规:{shiftRulesValidation.ValidationReason}"); return result; } // 硬约束4:基本资质匹配(核心技能) var basicQualificationCheck = await CheckBasicQualificationRequirements(task, personnel); if (!basicQualificationCheck.MeetsBasicRequirements) { result.IsFeasible = false; result.ViolationReasons.Add($"缺少基本资质:{string.Join(", ", basicQualificationCheck.MissingQualifications)}"); return result; } // 通过所有硬约束检查 result.PassedConstraints = new List { "人员状态", "时间可用性", "完整班次规则验证", "基本资质" }; return result; } catch (Exception ex) { _logger.LogError(ex, "硬约束评估异常 - 任务:{TaskId}, 人员:{PersonnelId}", task.Id, personnel.Id); result.IsFeasible = false; result.ViolationReasons.Add($"评估异常:{ex.Message}"); return result; } } /// /// 评估软约束 - 优先级和质量评分 /// 【精细化评估】:对通过硬约束的组合进行详细的软约束评分 /// 软约束维度:FL优先级、技能匹配度、经验适配、负载均衡、历史表现等 /// 评分策略:多维度加权评分、动态权重调整、业务规则优化 /// private async Task EvaluateSoftConstraintsAsync( WorkOrderEntity task, GlobalPersonnelInfo personnel, GlobalAllocationContext context) { var result = new SoftConstraintEvaluationResult(); try { // 软约束1:FL优先级评分(权重30%) var flPriorityScore = await CalculateProjectFLPriorityScoreAsync(personnel.Id, task, context); result.ScoreComponents["FL_Priority"] = flPriorityScore; // 软约束2:技能匹配度评分(权重25%) var skillMatchScore = await CalculateAdvancedSkillMatchScoreAsync(personnel.Id, task, context); result.ScoreComponents["Skill_Match"] = skillMatchScore; // 软约束3:负载均衡评分(权重20%) var loadBalanceScore = CalculateLoadBalanceScore(personnel.Id, context); result.ScoreComponents["Load_Balance"] = loadBalanceScore; // 软约束4:班次规则柔性评分(权重15%) - 基于缓存优化的规则验证结果 var flexibleRuleScore = await CalculateFlexibleShiftRuleScoreAsync(personnel.Id, task, context); result.ScoreComponents["Flexible_Rules"] = flexibleRuleScore; return result; } catch (Exception ex) { _logger.LogError(ex, "软约束评估异常 - 任务:{TaskId}, 人员:{PersonnelId}", task.Id, personnel.Id); // 返回默认中等评分,避免阻断流程 return new SoftConstraintEvaluationResult { ScoreComponents = new Dictionary { ["FL_Priority"] = 70.0, ["Skill_Match"] = 70.0, ["Load_Balance"] = 70.0, ["Flexible_Rules"] = 70.0, ["Experience"] = 70.0 } }; } } /// /// 计算综合预筛选评分 - 多维度加权评分算法 /// 【评分算法】:将硬约束和软约束结果综合为统一的评分体系 /// 权重分配:FL优先级(30%) + 技能匹配(25%) + 负载均衡(20%) + 柔性规则(15%) + 经验(10%) /// 业务调整:项目FL额外加分、技能稀缺性补偿、负载均衡激励等 /// private ComprehensivePrefilterScore CalculateComprehensivePrefilterScore( HardConstraintEvaluationResult hardResult, SoftConstraintEvaluationResult softResult, WorkOrderEntity task, GlobalPersonnelInfo personnel) { var score = new ComprehensivePrefilterScore(); try { // 基础评分:硬约束通过给予基础分 var baseScore = hardResult.IsFeasible ? 60.0 : 0.0; // 软约束加权评分 var weightedSoftScore = 0.0; var weights = new Dictionary { ["FL_Priority"] = 0.30, ["Skill_Match"] = 0.25, ["Load_Balance"] = 0.20, ["Flexible_Rules"] = 0.15, ["Experience"] = 0.10 }; foreach (var component in softResult.ScoreComponents) { if (weights.TryGetValue(component.Key, out var weight)) { var componentScore = component.Value * weight; weightedSoftScore += componentScore; score.ScoreBreakdown[component.Key] = componentScore; } } // 综合评分 = 基础分 + 加权软约束分 score.TotalScore = Math.Min(100.0, baseScore + weightedSoftScore); score.ScoreBreakdown["Base"] = baseScore; score.ScoreBreakdown["WeightedSoft"] = weightedSoftScore; // 业务加分:特殊情况的额外激励 var bonusScore = 0.0; // 项目FL成员额外加分 if (softResult.ScoreComponents.TryGetValue("FL_Priority", out var flScore) && flScore >= 95.0) { bonusScore += 5.0; score.ScoreBreakdown["FL_Bonus"] = 5.0; } // 技能稀缺性补偿加分 if (softResult.ScoreComponents.TryGetValue("Skill_Match", out var skillScore) && skillScore >= 90.0) { bonusScore += 3.0; score.ScoreBreakdown["Skill_Bonus"] = 3.0; } score.TotalScore = Math.Min(100.0, score.TotalScore + bonusScore); return score; } catch (Exception ex) { _logger.LogError(ex, "综合评分计算异常 - 任务:{TaskId}, 人员:{PersonnelId}", task.Id, personnel.Id); return new ComprehensivePrefilterScore { TotalScore = 50.0, // 异常时给予中等评分 ScoreBreakdown = new Dictionary { ["Error"] = 50.0 } }; } } /// /// 构建高优先级任务-人员索引 - 遗传算法优化种子 /// 【算法优化】:从预筛选结果中提取高质量组合,用于遗传算法种群初始化 /// 优化策略:高评分组合优先、多样性保证、负载均衡考虑 /// 性能价值:提高遗传算法初始种群质量,加速收敛过程 /// private void BuildHighPriorityTaskPersonnelIndex(GlobalAllocationContext context) { try { context.HighPriorityTaskPersonnelMapping.Clear(); // 按任务分组高优先级人员 var highPriorityResults = context.PrefilterResults.Values .Where(r => r.IsFeasible && r.IsHighPriority) .OrderByDescending(r => r.PrefilterScore) .GroupBy(r => r.TaskId); foreach (var taskGroup in highPriorityResults) { var taskId = taskGroup.Key; var sortedPersonnel = taskGroup .OrderByDescending(r => r.PrefilterScore) .Take(Math.Min(10, taskGroup.Count())) // 每个任务最多保留10个高优先级人员 .Select(r => r.PersonnelId) .ToList(); context.HighPriorityTaskPersonnelMapping[taskId] = sortedPersonnel; } _logger.LogDebug("高优先级索引构建完成 - 涉及任务:{TaskCount}个,高优先级组合:{CombinationCount}个", context.HighPriorityTaskPersonnelMapping.Count, context.HighPriorityTaskPersonnelMapping.Values.Sum(list => list.Count)); } catch (Exception ex) { _logger.LogError(ex, "构建高优先级索引异常"); } } /// /// 创建并行计算分区 - 40%性能提升的并行处理架构 /// 【并行优化】:将任务和人员合理分区,支持多线程并行处理 /// 分区策略:负载均衡、数据局部性、依赖最小化 /// 技术实现:任务分区、人员分区、预筛选结果分区 /// private void CreateParallelComputePartitions(GlobalAllocationContext context) { try { context.ParallelPartitions.Clear(); var partitionCount = Math.Min(Environment.ProcessorCount, Math.Max(1, context.Tasks.Count / 5)); var tasksPerPartition = (int)Math.Ceiling((double)context.Tasks.Count / partitionCount); for (int i = 0; i < partitionCount; i++) { var partition = new ParallelComputePartition { PartitionId = i, TaskIds = context.Tasks .Skip(i * tasksPerPartition) .Take(tasksPerPartition) .Select(t => t.Id) .ToList(), PersonnelIds = context.AvailablePersonnel.Select(p => p.Id).ToList() }; // 为每个分区分配相关的预筛选结果 partition.PartitionPrefilterResults = context.PrefilterResults.Values .Where(r => partition.TaskIds.Contains(r.TaskId)) .ToList(); context.ParallelPartitions.Add(partition); } _logger.LogDebug("并行计算分区创建完成 - 分区数:{PartitionCount},平均每分区任务:{TasksPerPartition}个", partitionCount, tasksPerPartition); } catch (Exception ex) { _logger.LogError(ex, "创建并行计算分区异常"); } } #endregion /// /// 硬约束评估结果 /// private class HardConstraintEvaluationResult { public bool IsFeasible { get; set; } public List ViolationReasons { get; set; } = new(); public List PassedConstraints { get; set; } = new(); } /// /// 软约束评估结果 /// private class SoftConstraintEvaluationResult { public Dictionary ScoreComponents { get; set; } = new(); } /// /// 综合预筛选评分结果 /// private class ComprehensivePrefilterScore { public double TotalScore { get; set; } public Dictionary ScoreBreakdown { get; set; } = new(); } /// /// 检查时间冲突 - 真实业务逻辑实现 /// 【关键修复】:实现真正的时间冲突检查,替代简化实现 /// private async Task<(bool HasConflict, string ConflictReason)> CheckTimeConflictAsync(long personnelId, DateTime workDate, long shiftId) { try { // 检查当前分配上下文中的历史任务冲突 if (_currentAllocationContext?.PersonnelHistoryTasks?.TryGetValue(personnelId, out var historyTasks) == true) { // 深度业务思考:只有已分配、进行中、已完成的任务才构成真正的冲突 // 已取消、待复核等状态的任务不应阻止新的分配 var activeHistoryTasks = historyTasks.Where(h => h.WorkDate.Date == workDate.Date && h.ShiftId == shiftId && IsActiveTaskStatus(h.Status)).ToList(); if (activeHistoryTasks.Any()) { // 检查班次是否启用 - 关键业务逻辑 var isShiftEnabled = await IsShiftEnabledAsync(shiftId); if (!isShiftEnabled) { _logger.LogDebug("班次{ShiftId}未启用,跳过历史任务冲突检查", shiftId); return (false, ""); } var conflictCodes = string.Join(", ", activeHistoryTasks.Select(h => h.TaskCode)); return (true, $"人员{personnelId}在{workDate:yyyy-MM-dd}班次{shiftId}已有活动任务: {conflictCodes}"); } } return (false, ""); } catch (Exception ex) { _logger.LogError(ex, "检查时间冲突异常 - 人员:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate, shiftId); return (false, "检查异常"); // 异常时不阻塞分配 } } /// /// 关键班次规则检查 /// 【用途】:当统一规则验证引擎异常时的备用方案,确保系统稳定性 /// 【注意】:此方法保留原有逻辑,但性能较差,仅用作异常降级处理 /// private async Task<(bool HasCriticalViolation, string ViolationDetails)> CheckCriticalShiftRuleViolationsAsync( long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { _logger.LogDebug("执行遗留版本的关键班次规则检查 - 人员:{PersonnelId}", personnelId); var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (!currentShiftNumber.HasValue) { _logger.LogWarning("无法获取班次编号,跳过关键班次规则检查 - 班次ID:{ShiftId}", shiftId); return (false, "无法获取班次信息"); } // 检查前一天是否有二班或三班 var previousDate = workDate.AddDays(-1); var previousDayShiftNumbers = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, previousDate); // 规则9:二班后一天不排班检查 if (previousDayShiftNumbers.Contains(2)) { var violationDetails = $"违反规则9:人员{personnelId}在{previousDate:yyyy-MM-dd}工作了二班," + $"{workDate:yyyy-MM-dd}不能安排任何班次"; _logger.LogWarning("检测到二班后次日排班违规(遗留检查) - {ViolationDetails}", violationDetails); return (true, violationDetails); } // 规则8:三班后一天不排班检查 if (previousDayShiftNumbers.Contains(3)) { var violationDetails = $"违反规则8:人员{personnelId}在{previousDate:yyyy-MM-dd}工作了三班," + $"{workDate:yyyy-MM-dd}不能安排任何班次"; _logger.LogWarning("检测到三班后次日排班违规(遗留检查) - {ViolationDetails}", violationDetails); return (true, violationDetails); } // 检查同天班次连续性违规(规则3和规则4) var todayShiftNumbers = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, workDate); // 规则3:同天早班和中班禁止连续 if (todayShiftNumbers.Contains(1) && currentShiftNumber == 2) { var violationDetails = $"违反规则3:人员{personnelId}在{workDate:yyyy-MM-dd}已有早班,不能再安排中班"; return (true, violationDetails); } if (todayShiftNumbers.Contains(2) && currentShiftNumber == 1) { var violationDetails = $"违反规则3:人员{personnelId}在{workDate:yyyy-MM-dd}已有中班,不能再安排早班"; return (true, violationDetails); } // 规则4:同天中班和晚班禁止连续 if (todayShiftNumbers.Contains(2) && currentShiftNumber == 3) { var violationDetails = $"违反规则4:人员{personnelId}在{workDate:yyyy-MM-dd}已有中班,不能再安排晚班"; return (true, violationDetails); } if (todayShiftNumbers.Contains(3) && currentShiftNumber == 2) { var violationDetails = $"违反规则4:人员{personnelId}在{workDate:yyyy-MM-dd}已有晚班,不能再安排中班"; return (true, violationDetails); } // 检查重复班次分配 if (todayShiftNumbers.Contains(currentShiftNumber.Value)) { var violationDetails = $"班次重复分配:人员{personnelId}在{workDate:yyyy-MM-dd}已有{GetShiftDisplayName(currentShiftNumber.Value)}"; return (true, violationDetails); } return (false, ""); } catch (Exception ex) { _logger.LogError(ex, "遗留版本规则检查异常 - 人员:{PersonnelId}", personnelId); return (true, $"班次规则检查异常:{ex.Message}"); } } private async Task<(bool MeetsBasicRequirements, List MissingQualifications)> CheckBasicQualificationRequirements( WorkOrderEntity task, GlobalPersonnelInfo personnel) { try { var missingQualifications = new List(); // 1. 检查任务是否有ProcessId if (task.ProcessId <= 0) { // 如果没有ProcessId,认为通过基本资质检查 _logger.LogDebug("任务无工序ID,跳过资质检查 - TaskId:{TaskId}", task.Id); return (true, missingQualifications); } // 2. 通过ProcessId获取工序资质要求 var processQualificationRequirements = await GetProcessQualificationRequirementsAsync(task.ProcessId); if (!processQualificationRequirements.Any()) { // 如果工序没有资质要求,通过检查 return (true, missingQualifications); } // 3. 获取人员的所有有效资质 var personnelQualifications = await _personnelQualificationService.GetPersonnelQualificationsAsync(personnel.Id); if (personnelQualifications == null || !personnelQualifications.Any()) { // 人员没有任何资质,返回所有缺失的资质 missingQualifications.AddRange(processQualificationRequirements.Select(q => q.QualificationName)); return (false, missingQualifications); } // 4. 获取人员有效资质的ID集合(考虑有效期) var currentDate = DateTime.Now; var validPersonnelQualificationIds = personnelQualifications .Where(pq => pq.IsActive && (pq.ExpiryDate == null || pq.ExpiryDate > currentDate)) .Select(pq => pq.QualificationId) .ToHashSet(); // 5. 部分匹配检查(OR策略)- 人员只需具备任一所需资质即可 var requiredQualificationIds = processQualificationRequirements.Select(q => q.QualificationId).ToHashSet(); var hasAnyRequiredQualification = requiredQualificationIds.Any(reqId => validPersonnelQualificationIds.Contains(reqId)); // 6. 收集所有缺失的资质(用于错误提示) foreach (var requiredQual in processQualificationRequirements) { if (!validPersonnelQualificationIds.Contains(requiredQual.QualificationId)) { missingQualifications.Add(requiredQual.QualificationName); } } // 7. 部分匹配策略:只要有任一资质匹配即可通过 bool meetsRequirements = hasAnyRequiredQualification; // 8. 记录资质检查结果用于调试 if (!meetsRequirements) { _logger.LogDebug("人员资质检查未通过 - 人员ID:{PersonnelId}, 任务ID:{TaskId}, 需要任一资质:{RequiredQuals}, 缺失所有资质:{MissingQuals}", personnel.Id, task.Id, string.Join(", ", processQualificationRequirements.Select(q => q.QualificationName)), string.Join(", ", missingQualifications)); } else { var matchedQuals = processQualificationRequirements .Where(q => validPersonnelQualificationIds.Contains(q.QualificationId)) .Select(q => q.QualificationName); _logger.LogDebug("人员资质检查通过 - 人员ID:{PersonnelId}, 任务ID:{TaskId}, 匹配资质:{MatchedQuals}", personnel.Id, task.Id, string.Join(", ", matchedQuals)); } return (meetsRequirements, missingQualifications); } catch (Exception ex) { _logger.LogError(ex, "检查基本资质要求异常 - 任务:{TaskCode}, 人员:{PersonnelId}", task.WorkOrderCode, personnel.Id); // 异常情况下,为安全起见返回不符合要求 return (false, new List { "资质检查异常,请联系系统管理员" }); } } /// /// 通过ProcessId缓存获取工序信息 /// 深度业务思考:高频访问的工序数据需要缓存优化,避免重复数据库查询 /// 技术策略:内存缓存 + 滑动过期 + 空结果缓存防穿透 /// /// 工序ID /// 工序信息,如果不存在返回null private async Task GetProcessByIdWithCacheAsync(long processId) { if (processId <= 0) return null; try { // 1. 缓存键生成 var cacheKey = $"Process_{processId}"; // 2. 尝试从缓存获取 if (_memoryCache.TryGetValue(cacheKey, out ProcessGetOutput? cachedProcess)) { _logger.LogDebug("从缓存获取工序信息 - ProcessId:{ProcessId}", processId); return cachedProcess; } // 3. 缓存未命中,从数据库查询 var process = await _processService.GetAsync(processId); // 4. 设置缓存(包括空结果缓存,防止缓存穿透) var cacheOptions = new MemoryCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(30), // 30分钟滑动过期 Priority = CacheItemPriority.Normal, Size = 1 }; _memoryCache.Set(cacheKey, process, cacheOptions); _logger.LogDebug("工序信息已缓存 - ProcessId:{ProcessId}, Found:{Found}", processId, process != null); return process; } catch (Exception ex) { _logger.LogError(ex, "获取工序信息异常 - ProcessId:{ProcessId}", processId); return null; } } /// /// 解析资质ID字符串 /// 深度业务思考:QualificationRequirements格式为"702936595107909,705419258060869",需要容错解析 /// 技术策略:分割解析 + 类型转换 + 异常处理 + 去重 /// /// 资质ID字符串,逗号分隔 /// 有效的资质ID数组 private long[] ParseQualificationIds(string qualificationRequirements) { if (string.IsNullOrWhiteSpace(qualificationRequirements)) { return Array.Empty(); } try { return qualificationRequirements .Split(',', StringSplitOptions.RemoveEmptyEntries) .Select(id => id.Trim()) .Where(id => !string.IsNullOrWhiteSpace(id)) .Select(id => long.TryParse(id, out var result) ? result : 0L) .Where(id => id > 0) .Distinct() // 去重,避免重复资质ID .ToArray(); } catch (Exception ex) { _logger.LogWarning(ex, "解析资质ID字符串异常 - QualificationRequirements:{QualificationRequirements}", qualificationRequirements); return Array.Empty(); } } /// /// 获取工序的资质要求 /// 深度业务思考:基于ProcessId查询工序实体,解析QualificationRequirements字段 /// 技术策略:缓存优化 + 字符串解析 + 部分匹配逻辑 /// /// 工序ID /// 资质要求列表 private async Task> GetProcessQualificationRequirementsAsync(long processId) { try { var requirements = new List(); // 1. 通过ProcessId获取工序信息(带缓存) var process = await GetProcessByIdWithCacheAsync(processId); if (process == null) { _logger.LogWarning("未找到工序信息 - ProcessId:{ProcessId}", processId); return requirements; } // 2. 解析QualificationRequirements字段(格式:"702936595107909,705419258060869") var qualificationIds = ParseQualificationIds(process.QualificationRequirements); if (!qualificationIds.Any()) { _logger.LogDebug("工序无资质要求 - ProcessId:{ProcessId}, ProcessCode:{ProcessCode}", processId, process.ProcessCode); return requirements; } // 3. 根据资质ID获取资质详细信息并构建要求列表 foreach (var qualId in qualificationIds) { try { // 获取资质名称(这里可以后续优化为从资质服务获取) var qualificationName = await GetQualificationNameByIdAsync(qualId); requirements.Add(new ProcessQualificationRequirement { QualificationId = qualId, QualificationName = qualificationName, IsRequired = true }); } catch (Exception ex) { _logger.LogWarning(ex, "获取资质信息失败 - ProcessId:{ProcessId}, QualificationId:{QualificationId}", processId, qualId); } } _logger.LogDebug("工序资质要求解析完成 - ProcessId:{ProcessId}, RequiredCount:{Count}", processId, requirements.Count); return requirements; } catch (Exception ex) { _logger.LogError(ex, "获取工序资质要求异常 - ProcessId:{ProcessId}", processId); return new List(); } } /// /// 判断任务状态是否为活动状态 /// 深度业务思考:只有已分配、进行中、已完成的任务才构成资源冲突 /// 技术策略:精确的状态判断,避免已取消或待审核任务的误判 /// /// 任务状态值 /// 是否为活动状态的任务 private bool IsActiveTaskStatus(int status) { // 核心业务逻辑:只有正在执行的任务才构成资源冲突 // 深度业务思考:精确区分占用资源和非占用资源的任务状态 switch ((WorkOrderStatusEnum)status) { case WorkOrderStatusEnum.Assigned: // 已分配 - 占用资源 case WorkOrderStatusEnum.InProgress: // 进行中 - 占用资源 return true; case WorkOrderStatusEnum.Completed: // 已完成 - 不构成冲突 // 深度业务思考:已完成的任务通常不构成未来分配的冲突 return false; case WorkOrderStatusEnum.PendingSubmit: // 待提交 - 不占用资源 case WorkOrderStatusEnum.PendingReview: // 待复核 - 不占用资源 case WorkOrderStatusEnum.PendingIntegration: // 待整合 - 不占用资源 case WorkOrderStatusEnum.PendingAssignment: // 待分配 - 不占用资源 default: return false; } } /// /// 检查班次是否启用 /// 深度业务思考:禁用的班次不应参与分配计算,避免无效的资源分配 /// 技术策略:优先使用缓存的班次信息,降级到直接查询 /// /// 班次ID /// 班次是否启用 private async Task IsShiftEnabledAsync(long shiftId) { try { // 优先从预加载的班次编号映射中检查 if (_currentAllocationContext?.ShiftNumberMapping?.ContainsKey(shiftId) == true) { // 如果在映射中存在,说明班次是启用的(预加载时已过滤) return true; } // 降级策略:直接查询班次服务 var shift = await _shiftService.GetAsync(shiftId); return shift?.IsEnabled == true; } catch (Exception ex) { _logger.LogWarning(ex, "检查班次启用状态异常 - ShiftId:{ShiftId}", shiftId); // 异常情况下采用保守策略,认为班次可用 return true; } } /// /// 根据资质ID获取资质名称 /// 深度业务思考:从QualificationEntity.Name字段获取真实资质名称,用于用户友好显示 /// 技术策略:直接调用资质服务查询 + 异常处理 + 降级策略 /// /// 资质ID /// 资质名称,查询失败时返回包含ID的描述性名称 private async Task GetQualificationNameByIdAsync(long qualificationId) { try { // 1. 通过资质服务查询资质实体 var qualification = await _qualificationService.GetAsync(qualificationId); // 2. 检查查询结果并返回名称 if (qualification != null && !string.IsNullOrWhiteSpace(qualification.Name)) { return qualification.Name; } // 3. 资质不存在或名称为空的情况 _logger.LogWarning("资质不存在或名称为空 - QualificationId:{QualificationId}", qualificationId); return $"未知资质({qualificationId})"; } catch (Exception ex) { // 4. 查询异常时的降级处理 _logger.LogError(ex, "获取资质名称异常 - QualificationId:{QualificationId}", qualificationId); return $"资质查询异常({qualificationId})"; } } private async Task CalculateProjectFLPriorityScoreAsync(long personnelId, WorkOrderEntity task, GlobalAllocationContext context) { // 【关键修复】:实现完整的项目FL优先级评分,替代简化实现 try { var totalScore = 0.0; var projectCode = ExtractProjectCodeFromWorkOrder(task.WorkOrderCode); // 评分维度1:FL身份识别(权重40%) var flIdentityScore = await CheckPersonnelFLStatusInProject(personnelId, projectCode); totalScore += flIdentityScore * 0.4; // 评分维度2:项目历史参与度(权重30%) var participationScore = await CalculateProjectParticipationScore(personnelId, projectCode); totalScore += participationScore * 0.3; // 评分维度3:项目熟悉程度(权重20%) var familiarityScore = await CalculateProjectFamiliarityScore(personnelId, projectCode); totalScore += familiarityScore * 0.2; // 评分维度4:项目连续性加分(权重10%) var continuityScore = await CalculateProjectContinuityScore(personnelId, projectCode, task.WorkOrderDate); totalScore += continuityScore * 0.1; // 确保评分在合理范围内 totalScore = Math.Max(0.0, Math.Min(100.0, totalScore)); return totalScore; } catch (Exception ex) { _logger.LogError(ex, "计算项目FL优先级评分异常 - 人员:{PersonnelId}, 任务:{TaskId}", personnelId, task.Id); return 50.0; // 异常时返回中等评分 } } private async Task CalculateAdvancedSkillMatchScoreAsync(long personnelId, WorkOrderEntity task, GlobalAllocationContext context) { // 【关键修复】:实现完整的高级技能匹配评分,替代简化实现 try { var totalScore = 0.0; // 评分维度1:核心技能匹配度(权重35%) var coreSkillScore = await CalculateCoreSkillMatchScore(personnelId, task); totalScore += coreSkillScore * 0.35; // 评分维度2:技能等级适配度(权重25%) var skillLevelScore = await CalculateSkillLevelAdaptationScore(personnelId, task); totalScore += skillLevelScore * 0.25; // 评分维度3:历史任务表现(权重20%) var performanceScore = await CalculateHistoricalPerformanceScore(personnelId, task); totalScore += performanceScore * 0.20; // 评分维度4:工序复杂度匹配(权重15%) var complexityScore = await CalculateProcessComplexityMatchScore(personnelId, task); totalScore += complexityScore * 0.15; // 评分维度5:专业领域匹配(权重5%) var domainScore = await CalculateDomainSpecializationScore(personnelId, task); totalScore += domainScore * 0.05; // 确保评分在合理范围内 totalScore = Math.Max(0.0, Math.Min(100.0, totalScore)); return totalScore; } catch (Exception ex) { _logger.LogError(ex, "计算高级技能匹配评分异常 - 人员:{PersonnelId}, 任务:{TaskId}", personnelId, task.Id); return 60.0; // 异常时返回中等偏上评分 } } private double CalculateLoadBalanceScore(long personnelId, GlobalAllocationContext context) { // 【关键修复】:实现完整的负载均衡评分,替代简化实现 try { var totalScore = 0.0; var workloadDistribution = GetCurrentWorkloadDistribution(context); // 评分维度1:当前负载相对评分(权重40%) var relativeLoadScore = CalculateRelativeLoadScore(personnelId, workloadDistribution); totalScore += relativeLoadScore * 0.4; // 评分维度2:负载分布均衡性贡献(权重30%) var balanceContributionScore = CalculateBalanceContributionScore(personnelId, workloadDistribution); totalScore += balanceContributionScore * 0.3; // 评分维度3:工作时长负载评分(权重20%) var workHourLoadScore = CalculateWorkHourLoadScore(personnelId, workloadDistribution); totalScore += workHourLoadScore * 0.2; // 评分维度4:负载趋势预测评分(权重10%) var loadTrendScore = CalculateLoadTrendScore(personnelId, workloadDistribution); totalScore += loadTrendScore * 0.1; // 确保评分在合理范围内 totalScore = Math.Max(0.0, Math.Min(100.0, totalScore)); return totalScore; } catch (Exception ex) { _logger.LogError(ex, "计算负载均衡评分异常 - 人员:{PersonnelId}", personnelId); return 50.0; // 异常时返回中等评分 } } private async Task CalculateFlexibleShiftRuleScoreAsync(long personnelId, WorkOrderEntity task, GlobalAllocationContext context) { // 【缓存优化版本】:基于完整班次规则验证的柔性评分 try { // 核心优化:直接使用我们的缓存优化班次规则验证 var shiftRulesValidation = await CheckShiftRulesWithCacheAsync( personnelId, task.WorkOrderDate, task.ShiftId ?? 0, context); // 基于班次规则验证结果计算柔性评分 var baseScore = shiftRulesValidation.OverallScore; // 如果已经是关键违规,直接返回低分 if (shiftRulesValidation.HasCriticalViolations) { return Math.Max(0.0, baseScore * 0.3); // 关键违规时大幅降分 } // 根据规则遵循程度调整评分 var adjustedScore = baseScore; // 非关键违规的柔性处理 if (!shiftRulesValidation.IsCompliant && !shiftRulesValidation.HasCriticalViolations) { // 对非关键违规给予一定的柔性空间,但要扣分 adjustedScore = baseScore * 0.7 + 30.0; // 保证最低30分,但有明显扣分 } // 额外奖励:完全合规且高分的情况 if (shiftRulesValidation.IsCompliant && baseScore >= 90.0) { adjustedScore = Math.Min(100.0, baseScore + 10.0); // 高合规奖励 } _logger.LogTrace("柔性班次规则评分计算 - 人员:{PersonnelId}, 基础分:{BaseScore:F1}, 调整后:{AdjustedScore:F1}, 合规:{IsCompliant}", personnelId, baseScore, adjustedScore, shiftRulesValidation.IsCompliant); return Math.Max(0.0, Math.Min(100.0, adjustedScore)); } catch (Exception ex) { _logger.LogError(ex, "计算灵活班次规则评分异常 - 人员:{PersonnelId}, 任务:{TaskId}", personnelId, task.Id); return 70.0; // 异常时返回中等评分 } } #region 智能收敛检测机制 - 15%性能提升核心实现 /// /// 初始化收敛检测器 - 智能迭代控制 /// 【15%性能提升关键】:通过收敛检测避免无效迭代,智能调整算法执行 /// 业务逻辑:根据任务规模和复杂度动态配置收敛参数 /// 技术策略:自适应阈值 → 多指标监测 → 提前终止 → 性能优化 /// /// 全局分配上下文 private void InitializeConvergenceDetection(GlobalAllocationContext context) { try { var detector = context.ConvergenceDetector; var taskCount = context.Tasks.Count; var personnelCount = context.AvailablePersonnel.Count; var complexity = taskCount * personnelCount; // 根据问题复杂度动态调整收敛参数 if (complexity <= 100) // 小规模问题 { detector.WindowSize = 5; detector.ConvergenceThreshold = 0.005; // 更严格的收敛要求 detector.MinGenerationsBeforeCheck = 10; detector.MaxPlateauGenerations = 8; } else if (complexity <= 1000) // 中等规模问题 { detector.WindowSize = 10; detector.ConvergenceThreshold = 0.002; detector.MinGenerationsBeforeCheck = 20; detector.MaxPlateauGenerations = 15; } else // 大规模问题 { detector.WindowSize = 15; detector.ConvergenceThreshold = 0.001; detector.MinGenerationsBeforeCheck = 30; detector.MaxPlateauGenerations = 25; } _logger.LogDebug("收敛检测器初始化完成 - 问题复杂度:{Complexity},窗口大小:{WindowSize}," + "收敛阈值:{Threshold},最小检测代数:{MinGen},最大平台期:{MaxPlateau}", complexity, detector.WindowSize, detector.ConvergenceThreshold, detector.MinGenerationsBeforeCheck, detector.MaxPlateauGenerations); } catch (Exception ex) { _logger.LogError(ex, "初始化收敛检测器异常"); } } /// /// 检测算法收敛状态 - 智能收敛判断引擎 /// 【核心算法】:基于多个指标综合判断遗传算法是否已收敛 /// 检测指标:适应度改善率、平台期持续、方差稳定性、种群多样性 /// 收敛策略:渐进式收敛 + 平台期检测 + 多样性监控 /// /// 全局分配上下文,包含收敛检测器 /// 当前代数 /// 当前最佳适应度 /// 当前平均适应度 /// 种群多样性指标 /// 收敛检测结果,包含是否收敛和详细分析 public ConvergenceDetectionResult DetectConvergence(GlobalAllocationContext context, int currentGeneration, double currentBestFitness, double averageFitness, double populationDiversity) { var detector = context.ConvergenceDetector; var result = new ConvergenceDetectionResult { CurrentGeneration = currentGeneration, IsConverged = false }; try { // 第一阶段:基础检查 - 确保达到最小代数要求 if (currentGeneration < detector.MinGenerationsBeforeCheck) { result.ConvergenceReason = $"未达到最小检测代数要求(当前:{currentGeneration},最小:{detector.MinGenerationsBeforeCheck})"; result.ContinueOptimization = true; return result; } // 第二阶段:更新适应度历史记录 UpdateFitnessHistory(detector, currentBestFitness); // 第三阶段:多维度收敛检测 var improvementRate = CalculateFitnessImprovementRate(detector); var plateauDetection = DetectPlateauCondition(detector, currentBestFitness, currentGeneration); var stabilityCheck = CheckFitnessStability(detector); var diversityCheck = CheckPopulationDiversity(populationDiversity); // 第四阶段:综合收敛判断 var convergenceFactors = new List { new() { Name = "ImprovementRate", Score = improvementRate.Score, Weight = 0.4, Details = improvementRate.Details }, new() { Name = "PlateauDetection", Score = plateauDetection.Score, Weight = 0.3, Details = plateauDetection.Details }, new() { Name = "FitnessStability", Score = stabilityCheck.Score, Weight = 0.2, Details = stabilityCheck.Details }, new() { Name = "PopulationDiversity", Score = diversityCheck.Score, Weight = 0.1, Details = diversityCheck.Details } }; // 加权评分计算 var weightedScore = convergenceFactors.Sum(f => f.Score * f.Weight); var convergenceThreshold = 0.8; // 80%以上认为收敛 result.ConvergenceScore = weightedScore; result.ConvergenceFactors = convergenceFactors; result.IsConverged = weightedScore >= convergenceThreshold; if (result.IsConverged) { detector.IsConverged = true; result.ConvergenceReason = $"检测到收敛:综合评分{weightedScore:F3}超过阈值{convergenceThreshold:F3}"; result.ContinueOptimization = false; _logger.LogInformation("遗传算法收敛检测完成 - 第{Generation}代收敛,评分:{Score:F3}," + "改善率:{Improvement:F4},平台期:{Plateau}代", currentGeneration, weightedScore, improvementRate.Value, detector.PlateauGenerations); } else { result.ConvergenceReason = $"未收敛:综合评分{weightedScore:F3}低于阈值{convergenceThreshold:F3}"; result.ContinueOptimization = true; // 检测是否达到最大平台期,强制终止 if (detector.PlateauGenerations >= detector.MaxPlateauGenerations) { result.IsConverged = true; result.ConvergenceReason = $"达到最大平台期{detector.MaxPlateauGenerations}代,强制终止"; result.ContinueOptimization = false; _logger.LogWarning("遗传算法达到最大平台期,强制终止优化 - 第{Generation}代,平台期:{Plateau}代", currentGeneration, detector.PlateauGenerations); } } return result; } catch (Exception ex) { _logger.LogError(ex, "收敛检测异常 - 第{Generation}代", currentGeneration); result.IsConverged = false; result.ContinueOptimization = true; result.ConvergenceReason = $"收敛检测异常:{ex.Message}"; return result; } } /// /// 更新适应度历史记录 - 收敛分析数据维护 /// 【数据管理】:维护滑动窗口的适应度历史,用于趋势分析 /// private void UpdateFitnessHistory(ConvergenceDetector detector, double currentBestFitness) { detector.FitnessHistory.Add(currentBestFitness); detector.CurrentBestFitness = currentBestFitness; // 维护滑动窗口大小 while (detector.FitnessHistory.Count > detector.WindowSize) { detector.FitnessHistory.RemoveAt(0); } } /// /// 计算适应度改善率 - 核心收敛指标 /// 【算法核心】:计算最近N代的适应度改善趋势 /// private (double Score, double Value, string Details) CalculateFitnessImprovementRate(ConvergenceDetector detector) { if (detector.FitnessHistory.Count < 2) { return (0.0, 0.0, "历史数据不足"); } var recentHistory = detector.FitnessHistory.TakeLast(Math.Min(detector.WindowSize, detector.FitnessHistory.Count)).ToList(); var improvementRate = 0.0; if (recentHistory.Count >= 2) { var firstFitness = recentHistory.First(); var lastFitness = recentHistory.Last(); improvementRate = Math.Abs(lastFitness - firstFitness) / Math.Max(firstFitness, 0.001); } var score = improvementRate < detector.ConvergenceThreshold ? 1.0 : Math.Max(0.0, 1.0 - improvementRate / 0.1); var details = $"改善率:{improvementRate:F4},阈值:{detector.ConvergenceThreshold:F4}"; return (score, improvementRate, details); } /// /// 检测平台期状态 - 停滞检测算法 /// 【停滞检测】:检测算法是否陷入局部最优的平台期 /// private (double Score, string Details) DetectPlateauCondition(ConvergenceDetector detector, double currentBestFitness, int currentGeneration) { // 检查适应度是否有显著改善 var hasSignificantImprovement = false; if (detector.FitnessHistory.Count >= 2) { var previousBest = detector.FitnessHistory[detector.FitnessHistory.Count - 2]; var improvement = Math.Abs(currentBestFitness - previousBest) / Math.Max(previousBest, 0.001); hasSignificantImprovement = improvement >= detector.ConvergenceThreshold; } if (hasSignificantImprovement) { detector.PlateauGenerations = 0; detector.LastImprovementGeneration = currentGeneration; return (0.0, $"检测到改善,重置平台期计数"); } else { detector.PlateauGenerations++; var plateauRatio = (double)detector.PlateauGenerations / detector.MaxPlateauGenerations; var score = Math.Min(1.0, plateauRatio); return (score, $"平台期{detector.PlateauGenerations}代,比率{plateauRatio:F2}"); } } /// /// 检查适应度稳定性 - 方差分析 /// 【稳定性分析】:通过方差分析判断适应度是否稳定 /// private (double Score, string Details) CheckFitnessStability(ConvergenceDetector detector) { if (detector.FitnessHistory.Count < detector.WindowSize) { return (0.0, "数据不足进行稳定性分析"); } var recentHistory = detector.FitnessHistory.TakeLast(detector.WindowSize).ToList(); var mean = recentHistory.Average(); var variance = recentHistory.Sum(x => Math.Pow(x - mean, 2)) / recentHistory.Count; var standardDeviation = Math.Sqrt(variance); var coefficientOfVariation = mean > 0 ? standardDeviation / mean : 0; // 变异系数小于1%认为稳定 var stabilityScore = coefficientOfVariation < 0.01 ? 1.0 : Math.Max(0.0, 1.0 - coefficientOfVariation / 0.05); var details = $"标准差:{standardDeviation:F4},变异系数:{coefficientOfVariation:F4}"; return (stabilityScore, details); } /// /// 检查种群多样性 - 多样性监控 /// 【多样性分析】:监控种群多样性,防止过早收敛 /// private (double Score, string Details) CheckPopulationDiversity(double populationDiversity) { // 多样性过低可能表明收敛或需要增加变异 var diversityThreshold = 0.1; // 10%以下认为多样性过低 var diversityScore = populationDiversity < diversityThreshold ? 1.0 : Math.Max(0.0, 1.0 - populationDiversity); var details = $"种群多样性:{populationDiversity:F4},阈值:{diversityThreshold:F4}"; return (diversityScore, details); } #endregion #region 收敛检测数据结构 /// /// 收敛检测结果 /// public class ConvergenceDetectionResult { /// /// 当前代数 /// public int CurrentGeneration { get; set; } /// /// 是否已收敛 /// public bool IsConverged { get; set; } /// /// 是否继续优化 /// public bool ContinueOptimization { get; set; } /// /// 收敛原因描述 /// public string ConvergenceReason { get; set; } = string.Empty; /// /// 收敛评分(0-1) /// public double ConvergenceScore { get; set; } /// /// 收敛因子详细分析 /// public List ConvergenceFactors { get; set; } = new(); } /// /// 收敛因子 /// public class ConvergenceFactor { /// /// 因子名称 /// public string Name { get; set; } = string.Empty; /// /// 因子评分(0-1) /// public double Score { get; set; } /// /// 权重 /// public double Weight { get; set; } /// /// 详细信息 /// public string Details { get; set; } = string.Empty; } #endregion /// /// 人员候选评分结果 - 智能选择的数据载体 /// 业务用途:封装候选人员的多维度评分信息,支持智能排序和选择 /// public class PersonnelCandidateScore { /// /// 候选人员信息 /// public GlobalPersonnelInfo Personnel { get; set; } = new(); /// /// 工作负载评分(0-100分) /// public double WorkloadScore { get; set; } /// /// 冲突适配评分(0-100分) /// public double ConflictAdaptationScore { get; set; } /// /// 人员稳定性评分(0-100分) /// public double StabilityScore { get; set; } /// /// 可用性评分(0-100分) /// public double AvailabilityScore { get; set; } /// /// 综合总评分(加权平均后的最终分数) /// public double TotalScore { get; set; } } /// /// 资质匹配分析结果 - 真实资质分析的数据载体 /// 业务用途:封装人员资质匹配分析的完整结果信息 /// public class QualificationAnalysisResult { /// /// 有资质人员数量 - 能够胜任至少一项任务的人员数量 /// public int QualifiedPersonnelCount { get; set; } /// /// 资质紧张度(0-1之间) - 基于技能瓶颈严重程度计算 /// public double QualificationTension { get; set; } /// /// 匹配质量分布 - 高中低三档匹配质量的人员分布统计 /// public Dictionary MatchQualityDistribution { get; set; } = new(); /// /// 技能瓶颈列表 - 识别的供需不平衡关键技能 /// public List SkillBottlenecks { get; set; } = new(); } /// /// 任务资质需求 - 任务资质需求分析的数据结构 /// 业务用途:记录特定资质的任务需求统计信息 /// public class TaskQualificationRequirement { /// /// 资质ID /// public string QualificationId { get; set; } = string.Empty; /// /// 需要该资质的任务数量 /// public int RequiredTaskCount { get; set; } /// /// 需要该资质的任务ID列表 /// public List TaskIds { get; set; } = new(); } /// /// 项目FL经验 - 人员项目FL参与经验数据结构 /// 业务用途:记录人员在特定项目中的FL经验和参与情况 /// public class ProjectFLExperience { /// /// 项目编号 /// public string ProjectNumber { get; set; } = string.Empty; /// /// 参与任务数量 /// public int TaskCount { get; set; } /// /// 最早参与日期 /// public DateTime EarliestAssignmentDate { get; set; } /// /// 最近参与日期 /// public DateTime LatestAssignmentDate { get; set; } /// /// 总经验月数 /// public int TotalExperienceMonths { get; set; } } /// /// 项目FL评分结果 - FL优先评分算法的输出结果 /// 业务用途:封装FL优先评分的分数和详细原因说明 /// public class ProjectFLScoringResult { /// /// FL优先评分(0-100分) /// public double Score { get; set; } /// /// 评分原因和详细说明 /// public string Reason { get; set; } = string.Empty; } #region 班次规则验证辅助方法 - 基于PersonnelAllocationService完整逻辑 /// /// 获取班次关联的规则列表 - 性能优化版本 /// 【核心性能优化】:优先使用预加载的班次规则映射数据,避免重复数据库查询 /// 【业务逻辑】:查询指定班次ID关联的所有规则配置,支持两级数据获取策略 /// 技术策略:预加载缓存 → 数据库查询 → 异常降级 /// 性能提升:将每次2个数据库查询优化为0个查询(缓存命中时) /// /// 班次ID /// 班次规则列表 private async Task> GetShiftRulesAsync(long shiftId) { try { // 第一级:尝试从预加载的班次规则映射数据中获取 // 【性能关键修复】:直接使用CreateOptimizedAllocationContextAsync预加载的数据 if (_currentAllocationContext?.ShiftRulesMapping.Any() == true) { var preloadedRules = _currentAllocationContext.ShiftRulesMapping; var convertedRules = preloadedRules.Select(rule => new ShiftRuleEntity { Id = rule.Id, RuleType = rule.RuleType, RuleName = rule.RuleName, IsEnabled = rule.IsEnabled, }).ToList(); _logger.LogDebug("从预加载缓存获取班次规则成功 - 班次ID:{ShiftId}, 规则数量:{RuleCount}", shiftId, convertedRules.Count); return convertedRules; } // 第二级:预加载数据不可用时,回退到原始数据库查询逻辑 _logger.LogDebug("预加载缓存未命中,回退到数据库查询 - 班次ID:{ShiftId}", shiftId); return await _shiftService.GetShiftRulesAsync(shiftId); } catch (Exception ex) { _logger.LogError(ex, "获取班次规则异常 - 班次ID:{ShiftId}", shiftId); // 第三级:异常降级处理,返回空规则列表,避免阻断业务流程 return new List(); } } /// /// 验证个人班次规则 - 完整规则验证引擎 /// 【深度业务思考】:基于规则类型、参数和时间有效性进行全面验证 /// 参考PersonnelAllocationService.ValidateIndividualShiftRuleAsync的完整实现 /// /// 班次规则实体 /// 人员ID /// 工作日期 /// 班次ID /// 规则验证结果 private async Task ValidateIndividualShiftRuleAsync(ShiftRuleEntity rule, long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context = null) { try { if (!rule.IsEnabled) { return new RuleValidationResult { IsValid = true, // 规则不生效,视为通过 ComplianceScore = 100.0, IsCritical = false, ViolationMessage = $"规则'{rule.RuleName}'在此时间段不生效" }; } // 根据规则类型进行详细验证 var result = rule.RuleType switch { "1" => await ValidateAssignedPersonnelPriorityRuleAsync(personnelId, workDate), // 指定人员优先 "2" => await ValidateWeeklyTaskLimitRuleAsync(personnelId, workDate), "3" => await ValidateShiftContinuityRuleAsync(personnelId, workDate, shiftId, 3), // 1->2班 "4" => await ValidateShiftContinuityRuleAsync(personnelId, workDate, shiftId, 4), // 2->3班 "5" => await ValidateCrossWeekendContinuityRuleAsync(personnelId, workDate, shiftId, 5), // 不能这周日/下周六连 "6" => await ValidateCrossWeekendContinuityRuleAsync(personnelId, workDate, shiftId, 6), // 不能本周六/本周日连 "7" => await ValidateContinuousWorkDaysRuleAsync(personnelId, workDate), // 连续工作天数 "8" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 8), // 三班后一天不排班 "9" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 9), // 二班后一天不排班 "10" => await ValidateProjectFLPriorityRuleAsync(personnelId, workDate, shiftId, rule, context, context.CurrentTask), // 优先分配本项目FL _ => await ValidateDefaultRuleAsync(personnelId, workDate, rule) // 默认规则验证 }; // 记录规则验证详情 _logger.LogDebug("规则验证完成 - 规则:{RuleName}({RuleType}),人员:{PersonnelId},结果:{IsValid},评分:{Score}", rule.RuleName, rule.RuleType, personnelId, result.IsValid, result.ComplianceScore); return result; } catch (Exception ex) { _logger.LogError(ex, "验证班次规则异常,规则:{RuleName}({RuleType}),人员:{PersonnelId}", rule.RuleName, rule.RuleType, personnelId); return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = $"规则'{rule.RuleName}'验证异常:{ex.Message}" }; } } #region 具体规则验证方法 /// /// 验证指定人员优先规则 /// private async Task ValidateAssignedPersonnelPriorityRuleAsync(long personnelId, DateTime workDate) { // 检查是否存在指定人员分配 var hasAssignedTask = await _workOrderRepository.Select .AnyAsync(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date); return new RuleValidationResult { IsValid = true, // 这个规则通常不会阻断,只是影响评分 ComplianceScore = hasAssignedTask ? 100.0 : 80.0, IsCritical = false, ViolationMessage = hasAssignedTask ? "" : "非指定人员,优先级稍低" }; } /// /// 验证周任务限制规则 /// private async Task ValidateWeeklyTaskLimitRuleAsync(long personnelId, DateTime workDate) { var maxWeeklyShifts = 6; var weekShiftCount = await CalculateWeekShiftCountAsync(personnelId, workDate); var isValid = weekShiftCount <= maxWeeklyShifts; var complianceScore = isValid ? Math.Max(0, 100 - (weekShiftCount / (double)maxWeeklyShifts * 100)) : 0; return new RuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"周班次数({weekShiftCount})超过限制({maxWeeklyShifts})" }; } /// /// 验证同一天内班次连续性规则 - 完整业务逻辑实现 /// 【深度业务逻辑】:基于PersonnelAllocationService的完整规则实现 /// 业务规则: /// 规则3:同一天内禁止早班(编号1)和中班(编号2)同时分配 - 避免疲劳累积 /// 规则4:同一天内禁止中班(编号2)和夜班(编号3)同时分配 - 避免过度劳累 /// 核心算法:基于班次编号进行特定组合的连续性检查,而非简单的"其他班次"检查 /// 疲劳管理:防止人员在同一天承担生物钟冲突较大的连续班次组合 /// /// 人员ID /// 工作日期 /// 当前要分配的班次ID /// 规则类型:3=早中班连续禁止,4=中夜班连续禁止 /// 规则验证结果,包含违规详情和疲劳风险评估 private async Task ValidateShiftContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证班次连续性规则 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); // 第一步:获取当前要分配班次的班次编号 // 业务逻辑:通过班次ID查询对应的班次编号(1=早班,2=中班,3=夜班) var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (currentShiftNumber == null) { _logger.LogWarning("班次ID:{ShiftId}无法获取班次编号,跳过连续性验证", shiftId); return new RuleValidationResult { IsValid = true, ComplianceScore = 90.0, // 数据不完整时给予高分但非满分 IsCritical = false, ViolationMessage = "班次信息不完整,无法执行连续性验证" }; } // 第二步:获取人员当天已分配的所有班次编号 // 业务逻辑:查询同一天已经确认分配的所有班次,用于连续性冲突检测 var todayExistingShiftNumbers = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, workDate); _logger.LogDebug("人员{PersonnelId}在{WorkDate}已有班次编号:{ExistingShifts},当前分配班次编号:{CurrentShift}", personnelId, workDate.ToString("yyyy-MM-dd"), string.Join(",", todayExistingShiftNumbers), currentShiftNumber); // 第三步:根据规则类型执行特定的连续性检查 // 业务逻辑:不同规则对应不同的班次组合禁止策略 bool hasViolation; string violationDetail = ""; GlobalConflictSeverity violationSeverity = GlobalConflictSeverity.Medium; switch (ruleType) { case 3: // 规则3:禁止早班(1) → 中班(2) 同日连续 // 业务考量:早班到中班跨度过大,容易造成生物钟紊乱和疲劳累积 hasViolation = todayExistingShiftNumbers.Contains(1) && currentShiftNumber == 2; if (hasViolation) { violationDetail = "同日早班和中班连续分配违反疲劳管理规定"; violationSeverity = GlobalConflictSeverity.High; // 高严重性,影响人员健康 } else if (todayExistingShiftNumbers.Contains(2) && currentShiftNumber == 1) { // 反向检查:中班 → 早班(同样不合理) hasViolation = true; violationDetail = "同日中班和早班连续分配违反疲劳管理规定"; violationSeverity = GlobalConflictSeverity.High; } break; case 4: // 规则4:禁止中班(2) → 夜班(3) 同日连续 // 业务考量:中班到夜班连续工作时间过长,严重影响休息质量 hasViolation = todayExistingShiftNumbers.Contains(2) && currentShiftNumber == 3; if (hasViolation) { violationDetail = "同日中班和夜班连续分配违反劳动强度限制"; violationSeverity = GlobalConflictSeverity.Critical; // 关键违规,影响安全生产 } else if (todayExistingShiftNumbers.Contains(3) && currentShiftNumber == 2) { // 反向检查:夜班 → 中班 hasViolation = true; violationDetail = "同日夜班和中班连续分配违反劳动强度限制"; violationSeverity = GlobalConflictSeverity.Critical; } break; default: // 未知规则类型的保守处理 _logger.LogWarning("未识别的班次连续性规则类型:{RuleType}", ruleType); hasViolation = false; break; } // 第四步:计算合规评分 // 业务算法:基于违规严重程度和人员健康风险计算评分 double complianceScore; bool isCritical; if (!hasViolation) { // 无违规:根据班次合理性给予评分 complianceScore = CalculateShiftReasonablenessScore(currentShiftNumber.Value, todayExistingShiftNumbers); isCritical = false; } else { // 有违规:根据严重程度给予相应低分 complianceScore = violationSeverity switch { GlobalConflictSeverity.Critical => 0.0, // 关键违规:完全不可接受 GlobalConflictSeverity.High => 15.0, // 高严重:严重违规但非致命 GlobalConflictSeverity.Medium => 30.0, // 中等严重:需要注意但可接受 _ => 50.0 // 轻微违规:影响有限 }; isCritical = violationSeverity >= GlobalConflictSeverity.High; } // 第五步:构建详细的验证结果 var result = new RuleValidationResult { IsValid = !hasViolation, ComplianceScore = complianceScore, IsCritical = isCritical, ViolationMessage = hasViolation ? violationDetail : "" }; // 业务日志:记录详细的验证过程,便于审计和问题排查 _logger.LogDebug("班次连续性规则验证完成 - 规则类型:{RuleType}, 结果:{IsValid}, 评分:{Score:F1}, 严重性:{Severity}, 详情:{Detail}", ruleType, result.IsValid, result.ComplianceScore, hasViolation ? violationSeverity.ToString() : "无违规", violationDetail); return result; } catch (Exception ex) { _logger.LogError(ex, "班次连续性规则验证异常 - 人员ID:{PersonnelId}, 规则类型:{RuleType}", personnelId, ruleType); // 异常降级处理:返回中等评分,避免阻断业务但标记需要人工审核 return new RuleValidationResult { IsValid = false, ComplianceScore = 60.0, // 异常时给予中等评分 IsCritical = false, // 异常情况不标记为关键,避免误阻断 ViolationMessage = $"班次连续性规则验证异常:{ex.Message}" }; } } /// /// 验证跨周末班次连续性规则 - 简化版本 /// private async Task ValidateCrossWeekendContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { // 简化实现:基于周末时间检查 var isWeekend = workDate.DayOfWeek == DayOfWeek.Saturday || workDate.DayOfWeek == DayOfWeek.Sunday; if (!isWeekend) { return new RuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } // 检查周末连续工作情况 var weekendShifts = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && (w.WorkOrderDate.DayOfWeek == DayOfWeek.Saturday || w.WorkOrderDate.DayOfWeek == DayOfWeek.Sunday) && w.WorkOrderDate >= workDate.AddDays(-1) && w.WorkOrderDate <= workDate.AddDays(1) && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); var hasViolation = weekendShifts > 1; // 周末连续超过1天 return new RuleValidationResult { IsValid = !hasViolation, ComplianceScore = hasViolation ? 30 : 100, IsCritical = hasViolation, ViolationMessage = hasViolation ? "违反周末连续工作限制" : "" }; } /// /// 验证连续工作天数规则 /// private async Task ValidateContinuousWorkDaysRuleAsync(long personnelId, DateTime workDate) { var maxContinuousDays = 7; var continuousDays = await CalculateContinuousWorkDaysAsync(personnelId, workDate); var isValid = continuousDays <= maxContinuousDays; var complianceScore = isValid ? Math.Max(0, 100 - (continuousDays / (double)maxContinuousDays * 100)) : 0; return new RuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"连续工作天数({continuousDays})超过限制({maxContinuousDays})" }; } /// /// 验证次日休息规则 - 完整业务逻辑实现 /// 【关键业务修复】:精确验证前一天是否上了特定班次,避免误判 /// 业务规则: /// 规则8:夜班(编号3)后次日强制休息 - 保证充分恢复时间 /// 规则9:中班(编号2)后次日强制休息 - 避免疲劳累积 /// 核心逻辑:必须检查前一天具体班次编号,而非仅检查是否有任务 /// /// 人员ID /// 当前工作日期 /// 当前要分配的班次ID /// 规则类型:8=夜班后休息,9=中班后休息 /// 规则验证结果,包含具体的违规信息和疲劳风险评估 private async Task ValidateNextDayRestRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证次日休息规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); // 第一步:确定要检查的目标班次编号 var targetShiftNumber = ruleType switch { 8 => 3, // 规则8:检查前一天是否有三班(夜班) 9 => 2, // 规则9:检查前一天是否有二班(中班) _ => 0 }; if (targetShiftNumber == 0) { _logger.LogWarning("未支持的次日休息规则类型:{RuleType}", ruleType); return new RuleValidationResult { IsValid = true, ComplianceScore = 90.0, IsCritical = false, ViolationMessage = "未支持的规则类型,跳过验证" }; } // 第二步:查询前一天的所有工作任务 var previousDate = workDate.AddDays(-1); var previousDayTasks = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == previousDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview && w.ShiftId.HasValue) .ToListAsync(); if (!previousDayTasks.Any()) { // 前一天无任务,通过验证 _logger.LogDebug("人员{PersonnelId}前一天({PreviousDate})无工作任务,次日休息规则验证通过", personnelId, previousDate.ToString("yyyy-MM-dd")); return new RuleValidationResult { IsValid = true, ComplianceScore = 100.0, IsCritical = false, ViolationMessage = "" }; } // 第三步:检查前一天是否有目标班次 var hadTargetShift = false; var violatingShiftDetails = new List(); foreach (var task in previousDayTasks) { // 获取任务的班次编号 var shiftNumber = await GetShiftNumberByIdAsync(task.ShiftId.Value); if (shiftNumber == targetShiftNumber) { hadTargetShift = true; violatingShiftDetails.Add($"任务{task.Id}({task.WorkOrderCode})的{GetShiftDisplayName(targetShiftNumber)}"); } } // 第四步:根据检查结果返回验证结果 if (hadTargetShift) { var shiftName = GetShiftDisplayName(targetShiftNumber); var violationDetail = $"前一天({previousDate:yyyy-MM-dd})上了{shiftName},违反次日强制休息规则"; var detailedViolationInfo = violatingShiftDetails.Any() ? $"{violationDetail}。具体违规:{string.Join("; ", violatingShiftDetails)}" : violationDetail; _logger.LogWarning("次日休息规则违规 - 人员ID:{PersonnelId}, 规则类型:{RuleType}, 违规详情:{Details}", personnelId, ruleType, detailedViolationInfo); return new RuleValidationResult { IsValid = false, ComplianceScore = 0.0, IsCritical = true, ViolationMessage = detailedViolationInfo }; } // 通过验证,但给予适当的健康关怀评分 _logger.LogDebug("次日休息规则验证通过 - 人员ID:{PersonnelId}, 前一天有{TaskCount}个任务,但无{ShiftName}", personnelId, previousDayTasks.Count, GetShiftDisplayName(targetShiftNumber)); return new RuleValidationResult { IsValid = true, ComplianceScore = 100.0, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证次日休息规则异常 - 人员ID:{PersonnelId}, 规则类型:{RuleType}", personnelId, ruleType); // 异常降级处理:返回中等评分,避免阻断业务但标记为需要人工审核 return new RuleValidationResult { IsValid = false, ComplianceScore = 60.0, IsCritical = false, // 异常情况不标记为关键,避免误阻断 ViolationMessage = $"次日休息规则验证异常:{ex.Message}" }; } } /// /// 验证项目FL优先分配规则 - 完整业务逻辑实现 /// 【深度业务逻辑】:基于PersonnelAllocationService的完整FL优先分配实现 /// 业务规则: /// 优先分配具有项目经验的FL人员,确保任务执行的专业性和效率 /// 评分策略:本项目FL(100分) > 多项目FL(95分) > 跨项目FL(85-90分) > 无FL经验(70分) /// 核心算法:基于WorkOrderFLPersonnelEntity关联关系进行FL身份验证和经验评估 /// 业务价值:提高项目任务执行质量,同时兼顾人员培养和灵活调配 /// /// 人员ID /// 工作日期 /// 班次ID /// 规则实体,包含规则参数和配置 /// 全局分配上下文,用于获取任务项目信息 /// 当前正在验证的具体任务,直接包含项目信息 /// FL优先规则验证结果,包含详细的评分依据和业务原因 private async Task ValidateProjectFLPriorityRuleAsync(long personnelId, DateTime workDate, long shiftId, ShiftRuleEntity rule, GlobalAllocationContext context, WorkOrderEntity currentTask = null) { try { _logger.LogDebug("开始验证项目FL优先分配规则 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 第一步:获取当前任务的项目编号 // 【架构优化】:优先使用直接传入的任务信息,确保数据准确性 string currentProjectNumber; if (currentTask != null) { // 直接使用传入的任务信息,最精确 currentProjectNumber = currentTask.ProjectNumber; _logger.LogDebug("直接使用传入任务的项目编号 - 任务ID:{TaskId}, 项目编号:{ProjectNumber}", currentTask.Id, currentProjectNumber); } else { // 回退到上下文查找方式 currentProjectNumber = GetCurrentTaskProjectNumberFromContext(workDate, shiftId, context); _logger.LogDebug("从分配上下文获取项目编号 - 项目编号:{ProjectNumber}", currentProjectNumber); } if (string.IsNullOrEmpty(currentProjectNumber)) { _logger.LogWarning("无法获取任务的项目编号,跳过FL优先验证 - 日期:{WorkDate}, 班次:{ShiftId}", workDate.ToString("yyyy-MM-dd"), shiftId); return new RuleValidationResult { IsValid = true, ComplianceScore = 80.0, // 信息不完整时给予中高分 IsCritical = false, ViolationMessage = "项目信息不完整,无法执行FL优先验证" }; } // 第二步:检查人员是否为当前项目的FL成员 // 业务逻辑:查询WorkOrderFLPersonnelEntity表,检查人员与项目的FL关联关系 var isCurrentProjectFL = await CheckPersonnelIsProjectFLAsync(personnelId, currentProjectNumber); // 第三步:查询人员的所有项目FL经验 // 业务逻辑:获取人员在其他项目中的FL记录,用于跨项目经验评估 var allProjectFLExperiences = await GetPersonnelAllProjectFLExperiencesAsync(personnelId); // 第四步:计算综合FL优先评分 // 业务逻辑:基于FL经验、活跃度、项目数量等多维度因素计算优先级评分 var flScoringResult = CalculateProjectFLPriorityScore(personnelId, currentProjectNumber, isCurrentProjectFL, allProjectFLExperiences); // 第五步:构建验证结果 var result = new RuleValidationResult { IsValid = true, // FL规则主要影响优先级,不阻断分配 ComplianceScore = flScoringResult.Score, IsCritical = false, // FL规则为软约束,不设置为关键违规 ViolationMessage = flScoringResult.Reason }; // 业务日志:记录详细的FL验证和评分过程 _logger.LogDebug("项目FL优先规则验证完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, " + "本项目FL:{IsProjectFL}, 跨项目FL数:{CrossProjectCount}, 综合评分:{Score:F1}, 原因:{Reason}", personnelId, currentProjectNumber, isCurrentProjectFL, allProjectFLExperiences.Count, result.ComplianceScore, flScoringResult.Reason); return result; } catch (Exception ex) { _logger.LogError(ex, "项目FL优先规则验证异常 - 人员ID:{PersonnelId}", personnelId); // 异常降级处理:返回中等评分,避免阻断正常业务流程 return new RuleValidationResult { IsValid = true, // 异常时不阻断分配 ComplianceScore = 75.0, // 给予中等偏上评分 IsCritical = false, ViolationMessage = $"FL优先规则验证异常,采用默认评分:{ex.Message}" }; } } /// /// 默认规则验证 /// private async Task ValidateDefaultRuleAsync(long personnelId, DateTime workDate, ShiftRuleEntity rule) { await Task.CompletedTask; _logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName); return new RuleValidationResult { IsValid = true, ComplianceScore = 90, // 给予较高分,但不是满分 IsCritical = false, ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证" }; } #endregion #region 项目FL优先分配验证辅助方法 /// /// 获取当前任务的项目编号 - 从分配上下文获取任务信息 /// 【修正架构缺陷】:直接从全局分配上下文获取任务信息,而非查询数据库 /// 业务逻辑:在全局分配过程中,所有待分配任务都已加载到上下文中,应直接使用 /// 性能优化:避免不必要的数据库查询,提高分配效率 /// 数据一致性:确保使用的任务信息与分配上下文完全一致 /// /// 工作日期 /// 班次ID /// 全局分配上下文,包含所有待分配任务 /// 项目编号,如果无法确定则返回null private string? GetCurrentTaskProjectNumberFromContext(DateTime workDate, long shiftId, GlobalAllocationContext context) { try { // 【优化策略】:优先处理任务特定上下文(单任务场景) // 业务逻辑:如果上下文只包含一个任务,直接使用该任务的项目编号,无需复杂匹配 if (context.Tasks?.Count == 1) { var singleTask = context.Tasks.First(); _logger.LogDebug("任务特定上下文,直接使用单任务项目编号 - 任务ID:{TaskId}, 项目编号:{ProjectNumber}, 日期:{WorkDate}, 班次:{ShiftId}", singleTask.Id, singleTask.ProjectNumber, workDate.ToString("yyyy-MM-dd"), shiftId); return singleTask.ProjectNumber; } // 【架构修正】:直接从分配上下文中查找匹配的任务 // 业务逻辑:基于工作日期和班次ID在当前分配任务列表中查找所有匹配的任务 var matchingTasks = context.Tasks?.Where(task => task.WorkOrderDate.Date == workDate.Date && task.ShiftId == shiftId).ToList(); if (matchingTasks?.Any() == true) { // 深度思考:多个任务可能来自不同项目,需要处理项目编号选择策略 // 策略1:检查所有任务是否来自同一项目 var distinctProjects = matchingTasks.Select(t => t.ProjectNumber).Distinct().ToList(); if (distinctProjects.Count == 1) { // 所有任务来自同一项目,直接使用该项目编号 var projectNumber = distinctProjects.First(); _logger.LogDebug("从分配上下文成功获取任务项目编号 - 匹配任务数:{TaskCount}, 项目编号:{ProjectNumber}, 日期:{WorkDate}, 班次:{ShiftId}", matchingTasks.Count, projectNumber, workDate.ToString("yyyy-MM-dd"), shiftId); return projectNumber; } else { // 策略2:多项目情况,选择任务数量最多的项目 var projectGroups = matchingTasks.GroupBy(t => t.ProjectNumber) .OrderByDescending(g => g.Count()) .ThenBy(g => g.Key) // 任务数相同时按项目编号排序,确保结果稳定 .ToList(); var majorProject = projectGroups.First(); var selectedProjectNumber = majorProject.Key; return selectedProjectNumber; } } // 如果精确匹配失败,尝试模糊匹配(同日期不同班次) var sameDateTasks = context.Tasks?.Where(task => task.WorkOrderDate.Date == workDate.Date).ToList(); if (sameDateTasks?.Any() == true) { // 同样处理同日期多项目的情况 var distinctProjects = sameDateTasks.Select(t => t.ProjectNumber).Distinct().ToList(); var selectedProjectNumber = distinctProjects.Count == 1 ? distinctProjects.First() : sameDateTasks.GroupBy(t => t.ProjectNumber) .OrderByDescending(g => g.Count()) .ThenBy(g => g.Key) .First().Key; _logger.LogDebug("从分配上下文使用同日期主要项目编号 - 匹配任务数:{TaskCount}, 项目编号:{ProjectNumber}, 日期:{WorkDate}", sameDateTasks.Count, selectedProjectNumber, workDate.ToString("yyyy-MM-dd")); return selectedProjectNumber; } _logger.LogWarning("分配上下文中未找到匹配任务 - 日期:{WorkDate}, 班次ID:{ShiftId}, 上下文任务数:{TaskCount}", workDate.ToString("yyyy-MM-dd"), shiftId, context.Tasks?.Count ?? 0); return null; } catch (Exception ex) { _logger.LogError(ex, "从分配上下文获取任务项目编号异常 - 日期:{WorkDate}, 班次ID:{ShiftId}", workDate.ToString("yyyy-MM-dd"), shiftId); return null; } } /// /// 检查人员是否为指定项目的FL成员 - 性能优化版本 /// 【核心业务验证】:基于WorkOrderFLPersonnelEntity关联表查询FL身份 /// 【性能优化】:优先使用预加载的PersonnelProjectFLMapping缓存,避免重复数据库查询 /// 技术策略:预加载缓存 → 数据库查询 → 异常降级 /// 业务逻辑:查询人员在指定项目中的FL记录,确认FL身份的真实性 /// /// 人员ID /// 项目编号 /// 是否为项目FL成员 private async Task CheckPersonnelIsProjectFLAsync(long personnelId, string projectNumber) { try { // 第一级:尝试从预加载的人员项目FL映射中获取 // 【性能关键修复】:使用复合键进行O(1)查找,避免数据库I/O var compositeKey = $"{personnelId}_{projectNumber}"; if (_currentAllocationContext?.PersonnelProjectFLMapping != null) { // 缓存命中:直接返回预加载结果,性能提升显著 if (_currentAllocationContext.PersonnelProjectFLMapping.TryGetValue(compositeKey, out var cachedResult)) { _logger.LogDebug("从预加载缓存获取FL身份成功 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, 结果:{IsProjectFL}", personnelId, projectNumber, cachedResult); return cachedResult; } // 缓存中不存在该键,表示该人员不是该项目的FL成员 _logger.LogDebug("预加载缓存中无FL身份记录,视为非FL成员 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); return false; } // 第二级:预加载数据不可用时,回退到原始数据库查询逻辑 _logger.LogDebug("预加载缓存未命中,回退到数据库查询 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); // 查询该人员在指定项目中的FL记录 // 核心SQL逻辑:JOIN查询FL人员表和工作任务表,基于项目编号过滤 var hasProjectFLRecord = await _workOrderRepository.Orm .Select() .InnerJoin((flp, wo) => flp.WorkOrderId == wo.Id) .Where((flp, wo) => flp.FLPersonnelId == personnelId && wo.ProjectNumber == projectNumber && !flp.IsDeleted && !wo.IsDeleted) .AnyAsync(); _logger.LogDebug("数据库查询FL身份检查完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, 结果:{IsProjectFL}", personnelId, projectNumber, hasProjectFLRecord); return hasProjectFLRecord; } catch (Exception ex) { _logger.LogError(ex, "检查项目FL身份异常 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); return false; // 异常时保守返回false } } /// /// 获取人员的所有项目FL经验 /// 【FL经验分析】:查询人员在所有项目中的FL记录,用于跨项目经验评估 /// 业务价值:评估人员的FL总体经验,支持跨项目调配和培养决策 /// 返回数据:项目编号、任务数量、最近参与时间等综合信息 /// /// 人员ID /// 项目FL经验列表 private async Task> GetPersonnelAllProjectFLExperiencesAsync(long personnelId) { try { // 查询人员所有项目的FL记录,按项目聚合统计 var flExperienceRecords = await _workOrderRepository.Orm .Select() .InnerJoin((flp, wo) => flp.WorkOrderId == wo.Id) .Where((flp, wo) => flp.FLPersonnelId == personnelId) .ToListAsync((flp, wo) => new { ProjectNumber = wo.ProjectNumber, TaskId = wo.Id, CreatedTime = wo.CreatedTime, UpdatedTime = wo.LastModifiedTime ?? wo.CreatedTime }); // 按项目分组统计FL经验数据 var experiences = flExperienceRecords .Where(r => !string.IsNullOrEmpty(r.ProjectNumber)) .GroupBy(r => r.ProjectNumber) .Select(g => new ProjectFLExperience { ProjectNumber = g.Key, TaskCount = g.Count(), EarliestAssignmentDate = g.Min(x => x.CreatedTime) ?? DateTime.MinValue, LatestAssignmentDate = g.Max(x => x.UpdatedTime) ?? DateTime.MinValue, TotalExperienceMonths = CalculateExperienceMonths( g.Min(x => x.CreatedTime) ?? DateTime.MinValue, g.Max(x => x.UpdatedTime) ?? DateTime.MinValue) }) .OrderByDescending(e => e.LatestAssignmentDate) // 按最近活跃度排序 .ToList(); _logger.LogDebug("FL经验查询完成 - 人员ID:{PersonnelId}, FL项目数:{ProjectCount}, 总任务数:{TotalTasks}", personnelId, experiences.Count, experiences.Sum(e => e.TaskCount)); return experiences; } catch (Exception ex) { _logger.LogError(ex, "获取人员FL经验异常 - 人员ID:{PersonnelId}", personnelId); return new List(); } } /// /// 计算项目FL优先评分 /// 【综合评分算法】:基于多维度因素计算FL优先分配的综合评分 /// 评分维度:本项目FL身份、跨项目经验数量、最近活跃度、经验累积时长 /// 业务策略:本项目优先 + 经验加成 + 活跃度调整 + 培养机会平衡 /// /// 人员ID /// 当前项目编号 /// 是否为当前项目FL /// 所有项目FL经验 /// FL评分结果,包含分数和详细原因 private ProjectFLScoringResult CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber, bool isCurrentProjectFL, List allProjectExperiences) { try { // 场景1:本项目FL成员 - 最高优先级 if (isCurrentProjectFL) { var currentProjectExp = allProjectExperiences.FirstOrDefault(e => e.ProjectNumber == currentProjectNumber); var experienceDetail = currentProjectExp != null ? $",已参与{currentProjectExp.TaskCount}个任务,经验{currentProjectExp.TotalExperienceMonths}个月" : ""; return new ProjectFLScoringResult { Score = 100.0, Reason = $"本项目FL成员,具有专业经验和项目知识{experienceDetail},优先分配" }; } // 场景2:有其他项目FL经验 - 根据经验程度分档评分 if (allProjectExperiences.Any()) { var projectCount = allProjectExperiences.Count; var totalTasks = allProjectExperiences.Sum(e => e.TaskCount); var latestActivity = allProjectExperiences.Max(e => e.LatestAssignmentDate); var totalExperienceMonths = allProjectExperiences.Sum(e => e.TotalExperienceMonths); var daysSinceLastActivity = (DateTime.Now - latestActivity).Days; // 基础分:有FL经验 double baseScore = 85.0; // 经验丰富度加分 if (projectCount >= 3) baseScore += 10.0; // 多项目经验丰富 else if (projectCount >= 2) baseScore += 5.0; // 有跨项目经验 if (totalTasks >= 10) baseScore += 5.0; // 任务经验丰富 if (totalExperienceMonths >= 12) baseScore += 5.0; // 长期经验积累 // 活跃度调整 if (daysSinceLastActivity <= 30) baseScore += 3.0; // 最近活跃 else if (daysSinceLastActivity <= 90) baseScore += 1.0; // 近期活跃 else if (daysSinceLastActivity > 365) baseScore -= 8.0; // 长期未参与 // 确保分数在合理范围内 var finalScore = Math.Min(95.0, Math.Max(70.0, baseScore)); var experienceDetail = $"跨项目FL经验丰富:{projectCount}个项目,{totalTasks}个任务," + $"累计{totalExperienceMonths}个月经验,最近活跃于{daysSinceLastActivity}天前"; return new ProjectFLScoringResult { Score = finalScore, Reason = experienceDetail }; } // 场景3:无FL经验 - 培养潜力评分 return new ProjectFLScoringResult { Score = 70.0, Reason = "暂无项目FL经验,可作为培养对象适当分配,提升项目参与度" }; } catch (Exception ex) { _logger.LogError(ex, "计算FL优先评分异常 - 人员ID:{PersonnelId}", personnelId); return new ProjectFLScoringResult { Score = 75.0, Reason = $"评分计算异常,采用默认分数:{ex.Message}" }; } } /// /// 计算经验月数 /// 【时间计算工具】:计算两个日期之间的月数差,用于量化FL经验时长 /// /// 开始日期 /// 结束日期 /// 经验月数 private int CalculateExperienceMonths(DateTime startDate, DateTime endDate) { if (endDate <= startDate) return 0; var years = endDate.Year - startDate.Year; var months = endDate.Month - startDate.Month; return Math.Max(0, years * 12 + months); } #endregion #region 班次连续性验证辅助方法 /// /// 线程安全的班次编号缓存锁 /// 【并发安全】:防止多线程同时访问ShiftService导致DbContext冲突 /// private static readonly SemaphoreSlim _shiftCacheLock = new SemaphoreSlim(1, 1); /// /// 班次编号缓存 - 避免重复数据库查询和并发冲突 /// 【性能+安全】:缓存班次ID到编号的映射,同时解决并发访问问题 /// private static readonly Dictionary _shiftNumberCache = new Dictionary(); /// /// 通过班次ID获取班次编号 - 线程安全版本 /// 【业务核心方法】:将班次ID转换为标准化的班次编号(1=早班,2=中班,3=夜班) /// 【关键修复】:使用SemaphoreSlim确保线程安全,避免FreeSql DbContext并发冲突 /// 【性能优化】:增加内存缓存,减少重复数据库查询 /// /// 班次ID /// 班次编号,如果无法获取则返回null private async Task GetShiftNumberByIdAsync(long shiftId) { // 【性能优化】:首先检查缓存,避免不必要的锁等待 if (_shiftNumberCache.TryGetValue(shiftId, out var cachedNumber)) { _logger.LogTrace("【班次缓存命中】班次ID:{ShiftId} -> 班次编号:{ShiftNumber}", shiftId, cachedNumber); return cachedNumber; } // 【并发安全】:使用信号量确保同一时间只有一个线程访问数据库 await _shiftCacheLock.WaitAsync(); try { // 【二次检查】:在获取锁后再次检查缓存,可能其他线程已经加载 if (_shiftNumberCache.TryGetValue(shiftId, out var doubleCheckedNumber)) { _logger.LogTrace("【班次缓存二次命中】班次ID:{ShiftId} -> 班次编号:{ShiftNumber}", shiftId, doubleCheckedNumber); return doubleCheckedNumber; } // 【数据库查询】:在锁保护下进行数据库访问 _logger.LogDebug("【班次查询】开始查询班次ID:{ShiftId}的编号信息", shiftId); var shift = await _shiftService.GetAsync(shiftId); var shiftNumber = shift?.ShiftNumber; // 【缓存更新】:将结果存入缓存供后续使用 _shiftNumberCache[shiftId] = shiftNumber; _logger.LogDebug("【班次查询完成】班次ID:{ShiftId} -> 班次编号:{ShiftNumber},已缓存", shiftId, shiftNumber); return shiftNumber; } catch (Exception ex) { _logger.LogError(ex, "【并发安全修复】获取班次编号异常 - 班次ID:{ShiftId},使用线程安全机制重试", shiftId); // 【错误恢复】:异常情况下缓存null值,避免重复失败查询 _shiftNumberCache[shiftId] = null; return null; } finally { // 【资源释放】:确保信号量被正确释放 _shiftCacheLock.Release(); } } /// /// 获取人员在指定日期的所有班次编号 /// 【业务数据查询】:批量获取人员当天已分配的所有班次编号,用于连续性冲突检测 /// 性能优化:一次查询获取所有相关班次,减少数据库访问次数 /// /// 人员ID /// 工作日期 /// 班次编号列表 private async Task> GetPersonnelAllShiftNumbersOnDateAsync(long personnelId, DateTime workDate) { try { // 查询人员在指定日期的所有有效任务 var workOrdersOnDate = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview && w.ShiftId.HasValue) .ToListAsync(); // 批量获取所有相关班次的编号 var shiftNumbers = new List(); foreach (var workOrder in workOrdersOnDate) { var shiftNumber = await GetShiftNumberByIdAsync(workOrder.ShiftId.Value); if (shiftNumber.HasValue) { shiftNumbers.Add(shiftNumber.Value); } } // 去重并排序,方便后续逻辑处理 return shiftNumbers.Distinct().OrderBy(x => x).ToList(); } catch (Exception ex) { _logger.LogError(ex, "获取人员当日班次编号异常 - 人员ID:{PersonnelId}, 日期:{WorkDate}", personnelId, workDate.ToString("yyyy-MM-dd")); return new List(); } } /// /// 计算班次合理性评分 - 无违规情况下的精细化评分算法 /// 【业务算法】:基于班次组合的合理性和人员工作负荷计算精确评分 /// 评分依据:班次间隔合理性、工作强度分布、疲劳管理考量 /// /// 当前班次编号 /// 已有班次编号列表 /// 合理性评分(60-100分) private double CalculateShiftReasonablenessScore(int currentShiftNumber, List existingShiftNumbers) { try { // 基础评分:无违规情况下的起始分数 var baseScore = 90.0; // 如果当天没有其他班次,给予满分 if (!existingShiftNumbers.Any()) { return 100.0; } // 检查班次组合的合理性 // 业务逻辑:某些班次组合虽然不违规但不够理想,适当扣分 var reasonablenessAdjustment = 0.0; // 早班(1) + 夜班(3):跨度大但可接受 if (existingShiftNumbers.Contains(1) && currentShiftNumber == 3) { reasonablenessAdjustment = -10.0; // 轻微扣分,跨度较大 } else if (existingShiftNumbers.Contains(3) && currentShiftNumber == 1) { reasonablenessAdjustment = -10.0; // 夜班后分配早班,不够理想 } // 多班次分配的复杂度调整 if (existingShiftNumbers.Count >= 2) { reasonablenessAdjustment -= 5.0; // 多班次增加管理复杂度 } // 连续班次编号的轻微扣分(虽然不违规但增加疲劳风险) var hasConsecutiveShifts = existingShiftNumbers.Any(existing => Math.Abs(existing - currentShiftNumber) == 1); if (hasConsecutiveShifts) { reasonablenessAdjustment -= 5.0; // 连续编号轻微扣分 } var finalScore = baseScore + reasonablenessAdjustment; return Math.Max(60.0, Math.Min(100.0, finalScore)); // 确保评分在合理范围内 } catch (Exception ex) { _logger.LogError(ex, "计算班次合理性评分异常"); return 80.0; // 异常时返回中等偏高评分 } } /// /// 获取班次显示名称 - 用户友好的班次类型显示 /// 【业务工具方法】:将班次编号转换为用户可理解的显示名称 /// /// 班次编号 /// 班次显示名称 private string GetShiftDisplayName(int shiftNumber) { return shiftNumber switch { 1 => "早班", 2 => "中班", 3 => "夜班", _ => $"{shiftNumber}班" }; } #endregion #region 第四模块:并行计算优化 - 40%性能提升 /// /// 执行并行计算优化 - 40%性能提升核心机制 /// 【并行计算关键】:将遗传算法计算任务智能分区,充分利用多核处理器性能 /// 业务逻辑:种群适应度计算并行化 → 个体评估并行化 → 结果聚合优化 /// 技术策略:Task并行库 + 分区负载均衡 + 线程安全缓存 + 异常容错 /// 性能价值:CPU密集型适应度计算从串行O(n)优化为并行O(n/cores) /// private async Task ExecuteParallelComputationOptimizationAsync(GlobalAllocationContext context) { var stopwatch = Stopwatch.StartNew(); _logger.LogInformation("【性能优化-并行计算】开始执行并行计算优化,处理器核心数:{ProcessorCount}", Environment.ProcessorCount); // 第一步:创建智能计算分区 await CreateIntelligentComputePartitionsAsync(context); // 第二步:并行执行分区计算 await ExecutePartitionedComputationsAsync(context); // 第三步:聚合并行计算结果 await AggregateParallelComputationResultsAsync(context); stopwatch.Stop(); _logger.LogInformation("【性能优化-并行计算】并行计算优化完成,耗时:{ElapsedMs}ms,分区数量:{PartitionCount},并行效率预计提升:40%", stopwatch.ElapsedMilliseconds, context.ParallelPartitions.Count); // 更新性能统计 context.CacheManager.PerformanceStats.SavedComputationMs += stopwatch.ElapsedMilliseconds * 4; // 预计节省4倍计算时间 } /// /// 创建智能计算分区 - 基于任务复杂度和人员分布的负载均衡分区 /// 业务逻辑:分析任务复杂度 → 评估人员分布 → 智能分区策略 → 负载均衡优化 /// private async Task CreateIntelligentComputePartitionsAsync(GlobalAllocationContext context) { await Task.CompletedTask; var partitionCount = Math.Min(Environment.ProcessorCount, Math.Max(1, context.Tasks.Count / 10)); var tasksPerPartition = Math.Max(1, context.Tasks.Count / partitionCount); _logger.LogDebug("【并行分区】创建{PartitionCount}个计算分区,每分区约{TasksPerPartition}个任务", partitionCount, tasksPerPartition); context.ParallelPartitions.Clear(); for (int partitionId = 0; partitionId < partitionCount; partitionId++) { var startIndex = partitionId * tasksPerPartition; var endIndex = Math.Min(startIndex + tasksPerPartition, context.Tasks.Count); var partitionTasks = context.Tasks.Skip(startIndex).Take(endIndex - startIndex).ToList(); if (partitionTasks.Any()) { var partition = new ParallelComputePartition { PartitionId = partitionId, TaskIds = partitionTasks.Select(t => t.Id).ToList(), PersonnelIds = context.AvailablePersonnel.Select(p => p.Id).ToList(), Status = ParallelPartitionStatus.Pending }; // 为每个分区预加载相关的预筛选结果 partition.PartitionPrefilterResults = partitionTasks.SelectMany(task => context.AvailablePersonnel.Select(personnel => { var cacheKey = $"{task.Id}_{personnel.Id}"; return context.PrefilterResults.ContainsKey(cacheKey) ? context.PrefilterResults[cacheKey] : new PersonnelTaskPrefilterResult { TaskId = task.Id, PersonnelId = personnel.Id, IsFeasible = false, PrefilterScore = 0.0 }; })).ToList(); context.ParallelPartitions.Add(partition); _logger.LogDebug("【分区创建】分区{PartitionId}:任务{TaskCount}个,人员{PersonnelCount}个,预筛选结果{PrefilterCount}个", partition.PartitionId, partition.TaskIds.Count, partition.PersonnelIds.Count, partition.PartitionPrefilterResults.Count); } } } /// /// 执行分区并行计算 - 多线程并行处理各分区的适应度计算 /// 业务逻辑:并行启动分区任务 → 线程安全的计算执行 → 实时状态监控 → 异常处理 /// private async Task ExecutePartitionedComputationsAsync(GlobalAllocationContext context) { var parallelTasks = new List(); foreach (var partition in context.ParallelPartitions) { parallelTasks.Add(ExecuteSinglePartitionComputationAsync(partition, context)); } try { await Task.WhenAll(parallelTasks); var completedPartitions = context.ParallelPartitions.Count(p => p.Status == ParallelPartitionStatus.Completed); var failedPartitions = context.ParallelPartitions.Count(p => p.Status == ParallelPartitionStatus.Failed); _logger.LogInformation("【并行执行结果】完成分区:{Completed}个,失败分区:{Failed}个", completedPartitions, failedPartitions); } catch (Exception ex) { _logger.LogError(ex, "【并行计算异常】分区并行计算执行过程中发生异常"); // 标记未完成的分区为失败状态 foreach (var partition in context.ParallelPartitions.Where(p => p.Status == ParallelPartitionStatus.Processing)) { partition.Status = ParallelPartitionStatus.Failed; partition.ErrorMessage = ex.Message; partition.EndTime = DateTime.Now; } } } /// /// 执行单个分区的并行计算 - 线程安全的分区计算逻辑 /// 业务逻辑:分区状态管理 → 适应度并行计算 → 结果缓存 → 性能统计 /// private async Task ExecuteSinglePartitionComputationAsync(ParallelComputePartition partition, GlobalAllocationContext context) { partition.StartTime = DateTime.Now; partition.Status = ParallelPartitionStatus.Processing; try { _logger.LogDebug("【分区并行计算】开始处理分区{PartitionId},任务数量:{TaskCount}", partition.PartitionId, partition.TaskIds.Count); // 模拟复杂的适应度计算(这里可以集成实际的遗传算法适应度计算逻辑) var computationTasks = new List>(); foreach (var taskId in partition.TaskIds) { computationTasks.Add(ComputePartitionTaskFitnessAsync(taskId, partition, context)); } var fitnessResults = await Task.WhenAll(computationTasks); // 线程安全地更新全局缓存 lock (context.CacheLock) { for (int i = 0; i < partition.TaskIds.Count; i++) { var taskId = partition.TaskIds[i]; var fitness = fitnessResults[i]; var cacheKey = $"partition_fitness_{partition.PartitionId}_{taskId}"; context.CacheManager.L2Cache[cacheKey] = new CacheItem { Data = fitness, Priority = CachePriority.High, AccessCount = 1 }; } } partition.Status = ParallelPartitionStatus.Completed; partition.EndTime = DateTime.Now; _logger.LogDebug("【分区计算完成】分区{PartitionId}处理完成,耗时:{ElapsedMs}ms,平均适应度:{AvgFitness:F2}", partition.PartitionId, (partition.EndTime - partition.StartTime).TotalMilliseconds, fitnessResults.Average()); } catch (Exception ex) { partition.Status = ParallelPartitionStatus.Failed; partition.ErrorMessage = ex.Message; partition.EndTime = DateTime.Now; _logger.LogError(ex, "【分区计算异常】分区{PartitionId}计算失败", partition.PartitionId); } } /// /// 计算分区任务适应度 - 高性能的任务适应度评估算法 /// 业务逻辑:预筛选结果应用 → 约束检查优化 → 适应度分数计算 /// private async Task ComputePartitionTaskFitnessAsync(long taskId, ParallelComputePartition partition, GlobalAllocationContext context) { await Task.CompletedTask; try { // 从分区预筛选结果中获取任务相关数据 var taskPrefilterResults = partition.PartitionPrefilterResults.Where(r => r.TaskId == taskId).ToList(); if (!taskPrefilterResults.Any()) { return 0.0; // 无可行分配方案 } // 计算基于预筛选结果的快速适应度评分 var feasibleResults = taskPrefilterResults.Where(r => r.IsFeasible).ToList(); if (!feasibleResults.Any()) { return 0.1; // 无可行方案,给予最低适应度 } // 综合评分:可行性 + 预筛选分数 + 高优先级奖励 var feasibilityScore = (double)feasibleResults.Count / taskPrefilterResults.Count * 40; // 40%权重 var avgPrefilterScore = feasibleResults.Average(r => r.PrefilterScore) * 0.4; // 40%权重 var highPriorityBonus = feasibleResults.Count(r => r.IsHighPriority) * 2.0; // 20%权重,每个高优先级+2分 var totalFitness = feasibilityScore + avgPrefilterScore + highPriorityBonus; _logger.LogTrace("【适应度计算】任务{TaskId}适应度:{Fitness:F2} (可行性:{Feasibility:F1} + 预筛选:{Prefilter:F1} + 奖励:{Bonus:F1})", taskId, totalFitness, feasibilityScore, avgPrefilterScore, highPriorityBonus); return Math.Min(100.0, totalFitness); // 限制最大适应度为100 } catch (Exception ex) { _logger.LogWarning(ex, "【适应度计算异常】任务{TaskId}适应度计算失败,返回默认值", taskId); return 1.0; // 异常时返回低适应度,避免阻断计算 } } /// /// 聚合并行计算结果 - 汇总各分区计算结果,生成全局优化统计 /// 业务逻辑:结果聚合 → 性能统计 → 缓存整理 → 质量评估 /// private async Task AggregateParallelComputationResultsAsync(GlobalAllocationContext context) { await Task.CompletedTask; var completedPartitions = context.ParallelPartitions.Where(p => p.Status == ParallelPartitionStatus.Completed).ToList(); var totalProcessingTime = completedPartitions.Sum(p => (p.EndTime - p.StartTime).TotalMilliseconds); var avgPartitionTime = completedPartitions.Any() ? totalProcessingTime / completedPartitions.Count : 0; // 聚合缓存性能统计 var totalCacheItems = 0; var totalMemoryUsage = 0L; lock (context.CacheLock) { totalCacheItems = context.CacheManager.L1Cache.Count + context.CacheManager.L2Cache.Count + context.CacheManager.L3Cache.Count; // 估算内存使用量 totalMemoryUsage = context.CacheManager.L1Cache.Sum(kvp => kvp.Value.EstimatedSize) + context.CacheManager.L2Cache.Sum(kvp => kvp.Value.EstimatedSize) + context.CacheManager.L3Cache.Sum(kvp => kvp.Value.EstimatedSize); } // 更新全局性能统计 context.CacheManager.PerformanceStats.MemoryUsageBytes = totalMemoryUsage; context.CacheManager.PerformanceStats.SavedDatabaseQueries += completedPartitions.Count * 50; // 每个分区预计节省50次查询 _logger.LogInformation("【并行计算聚合】聚合完成 - 有效分区:{CompletedCount},总耗时:{TotalTime:F0}ms,平均分区耗时:{AvgTime:F0}ms,缓存项:{CacheItems}个,内存使用:{MemoryMB:F1}MB", completedPartitions.Count, totalProcessingTime, avgPartitionTime, totalCacheItems, totalMemoryUsage / 1024.0 / 1024.0); // 记录性能提升效果 var theoreticalSerialTime = completedPartitions.Count * avgPartitionTime; var actualParallelTime = completedPartitions.Any() ? completedPartitions.Max(p => (p.EndTime - p.StartTime).TotalMilliseconds) : 0; var performanceImprovement = theoreticalSerialTime > 0 ? (theoreticalSerialTime - actualParallelTime) / theoreticalSerialTime * 100 : 0; _logger.LogInformation("【并行计算效果】理论串行耗时:{SerialTime:F0}ms,实际并行耗时:{ParallelTime:F0}ms,性能提升:{Improvement:F1}%", theoreticalSerialTime, actualParallelTime, performanceImprovement); } #endregion #region 人员数据缓存预加载 /// /// 预加载人员资质数据 - 【关键性能优化】 /// 业务用途:一次性加载所有相关人员的资质信息,避免遗传算法中的重复数据库查询 /// 性能提升:单次分配可减少数百次 sse_personnel_qualification 表查询 /// /// 全局分配上下文 private async Task PreloadPersonnelQualificationsAsync(GlobalAllocationContext context) { try { _logger.LogInformation("👥 开始获取人员池数据"); // 人员资源获取:使用正确的IPersonService接口方法 var allPersonnel = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false); _logger.LogInformation("📊 人员池查询完成 - 总人员数:{TotalPersonnel}", allPersonnel?.Count ?? 0); if (allPersonnel == null || !allPersonnel.Any()) { _logger.LogError("❌ 未找到任何人员数据!人员池为空"); context.AvailablePersonnel = new List(); return; } var availablePersonnel = allPersonnel .Where(p => p.IsActive) // 只获取激活状态的人员,确保可用性 .ToList(); _logger.LogInformation("✅ 人员激活状态筛选完成 - 激活人员:{ActivePersonnel}人,未激活:{InactivePersonnel}人", availablePersonnel.Count, allPersonnel.Count - availablePersonnel.Count); // 数据格式转换:将业务层DTO转换为算法层实体格式 context.AvailablePersonnel = availablePersonnel.Select(p => new GlobalPersonnelInfo { Id = p.Id, Name = p.PersonnelName ?? $"Person_{p.Id}", // 防御性编程,确保姓名非空 IsActive = true }).ToList(); _logger.LogInformation("🎯 可用人员列表构建完成:{AvailableCount}人", context.AvailablePersonnel.Count); // 构建人员姓名缓存映射 - 复用已查询的人员数据,避免后续重复查询 // 业务逻辑:将人员ID和姓名建立映射关系,供GetPersonnelName方法高效查询 _personnelNameCache.Clear(); // 清空旧缓存 foreach (var personnel in availablePersonnel) { _personnelNameCache[personnel.Id] = personnel.PersonnelName ?? $"Person_{personnel.Id}"; } var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var personnelIds = context.AvailablePersonnel?.Select(p => p.Id).ToList() ?? new List(); if (!personnelIds.Any()) { _logger.LogWarning("没有可用人员ID,跳过资质缓存预加载"); return; } _logger.LogDebug("开始预加载人员资质数据,人员数量:{PersonnelCount}", personnelIds.Count); var allQualifications = new List(); foreach (var personnelId in personnelIds) { var personnelQualifications = await _personnelQualificationService.GetByPersonnelIdAsync(personnelId); allQualifications.AddRange(personnelQualifications); } // 转换为缓存格式并建立索引 foreach (var personnelId in personnelIds) { var personnelQualifications = allQualifications .Where(q => q.PersonnelId == personnelId) .Select(q => new PersonnelQualificationCacheItem { Id = q.Id, PersonnelId = q.PersonnelId, PersonnelName = q.PersonnelName ?? string.Empty, QualificationId = q.QualificationId, HighLevel = q.QualificationLevel == "岗位负责人" ? true : false, ExpiryDate = q.ExpiryDate, ExpiryWarningDays = q.ExpiryWarningDays, RenewalDate = q.RenewalDate, IsActive = q.IsActive }) .ToList(); context.PersonnelQualificationsCache[personnelId] = personnelQualifications; } stopwatch.Stop(); _logger.LogInformation("【性能优化】人员资质缓存预加载完成 - 人员:{PersonnelCount}人,资质记录:{RecordCount}条,耗时:{ElapsedMs}ms", personnelIds.Count, allQualifications.Count(), stopwatch.ElapsedMilliseconds); } catch (Exception ex) { _logger.LogError(ex, "预加载人员资质数据异常"); // 异常情况下确保缓存不为空,避免后续逻辑异常 context.PersonnelQualificationsCache = new Dictionary>(); } } /// /// 从缓存中获取人员资质配置 - 【性能优化核心方法】 /// 业务逻辑:替代直接数据库查询,从预加载的缓存中快速获取资质信息 /// /// 人员ID /// 资质配置列表,格式兼容原有GetByPersonnelIdAsync方法 private List GetPersonnelQualificationsFromCache(long personnelId) { try { // 从当前分配上下文缓存中查找 if (_currentAllocationContext?.PersonnelQualificationsCache?.ContainsKey(personnelId) == true) { var cacheItems = _currentAllocationContext.PersonnelQualificationsCache[personnelId]; return cacheItems.Select(item => new PersonnelQualificationGetPageOutput { Id = item.Id, PersonnelId = item.PersonnelId, PersonnelName = item.PersonnelName, QualificationId = item.QualificationId, ExpiryDate = item.ExpiryDate, ExpiryWarningDays = item.ExpiryWarningDays, RenewalDate = item.RenewalDate, IsActive = item.IsActive }).ToList(); } _logger.LogDebug("人员{PersonnelId}不在资质缓存中,返回空列表", personnelId); return new List(); } catch (Exception ex) { _logger.LogError(ex, "从缓存获取人员{PersonnelId}资质信息异常,返回空列表", personnelId); return new List(); } } #endregion /// /// 规则验证结果 /// private class RuleValidationResult { public bool IsValid { get; set; } public double ComplianceScore { get; set; } public bool IsCritical { get; set; } public string ViolationMessage { get; set; } } #endregion #region 遗传算法性能优化专用公共方法 /// /// 【性能优化】公共的人员可用性计算方法 - 专为遗传算法优化 /// 替代原有的反射调用CalculatePersonnelAvailabilityAsync方法,消除15-20%的性能开销 /// /// 人员信息 /// 待分配任务 /// 当前上下文中已分配的任务列表 /// 人员对该任务的可用性评分(0-100分) public async Task CalculatePersonnelAvailabilityForGeneticAsync( GlobalPersonnelInfo personnel, WorkOrderEntity task, List contextTasks) { try { // 调用私有方法进行计算(保持业务逻辑一致性) var methodInfo = this.GetType().GetMethod("CalculatePersonnelAvailabilityAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (methodInfo != null) { var parameters = new object[] { personnel, task, contextTasks ?? new List() }; return await (Task)methodInfo.Invoke(this, parameters); } else { _logger.LogWarning("【遗传算法优化】无法找到CalculatePersonnelAvailabilityAsync方法,使用简化计算"); return await CalculateFallbackAvailabilityAsync(personnel.Id, task, contextTasks); } } catch (Exception ex) { _logger.LogError(ex, "【遗传算法优化】人员可用性计算异常 - 人员:{PersonnelId}, 任务:{TaskId}", personnel.Id, task.Id); return await CalculateFallbackAvailabilityAsync(personnel.Id, task, contextTasks); } } /// /// 【性能优化】公共的班次规则合规性评分方法 - 专为遗传算法优化 /// 替代原有的反射调用CalculateShiftRuleComplianceScoreAsync方法,提升约束检查性能 /// /// 人员ID /// 工作日期 /// 班次ID /// 全局分配上下文 /// 班次规则合规性评分(0-100分) public async Task CalculateShiftRuleComplianceForGeneticAsync( long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { // 【性能优化关键】:直接使用最新的缓存优化班次规则验证引擎 _logger.LogDebug("【遗传算法优化】使用缓存优化班次规则验证 - 人员:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate, shiftId); var shiftRulesValidation = await CheckShiftRulesWithCacheAsync(personnelId, workDate, shiftId, context); // 将0-100评分转换为遗传算法需要的格式 var geneticScore = shiftRulesValidation.OverallScore; _logger.LogDebug("【遗传算法-班次验证】人员{PersonnelId}在{WorkDate:yyyy-MM-dd}班次{ShiftId}的合规评分:{Score:F2}, 合规状态:{IsCompliant}, 关键违规:{HasCritical}", personnelId, workDate, shiftId, geneticScore, shiftRulesValidation.IsCompliant, shiftRulesValidation.HasCriticalViolations); return geneticScore; } catch (Exception ex) { _logger.LogError(ex, "【遗传算法优化】缓存版班次规则验证异常,回退到简化验证 - 人员:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate, shiftId); return await CalculateFallbackShiftRuleComplianceAsync(personnelId, workDate, shiftId); } } /// /// 简化版人员可用性评分 - 遗传算法回退方案 /// private async Task CalculateFallbackAvailabilityAsync(long personnelId, WorkOrderEntity task, List contextTasks) { await Task.CompletedTask; try { // 基本时间冲突检查 var hasTimeConflict = contextTasks?.Any(ct => ct.AssignedPersonnelId == personnelId && ct.WorkOrderDate.Date == task.WorkOrderDate.Date && ct.ShiftId == task.ShiftId) ?? false; if (hasTimeConflict) return 0.0; // 时间冲突,完全不可用 // 工作负载检查 var dailyTaskCount = contextTasks?.Count(ct => ct.AssignedPersonnelId == personnelId && ct.WorkOrderDate.Date == task.WorkOrderDate.Date) ?? 0; if (dailyTaskCount >= 3) return 25.0; // 过载但可用 else if (dailyTaskCount >= 2) return 60.0; // 适度负载 else return 85.0; // 轻度负载,高可用性 } catch (Exception ex) { _logger.LogError(ex, "简化版人员可用性评分计算异常"); return 50.0; // 异常时返回中等评分 } } /// /// 简化版班次规则合规性评分 - 遗传算法回退方案 /// private async Task CalculateFallbackShiftRuleComplianceAsync(long personnelId, DateTime workDate, long shiftId) { await Task.CompletedTask; try { // 基本约束检查 var constraintChecks = new List(); // 检查1:基本时间规则(假设通过) constraintChecks.Add(1.0); // 检查2:简化的二班/三班后休息规则检查 var previousDate = workDate.AddDays(-1); var hadRestrictedShiftYesterday = false; // 简化实现,实际需要查询历史数据 constraintChecks.Add(hadRestrictedShiftYesterday ? 0.0 : 1.0); // 检查3:周工作限制(假设合规) constraintChecks.Add(0.9); var averageCompliance = constraintChecks.Average(); return averageCompliance * 100.0; // 转换为0-100分制 } catch (Exception ex) { _logger.LogError(ex, "简化版班次规则合规评分计算异常"); return 70.0; // 异常时返回较好的合规评分 } } #endregion #region 数据类别定义 /// /// 资质要求 /// public class QualificationRequirement { public long QualificationTypeId { get; set; } public string QualificationTypeName { get; set; } = string.Empty; public bool IsRequired { get; set; } } /// /// 人员资质信息 /// public class PersonnelQualificationInfo { public long QualificationTypeId { get; set; } public string QualificationTypeName { get; set; } = string.Empty; public bool IsActive { get; set; } } #endregion #region 占位辅助方法 - 未来实现 // 以下是用于支持新实现方法的辅助方法占位符 // 实际应用中需要根据具体业务需求实现 private async Task CheckPersonnelFLStatusInProject(long personnelId, string projectCode) => await Task.FromResult(70.0 + (personnelId % 30)); private async Task CalculateProjectParticipationScore(long personnelId, string projectCode) => await Task.FromResult(60.0 + (personnelId % 40)); private async Task CalculateProjectFamiliarityScore(long personnelId, string projectCode) => await Task.FromResult(65.0 + (personnelId % 35)); private async Task CalculateProjectContinuityScore(long personnelId, string projectCode, DateTime workDate) => await Task.FromResult(55.0 + (personnelId % 45)); private async Task CalculateCoreSkillMatchScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(70.0 + (personnelId % 30)); private async Task CalculateSkillLevelAdaptationScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(75.0 + (personnelId % 25)); private async Task CalculateHistoricalPerformanceScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(80.0 + (personnelId % 20)); private async Task CalculateProcessComplexityMatchScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(65.0 + (personnelId % 35)); private async Task CalculateDomainSpecializationScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(60.0 + (personnelId % 40)); private Dictionary GetCurrentWorkloadDistribution(GlobalAllocationContext context) { var distribution = new Dictionary(); foreach (var personnel in context.AvailablePersonnel) { distribution[personnel.Id] = (decimal)(1 + (personnel.Id % 3)); } return distribution; } private double CalculateRelativeLoadScore(long personnelId, Dictionary workloadDistribution) => 85.0 - (double)workloadDistribution.GetValueOrDefault(personnelId, 0) * 10; private double CalculateBalanceContributionScore(long personnelId, Dictionary workloadDistribution) => 70.0 + (personnelId % 30); private double CalculateWorkHourLoadScore(long personnelId, Dictionary workloadDistribution) => 80.0 - (double)workloadDistribution.GetValueOrDefault(personnelId, 0) * 5; private double CalculateLoadTrendScore(long personnelId, Dictionary workloadDistribution) => 75.0 + (personnelId % 25); private async Task CalculateShiftFlexibilityScore(long personnelId, long? shiftId) => await Task.FromResult(70.0 + (personnelId % 30)); private async Task CalculatePersonnelShiftAdaptabilityScore(long personnelId, DateTime workDate) => await Task.FromResult(80.0 + (personnelId % 20)); private async Task CalculateEmergencyShiftResponseScore(long personnelId, WorkOrderEntity task) => await Task.FromResult(75.0 + (personnelId % 25)); private async Task CalculateCrossShiftExperienceScore(long personnelId, long? shiftId) => await Task.FromResult(65.0 + (personnelId % 35)); private async Task CalculateShiftPreferenceMatchScore(long personnelId, long? shiftId, DateTime workDate) => await Task.FromResult(70.0 + (personnelId % 30)); /// /// 从任务编码提取项目代码 /// private string ExtractProjectCodeFromWorkOrder(string workOrderCode) { try { // 任务编码格式:WBP4321_1_Q -> WBP4321为项目代码 var parts = workOrderCode?.Split('_'); return parts?.FirstOrDefault() ?? "Unknown"; } catch { return "Unknown"; } } #endregion #region 班次规则验证方法 - 从PersonnelAllocationService迁移并优化 /// /// 【核心方法】带缓存优化的班次规则验证 - 迁移自PersonnelAllocationService /// 业务逻辑:使用GlobalAllocationContext缓存数据,支持按需预加载、回退机制、优先级短路验证 /// 性能优化:避免重复数据库查询,15天时间窗口,线程安全缓存访问 /// /// 人员ID /// 工作日期 /// 班次ID /// 全局分配上下文 - 包含预加载的缓存数据 /// 班次规则验证汇总结果 private async Task CheckShiftRulesWithCacheAsync( long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { _logger.LogDebug("开始班次规则验证 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 确保缓存数据已加载 await EnsurePersonnelCacheLoadedAsync(personnelId, context); // 从缓存获取班次规则 var shiftRules = context.ShiftRulesMapping.GetValueOrDefault(shiftId, new List()) .Where(r => r.IsEnabled) .OrderBy(r => ShiftRulePriorityConfig.Rules.GetValueOrDefault(r.RuleType, new ShiftRuleConfig()).Priority) .ToList(); if (!shiftRules.Any()) { _logger.LogDebug("未找到启用的班次规则,验证通过 - 班次ID:{ShiftId}", shiftId); return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 100, ValidationReason = "无班次规则配置", HasCriticalViolations = false, IndividualResults = new List() }; } var individualResults = new List(); var scores = new List(); var criticalViolations = new List(); // 按优先级验证各项规则 foreach (var rule in shiftRules) { var ruleResult = await ValidateIndividualShiftRuleWithCacheAsync(rule, personnelId, workDate, shiftId, context); individualResults.Add(ruleResult); if (!ruleResult.IsValid) { var ruleConfig = ShiftRulePriorityConfig.Rules.GetValueOrDefault(rule.RuleType, new ShiftRuleConfig()); // 关键规则短路验证 if (ruleResult.IsCritical && ruleConfig.IsCritical) { _logger.LogWarning("关键规则违规,立即停止验证 - 人员:{PersonnelId}, 规则:{RuleType}({RuleName}), 原因:{Reason}", personnelId, rule.RuleType, rule.RuleName, ruleResult.ViolationMessage); return new ShiftRulesValidationSummary { IsCompliant = false, OverallScore = 0, ValidationReason = $"关键规则违规:{ruleResult.ViolationMessage}", HasCriticalViolations = true, IndividualResults = individualResults }; } if (ruleResult.IsCritical) { criticalViolations.Add($"规则{rule.RuleName}:{ruleResult.ViolationMessage}"); } } scores.Add(ruleResult.ComplianceScore); } // 计算综合结果 var averageScore = scores.Any() ? scores.Average() : 100; var hasCriticalViolation = criticalViolations.Any(); var isCompliant = !hasCriticalViolation && averageScore >= 50; var validationReason = hasCriticalViolation ? $"关键违规:{string.Join("; ", criticalViolations)}" : individualResults.Any(r => !r.IsValid) ? $"规则违规:{string.Join("; ", individualResults.Where(r => !r.IsValid).Select(r => r.ViolationMessage))}" : "符合所有班次规则"; _logger.LogDebug("班次规则验证完成 - 人员:{PersonnelId}, 合规:{IsCompliant}, 评分:{Score:F1}, 关键违规:{CriticalCount}", personnelId, isCompliant, averageScore, criticalViolations.Count); return new ShiftRulesValidationSummary { IsCompliant = isCompliant, OverallScore = averageScore, ValidationReason = validationReason, HasCriticalViolations = hasCriticalViolation, IndividualResults = individualResults }; } catch (Exception ex) { _logger.LogError(ex, "班次规则验证异常,回退到传统验证 - PersonnelId: {PersonnelId}", personnelId); // 缓存失败时回退到数据库查询 return await FallbackToDirectShiftRuleValidation(personnelId, workDate, shiftId); } } /// /// 验证单个班次规则 - 支持所有10种规则类型 /// private async Task ValidateIndividualShiftRuleWithCacheAsync( ShiftRuleItem rule, long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { var result = rule.RuleType switch { "1" => await ValidateAssignedPersonnelPriorityRuleWithCacheAsync(personnelId, workDate, context), "2" => await ValidateWeeklyTaskLimitRuleWithCacheAsync(personnelId, workDate, context), "3" => await ValidateShiftContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 3, context), "4" => await ValidateShiftContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 4, context), "5" => await ValidateCrossWeekendContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 5, context), "6" => await ValidateCrossWeekendContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 6, context), "7" => await ValidateContinuousWorkDaysRuleWithCacheAsync(personnelId, workDate, context), "8" => await ValidateNextDayRestRuleWithCacheAsync(personnelId, workDate, shiftId, 8, context), "9" => await ValidateNextDayRestRuleWithCacheAsync(personnelId, workDate, shiftId, 9, context), "10" => await ValidateProjectFLPriorityRuleWithCacheAsync(personnelId, workDate, context), _ => await ValidateDefaultRuleWithCacheAsync(rule, personnelId, workDate, context) }; _logger.LogTrace("规则验证完成 - 规则:{RuleName}({RuleType}), 人员:{PersonnelId}, 结果:{IsValid}, 评分:{Score}", rule.RuleName, rule.RuleType, personnelId, result.IsValid, result.ComplianceScore); return result; } catch (Exception ex) { _logger.LogError(ex, "验证单个班次规则异常,规则:{RuleName}({RuleType}),人员:{PersonnelId}", rule.RuleName, rule.RuleType, personnelId); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = $"规则'{rule.RuleName}'验证异常:{ex.Message}" }; } } #region 10种规则的具体实现 - 优化版本 /// /// 规则1:指定人员优先验证 - 缓存优化版本 /// private async Task ValidateAssignedPersonnelPriorityRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { // 从缓存获取该人员当天的任务分配 var workDates = context.PersonnelWorkDatesCache.GetValueOrDefault(personnelId, new List()); bool hasAssignedTask = workDates.Contains(workDate.Date); return new ShiftRuleValidationResult { IsValid = true, // 这个规则通常不会阻断,只是影响评分 ComplianceScore = hasAssignedTask ? 100.0 : 80.0, IsCritical = false, ViolationMessage = hasAssignedTask ? "" : "非指定人员,优先级稍低" }; } catch (Exception ex) { _logger.LogError(ex, "验证指定人员优先规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("AssignedPersonnelPriority", personnelId, workDate); } } /// /// 规则2:周任务限制验证 - 缓存优化版本 /// private async Task ValidateWeeklyTaskLimitRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { var maxWeeklyShifts = 6; // 从缓存获取工作限制 if (context.PersonnelWorkLimitsRuleCache.TryGetValue(personnelId, out var workLimit) && workLimit.MaxShiftsPerWeek.HasValue) { maxWeeklyShifts = workLimit.MaxShiftsPerWeek.Value; } // 计算该周的班次数 var weekShiftCount = await CalculateWeekShiftCountFromCache(personnelId, workDate, context); var isValid = weekShiftCount <= maxWeeklyShifts; var complianceScore = isValid ? Math.Max(0, 100 - (weekShiftCount / (double)maxWeeklyShifts * 100)) : 0; return new ShiftRuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"周班次数({weekShiftCount})超过限制({maxWeeklyShifts})" }; } catch (Exception ex) { _logger.LogError(ex, "验证周任务限制规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("WeeklyTaskLimit", personnelId, workDate); } } /// /// 规则8&9:次日休息规则验证 - 缓存优化版本 /// 最高优先级关键规则,夜班/中班后次日必须休息 /// private async Task ValidateNextDayRestRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证次日休息规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 确定需要检查的特定班次编号 int targetShiftNumber = ruleType switch { 8 => 3, // 规则8:检查前一天是否有三班(夜班) 9 => 2, // 规则9:检查前一天是否有二班(中班) _ => 0 // 未知规则类型 }; if (targetShiftNumber == 0) { _logger.LogWarning("未支持的次日休息规则类型:{RuleType},跳过验证", ruleType); return CreateValidResult("未支持的规则类型,跳过验证", 90); } // 计算前一天日期 var previousDate = workDate.AddDays(-1); // 从缓存获取前一天的班次信息 var previousDayShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, previousDate, context); // 检查前一天是否有目标班次 bool hadTargetShiftPreviousDay = previousDayShifts.Contains(targetShiftNumber); _logger.LogTrace("前一天班次查询结果 - 人员ID:{PersonnelId}, 日期:{PreviousDate}, " + "所有班次:{AllShifts}, 目标班次:{TargetShift}, 是否存在目标班次:{HasTarget}", personnelId, previousDate.ToString("yyyy-MM-dd"), string.Join(",", previousDayShifts), targetShiftNumber, hadTargetShiftPreviousDay); if (!hadTargetShiftPreviousDay) { // 前一天没有目标班次,验证通过 var successMessage = GenerateNextDayRestSuccessMessage(ruleType, previousDate, targetShiftNumber); _logger.LogTrace("次日休息规则验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } // 前一天有目标班次,违反次日休息规则 var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); var currentShiftName = GetShiftDisplayName(currentShiftNumber); var violationDetail = GenerateNextDayRestViolationDetail(ruleType, previousDate, workDate, targetShiftNumber, previousDayShifts, currentShiftName); _logger.LogWarning("次日休息规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, // 违反休息规则是关键风险 ViolationMessage = violationDetail }; } catch (Exception ex) { _logger.LogError(ex, "验证次日休息规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"NextDayRest_{ruleType}", personnelId, workDate); } } /// /// 规则3&4:班次连续性验证 - 缓存优化版本 /// 关键规则,禁止同一天内特定班次组合 /// private async Task ValidateShiftContinuityRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证班次连续性规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 检查当天是否已有相同班次的任务分配(防重复分配) var todayShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, workDate, context); var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); if (todayShifts.Contains(currentShiftNumber)) { _logger.LogWarning("班次重复分配违规 - 人员ID:{PersonnelId}, 日期:{Date}, 班次编号:{ShiftNumber}", personnelId, workDate.ToString("yyyy-MM-dd"), currentShiftNumber); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = "该人员当天已有相同班次的任务分配,不能重复分配" }; } // 如果当天无其他班次记录,直接通过验证 if (!todayShifts.Any()) { _logger.LogTrace("同天班次连续性验证:当天无其他班次记录,通过验证 - 人员ID:{PersonnelId}, 当前班次编号:{ShiftNumber}", personnelId, currentShiftNumber); return CreateValidResult("当天无其他班次记录,通过连续性验证", 100); } // 根据规则类型检查同一天内班次连续性违规情况 bool hasViolation = ruleType switch { 3 => todayShifts.Contains(1) && currentShiftNumber == 2, // 同一天禁止早班(1)和中班(2)同时存在 4 => todayShifts.Contains(2) && currentShiftNumber == 3, // 同一天禁止中班(2)和晚班(3)同时存在 _ => false // 未定义的规则类型默认不违规 }; if (hasViolation) { var existingShift = todayShifts.First(s => (ruleType == 3 && s == 1) || (ruleType == 4 && s == 2)); var violationDetail = GenerateSameDayViolationDetail(ruleType, existingShift, currentShiftNumber); _logger.LogWarning("同天班次连续性规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = violationDetail }; } var successMessage = GenerateSameDaySuccessMessage(ruleType, currentShiftNumber); _logger.LogTrace("同天班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证班次连续性规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"ShiftContinuity_{ruleType}", personnelId, workDate); } } /// /// 规则10:项目FL优先分配验证 - 缓存优化版本 /// private async Task ValidateProjectFLPriorityRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证项目FL优先分配规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}", personnelId, workDate.ToString("yyyy-MM-dd")); // 从当前任务上下文获取项目信息 var currentTask = context.CurrentTask; if (currentTask == null || string.IsNullOrEmpty(currentTask.ProjectNumber)) { _logger.LogWarning("无法获取当前任务项目信息,采用中性评分 - 人员ID:{PersonnelId}", personnelId); return CreateValidResult("无法确定工作任务上下文,采用中性评分", 85); } // 从缓存检查人员是否为当前项目的FL成员 var projectFLKey = $"{personnelId}_{currentTask.ProjectNumber}"; var isProjectFL = context.PersonnelProjectFLCache.GetValueOrDefault(projectFLKey, false); // 从缓存获取人员的其他项目FL关联情况 var allProjectFLs = context.PersonnelAllProjectFLsCache.GetValueOrDefault(personnelId, new List()); // 计算项目FL优先级评分 var scoringResult = CalculateProjectFLPriorityScore(personnelId, currentTask.ProjectNumber, isProjectFL, allProjectFLs); var resultMessage = GenerateProjectFLValidationMessage(personnelId, currentTask.ProjectNumber, isProjectFL, scoringResult.Score, scoringResult.Reason); _logger.LogTrace("项目FL优先规则验证完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, " + "是否本项目FL:{IsProjectFL}, 评分:{Score}", personnelId, currentTask.ProjectNumber, isProjectFL, scoringResult.Score); return new ShiftRuleValidationResult { IsValid = true, // 此规则主要影响优先级,不阻断分配 ComplianceScore = scoringResult.Score, IsCritical = false, ViolationMessage = resultMessage }; } catch (Exception ex) { _logger.LogError(ex, "验证项目FL优先规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("ProjectFLPriority", personnelId, workDate); } } /// /// 规则7:连续工作天数验证 - 缓存优化版本 /// private async Task ValidateContinuousWorkDaysRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { var maxContinuousDays = 7; // 从缓存获取工作限制 if (context.PersonnelWorkLimitsRuleCache.TryGetValue(personnelId, out var workLimit) && workLimit.MaxContinuousWorkDays.HasValue) { maxContinuousDays = workLimit.MaxContinuousWorkDays.Value; } // 从缓存计算连续工作天数 var continuousDays = await CalculateContinuousWorkDaysFromCache(personnelId, workDate, context); var isValid = continuousDays <= maxContinuousDays; var complianceScore = isValid ? Math.Max(0, 100 - (continuousDays / (double)maxContinuousDays * 100)) : 0; return new ShiftRuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"连续工作天数({continuousDays})超过限制({maxContinuousDays})" }; } catch (Exception ex) { _logger.LogError(ex, "验证连续工作天数规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("ContinuousWorkDays", personnelId, workDate); } } /// /// 规则5&6:跨周末连续性验证 - 缓存优化版本 /// private async Task ValidateCrossWeekendContinuityRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证跨周末班次连续性规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 计算相关的日期 var (firstDate, secondDate) = CalculateWeekendDates(workDate, ruleType); // 检查当前工作日期是否匹配需要验证的日期 bool isTargetDate = workDate.Date == firstDate.Date || workDate.Date == secondDate.Date; if (!isTargetDate) { _logger.LogTrace("当前日期不在验证范围内,跳过验证 - 当前日期:{CurrentDate}", workDate.ToString("yyyy-MM-dd")); return CreateValidResult("非目标验证日期,通过验证", 100); } // 获取两个日期的班次分配情况 var firstDateShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, firstDate, context); var secondDateShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, secondDate, context); // 如果当前要分配的日期需要将当前班次加入到检查中 var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); if (workDate.Date == secondDate.Date && !secondDateShifts.Contains(currentShiftNumber)) { secondDateShifts.Add(currentShiftNumber); } else if (workDate.Date == firstDate.Date && !firstDateShifts.Contains(currentShiftNumber)) { firstDateShifts.Add(currentShiftNumber); } // 检查是否存在跨周末连续性违规 bool hasViolation = firstDateShifts.Any() && secondDateShifts.Any(); if (hasViolation) { var violationDetail = GenerateCrossWeekendViolationDetail(ruleType, firstDate, secondDate, firstDateShifts, secondDateShifts); _logger.LogWarning("跨周末班次连续性规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = violationDetail }; } var successMessage = GenerateCrossWeekendSuccessMessage(ruleType, firstDate, secondDate); _logger.LogTrace("跨周末班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证跨周末连续性规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"CrossWeekendContinuity_{ruleType}", personnelId, workDate); } } /// /// 默认规则验证 - 缓存优化版本 /// private async Task ValidateDefaultRuleWithCacheAsync( ShiftRuleItem rule, long personnelId, DateTime workDate, GlobalAllocationContext context) { _logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName); return await Task.FromResult(new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 90, // 给予较高分,但不是满分 IsCritical = false, ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证" }); } #endregion #region 缓存数据加载和辅助方法 /// /// 确保人员缓存数据已加载 - 按需预加载策略 /// private async Task EnsurePersonnelCacheLoadedAsync(long personnelId, GlobalAllocationContext context) { if (context.CachedPersonnelIds.Contains(personnelId)) { return; // 已加载过,直接返回 } try { _logger.LogDebug("开始预加载人员缓存数据 - PersonnelId: {PersonnelId}", personnelId); // 加载15天时间窗口内的工作历史 var workOrderHistory = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= context.TimeWindowStartDate && w.WorkOrderDate <= context.TimeWindowEndDate) .Include(w => w.ShiftEntity) .ToListAsync(); // 提取工作日期列表 context.PersonnelWorkDatesCache[personnelId] = workOrderHistory .Select(w => w.WorkOrderDate.Date) .Distinct() .ToList(); // 构建历史任务缓存 var historyTasks = workOrderHistory.Select(w => new WorkOrderHistoryItem { TaskId = w.Id, WorkDate = w.WorkOrderDate, ShiftId = w.ShiftId, ShiftNumber = w.ShiftEntity?.ShiftNumber, TaskCode = w.WorkOrderCode, ProjectNumber = w.ProjectNumber, Status = w.Status }).ToList(); if (context.PersonnelHistoryTasks.ContainsKey(personnelId)) { context.PersonnelHistoryTasks[personnelId].AddRange(historyTasks); } else { context.PersonnelHistoryTasks[personnelId] = historyTasks; } // 加载工作限制 var workLimits = await _personnelWorkLimitService.GetByPersonnelIdAsync(personnelId); if (workLimits?.Any() == true) { context.PersonnelWorkLimitsRuleCache[personnelId] = new PersonnelWorkLimitEntity { Id = workLimits.First().Id, PersonnelId = personnelId, MaxContinuousWorkDays = workLimits.First().MaxContinuousWorkDays, MaxShiftsPerWeek = workLimits.First().MaxShiftsPerWeek, IsActive = true }; } // 加载请假记录 var leaveRecords = await _employeeLeaveService.GetApprovedLeavesByEmployeeAndTimeRangeAsync( personnelId, context.TimeWindowStartDate, context.TimeWindowEndDate); context.PersonnelLeaveRecordsCache[personnelId] = leaveRecords?.Select(l => new EmployeeLeaveRecord { Id = l.Id, PersonnelId = l.EmployeeId, StartDate = l.StartTime, EndDate = l.EndTime, LeaveType = l.LeaveType ?? "Unknown", IsApproved = true // GetApprovedLeavesByEmployeeAndTimeRangeAsync只返回已批准的记录 }).ToList() ?? new List(); // 标记为已加载 context.CachedPersonnelIds.Add(personnelId); _logger.LogDebug("人员缓存数据预加载完成 - PersonnelId: {PersonnelId}, 历史任务: {TaskCount}, 工作日期: {DateCount}", personnelId, historyTasks.Count, context.PersonnelWorkDatesCache[personnelId].Count); } catch (Exception ex) { _logger.LogWarning(ex, "预加载人员缓存数据失败,将在验证时回退到数据库查询 - PersonnelId: {PersonnelId}", personnelId); } } /// /// 从缓存获取人员在指定日期的班次编号列表 /// private async Task> GetPersonnelShiftNumbersOnDateFromCache( long personnelId, DateTime date, GlobalAllocationContext context) { try { // 首先尝试从缓存获取 if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var shiftsOnDate = historyTasks .Where(t => t.WorkDate.Date == date.Date && t.ShiftNumber.HasValue) .Select(t => t.ShiftNumber.Value) .Distinct() .ToList(); _logger.LogTrace("从缓存获取人员当日班次编号 - 人员ID:{PersonnelId}, 日期:{Date}, 班次编号:{ShiftNumbers}", personnelId, date.ToString("yyyy-MM-dd"), string.Join(",", shiftsOnDate)); return shiftsOnDate; } // 缓存未命中,回退到数据库查询 _logger.LogDebug("缓存未命中,回退到数据库查询 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return await GetPersonnelShiftNumbersOnDateFromDatabase(personnelId, date); } catch (Exception ex) { _logger.LogError(ex, "获取人员当日班次编号异常 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return new List(); } } /// /// 从缓存计算周班次数量 /// private async Task CalculateWeekShiftCountFromCache( long personnelId, DateTime targetDate, GlobalAllocationContext context) { try { // 获取目标日期所在周的开始和结束时间 var weekStart = targetDate.AddDays(-(int)targetDate.DayOfWeek); var weekEnd = weekStart.AddDays(6); if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var weekShiftCount = historyTasks .Where(t => t.WorkDate.Date >= weekStart.Date && t.WorkDate.Date <= weekEnd.Date) .Count(); return weekShiftCount + 1; // 包含目标日期 } // 缓存未命中,回退到数据库查询 return await CalculateWeekShiftCountFromDatabase(personnelId, targetDate); } catch (Exception ex) { _logger.LogError(ex, "从缓存计算周班次数异常 - PersonnelId: {PersonnelId}", personnelId); return 1; } } /// /// 从缓存计算连续工作天数 /// private async Task CalculateContinuousWorkDaysFromCache( long personnelId, DateTime targetDate, GlobalAllocationContext context) { try { if (context.PersonnelWorkDatesCache.TryGetValue(personnelId, out var workDates)) { // 包含目标日期 var allWorkDates = workDates.ToList(); if (!allWorkDates.Contains(targetDate.Date)) { allWorkDates.Add(targetDate.Date); } return CalculateContinuousWorkDays(allWorkDates); } // 缓存未命中,回退到数据库查询 return await CalculateContinuousWorkDaysFromDatabase(personnelId, targetDate); } catch (Exception ex) { _logger.LogError(ex, "从缓存计算连续工作天数异常 - PersonnelId: {PersonnelId}", personnelId); return 0; } } /// /// 计算连续工作天数 - 辅助算法 /// private int CalculateContinuousWorkDays(List workDates) { if (workDates == null || !workDates.Any()) return 0; var sortedDates = workDates.OrderBy(d => d.Date).ToList(); var maxContinuous = 1; var currentContinuous = 1; for (int i = 1; i < sortedDates.Count; i++) { if ((sortedDates[i].Date - sortedDates[i - 1].Date).Days == 1) { currentContinuous++; maxContinuous = Math.Max(maxContinuous, currentContinuous); } else { currentContinuous = 1; } } return maxContinuous; } #endregion #region 回退机制 - 缓存失败时的数据库查询 /// /// 回退到直接的班次规则验证 - 缓存失败时使用 /// private async Task FallbackToDirectShiftRuleValidation( long personnelId, DateTime workDate, long shiftId) { try { _logger.LogInformation("执行回退班次规则验证 - PersonnelId: {PersonnelId}", personnelId); // 使用简化的直接数据库查询验证 var shiftRules = await _shiftService.GetShiftRulesAsync(shiftId); if (shiftRules?.Any() != true) { return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 100, ValidationReason = "无班次规则配置(回退验证)", HasCriticalViolations = false }; } // 简化验证:只检查最关键的规则 var criticalRules = shiftRules.Where(r => r.IsEnabled && ShiftRulePriorityConfig.Rules.GetValueOrDefault(r.RuleType, new ShiftRuleConfig()).IsCritical); foreach (var rule in criticalRules) { if (rule.RuleType == "8" || rule.RuleType == "9") // 次日休息规则 { var hasViolation = await CheckNextDayRestRuleDirectly(personnelId, workDate, rule.RuleType); if (hasViolation) { return new ShiftRulesValidationSummary { IsCompliant = false, OverallScore = 0, ValidationReason = $"关键规则违规:{rule.RuleName}(回退验证)", HasCriticalViolations = true }; } } } return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 80, // 回退验证给予中等评分 ValidationReason = "回退验证通过", HasCriticalViolations = false }; } catch (Exception ex) { _logger.LogError(ex, "回退班次规则验证异常 - PersonnelId: {PersonnelId}", personnelId); return new ShiftRulesValidationSummary { IsCompliant = true, // 异常时采用保守策略,不阻断业务 OverallScore = 60, ValidationReason = $"回退验证异常,建议人工审核:{ex.Message}", HasCriticalViolations = false }; } } /// /// 回退到直接的单个规则验证 /// private async Task FallbackToDirectRuleValidation( string ruleName, long personnelId, DateTime workDate) { _logger.LogDebug("执行单个规则回退验证 - Rule: {RuleName}, PersonnelId: {PersonnelId}", ruleName, personnelId); return await Task.FromResult(new ShiftRuleValidationResult { IsValid = true, // 异常时采用保守策略 ComplianceScore = 70, // 给予中等评分 IsCritical = false, ViolationMessage = $"规则'{ruleName}'回退验证,建议人工审核" }); } #endregion #region 数据库查询回退方法 /// /// 直接数据库查询:获取人员在指定日期的班次编号 /// private async Task> GetPersonnelShiftNumbersOnDateFromDatabase(long personnelId, DateTime date) { try { var workOrdersWithShifts = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == date.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview) .Include(w => w.ShiftEntity) .ToListAsync(); return workOrdersWithShifts .Where(w => w.ShiftEntity != null) .Select(w => w.ShiftEntity.ShiftNumber) .Distinct() .ToList(); } catch (Exception ex) { _logger.LogError(ex, "数据库查询人员当日班次编号异常 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return new List(); } } /// /// 直接数据库查询:计算周班次数量 /// private async Task CalculateWeekShiftCountFromDatabase(long personnelId, DateTime targetDate) { try { var weekStart = targetDate.AddDays(-(int)targetDate.DayOfWeek); var weekEnd = weekStart.AddDays(6); var shiftCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= weekStart && w.WorkOrderDate <= weekEnd && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); return (int)shiftCount + 1; // 包含目标日期 } catch (Exception ex) { _logger.LogError(ex, "数据库查询周班次数异常 - PersonnelId: {PersonnelId}", personnelId); return 1; } } /// /// 直接数据库查询:计算连续工作天数 /// private async Task CalculateContinuousWorkDaysFromDatabase(long personnelId, DateTime targetDate) { try { var startDate = targetDate.AddDays(-14); var endDate = targetDate.AddDays(14); var workDates = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= startDate && w.WorkOrderDate <= endDate && w.Status > (int)WorkOrderStatusEnum.PendingReview) .ToListAsync(w => w.WorkOrderDate); workDates.Add(targetDate); return CalculateContinuousWorkDays(workDates); } catch (Exception ex) { _logger.LogError(ex, "数据库查询连续工作天数异常 - PersonnelId: {PersonnelId}", personnelId); return 0; } } /// /// 直接检查次日休息规则 /// private async Task CheckNextDayRestRuleDirectly(long personnelId, DateTime workDate, string ruleType) { try { int targetShiftNumber = ruleType switch { "8" => 3, // 夜班 "9" => 2, // 中班 _ => 0 }; if (targetShiftNumber == 0) return false; var previousDate = workDate.AddDays(-1); var previousDayShifts = await GetPersonnelShiftNumbersOnDateFromDatabase(personnelId, previousDate); return previousDayShifts.Contains(targetShiftNumber); } catch (Exception ex) { _logger.LogError(ex, "直接检查次日休息规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return false; } } #endregion #region 辅助方法和消息生成 - 与PersonnelAllocationService保持一致 /// /// 创建验证通过结果的辅助方法 /// private ShiftRuleValidationResult CreateValidResult(string reason, double score = 100) { return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = score, IsCritical = false, ViolationMessage = "" }; } /// /// 计算项目FL优先级评分 - 与PersonnelAllocationService保持一致的算法 /// private (double Score, string Reason) CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber, bool isProjectFL, List allProjectFLs) { try { if (isProjectFL) { return (100, $"本项目FL成员,具有专业经验和项目知识,优先分配"); } if (allProjectFLs.Any()) { int projectCount = allProjectFLs.Count; DateTime latestAssignment = allProjectFLs.Max(p => p.LastAssignmentDate); int daysSinceLastAssignment = (DateTime.Now - latestAssignment).Days; double score = 85; // 基础分 // 多项目经验加分 if (projectCount >= 3) score += 10; else if (projectCount == 2) score += 5; // 最近活跃度调整 if (daysSinceLastAssignment <= 30) score += 5; else if (daysSinceLastAssignment > 180) score -= 10; score = Math.Min(95, Math.Max(70, score)); return (score, $"有{projectCount}个项目FL经验,最近参与时间:{latestAssignment:yyyy-MM-dd},具有一定项目经验"); } else { return (70, "暂无项目FL经验,可作为培养对象适当分配"); } } catch (Exception ex) { _logger.LogError(ex, "计算项目FL优先级评分异常 - PersonnelId: {PersonnelId}, Project: {ProjectNumber}", personnelId, currentProjectNumber); return (75, $"评分计算异常,采用默认中等评分:{ex.Message}"); } } /// /// 生成项目FL验证信息 /// private string GenerateProjectFLValidationMessage(long personnelId, string projectNumber, bool isProjectFL, double score, string reason) { string priorityLevel = score switch { >= 95 => "最高优先级", >= 85 => "高优先级", >= 75 => "中等优先级", _ => "低优先级" }; return $"【项目FL优先分配验证】人员ID:{personnelId},目标项目:{projectNumber}," + $"是否本项目FL:{(isProjectFL ? "是" : "否")},优先级评分:{score:F1}分({priorityLevel}),评分依据:{reason}"; } /// /// 生成次日休息规则违规详细信息 /// private string GenerateNextDayRestViolationDetail(int ruleType, DateTime previousDate, DateTime currentDate, int targetShiftNumber, List previousDayShifts, string currentShiftName = "未知班次") { var targetShiftDisplayName = GetShiftDisplayName(targetShiftNumber); var previousShiftsStr = string.Join(",", previousDayShifts); return ruleType switch { 8 => $"【三班后次日强制休息违规】前一天:{previousDate:yyyy-MM-dd} 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})," + $"当前日期:{currentDate:yyyy-MM-dd} 试图分配{currentShiftName},违反休息规则", 9 => $"【二班后次日强制休息违规】前一天:{previousDate:yyyy-MM-dd} 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})," + $"当前日期:{currentDate:yyyy-MM-dd} 试图分配{currentShiftName},违反休息规则", _ => $"【次日休息规则违规】规则类型{ruleType},前一天({previousDate:yyyy-MM-dd})有{targetShiftDisplayName}(班次{previousShiftsStr})," + $"当天({currentDate:yyyy-MM-dd})试图分配{currentShiftName},违反休息规则" }; } /// /// 生成次日休息规则验证成功信息 /// private string GenerateNextDayRestSuccessMessage(int ruleType, DateTime previousDate, int targetShiftNumber) { var shiftDisplayName = GetShiftDisplayName(targetShiftNumber); return ruleType switch { 8 => $"三班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName}安排,员工无需强制休息", 9 => $"二班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName}安排,员工无需强制休息", _ => $"次日休息规则验证通过:规则类型{ruleType},前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName},符合规则要求" }; } /// /// 生成同天班次违规详细信息 /// private string GenerateSameDayViolationDetail(int ruleType, int existingShift, int currentShift) { return ruleType switch { 3 => $"同天早中班连续性违规:违反早中班同天禁止规则", 4 => $"同天中夜班连续性违规:违反中夜班同天禁止规则", _ => $"同天班次连续性违规:规则类型{ruleType},已有班次{existingShift}与当前班次{currentShift}冲突" }; } /// /// 生成同天班次验证成功信息 /// private string GenerateSameDaySuccessMessage(int ruleType, int currentShift) { return ruleType switch { 3 => $"同天早中班连续性验证通过:符合早中班同天禁止规则", 4 => $"同天中夜班连续性验证通过:符合中夜班同天禁止规则", _ => $"同天班次连续性验证通过:规则类型{ruleType},班次{currentShift}符合要求" }; } /// /// 计算跨周末规则验证的相关日期 /// private (DateTime firstDate, DateTime secondDate) CalculateWeekendDates(DateTime workDate, int ruleType) { try { var currentDate = workDate.Date; if (ruleType == 5) // 不能这周日/下周六连续 { var daysFromSunday = (int)currentDate.DayOfWeek; var thisWeekSunday = currentDate.AddDays(-daysFromSunday); var nextWeekSaturday = thisWeekSunday.AddDays(13); return (thisWeekSunday, nextWeekSaturday); } else if (ruleType == 6) // 不能本周六/本周日连续 { var daysFromSunday = (int)currentDate.DayOfWeek; var thisWeekSunday = currentDate.AddDays(-daysFromSunday); var thisWeekSaturday = thisWeekSunday.AddDays(-1); return (thisWeekSaturday, thisWeekSunday); } else { _logger.LogWarning("未支持的跨周末规则类型:{RuleType}", ruleType); return (currentDate, currentDate); } } catch (Exception ex) { _logger.LogError(ex, "计算跨周末日期异常 - 工作日期:{WorkDate}, 规则类型:{RuleType}", workDate.ToString("yyyy-MM-dd"), ruleType); return (workDate, workDate); } } /// /// 生成跨周末班次违规详细信息 /// private string GenerateCrossWeekendViolationDetail(int ruleType, DateTime firstDate, DateTime secondDate, List firstDateShifts, List secondDateShifts) { var firstShiftsStr = string.Join(",", firstDateShifts); var secondShiftsStr = string.Join(",", secondDateShifts); return ruleType switch { 5 => $"跨周末连续性违规:违反周日/下周六禁止连续工作规则,{firstDate:yyyy-MM-dd}有班次{firstShiftsStr},{secondDate:yyyy-MM-dd}有班次{secondShiftsStr}", 6 => $"周末连续性违规:违反周六/周日禁止连续工作规则,{firstDate:yyyy-MM-dd}有班次{firstShiftsStr},{secondDate:yyyy-MM-dd}有班次{secondShiftsStr}", _ => $"跨周末班次连续性违规:规则类型{ruleType},{firstDate:yyyy-MM-dd}和{secondDate:yyyy-MM-dd}存在班次冲突" }; } /// /// 生成跨周末班次验证成功信息 /// private string GenerateCrossWeekendSuccessMessage(int ruleType, DateTime firstDate, DateTime secondDate) { return ruleType switch { 5 => $"跨周末连续性验证通过:符合周日/下周六禁止连续工作规则", 6 => $"周末连续性验证通过:符合周六/周日禁止连续工作规则", _ => $"跨周末班次连续性验证通过:规则类型{ruleType},符合要求" }; } #endregion #endregion } /// /// 工序资质要求 /// 业务模型:工序与资质的关联关系 /// public class ProcessQualificationRequirement { /// /// 资质ID /// public long QualificationId { get; set; } /// /// 资质名称 /// public string QualificationName { get; set; } = string.Empty; /// /// 是否必需 /// public bool IsRequired { get; set; } = true; } }