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 ZhonTai.Admin.Services; using ZhonTai.DynamicApi; using ZhonTai.DynamicApi.Attributes; using NPP.SmartSchedue.Api.Contracts.Services.Integration; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Input; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Output; using NPP.SmartSchedue.Api.Contracts.Services.Work; using NPP.SmartSchedue.Api.Contracts.Services.Personnel; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Contracts.Domain.Personnel; using NPP.SmartSchedue.Api.Services.Integration.Models; using NPP.SmartSchedue.Api.Contracts.Domain.Time; using NPP.SmartSchedue.Api.Repositories.Work; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Models; using Microsoft.Extensions.Caching.Memory; using NPP.SmartSchedue.Api.Contracts.Services.Time; using SkiaSharp; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 智能人员分配服务 /// 实现基于五层决策模型的智能分配算法:资格过滤→约束评估→优化决策→结果生成→统一验证 /// 深度业务思考:支持复杂的多维度约束和智能优化,确保分配结果的合理性和公平性 /// [DynamicApi(Area = "app")] public partial class PersonnelAllocationService : BaseService, IPersonnelAllocationService { private readonly WorkOrderRepository _workOrderRepository; private readonly IPersonService _personService; private readonly IPersonnelWorkLimitService _personnelWorkLimitService; private readonly IPersonnelQualificationService _personnelQualificationService; private readonly IShiftService _shiftService; private readonly IEmployeeLeaveService _employeeLeaveService; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; // 性能监控 private readonly ActivitySource _activitySource; // 权重配置(可配置化) private readonly OptimizationWeights _defaultWeights = OptimizationWeights.CreateEfficiencyFirstConfiguration(); public PersonnelAllocationService( WorkOrderRepository workOrderRepository, IPersonService personService, IPersonnelWorkLimitService personnelWorkLimitService, IPersonnelQualificationService personnelQualificationService, IShiftService shiftService, IEmployeeLeaveService employeeLeaveService, ILogger logger, IMemoryCache memoryCache) { _workOrderRepository = workOrderRepository; _personService = personService; _personnelWorkLimitService = personnelWorkLimitService; _personnelQualificationService = personnelQualificationService; _shiftService = shiftService; _employeeLeaveService = employeeLeaveService; _logger = logger; _memoryCache = memoryCache; // 初始化性能监控 _activitySource = new ActivitySource("NPP.SmartSchedule.PersonnelAllocation"); } /// /// 智能人员分配核心方法 /// 基于五层决策模型的完整实现:资格过滤→约束评估→优化决策→结果生成→验证 /// 深度业务思考:考虑所有约束条件,确保分配结果既满足业务规则又实现最优化 /// [HttpPost] public async Task AllocatePersonnelSmartlyAsync(PersonnelAllocationInput input) { using var activity = _activitySource?.StartActivity("AllocatePersonnelSmart"); var stopwatch = Stopwatch.StartNew(); try { _logger.LogInformation("开始智能人员分配,任务数量:{TaskCount},策略:{Strategy}", input.Tasks?.Count ?? input.TaskIds?.Count ?? 0, input.Strategy); // 输入验证和数据准备 var validationResult = await ValidateAndPrepareInputAsync(input); if (!validationResult.IsValid) { return CreateFailureResult(validationResult.ErrorMessage); } // 构建分配上下文 var context = await BuildAllocationContextAsync(input, validationResult.WorkOrders); activity?.SetTag("task.count", context.WorkOrders.Count); activity?.SetTag("personnel.pool.size", context.AvailablePersonnel.Count); // 第一层:资格过滤处理 var qualificationResult = await ExecuteQualificationFilterAsync(context); if (!qualificationResult.IsSuccess) { _logger.LogWarning("资格过滤失败:{Message}", qualificationResult.Message); return CreatePartialFailureResult(context, "资格过滤阶段失败"); } // 第二层:约束评估处理 var constraintResult = await ExecuteConstraintEvaluationAsync(context); if (!constraintResult.IsSuccess) { _logger.LogWarning("约束评估失败:{Message}", constraintResult.Message); return CreatePartialFailureResult(context, "约束评估阶段失败"); } // 第三层:优化决策处理 var optimizationResult = await ExecuteOptimizationDecisionAsync(context); if (!optimizationResult.IsSuccess) { _logger.LogWarning("优化决策失败:{Message}", optimizationResult.Message); return CreatePartialFailureResult(context, "优化决策阶段失败"); } // 第四层:结果生成处理 var generationResult = await ExecuteResultGenerationAsync(context); if (!generationResult.IsSuccess) { _logger.LogError("结果生成失败:{Message}", generationResult.Message); return CreateFailureResult("结果生成阶段失败"); } // 第五层:最终验证和质量评估 var finalResult = await ExecuteFinalValidationAsync(context, generationResult.ResultData as AllocationGenerationResult); stopwatch.Stop(); activity?.SetTag("execution.time.ms", stopwatch.ElapsedMilliseconds); activity?.SetTag("allocation.success", finalResult.IsSuccess); _logger.LogInformation("智能人员分配完成,耗时:{ElapsedMs}ms,成功:{Success}", stopwatch.ElapsedMilliseconds, finalResult.IsSuccess); return finalResult; } catch (Exception ex) { stopwatch.Stop(); if (activity != null) { activity.SetStatus(System.Diagnostics.ActivityStatusCode.Error, ex.Message); } _logger.LogError(ex, "智能人员分配异常,耗时:{ElapsedMs}ms", stopwatch.ElapsedMilliseconds); return CreateFailureResult($"系统异常:{ex.Message}"); } } /// /// 获取任务的候选人员列表(带评分和推理) /// public async Task> GetCandidatesAsync(long workOrderId) { try { var workOrder = await _workOrderRepository.GetAsync(workOrderId); if (workOrder == null) { _logger.LogWarning("工作任务不存在:{WorkOrderId}", workOrderId); return new List(); } // 构建单任务分配上下文 var context = await BuildSingleTaskContextAsync(workOrder); // 执行资格过滤和约束评估 await ExecuteQualificationFilterAsync(context); await ExecuteConstraintEvaluationAsync(context); await ExecuteOptimizationDecisionAsync(context); // 返回候选人员列表 if (context.FilteredCandidates.TryGetValue(workOrderId, out var candidates)) { return candidates.OrderByDescending(c => c.TotalScore).ToList(); } return new List(); } catch (Exception ex) { _logger.LogError(ex, "获取候选人员异常,任务ID:{WorkOrderId}", workOrderId); return new List(); } } /// /// 批量获取任务的候选人员映射(性能优化) /// public async Task>> GetBatchCandidatesAsync(List workOrderIds) { try { if (workOrderIds == null || !workOrderIds.Any()) { return new Dictionary>(); } // 批量获取工作任务 var workOrders = await _workOrderRepository.Select .Where(w => workOrderIds.Contains(w.Id)) .Include(w => w.ProcessEntity) .ToListAsync(); if (!workOrders.Any()) { return new Dictionary>(); } // 构建批量分配上下文 var input = new PersonnelAllocationInput { Tasks = workOrders, Strategy = PersonnelAllocationStrategy.FairDistribution }; var context = await BuildAllocationContextAsync(input, workOrders); // 执行前三层处理 await ExecuteQualificationFilterAsync(context); await ExecuteConstraintEvaluationAsync(context); await ExecuteOptimizationDecisionAsync(context); // 排序并返回结果 var result = new Dictionary>(); foreach (var kvp in context.FilteredCandidates) { result[kvp.Key] = kvp.Value.OrderByDescending(c => c.TotalScore).ToList(); } return result; } catch (Exception ex) { _logger.LogError(ex, "批量获取候选人员异常,任务数量:{TaskCount}", workOrderIds?.Count ?? 0); return new Dictionary>(); } } /// /// 验证人员分配的可行性 /// public async Task ValidateAllocationAsync( PersonnelAllocationValidationInput validationInput) { try { var result = new PersonnelAllocationValidationResult(); var violations = new List(); var warnings = new List(); foreach (var assignment in validationInput.Assignments) { // 验证资质匹配 var qualificationValid = await ValidatePersonnelQualificationAsync(assignment.WorkOrderId, assignment.PersonnelId); if (!qualificationValid.IsValid) { violations.AddRange(qualificationValid.Violations); } // 验证时间冲突 var timeConflictValid = await ValidateTimeConflictAsync(assignment.PersonnelId, assignment.AssignmentDate, assignment.ShiftId ?? 0); if (!timeConflictValid.IsValid) { violations.AddRange(timeConflictValid.Violations); } // 验证工作限制 var workLimitValid = await ValidateWorkLimitAsync(assignment.PersonnelId, assignment.AssignmentDate); if (!workLimitValid.IsValid) { violations.AddRange(workLimitValid.Violations); } // 验证班次规则 if (validationInput.IncludeConflictDetection) { var shiftRuleValid = await ValidateShiftRuleAsync(assignment.PersonnelId, assignment.AssignmentDate, assignment.ShiftId ?? 0); if (!shiftRuleValid.IsValid) { violations.AddRange(shiftRuleValid.Violations); } } } result.IsValid = !violations.Any(v => v.Severity >= ViolationSeverity.Error); result.Violations = violations; result.Warnings = warnings; result.PassedAssignmentCount = validationInput.Assignments.Count - result.FailedAssignmentCount; result.FailedAssignmentCount = violations.Count(v => v.Severity >= ViolationSeverity.Error); result.ValidationScore = CalculateValidationScore(violations, warnings, validationInput.Assignments.Count); result.ValidationReport = GenerateValidationReport(result); return result; } catch (Exception ex) { _logger.LogError(ex, "人员分配验证异常"); return new PersonnelAllocationValidationResult { IsValid = false, ValidationReport = $"验证异常:{ex.Message}" }; } } /// /// 获取人员工作负载分析 /// public async Task> AnalyzePersonnelWorkloadAsync(List personnelIds, DateRange dateRange) { try { var result = new List(); foreach (var personnelId in personnelIds) { // 获取指定时间范围内的任务分配 var assignments = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= dateRange.StartDate && w.WorkOrderDate <= dateRange.EndDate) .ToListAsync(); // 计算工作负载 var workloadAnalysis = new PersonnelWorkloadAnalysis { PersonnelId = personnelId, AssignedTaskCount = assignments.Count, WorkDates = assignments.Select(a => a.WorkOrderDate).Distinct().ToList() }; // 计算连续工作天数 workloadAnalysis.ContinuousWorkDays = CalculateContinuousWorkDays(workloadAnalysis.WorkDates); // 估算总工时 workloadAnalysis.TotalEstimatedHours = assignments.Sum(a => a.EstimatedHours ?? 8.0m); // 计算工作负载百分比(基于标准工时) var standardHours = dateRange.Duration.Days * 8.0m; // 假设每天8小时标准工时 workloadAnalysis.WorkloadPercentage = standardHours > 0 ? (double)(workloadAnalysis.TotalEstimatedHours / standardHours * 100) : 0; // 确定工作负载状态 workloadAnalysis.WorkloadStatus = workloadAnalysis.WorkloadPercentage switch { < 50 => "轻松", < 80 => "适中", < 100 => "繁重", _ => "超负荷" }; result.Add(workloadAnalysis); } return result.OrderByDescending(w => w.WorkloadPercentage).ToList(); } catch (Exception ex) { _logger.LogError(ex, "人员工作负载分析异常"); return new List(); } } #region 核心流程方法实现 /// /// 验证和准备输入数据 /// 深度业务思考:确保输入数据的完整性和有效性,避免后续处理出现异常 /// private async Task ValidateAndPrepareInputAsync(PersonnelAllocationInput input) { var result = new InputValidationResult(); try { // 基础验证 if (input == null) { result.IsValid = false; result.ErrorMessage = "输入参数不能为空"; return result; } // 获取工作任务 List workOrders; if (input.Tasks != null && input.Tasks.Any()) { // 优先使用传入的Tasks对象(性能优化) workOrders = input.Tasks; _logger.LogInformation("使用传入的Tasks对象,任务数量:{TaskCount}", workOrders.Count); } else if (input.TaskIds != null && input.TaskIds.Any()) { // 从数据库查询Tasks workOrders = await _workOrderRepository.Select .Where(w => input.TaskIds.Contains(w.Id)) .Include(w => w.ProcessEntity) .ToListAsync(); if (!workOrders.Any()) { result.IsValid = false; result.ErrorMessage = "指定的工作任务不存在"; return result; } _logger.LogInformation("从数据库查询Tasks,任务数量:{TaskCount}", workOrders.Count); } else { result.IsValid = false; result.ErrorMessage = "必须提供Tasks或TaskIds"; return result; } // 验证任务状态 var invalidTasks = workOrders.Where(w => w.Status != (int)WorkOrderStatusEnum.PendingIntegration).ToList(); if (invalidTasks.Any()) { result.IsValid = false; result.ErrorMessage = $"存在非待分配状态的任务:{string.Join(",", invalidTasks.Select(t => t.WorkOrderCode))}"; return result; } // 验证任务是否包含必要信息 var invalidProcessTasks = workOrders.Where(w => w.ProcessEntity == null).ToList(); if (invalidProcessTasks.Any()) { _logger.LogWarning("存在工序信息缺失的任务:{TaskCodes}", string.Join(",", invalidProcessTasks.Select(t => t.WorkOrderCode))); } result.IsValid = true; result.WorkOrders = workOrders; return result; } catch (Exception ex) { _logger.LogError(ex, "验证和准备输入数据异常"); result.IsValid = false; result.ErrorMessage = $"输入验证异常:{ex.Message}"; return result; } } /// /// 构建分配上下文 /// 深度架构思考:集中管理分配过程中的所有状态和数据,支持复杂的多任务分配场景 /// private async Task BuildAllocationContextAsync(PersonnelAllocationInput input, List workOrders) { try { var context = new AllocationContext { ContextId = Guid.NewGuid().ToString(), CreatedAt = DateTime.UtcNow, WorkOrders = workOrders, Strategy = input.Strategy, WeightConfiguration = _defaultWeights.ToDictionary() }; // 获取可用人员池 context.AvailablePersonnel = await GetAvailablePersonnelPoolAsync(); // 过滤排除的人员 if (input.ExcludedPersonnelIds != null && input.ExcludedPersonnelIds.Any()) { context.AvailablePersonnel = context.AvailablePersonnel .Where(p => !input.ExcludedPersonnelIds.Contains(p.Id)) .ToList(); } // 记录处理开始 context.ProcessingLog.Add($"构建分配上下文完成,上下文ID:{context.ContextId}"); context.ProcessingLog.Add($"任务数:{workOrders.Count},人员池大小:{context.AvailablePersonnel.Count}"); context.ProcessingLog.Add($"分配策略:{input.Strategy},排除人员:{input.ExcludedPersonnelIds?.Count ?? 0}个"); // 记录权重配置 var weightInfo = string.Join(";", context.WeightConfiguration.Select(kv => $"{kv.Key}={kv.Value:P0}")); context.ProcessingLog.Add($"权重配置:{weightInfo}"); return context; } catch (Exception ex) { _logger.LogError(ex, "构建分配上下文异常"); throw new InvalidOperationException($"构建分配上下文失败:{ex.Message}", ex); } } /// /// 构建单任务分配上下文(用于GetCandidatesAsync) /// private async Task BuildSingleTaskContextAsync(WorkOrderEntity workOrder) { try { var context = new AllocationContext { ContextId = Guid.NewGuid().ToString(), CreatedAt = DateTime.UtcNow, WorkOrders = new List { workOrder }, Strategy = PersonnelAllocationStrategy.FairDistribution, WeightConfiguration = _defaultWeights.ToDictionary() }; context.AvailablePersonnel = await GetAvailablePersonnelPoolAsync(); context.ProcessingLog.Add($"构建单任务上下文完成,任务:{workOrder.WorkOrderCode}"); return context; } catch (Exception ex) { _logger.LogError(ex, "构建单任务分配上下文异常,任务ID:{WorkOrderId}", workOrder.Id); throw; } } /// /// 获取完整人员池 /// 深度架构思考:严格遵循五层决策模型,获取全量人员池,由各决策层负责筛选 /// 业务逻辑:根据资质表获取所有人员并去重,确保决策模型的完整性和公正性 /// private async Task> GetAvailablePersonnelPoolAsync() { try { // 获取所有人员池(从资质表关联获取并去重) // 五层决策模型要求:人员池应该是完整的,筛选工作由各决策层完成 var personnelPoolOutputs = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false); if (personnelPoolOutputs == null || !personnelPoolOutputs.Any()) { _logger.LogWarning("系统中无任何人员数据"); return new List(); } // 转换为PersonnelEntity格式,只进行最基础的数据完整性过滤 var personnelEntities = new List(); foreach (var output in personnelPoolOutputs) { if (output != null && output.Id > 0) { var entity = new PersonnelEntity { Id = output.Id, PersonnelCode = output.PersonnelCode, PersonnelName = output.PersonnelName, DepartmentId = output.DepartmentId, DepartmentName = output.DepartmentName, Position = output.Position, IsActive = output.IsActive, Contact = output.Contact, Remarks = output.Remarks, CreatedTime = output.CreatedTime, ModifiedTime = output.ModifiedTime }; personnelEntities.Add(entity); } } _logger.LogInformation("获取完整人员池完成,原始数量:{Total},有效数量:{Valid}", personnelPoolOutputs.Count, personnelEntities.Count); _logger.LogDebug("人员池获取策略:从资质表关联获取全量人员并去重,由五层决策模型负责业务筛选"); return personnelEntities; } catch (Exception ex) { _logger.LogError(ex, "获取人员池异常,将影响整个分配流程"); return new List(); } } #endregion #region 五层处理器方法实现 /// /// 执行第一层:资格过滤处理 /// 🎯 架构说明:这是一个可插拔的处理器架构 /// 📌 设计理念:既要开箱即用(内置实现),又要支持企业定制(外部处理器) /// private async Task ExecuteQualificationFilterAsync(AllocationContext context) { var stopwatch = Stopwatch.StartNew(); try { context.ProcessingLog.Add("开始资格过滤处理"); // ⚡ 内置资格过滤实现(默认实现,覆盖99%的业务场景) // 📈 技术特点:高性能、业务完整、可靠稳定 var filteredCandidates = new Dictionary>(); var totalProcessed = 0; var totalQualified = 0; var totalBasicEligible = 0; foreach (var workOrder in context.WorkOrders) { var candidates = new List(); // 获取工序资质要求 var requiredQualifications = GetRequiredQualifications(workOrder); foreach (var personnel in context.AvailablePersonnel) { totalProcessed++; // 第一层决策:基础条件筛选 // 1. 检查人员基础状态(在职、可用等) var personnelStatusValid = await IsPersonnelBasicallyEligibleAsync(personnel); if (!personnelStatusValid.IsEligible) { // 记录但不计入后续处理 _logger.LogDebug("人员 {PersonnelName}({PersonnelId}) 未通过基础资格检查:{Reasons}", personnel.PersonnelName, personnel.Id, string.Join(",", personnelStatusValid.EligibilityReasons)); continue; } totalBasicEligible++; // 2. 检查资质匹配 var qualificationMatch = await CheckQualificationMatchAsync(personnel.Id, requiredQualifications); if (qualificationMatch.IsMatched) { var candidate = new PersonnelCandidate { PersonnelId = personnel.Id, PersonnelName = personnel.PersonnelName, QualificationScore = new QualificationScore { MatchScore = qualificationMatch.MatchScore, MatchedQualifications = qualificationMatch.MatchedQualifications, MissingQualifications = qualificationMatch.MissingQualifications, IsQualificationValid = qualificationMatch.IsValid, QualificationDetails = qualificationMatch.Details } }; candidates.Add(candidate); totalQualified++; } } filteredCandidates[workOrder.Id] = candidates; context.ProcessingLog.Add($"任务 {workOrder.WorkOrderCode} 资格过滤完成,候选人:{candidates.Count}"); } context.FilteredCandidates = filteredCandidates; stopwatch.Stop(); context.ProcessingLog.Add( $"资格过滤完成,总处理:{totalProcessed},基础合格:{totalBasicEligible},资质匹配:{totalQualified},耗时:{stopwatch.ElapsedMilliseconds}ms"); context.ProcessingLog.Add( $"五层决策模型-第一层:从完整人员池({context.AvailablePersonnel.Count})中筛选出资质合格候选人({totalQualified})"); context.Metrics["QualificationFilter_ExecutionTime"] = stopwatch.ElapsedMilliseconds; context.Metrics["QualificationFilter_ProcessedCount"] = totalProcessed; context.Metrics["QualificationFilter_BasicEligibleCount"] = totalBasicEligible; context.Metrics["QualificationFilter_QualifiedCount"] = totalQualified; return new ProcessorResult { IsSuccess = true, ProcessorName = "QualificationFilter", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, SuccessCount = totalQualified, ProcessedCount = totalProcessed, Message = "资格过滤处理成功" }; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "资格过滤处理异常"); context.Errors.Add($"资格过滤异常:{ex.Message}"); return new ProcessorResult { IsSuccess = false, ProcessorName = "QualificationFilter", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, Message = $"资格过滤处理失败:{ex.Message}" }; } } /// /// 执行第二层:约束评估处理 /// private async Task ExecuteConstraintEvaluationAsync(AllocationContext context) { var stopwatch = Stopwatch.StartNew(); try { context.ProcessingLog.Add("开始约束评估处理"); // ⚡ 内置约束评估实现(默认实现,包含完整的11种班次规则验证) var constraintResults = new Dictionary>(); var totalEvaluated = 0; var totalPassed = 0; // 任务循环 foreach (var kvp in context.FilteredCandidates) { var workOrderId = kvp.Key; var candidates = kvp.Value; var workOrder = context.WorkOrders.First(w => w.Id == workOrderId); var evaluationResults = new List(); // 人员循环 foreach (var candidate in candidates) { totalEvaluated++; // 约束检查 var constraintCheck = await EvaluateCandidateConstraintsAsync(candidate, workOrder); // 更新候选人约束评分 candidate.ConstraintScore = new ConstraintScore { ComplianceScore = constraintCheck.ConstraintScore, TimeAvailabilityScore = constraintCheck.TimeAvailabilityScore ?? 0, WorkLimitComplianceScore = constraintCheck.WorkLimitScore ?? 0, ShiftRuleComplianceScore = constraintCheck.ShiftRuleScore ?? 0, Violations = constraintCheck.Violations, PassedConstraints = constraintCheck.PassedConstraints ?? new List(), ConstraintDetails = constraintCheck.EvaluationDetails }; // 转换为Processors命名空间的类型 var processorsResult = new ConstraintEvaluationResult { Candidate = candidate, IsValid = constraintCheck.IsValid, ConstraintScore = constraintCheck.ConstraintScore, Violations = constraintCheck.Violations?.Select(v => new ConstraintViolation { ViolationType = "General", Description = v.Description, Severity = ViolationSeverity.Warning }).ToList() ?? new List(), PassedConstraints = constraintCheck.PassedConstraints ?? new List(), EvaluationDetails = constraintCheck.EvaluationDetails, RiskLevel = constraintCheck.ConstraintScore < 60 ? RiskLevel.High : constraintCheck.ConstraintScore < 80 ? RiskLevel.Medium : RiskLevel.Low }; evaluationResults.Add(processorsResult); if (constraintCheck.IsValid) { totalPassed++; } } constraintResults[workOrderId] = evaluationResults; // 过滤掉不符合约束的候选人(约束符合度阈值60分) context.FilteredCandidates[workOrderId] = candidates .Where(c => c.ConstraintScore.ComplianceScore >= 60) .ToList(); var passedCount = context.FilteredCandidates[workOrderId].Count; context.ProcessingLog.Add( $"任务 {workOrder.WorkOrderCode} 约束评估完成,通过:{passedCount}/{candidates.Count}"); } context.ConstraintResults = constraintResults; stopwatch.Stop(); context.ProcessingLog.Add( $"约束评估完成,总评估:{totalEvaluated},通过:{totalPassed},耗时:{stopwatch.ElapsedMilliseconds}ms"); context.Metrics["ConstraintEvaluation_ExecutionTime"] = stopwatch.ElapsedMilliseconds; context.Metrics["ConstraintEvaluation_EvaluatedCount"] = totalEvaluated; context.Metrics["ConstraintEvaluation_PassedCount"] = totalPassed; return new ProcessorResult { IsSuccess = true, ProcessorName = "ConstraintEvaluation", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, SuccessCount = totalPassed, ProcessedCount = totalEvaluated, Message = "约束评估处理成功" }; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "约束评估处理异常"); context.Errors.Add($"约束评估异常:{ex.Message}"); return new ProcessorResult { IsSuccess = false, ProcessorName = "ConstraintEvaluation", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, Message = $"约束评估处理失败:{ex.Message}" }; } } /// /// 执行第三层:优化决策处理 /// private async Task ExecuteOptimizationDecisionAsync(AllocationContext context) { var stopwatch = Stopwatch.StartNew(); try { context.ProcessingLog.Add("开始优化决策处理"); // 内置优化决策实现 var optimizationResults = new Dictionary>(); var totalOptimized = 0; foreach (var kvp in context.FilteredCandidates) { var workOrderId = kvp.Key; var candidates = kvp.Value; var workOrder = context.WorkOrders.First(w => w.Id == workOrderId); var scoreResults = new List(); foreach (var candidate in candidates) { totalOptimized++; // 计算优化评分 var optimizationScore = await CalculateOptimizationScoreAsync(candidate, workOrder, context); // 更新候选人优化评分 candidate.OptimizationScore = new OptimizationScore { AssignedPersonnelScore = optimizationScore.DimensionScores.GetValueOrDefault("AssignedPersonnel", 0), ProjectFLScore = optimizationScore.DimensionScores.GetValueOrDefault("ProjectFL", 0), WorkloadBalanceScore = optimizationScore.DimensionScores.GetValueOrDefault("WorkloadBalance", 0), ShiftContinuityScore = optimizationScore.DimensionScores.GetValueOrDefault("ShiftContinuity", 0), SkillMatchScore = optimizationScore.DimensionScores.GetValueOrDefault("SkillMatch", 0), EfficiencyScore = optimizationScore.DimensionScores.GetValueOrDefault("CollaborationHistory", 0), OptimizationDetails = optimizationScore.OptimizationReason }; candidate.DecisionScore = new DecisionScore { FinalScore = optimizationScore.OptimizationScore, ConfidenceLevel = optimizationScore.Confidence, RiskLevel = optimizationScore.RiskAssessment?.OverallRiskLevel ?? RiskLevel.Low, DecisionReason = optimizationScore.OptimizationReason }; // 计算综合总分 candidate.TotalScore = CalculateTotalScore(candidate, context.WeightConfiguration); // 设置推荐等级和理由 candidate.RecommendationLevel = DetermineRecommendationLevel(candidate.TotalScore, candidate.DecisionScore.ConfidenceLevel); candidate.AllocationReason = GenerateAllocationReason(candidate); scoreResults.Add(optimizationScore); } optimizationResults[workOrderId] = scoreResults; // 对候选人按总分排序 context.FilteredCandidates[workOrderId] = candidates.OrderByDescending(c => c.TotalScore).ToList(); var bestScore = candidates.Any() ? candidates.Max(c => c.TotalScore) : 0; context.ProcessingLog.Add($"任务 {workOrder.WorkOrderCode} 优化决策完成,最高评分:{bestScore:F1}"); } context.OptimizationResults = optimizationResults; stopwatch.Stop(); context.ProcessingLog.Add($"优化决策完成,总处理:{totalOptimized},耗时:{stopwatch.ElapsedMilliseconds}ms"); context.Metrics["OptimizationDecision_ExecutionTime"] = stopwatch.ElapsedMilliseconds; context.Metrics["OptimizationDecision_OptimizedCount"] = totalOptimized; return new ProcessorResult { IsSuccess = true, ProcessorName = "OptimizationDecision", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, SuccessCount = totalOptimized, ProcessedCount = totalOptimized, Message = "优化决策处理成功" }; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "优化决策处理异常"); context.Errors.Add($"优化决策异常:{ex.Message}"); return new ProcessorResult { IsSuccess = false, ProcessorName = "OptimizationDecision", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, Message = $"优化决策处理失败:{ex.Message}" }; } } /// /// 执行第四层:结果生成处理 /// private async Task ExecuteResultGenerationAsync(AllocationContext context) { var stopwatch = Stopwatch.StartNew(); try { context.ProcessingLog.Add("开始结果生成处理"); // 内置结果生成实现 var allocationResult = new AllocationGenerationResult { SuccessfulAllocations = new Dictionary(), FailedAllocations = new List(), DetailedReport = new AllocationReport() }; foreach (var kvp in context.FilteredCandidates) { var workOrderId = kvp.Key; var candidates = kvp.Value.OrderByDescending(c => c.TotalScore).ToList(); var workOrder = context.WorkOrders.First(w => w.Id == workOrderId); if (candidates.Any()) { // 选择最佳候选人 var bestCandidate = candidates.First(); bestCandidate.IsOptimalChoice = true; bestCandidate.Rank = 1; // 为其他候选人设置排名 for (int i = 1; i < candidates.Count; i++) { candidates[i].Rank = i + 1; } allocationResult.SuccessfulAllocations[workOrderId] = bestCandidate; context.ProcessingLog.Add( $"任务 {workOrder.WorkOrderCode} 分配成功,选择:{bestCandidate.PersonnelName}({bestCandidate.TotalScore:F1}分)"); } else { // 记录分配失败 var failedAllocation = new FailedAllocation { WorkOrderId = workOrderId, FailureReason = "无符合条件的候选人员", FailureType = AllocationFailureType.NoAvailablePersonnel, SuggestedSolutions = GenerateFailureSuggestions(workOrder) }; allocationResult.FailedAllocations.Add(failedAllocation); context.ProcessingLog.Add( $"任务 {workOrder.WorkOrderCode} 分配失败:{failedAllocation.FailureReason}"); } } // 计算整体质量评分和生成报告 allocationResult.OverallQualityScore = CalculateOverallQualityScore(allocationResult); allocationResult.AllocationSummary = GenerateAllocationSummary(allocationResult, context); allocationResult.DetailedReport = GenerateDetailedReport(allocationResult, context); // 记录性能指标 allocationResult.PerformanceMetrics = new Dictionary(context.Metrics); stopwatch.Stop(); context.Metrics["ResultGeneration_ExecutionTime"] = stopwatch.ElapsedMilliseconds; var successCount = allocationResult.SuccessfulAllocations.Count; var failureCount = allocationResult.FailedAllocations.Count; context.ProcessingLog.Add( $"结果生成完成,成功:{successCount},失败:{failureCount},耗时:{stopwatch.ElapsedMilliseconds}ms"); return new ProcessorResult { IsSuccess = true, ProcessorName = "ResultGeneration", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, SuccessCount = successCount, FailureCount = failureCount, ProcessedCount = context.WorkOrders.Count, Message = "结果生成处理成功", ResultData = allocationResult }; } catch (Exception ex) { stopwatch.Stop(); _logger.LogError(ex, "结果生成处理异常"); context.Errors.Add($"结果生成异常:{ex.Message}"); return new ProcessorResult { IsSuccess = false, ProcessorName = "ResultGeneration", ExecutionTimeMs = stopwatch.ElapsedMilliseconds, Message = $"结果生成处理失败:{ex.Message}" }; } } /// /// 执行第五层:最终验证和质量评估 /// private async Task ExecuteFinalValidationAsync(AllocationContext context, AllocationGenerationResult generationResult) { try { context.ProcessingLog.Add("开始最终验证和质量评估"); var finalResult = new PersonnelAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), WorkloadAnalysis = new List() }; // 转换成功分配 foreach (var kvp in generationResult.SuccessfulAllocations) { var workOrder = context.WorkOrders.First(w => w.Id == kvp.Key); var candidate = kvp.Value; finalResult.SuccessfulMatches.Add(new TaskPersonnelMatch { TaskId = kvp.Key, TaskCode = workOrder.WorkOrderCode, PersonnelId = candidate.PersonnelId, PersonnelName = candidate.PersonnelName, MatchScore = (int)Math.Round(candidate.TotalScore), MatchReason = candidate.AllocationReason ?? "综合评分最优", QualificationMatches = candidate.QualificationScore.MatchedQualifications, EstimatedEfficiency = CalculateEstimatedEfficiency(candidate) }); } // 转换失败分配 foreach (var failedAllocation in generationResult.FailedAllocations) { var workOrder = context.WorkOrders.First(w => w.Id == failedAllocation.WorkOrderId); finalResult.FailedAllocations.Add(new FailedPersonnelAllocation { TaskId = failedAllocation.WorkOrderId, TaskCode = workOrder.WorkOrderCode, FailureReason = failedAllocation.FailureReason, ConflictDetails = failedAllocation.SuggestedSolutions }); } // 生成工作负载分析 finalResult.WorkloadAnalysis = await GenerateWorkloadAnalysisAsync(generationResult.SuccessfulAllocations); // 计算公平性评分 finalResult.FairnessScore = CalculateFairnessScore(finalResult.WorkloadAnalysis); // 设置其他属性 finalResult.AllocationSummary = generationResult.AllocationSummary; finalResult.AllocationStrategy = context.Strategy.ToString(); finalResult.ProcessingDetails = string.Join("; ", context.ProcessingLog); // 兼容性验证结果 finalResult.ValidationResult = new { OverallScore = generationResult.OverallQualityScore, ProcessingMetrics = context.Metrics, ValidationPassed = !context.Errors.Any(), ProcessingTime = context.Metrics.GetValueOrDefault("Total_ExecutionTime", 0), QualityAssessment = new { AverageScore = generationResult.SuccessfulAllocations.Any() ? generationResult.SuccessfulAllocations.Values.Average(c => c.TotalScore) : 0, SuccessRate = (double)generationResult.SuccessfulAllocations.Count / context.WorkOrders.Count * 100, FairnessScore = finalResult.FairnessScore } }; context.ProcessingLog.Add( $"最终验证完成,成功分配:{finalResult.SuccessfulMatches.Count},失败:{finalResult.FailedAllocations.Count},公平性评分:{finalResult.FairnessScore}"); return finalResult; } catch (Exception ex) { _logger.LogError(ex, "最终验证异常"); return CreateFailureResult($"最终验证失败:{ex.Message}"); } } #endregion #region 执行第一层:资格过滤辅助方法 /// /// 获取工序资质要求 /// 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(); } /// /// 生成分配理由 /// private string GenerateAllocationReason(PersonnelCandidate candidate) { var reasons = new List(); // 资质匹配 if (candidate.QualificationScore.MatchScore >= 90) reasons.Add("资质完全匹配"); else if (candidate.QualificationScore.MatchScore >= 70) reasons.Add("资质基本符合"); // 约束符合 if (candidate.ConstraintScore.ComplianceScore >= 90) reasons.Add("约束条件优秀"); else if (candidate.ConstraintScore.ComplianceScore >= 70) reasons.Add("约束条件良好"); // 优化评分 if (candidate.OptimizationScore != null) { if (candidate.OptimizationScore.AssignedPersonnelScore > 80) reasons.Add("指定人员匹配"); if (candidate.OptimizationScore.ProjectFLScore > 80) reasons.Add("项目FL人员"); if (candidate.OptimizationScore.WorkloadBalanceScore > 80) reasons.Add("负载均衡良好"); } // 综合评分 reasons.Add($"综合评分{candidate.TotalScore:F1}分"); return string.Join(";", reasons); } /// /// 生成失败建议 /// private List GenerateFailureSuggestions(WorkOrderEntity workOrder) { var suggestions = new List(); // 基于工序特点给出建议 if (!string.IsNullOrEmpty(workOrder.ProcessEntity?.QualificationRequirements)) { suggestions.Add("考虑放宽资质要求或安排人员培训"); } suggestions.Add("调整任务时间安排"); suggestions.Add("增加临时人员或外包"); suggestions.Add("与其他项目协调人员调配"); return suggestions; } /// /// 生成详细报告 /// private AllocationReport GenerateDetailedReport(AllocationGenerationResult result, AllocationContext context) { var report = new AllocationReport { SuccessfulAllocationCount = result.SuccessfulAllocations.Count, FailedAllocationCount = result.FailedAllocations.Count, SuccessRate = context.WorkOrders.Count > 0 ? (double)result.SuccessfulAllocations.Count / context.WorkOrders.Count * 100 : 0 }; if (result.SuccessfulAllocations.Any()) { report.AverageQualityScore = result.SuccessfulAllocations.Values.Average(c => c.TotalScore); } // 人员利用率分析 report.PersonnelUtilizationRate = result.SuccessfulAllocations.Values .GroupBy(c => c.PersonnelId) .ToDictionary(g => g.Key, g => (double)g.Count()); // 风险分析 report.RiskAnalysis = new RiskAnalysis { OverallRiskScore = result.SuccessfulAllocations.Values .Where(c => c.DecisionScore?.RiskLevel != null) .Average(c => (int)c.DecisionScore.RiskLevel) * 25, // 转换为百分比 RiskDistribution = result.SuccessfulAllocations.Values .Where(c => c.DecisionScore?.RiskLevel != null) .GroupBy(c => c.DecisionScore.RiskLevel) .ToDictionary(g => g.Key, g => g.Count()) }; return report; } /// /// 检查人员基础资格(第一层决策的基础筛选) /// 深度架构思考:在资格过滤层进行人员基础状态检查,符合五层决策模型设计 /// private async Task IsPersonnelBasicallyEligibleAsync(PersonnelEntity personnel) { try { var result = new PersonnelEligibilityResult { PersonnelId = personnel.Id, IsEligible = true, EligibilityReasons = new List() }; // 1. 检查人员基础信息完整性 if (string.IsNullOrEmpty(personnel.PersonnelName)) { result.IsEligible = false; result.EligibilityReasons.Add("人员基础信息不完整"); return result; } // 通过基础资格检查 result.EligibilityReasons.Add("基础资格检查通过"); return result; } catch (Exception ex) { _logger.LogError(ex, "检查人员基础资格异常,人员ID:{PersonnelId}", personnel.Id); return new PersonnelEligibilityResult { PersonnelId = personnel.Id, IsEligible = false, EligibilityReasons = new List { $"基础资格检查异常:{ex.Message}" } }; } } /// /// 检查资质匹配 /// private async Task CheckQualificationMatchAsync(long personnelId, string[] requiredQualifications) { try { if (requiredQualifications == null || !requiredQualifications.Any()) { return new QualificationMatchResult { IsMatched = true, IsValid = true, MatchScore = 100, MatchedQualifications = new List(), MissingQualifications = new List(), Details = "无特殊资质要求" }; } // 获取人员资质 var personnelQualifications = await GetPersonnelQualificationsAsync(personnelId); var validQualificationIds = personnelQualifications .Where(q => q.ExpiryDate == null || q.ExpiryDate > DateTime.Now) .Select(q => q.QualificationId.ToString()) .ToList(); var matchedQualifications = requiredQualifications.Intersect(validQualificationIds).ToList(); var missingQualifications = requiredQualifications.Except(validQualificationIds).ToList(); var matchScore = requiredQualifications.Length > 0 ? (double)matchedQualifications.Count / requiredQualifications.Length * 100 : 100; return new QualificationMatchResult { IsMatched = matchedQualifications.Any() || !requiredQualifications.Any(), IsValid = !missingQualifications.Any(), MatchScore = matchScore, MatchedQualifications = matchedQualifications, MissingQualifications = missingQualifications, Details = $"匹配资质:{matchedQualifications.Count}/{requiredQualifications.Length},匹配度:{matchScore:F1}%" }; } catch (Exception ex) { _logger.LogError(ex, "检查资质匹配异常,人员ID:{PersonnelId}", personnelId); return new QualificationMatchResult { IsMatched = false, IsValid = false, MatchScore = 0, MatchedQualifications = new List(), MissingQualifications = requiredQualifications?.ToList() ?? new List(), Details = $"资质检查异常:{ex.Message}" }; } } /// /// 获取人员资质列表 /// private async Task> GetPersonnelQualificationsAsync(long personnelId) { try { return await _personnelQualificationService.GetPersonnelQualificationsAsync(personnelId); } catch (Exception ex) { _logger.LogError(ex, "获取人员资质异常,人员ID:{PersonnelId}", personnelId); return new List(); } } #endregion #region 执行第二层:约束评估处理辅助方法 /// /// 评估候选人约束条件 /// 深度业务思考:全面检查时间可用性、工作限制、班次规则等多维度约束 /// private async Task EvaluateCandidateConstraintsAsync(PersonnelCandidate candidate, WorkOrderEntity workOrder) { var result = new ConstraintEvaluationResult { Candidate = candidate, IsValid = true, PassedConstraints = new List(), Violations = new List() }; try { var scores = new List(); // 1. 时间可用性检查 var timeAvailability = await CheckTimeAvailabilityAsync(candidate.PersonnelId, workOrder.WorkOrderDate, workOrder.ShiftId ?? 0); result.TimeAvailabilityScore = timeAvailability.Score; scores.Add(timeAvailability.Score); if (timeAvailability.IsAvailable) { result.PassedConstraints.Add("时间可用性检查"); } else { result.Violations.Add(new ConstraintViolation() { Severity = ViolationSeverity.High, IsBlockingIssue = true, CanOverrideWithApproval = false, Description = $"时间不可用:{timeAvailability.Reason}" }); if (timeAvailability.Score == 0) { result.IsValid = false; } } // 2. 工作限制检查 var workLimitCheck = await CheckWorkLimitAsync(candidate.PersonnelId, workOrder.WorkOrderDate); result.WorkLimitScore = workLimitCheck.Score; scores.Add(workLimitCheck.Score); if (workLimitCheck.IsCompliant) { result.PassedConstraints.Add("工作限制检查"); } else { result.Violations.Add(new ConstraintViolation() { Severity = ViolationSeverity.High, IsBlockingIssue = true, CanOverrideWithApproval = false, Description = $"工作限制违规:{workLimitCheck.Reason}" }); if (timeAvailability.Score == 0) { result.IsValid = false; } if (workLimitCheck.Severity == "Critical") { result.IsValid = false; } } // 3. 班次规则检查 var shiftRuleCheck = await CheckShiftRulesAsync(candidate.PersonnelId, workOrder.WorkOrderDate, workOrder.ShiftId ?? 0); result.ShiftRuleScore = shiftRuleCheck.Score; scores.Add(shiftRuleCheck.Score); if (shiftRuleCheck.IsCompliant) { result.PassedConstraints.Add("班次规则检查"); } else { result.Violations.Add(new ConstraintViolation() { Severity = ViolationSeverity.Medium, IsBlockingIssue = true, CanOverrideWithApproval = false, Description = $"班次规则违规:{shiftRuleCheck.Reason}" }); if (shiftRuleCheck.IsCritical) { result.IsValid = false; } } // 计算综合约束评分 result.ConstraintScore = scores.Any() ? scores.Average() : 0; // 生成评估详情 result.EvaluationDetails = GenerateConstraintEvaluationDetails(result, scores); return result; } catch (Exception ex) { _logger.LogError(ex, "约束评估异常,候选人:{PersonnelId},任务:{WorkOrderId}", candidate.PersonnelId, workOrder.Id); result.IsValid = false; result.ConstraintScore = 0; result.EvaluationDetails = $"约束评估异常:{ex.Message}"; return result; } } /// /// 生成约束评估详情 /// private string GenerateConstraintEvaluationDetails(ConstraintEvaluationResult result, List scores) { var details = new List { $"约束评分:{result.ConstraintScore:F1}分", $"通过检查:{result.PassedConstraints.Count}项", $"违规项目:{result.Violations.Count}项" }; if (result.TimeAvailabilityScore.HasValue) details.Add($"时间可用性:{result.TimeAvailabilityScore.Value:F1}分"); if (result.WorkLimitScore.HasValue) details.Add($"工作限制:{result.WorkLimitScore.Value:F1}分"); if (result.ShiftRuleScore.HasValue) details.Add($"班次规则:{result.ShiftRuleScore.Value:F1}分"); // 评估等级 var evaluationLevel = result.ConstraintScore switch { >= 90 => "优秀", >= 80 => "良好", >= 70 => "一般", >= 60 => "勉强", _ => "不合格" }; details.Add($"评估等级:{evaluationLevel}"); return string.Join(";", details); } #region 约束检查方法 /// /// 检查时间可用性 /// private async Task CheckTimeAvailabilityAsync(long personnelId, DateTime workDate, long shiftId) { try { // 检查是否在请假期间 var leaveInfo = await _employeeLeaveService.IsOnLeaveAsync(personnelId, workDate); if (leaveInfo.IsOnLeave) { return new TimeAvailabilityResult { IsAvailable = false, Score = 0, Reason = $"人员在请假期间:{leaveInfo.LeaveType}" }; } // 检查是否已有其他任务分配 var existingTasks = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.ShiftId == shiftId && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); if (existingTasks > 0) { return new TimeAvailabilityResult { IsAvailable = false, Score = 0, Reason = $"该时间段已有{existingTasks}个任务分配" }; } return new TimeAvailabilityResult { IsAvailable = true, Score = 100, Reason = "时间完全可用" }; } catch (Exception ex) { _logger.LogError(ex, "检查时间可用性异常,人员ID:{PersonnelId},日期:{WorkDate}", personnelId, workDate); return new TimeAvailabilityResult { IsAvailable = false, Score = 0, Reason = $"时间检查异常:{ex.Message}" }; } } /// /// 检查工作限制 /// private async Task CheckWorkLimitAsync(long personnelId, DateTime workDate) { try { var workLimits = await _personnelWorkLimitService.GetByPersonnelIdAsync(personnelId); if (workLimits == null || !workLimits.Any()) { return new WorkLimitResult { IsCompliant = true, Score = 100, Reason = "无工作限制配置", Severity = "Info" }; } // 选择第一个工作限制记录 var workLimit = workLimits.First(); var violations = new List(); var scores = new List(); // 检查连续工作天数限制 var continuousWorkDays = await CalculateContinuousWorkDaysAsync(personnelId, workDate); if (workLimit.MaxContinuousWorkDays.HasValue && continuousWorkDays >= workLimit.MaxContinuousWorkDays.Value) { violations.Add($"连续工作天数({continuousWorkDays}) 超过限制({workLimit.MaxContinuousWorkDays.Value})"); scores.Add(0); } else { var continuousScore = workLimit.MaxContinuousWorkDays.HasValue ? Math.Max(0, 100 - (continuousWorkDays / (double)workLimit.MaxContinuousWorkDays.Value * 100)) : 100; scores.Add(continuousScore); } // 检查周班次数限制 var weekShiftCount = await CalculateWeekShiftCountAsync(personnelId, workDate); if (workLimit.MaxShiftsPerWeek.HasValue && weekShiftCount >= workLimit.MaxShiftsPerWeek.Value) { violations.Add($"周班次数({weekShiftCount})超过限制({workLimit.MaxShiftsPerWeek.Value})"); scores.Add(0); } else { var weekScore = workLimit.MaxShiftsPerWeek.HasValue ? Math.Max(0, 100 - (weekShiftCount / (double)workLimit.MaxShiftsPerWeek.Value * 100)) : 100; scores.Add(weekScore); } var averageScore = scores.Any() ? scores.Average() : 100; var isCompliant = !violations.Any(); return new WorkLimitResult { IsCompliant = isCompliant, Score = averageScore, Reason = violations.Any() ? string.Join("; ", violations) : "符合所有工作限制", Severity = violations.Any() ? "Warning" : "Info" }; } catch (Exception ex) { _logger.LogError(ex, "检查工作限制异常,人员ID:{PersonnelId}", personnelId); return new WorkLimitResult { IsCompliant = false, Score = 0, Reason = $"工作限制检查异常:{ex.Message}", Severity = "Error" }; } } /// /// 检查班次规则 /// private async Task CheckShiftRulesAsync(long personnelId, DateTime workDate, long shiftId) { try { // 获取班次规则配置 var shiftRules = await _shiftService.GetShiftRulesAsync(shiftId); if (shiftRules == null || !shiftRules.Any()) { return new ShiftRuleResult { IsCompliant = true, Score = 100, Reason = "无班次规则配置", IsCritical = false }; } var violations = new List(); var scores = new List(); var criticalViolations = new List(); foreach (var rule in shiftRules.Where(r => r.IsEnabled)) { var ruleResult = await ValidateIndividualShiftRuleAsync(rule, personnelId, workDate, shiftId); if (!ruleResult.IsValid) { violations.Add($"规则{rule.RuleName}:{ruleResult.ViolationMessage}"); if (ruleResult.IsCritical) { criticalViolations.Add(ruleResult.ViolationMessage); } } scores.Add(ruleResult.ComplianceScore); } var averageScore = scores.Any() ? scores.Average() : 100; var hasCriticalViolation = criticalViolations.Any(); return new ShiftRuleResult { IsCompliant = !violations.Any() || (!hasCriticalViolation && averageScore >= 50), Score = averageScore, Reason = violations.Any() ? string.Join("; ", violations) : "符合所有班次规则", IsCritical = hasCriticalViolation }; } catch (Exception ex) { _logger.LogError(ex, "检查班次规则异常,人员ID:{PersonnelId}", personnelId); return new ShiftRuleResult { IsCompliant = false, Score = 0, Reason = $"班次规则检查异常:{ex.Message}", IsCritical = true }; } } #region ????? /// /// 验证人员资质 /// private async Task ValidatePersonnelQualificationAsync(long workOrderId, long personnelId) { try { var workOrder = await _workOrderRepository.GetAsync(workOrderId); if (workOrder?.ProcessEntity?.QualificationRequirements == null) { return new ValidationResult { IsValid = true, Violations = new List() }; } var requiredQualifications = workOrder.ProcessEntity.QualificationRequirements.Split(','); var qualificationMatch = await CheckQualificationMatchAsync(personnelId, requiredQualifications); if (qualificationMatch.IsMatched) { return new ValidationResult { IsValid = true, Violations = new List() }; } return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.QualificationMismatch.ToString(), Severity = ViolationSeverity.Error, Description = $"人员资质不匹配,缺失:{string.Join(",", qualificationMatch.MissingQualifications)}" } } }; } catch (Exception ex) { _logger.LogError(ex, "验证人员资质异常,任务:{WorkOrderId},人员:{PersonnelId}", workOrderId, personnelId); return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.QualificationMismatch.ToString(), Severity = ViolationSeverity.Critical, Description = $"资质验证异常:{ex.Message}" } } }; } } /// /// 验证时间冲突 /// private async Task ValidateTimeConflictAsync(long personnelId, DateTime assignmentDate, long shiftId) { try { var timeAvailability = await CheckTimeAvailabilityAsync(personnelId, assignmentDate, shiftId); if (timeAvailability.IsAvailable) { return new ValidationResult { IsValid = true, Violations = new List() }; } return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.TimeConflict.ToString(), Severity = ViolationSeverity.Error, Description = timeAvailability.Reason } } }; } catch (Exception ex) { _logger.LogError(ex, "验证时间冲突异常,人员:{PersonnelId}", personnelId); return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.TimeConflict.ToString(), Severity = ViolationSeverity.Critical, Description = $"时间冲突验证异常:{ex.Message}" } } }; } } /// /// 验证工作限制 /// private async Task ValidateWorkLimitAsync(long personnelId, DateTime assignmentDate) { try { var workLimitResult = await CheckWorkLimitAsync(personnelId, assignmentDate); if (workLimitResult.IsCompliant) { return new ValidationResult { IsValid = true, Violations = new List() }; } var severity = workLimitResult.Severity switch { "Critical" => ViolationSeverity.Critical, "Error" => ViolationSeverity.Error, "Warning" => ViolationSeverity.Warning, _ => ViolationSeverity.Info }; return new ValidationResult { IsValid = severity < ViolationSeverity.Error, Violations = new List { new ValidationViolation { ViolationType = ViolationType.WorkloadExceeded.ToString(), Severity = severity, Description = workLimitResult.Reason } } }; } catch (Exception ex) { _logger.LogError(ex, "验证工作限制异常,人员:{PersonnelId}", personnelId); return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.WorkloadExceeded.ToString(), Severity = ViolationSeverity.Critical, Description = $"工作限制验证异常:{ex.Message}" } } }; } } /// /// 验证班次规则 /// private async Task ValidateShiftRuleAsync(long personnelId, DateTime assignmentDate, long shiftId) { try { var shiftRuleResult = await CheckShiftRulesAsync(personnelId, assignmentDate, shiftId); if (shiftRuleResult.IsCompliant) { return new ValidationResult { IsValid = true, Violations = new List() }; } var severity = shiftRuleResult.IsCritical ? ViolationSeverity.Critical : ViolationSeverity.Warning; return new ValidationResult { IsValid = !shiftRuleResult.IsCritical, Violations = new List { new ValidationViolation { ViolationType = ViolationType.ShiftRuleViolation.ToString(), Severity = severity, Description = shiftRuleResult.Reason } } }; } catch (Exception ex) { _logger.LogError(ex, "验证班次规则异常,人员:{PersonnelId}", personnelId); return new ValidationResult { IsValid = false, Violations = new List { new ValidationViolation { ViolationType = ViolationType.ShiftRuleViolation.ToString(), Severity = ViolationSeverity.Critical, Description = $"班次规则验证异常:{ex.Message}" } } }; } } #endregion #region 检查班次规则实现 /// /// 验证个人班次规则 /// 深度业务思考:基于规则类型、参数和时间有效性进行全面验证 /// private async Task ValidateIndividualShiftRuleAsync(ShiftRuleEntity rule, long personnelId, DateTime workDate, long shiftId) { 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, new Dictionary()), // 连续工作天数 "8" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 8), // 三班后一天不排班 "9" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 9), // 二班后一天不排班 "10" => await ValidateProjectFLPriorityRuleAsync(personnelId, workDate, shiftId, rule), // 优先分配本项目FL _ => await ValidateDefaultRuleAsync(personnelId, workDate, rule, new Dictionary()) // 默认规则验证 }; // 记录规则验证详情 _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})" }; } /// /// 验证同一天内班次连续性规则 /// 深度业务思考:检查同一天内是否存在不合理的班次连续安排 /// /// 人员ID /// 当前工作日期 /// 当前班次ID /// 规则类型(3:同天早中班连续性,4:同天中晚班连续性) /// 规则验证结果,包含合规状态、评分和详细说明 private async Task ValidateShiftContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { // 第一步:检查当天是否已有相同班次的任务分配(防重复分配) var hasDuplicateShift = await CheckDuplicateShiftOnSameDateAsync(personnelId, workDate, shiftId); if (hasDuplicateShift) { _logger.LogWarning("班次重复分配违规 - 人员ID:{PersonnelId}, 日期:{Date}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = "该人员当天已有相同班次的任务分配,不能重复分配" }; } // 第二步:获取当前要分配班次的编号 var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (!currentShiftNumber.HasValue) { _logger.LogWarning("无法获取班次编号 - 班次ID:{ShiftId}", shiftId); return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = $"无法获取班次ID {shiftId} 对应的班次编号" }; } // 第三步:获取当天已分配的所有班次编号 var todayShiftNumbers = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, workDate); // 如果当天无其他班次记录,直接通过验证 if (todayShiftNumbers == null || !todayShiftNumbers.Any()) { _logger.LogDebug("同天班次连续性验证:当天无其他班次记录,通过验证 - 人员ID:{PersonnelId}, 当前班次编号:{ShiftNumber}", personnelId, currentShiftNumber.Value); return CreateValidResult("当天无其他班次记录,通过连续性验证", 100); } // 第四步:根据规则类型检查同一天内班次连续性违规情况 bool hasViolation = ruleType switch { 3 => todayShiftNumbers.Contains(1) && currentShiftNumber == 2, // 同一天禁止早班(1)和中班(2)同时存在 4 => todayShiftNumbers.Contains(2) && currentShiftNumber == 3, // 同一天禁止中班(2)和晚班(3)同时存在 _ => false // 未定义的规则类型默认不违规 }; // 第五步:生成详细的验证结果 if (hasViolation) { var existingShift = todayShiftNumbers.First(s => (ruleType == 3 && s == 1) || (ruleType == 4 && s == 2)); var violationDetail = GenerateSameDayViolationDetail(ruleType, existingShift, currentShiftNumber.Value); _logger.LogWarning("同天班次连续性规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, // 同天连续班次违规是关键风险 ViolationMessage = violationDetail }; } else { var successMessage = GenerateSameDaySuccessMessage(ruleType, currentShiftNumber.Value); _logger.LogDebug("同天班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new RuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } } catch (Exception ex) { // 记录异常并返回保守结果 _logger.LogError(ex, "验证班次连续性规则异常 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); return new RuleValidationResult { IsValid = true, // 异常情况下采用保守策略,不阻断分配 ComplianceScore = 60, // 给予中等评分 IsCritical = false, ViolationMessage = $"验证异常,建议人工审核:{ex.Message}" }; } } /// /// 验证连续工作天数规则 /// private async Task ValidateContinuousWorkDaysRuleAsync(long personnelId, DateTime workDate, Dictionary ruleParams) { 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})" }; } /// /// 验证跨周末班次连续性规则 /// 深度业务思考:检查跨周末时期是否存在违反休息间隔的班次连续安排 /// 规则5:不能这周日/下周六连续工作 /// 规则6:不能本周六/本周日连续工作 /// /// 人员ID /// 当前工作日期 /// 当前班次ID /// 规则类型(5:周日/下周六禁止连续,6:周六/周日禁止连续) /// 规则验证结果,包含合规状态、评分和详细说明 private async Task ValidateCrossWeekendContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证跨周末班次连续性规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 第一步:计算相关的日期 var (firstDate, secondDate) = CalculateWeekendDates(workDate, ruleType); _logger.LogDebug("跨周末日期计算完成 - 第一日期:{FirstDate}, 第二日期:{SecondDate}", firstDate.ToString("yyyy-MM-dd"), secondDate.ToString("yyyy-MM-dd")); // 第二步:检查当前工作日期是否匹配需要验证的日期 bool isTargetDate = workDate.Date == firstDate.Date || workDate.Date == secondDate.Date; if (!isTargetDate) { // 如果当前日期不是目标验证日期,直接通过验证 _logger.LogDebug("当前日期不在验证范围内,跳过验证 - 当前日期:{CurrentDate}", workDate.ToString("yyyy-MM-dd")); return CreateValidResult("非目标验证日期,通过验证", 100); } // 第三步:获取两个日期的班次分配情况 var firstDateShifts = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, firstDate); var secondDateShifts = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, secondDate); // 如果当前要分配的是第二个日期,需要将当前班次加入到检查中 if (workDate.Date == secondDate.Date) { var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (currentShiftNumber.HasValue && !secondDateShifts.Contains(currentShiftNumber.Value)) { secondDateShifts.Add(currentShiftNumber.Value); } } // 如果当前要分配的是第一个日期,需要将当前班次加入到检查中 else if (workDate.Date == firstDate.Date) { var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (currentShiftNumber.HasValue && !firstDateShifts.Contains(currentShiftNumber.Value)) { firstDateShifts.Add(currentShiftNumber.Value); } } // 第四步:检查是否存在跨周末连续性违规 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 RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, // 跨周末连续工作是关键风险 ViolationMessage = violationDetail }; } else { var successMessage = GenerateCrossWeekendSuccessMessage(ruleType, firstDate, secondDate); _logger.LogDebug("跨周末班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new RuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } } catch (Exception ex) { // 记录异常并返回保守结果 _logger.LogError(ex, "验证跨周末班次连续性规则异常 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); return new RuleValidationResult { IsValid = true, // 异常情况下采用保守策略,不阻断分配 ComplianceScore = 60, // 给予中等评分 IsCritical = false, ViolationMessage = $"验证异常,建议人工审核:{ex.Message}" }; } } /// /// 验证项目FL优先分配规则 /// 【深度业务思考】:确保项目专业性和工作效率的关键规则 /// 业务背景: /// - FL(Front Line,一线人员)是项目的核心执行者 /// - 本项目FL具有专业知识和操作经验优势 /// - 优先分配本项目FL有助于提高工作质量和效率 /// - 降低项目间人员切换的学习成本和沟通成本 /// /// 规则10业务逻辑: /// - 检查人员是否为当前工作任务所属项目的FL成员 /// - 本项目FL获得优先分配机会(高评分) /// - 非本项目FL仍可分配但评分较低 /// - 确保项目专业性与人员灵活调配的平衡 /// /// 【边界情况深度分析】: /// 1. 跨项目FL:人员同时是多个项目的FL,按关联度评分 /// 2. 项目FL不足:当本项目FL都不可用时,允许跨项目分配 /// 3. 新项目场景:项目初期可能还没有指定FL,采用宽松策略 /// 4. FL资质变更:人员FL资格变更后的历史数据处理 /// 5. 项目暂停/结束:项目状态变更对FL优先级的影响 /// 6. 紧急任务:紧急情况下可适当放宽FL限制 /// /// 人员ID /// 工作日期 /// 班次ID /// 规则实体(包含规则参数配置) /// 规则验证结果,包含优先级评分和详细说明 private async Task ValidateProjectFLPriorityRuleAsync(long personnelId, DateTime workDate, long shiftId, ShiftRuleEntity rule) { try { _logger.LogDebug("开始验证项目FL优先分配规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}", personnelId, workDate.ToString("yyyy-MM-dd")); // 第一步:获取当前分配上下文中的工作任务信息 // 【重要说明】:由于此方法是规则验证,我们需要从上下文中获取当前要分配的任务信息 var currentWorkOrder = await GetCurrentWorkOrderFromContextAsync(personnelId, workDate, shiftId); if (currentWorkOrder == null) { _logger.LogWarning("无法获取当前工作任务上下文 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); return CreateValidResult("无法确定工作任务上下文,采用中性评分", 85); } // 第二步:检查人员是否为当前项目的FL成员 var isProjectFL = await CheckPersonnelIsProjectFLAsync(personnelId, currentWorkOrder.ProjectNumber); // 第三步:查询人员的其他项目FL关联情况(用于综合评分) var allProjectFLs = await GetPersonnelAllProjectFLsAsync(personnelId); // 第四步:根据FL关联情况进行综合评分 var scoringResult = CalculateProjectFLPriorityScore(personnelId, currentWorkOrder.ProjectNumber, isProjectFL, allProjectFLs, rule); // 第五步:生成详细的验证结果 var resultMessage = GenerateProjectFLValidationMessage(personnelId, currentWorkOrder.ProjectNumber, isProjectFL, scoringResult.Score, scoringResult.Reason); _logger.LogDebug("项目FL优先规则验证完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, " + "是否本项目FL:{IsProjectFL}, 评分:{Score}", personnelId, currentWorkOrder.ProjectNumber, isProjectFL, scoringResult.Score); return new RuleValidationResult { IsValid = true, // 此规则主要影响优先级,不阻断分配 ComplianceScore = scoringResult.Score, IsCritical = false, ViolationMessage = resultMessage }; } catch (Exception ex) { // 【异常处理策略】:记录详细异常信息并返回保守结果 _logger.LogError(ex, "验证项目FL优先规则异常 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 【保守策略】:异常情况下给予中等评分,不阻断分配流程 return new RuleValidationResult { IsValid = true, ComplianceScore = 80, // 给予中等偏上评分 IsCritical = false, ViolationMessage = $"项目FL优先规则验证异常,建议人工审核:{ex.Message}" }; } } /// /// 默认规则验证 /// private async Task ValidateDefaultRuleAsync(long personnelId, DateTime workDate, ShiftRuleEntity rule, Dictionary ruleParams) { // 对于未识别的规则类型,进行基础验证 _logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName); return new RuleValidationResult { IsValid = true, ComplianceScore = 90, // 给予较高分,但不是满分 IsCritical = false, ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证" }; } #region 十一种规则辅助方法 /// /// 检查指定人员在同一天是否已有相同班次的任务分配 /// 业务逻辑:防止同一人员在同一天被分配多个相同班次的任务 /// 深度思考:考虑任务状态、班次类型、数据一致性等因素 /// /// 人员ID /// 工作日期 /// 班次ID /// true表示存在重复班次分配,false表示无重复 private async Task CheckDuplicateShiftOnSameDateAsync(long personnelId, DateTime workDate, long shiftId) { try { // 查询该人员在指定日期是否已有相同班次的任务分配 // 只考虑非取消和非拒绝状态的任务 var existingAssignment = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.ShiftId == shiftId && w.Status > (int)WorkOrderStatusEnum.PendingReview) .FirstAsync(); var hasDuplicate = existingAssignment != null; if (hasDuplicate) { _logger.LogWarning("发现重复班次分配 - 人员ID:{PersonnelId}, 日期:{Date}, 班次ID:{ShiftId}, 已存在任务ID:{ExistingTaskId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, existingAssignment.Id); } else { _logger.LogDebug("无重复班次分配 - 人员ID:{PersonnelId}, 日期:{Date}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); } return hasDuplicate; } catch (Exception ex) { _logger.LogError(ex, "检查重复班次分配异常 - 人员ID:{PersonnelId}, 日期:{Date}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 异常情况下采用保守策略,假设无重复 return false; } } /// /// 线程安全的班次编号缓存锁 - 与GlobalPersonnelAllocationService共享 /// 【并发安全】:防止多个服务同时访问ShiftService导致DbContext冲突 /// private static readonly SemaphoreSlim _shiftCacheLock = new SemaphoreSlim(1, 1); /// /// 班次编号缓存 - 与GlobalPersonnelAllocationService共享缓存 /// 【性能+安全】:跨服务共享班次缓存,避免重复查询和并发冲突 /// private static readonly Dictionary _shiftNumberCache = new Dictionary(); /// /// 根据班次ID获取班次编号 - 线程安全版本 /// 业务逻辑:将班次ID转换为班次编号,用于班次连续性规则计算 /// 【关键修复】:使用SemaphoreSlim确保线程安全,避免FreeSql DbContext并发冲突 /// 深度思考:处理班次不存在、数据异常、并发访问等所有边界情况 /// /// 班次ID /// 班次编号(1,2,3等),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); int? shiftNumber = null; if (shift != null) { shiftNumber = shift.ShiftNumber; _logger.LogDebug("获取班次编号成功 - 班次ID:{ShiftId}, 班次编号:{ShiftNumber}", shiftId, shift.ShiftNumber); } else { _logger.LogWarning("班次不存在 - 班次ID:{ShiftId}", shiftId); } // 【缓存更新】:将结果存入缓存供后续使用(包括null值) _shiftNumberCache[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 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(); // 提取班次编号并去重 var shiftNumbers = workOrdersWithShifts .Where(w => w.ShiftEntity != null) .Select(w => w.ShiftEntity.ShiftNumber) .Distinct() .ToList(); _logger.LogDebug("获取人员当日班次编号成功 - 人员ID:{PersonnelId}, 日期:{Date}, 班次编号:{ShiftNumbers}", personnelId, date.ToString("yyyy-MM-dd"), string.Join(",", shiftNumbers)); return shiftNumbers; } catch (Exception ex) { _logger.LogError(ex, "获取人员当日班次编号异常 - 人员ID:{PersonnelId}, 日期:{Date}", personnelId, date.ToString("yyyy-MM-dd")); // 异常情况下返回空列表,避免阻断验证流程 return new List(); } } /// /// 创建验证通过结果的辅助方法 /// private RuleValidationResult CreateValidResult(string reason, double score = 100) { return new RuleValidationResult { IsValid = true, ComplianceScore = score, IsCritical = false, ViolationMessage = "" }; } /// /// 生成同天班次违规详细信息 /// 业务逻辑:为同天班次连续性违规生成详细的错误说明 /// /// 规则类型 /// 已存在的班次编号 /// 当前要分配的班次编号 /// 详细的违规说明 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}符合要求" }; } /// /// 计算跨周末规则验证的相关日期 /// 业务逻辑:根据规则类型和当前工作日期,计算需要检查的两个关键日期 /// 深度思考:考虑周历边界、时区、月份跨越等复杂场景 /// /// 当前工作日期 /// 规则类型(5:周日/下周六,6:周六/周日) /// 需要检查的两个日期(第一日期,第二日期) private (DateTime firstDate, DateTime secondDate) CalculateWeekendDates(DateTime workDate, int ruleType) { try { var currentDate = workDate.Date; if (ruleType == 5) // 不能这周日/下周六连续 { // 计算当前日期所在周的周日(本周日) var daysFromSunday = (int)currentDate.DayOfWeek; // Sunday = 0, Monday = 1, ..., Saturday = 6 var thisWeekSunday = currentDate.AddDays(-daysFromSunday); // 计算下周六(下一周的周六) var nextWeekSaturday = thisWeekSunday.AddDays(13); // 下周六 = 本周日 + 13天 _logger.LogDebug("规则5日期计算 - 本周日:{ThisSunday}, 下周六:{NextSaturday}", thisWeekSunday.ToString("yyyy-MM-dd"), nextWeekSaturday.ToString("yyyy-MM-dd")); return (thisWeekSunday, nextWeekSaturday); } else if (ruleType == 6) // 不能本周六/本周日连续 { // 计算当前日期所在周的周六和周日 var daysFromSunday = (int)currentDate.DayOfWeek; var thisWeekSunday = currentDate.AddDays(-daysFromSunday); var thisWeekSaturday = thisWeekSunday.AddDays(-1); // 本周六 = 本周日 - 1天 _logger.LogDebug("规则6日期计算 - 本周六:{ThisSaturday}, 本周日:{ThisSunday}", thisWeekSaturday.ToString("yyyy-MM-dd"), thisWeekSunday.ToString("yyyy-MM-dd")); 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 firstDateName = GetDateDisplayName(firstDate, ruleType, true); var secondDateName = GetDateDisplayName(secondDate, ruleType, false); var firstShiftsStr = string.Join(",", firstDateShifts); var secondShiftsStr = string.Join(",", secondDateShifts); return ruleType switch { 5 => $"跨周末连续性违规:违反周日/下周六禁止连续工作规则,{firstDateName}有班次{firstShiftsStr},{secondDateName}有班次{secondShiftsStr}", 6 => $"周末连续性违规:违反周六/周日禁止连续工作规则,{firstDateName}有班次{firstShiftsStr},{secondDateName}有班次{secondShiftsStr}", _ => $"跨周末班次连续性违规:规则类型{ruleType},{firstDateName}({firstDate:yyyy-MM-dd})和{secondDateName}({secondDate:yyyy-MM-dd})存在班次冲突" }; } /// /// 生成跨周末班次验证成功信息 /// 业务逻辑:为跨周末班次连续性验证通过生成详细的成功说明 /// /// 规则类型 /// 第一个日期 /// 第二个日期 /// 成功验证的详细说明 private string GenerateCrossWeekendSuccessMessage(int ruleType, DateTime firstDate, DateTime secondDate) { var firstDateName = GetDateDisplayName(firstDate, ruleType, true); var secondDateName = GetDateDisplayName(secondDate, ruleType, false); return ruleType switch { 5 => $"跨周末连续性验证通过:符合周日/下周六禁止连续工作规则", 6 => $"周末连续性验证通过:符合周六/周日禁止连续工作规则", _ => $"跨周末班次连续性验证通过:规则类型{ruleType},{firstDateName}和{secondDateName}符合要求" }; } /// /// 验证次日休息规则 /// 【深度业务思考】:该规则确保员工在高强度班次(二班/三班)工作后获得充分的休息时间 /// 业务背景: /// - 二班(中班)通常是下午14:00-22:00,工作强度较大 /// - 三班(夜班)通常是晚上22:00-次日6:00,对身体负担最重 /// - 劳动保护要求:夜班工作后必须有24小时的休息时间 /// - 生产效率考虑:疲劳工作会导致操作失误和安全隐患 /// /// 规则8:三班后一天不排班 - 检查前一天是否有三班(班次编号3),如有则当天不能排班 /// 规则9:二班后一天不排班 - 检查前一天是否有二班(班次编号2),如有则当天不能排班 /// /// 【边界情况深度分析】: /// 1. 跨周末场景:周六三班→周日必须休息 /// 2. 跨月场景:月末最后一天三班→次月第一天必须休息 /// 3. 节假日场景:节假日前的二班/三班→节假日当天必须休息 /// 4. 紧急任务场景:即使有紧急任务,也不能违反休息规则 /// 5. 多班次冲突:前一天有多个班次时,只要包含目标班次就触发规则 /// 6. 任务状态过滤:只考虑已确认和执行中的任务,草稿状态不算 /// /// 人员ID /// 当前工作日期(即将分配班次的日期) /// 当前班次ID(即将分配的班次) /// 规则类型(8:三班后次日休息,9:二班后次日休息) /// 规则验证结果,包含合规状态、评分和详细说明 private async Task ValidateNextDayRestRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证次日休息规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 第一步:确定需要检查的特定班次编号 // 【业务逻辑】:根据规则类型确定需要检查的前置班次 int targetShiftNumber = ruleType switch { 8 => 3, // 规则8:检查前一天是否有三班(夜班) 9 => 2, // 规则9:检查前一天是否有二班(中班) _ => 0 // 未知规则类型,返回0表示无效 }; if (targetShiftNumber == 0) { _logger.LogWarning("未支持的次日休息规则类型:{RuleType},跳过验证", ruleType); return CreateValidResult("未支持的规则类型,跳过验证", 90); } // 第二步:计算前一天日期并进行边界检查 var previousDate = workDate.AddDays(-1); // 【边界情况处理】:检查日期跨度的合理性 var daysDifference = (workDate.Date - previousDate.Date).TotalDays; if (Math.Abs(daysDifference - 1) > 0.01) // 允许微小的浮点误差 { _logger.LogWarning("日期计算异常 - 工作日期:{WorkDate}, 前一天:{PreviousDate}, 天数差:{Days}", workDate.ToString("yyyy-MM-dd"), previousDate.ToString("yyyy-MM-dd"), daysDifference); return CreateValidResult("日期计算异常,建议人工审核", 70); } _logger.LogDebug("次日休息规则验证 - 检查前一天:{PreviousDate}是否有{TargetShift}班", previousDate.ToString("yyyy-MM-dd"), GetShiftDisplayName(targetShiftNumber)); // 第三步:获取前一天的所有班次编号(包含状态过滤) // 【业务逻辑】:只检查已确认的工作任务,避免草稿状态的干扰 var previousDayShifts = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, previousDate); // 【数据完整性检查】:验证查询结果的有效性 if (previousDayShifts == null) { _logger.LogWarning("获取前一天班次信息失败 - 人员ID:{PersonnelId}, 日期:{PreviousDate}", personnelId, previousDate.ToString("yyyy-MM-dd")); return CreateValidResult("数据查询异常,建议人工审核", 70); } // 第四步:检查前一天是否有目标班次 bool hadTargetShiftPreviousDay = previousDayShifts.Contains(targetShiftNumber); // 【详细日志记录】:记录查询到的班次信息,便于调试 _logger.LogDebug("前一天班次查询结果 - 人员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, false); _logger.LogDebug("次日休息规则验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new RuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } // 第五步:【关键业务逻辑】前一天有目标班次,检查当天是否安排了班次(违规检查) // 【重要说明】:当前方法是在分配前进行验证,如果执行到这里说明: // 1. 前一天确实有目标班次(二班或三班) // 2. 当天正在尝试分配新的班次 // 3. 这违反了"次日必须休息"的规则 // 【获取当前即将分配的班次信息】:用于生成更详细的违规说明 var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); var currentShiftName = currentShiftNumber.HasValue ? GetShiftDisplayName(currentShiftNumber.Value) : "未知班次"; var violationDetail = GenerateNextDayRestViolationDetail(ruleType, previousDate, workDate, targetShiftNumber, previousDayShifts, currentShiftName); _logger.LogWarning("次日休息规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); // 【关键决策】:这是强制性的安全规则,必须严格执行 return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, // 违反休息规则是关键风险,涉及员工健康和生产安全 ViolationMessage = violationDetail }; } catch (Exception ex) { // 【异常处理策略】:记录详细异常信息并返回保守结果 _logger.LogError(ex, "验证次日休息规则异常 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); // 【保守策略说明】:在系统异常情况下,倾向于不阻断业务流程,但要求人工审核 // 这样既避免了系统故障导致的业务停滞,又保证了潜在违规得到人工关注 return new RuleValidationResult { IsValid = true, // 异常情况下采用保守策略,不阻断分配 ComplianceScore = 60, // 给予中等评分,表示存在不确定性 IsCritical = false, // 标记为非关键,但需要人工审核 ViolationMessage = $"系统验证异常,强烈建议人工审核此次班次分配的合规性:{ex.Message}" }; } } /// /// 生成次日休息规则违规详细信息 /// 【业务逻辑深度思考】:生成详细的违规说明,帮助管理人员理解违规原因和影响 /// 错误信息设计原则: /// 1. 明确指出违规的具体规则 /// 2. 详细说明前一天的班次情况 /// 3. 明确当前试图分配的班次信息 /// 4. 提供易于理解的业务背景说明 /// /// 规则类型(8:三班后休息,9:二班后休息) /// 前一天日期 /// 当前日期(即将分配班次的日期) /// 触发规则的班次编号(2或3) /// 前一天的所有班次编号列表 /// 当前试图分配的班次名称 /// 详细的违规说明,包含业务上下文和建议 private string GenerateNextDayRestViolationDetail(int ruleType, DateTime previousDate, DateTime currentDate, int targetShiftNumber, List previousDayShifts, string currentShiftName = "未知班次") { var targetShiftDisplayName = GetShiftDisplayName(targetShiftNumber); var previousShiftsStr = string.Join(",", previousDayShifts); var dayOfWeekPrevious = previousDate.ToString("dddd", new System.Globalization.CultureInfo("zh-CN")); var dayOfWeekCurrent = currentDate.ToString("dddd", new System.Globalization.CultureInfo("zh-CN")); // 【生成详细的违规说明】:包含时间、班次、规则背景等完整信息 return ruleType switch { 8 => $"【三班后次日强制休息违规】" + Environment.NewLine + $"违规详情:" + Environment.NewLine + $"• 前一天:{previousDate:yyyy-MM-dd}({dayOfWeekPrevious}) 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})" + Environment.NewLine + $"• 前一天所有班次:{previousShiftsStr}" + Environment.NewLine + $"• 当前日期:{currentDate:yyyy-MM-dd}({dayOfWeekCurrent}) 试图分配{currentShiftName}" + Environment.NewLine + $"• 违规原因:夜班工作强度大,员工需要24小时恢复时间" + Environment.NewLine + $"• 建议方案:将该班次分配给其他人员,或调整至{currentDate.AddDays(1):yyyy-MM-dd}后", 9 => $"【二班后次日强制休息违规】" + Environment.NewLine + $"违规详情:" + Environment.NewLine + $"• 前一天:{previousDate:yyyy-MM-dd}({dayOfWeekPrevious}) 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})" + Environment.NewLine + $"• 前一天所有班次:{previousShiftsStr}" + Environment.NewLine + $"• 当前日期:{currentDate:yyyy-MM-dd}({dayOfWeekCurrent}) 试图分配{currentShiftName}" + Environment.NewLine + $"• 违规原因:中班工作时间长,员工需要充分休息时间" + Environment.NewLine + $"• 建议方案:将该班次分配给其他人员,或调整至{currentDate.AddDays(1):yyyy-MM-dd}后", _ => $"【次日休息规则违规】规则类型{ruleType}" + Environment.NewLine + $"前一天({previousDate:yyyy-MM-dd})有{targetShiftDisplayName}(班次{previousShiftsStr})," + $"当天({currentDate:yyyy-MM-dd})试图分配{currentShiftName},违反休息规则" }; } /// /// 生成次日休息规则验证成功信息 /// 【业务逻辑思考】:为次日休息规则验证通过生成详细的成功说明 /// 成功信息的价值: /// 1. 确认规则检查已正确执行 /// 2. 记录验证的具体条件和结果 /// 3. 为系统审计提供完整的决策轨迹 /// /// 规则类型(8:三班后休息,9:二班后休息) /// 前一天日期 /// 目标班次编号(2或3) /// 前一天是否有目标班次(当前总是false,为扩展保留) /// 成功验证的详细说明,包含验证条件和结论 private string GenerateNextDayRestSuccessMessage(int ruleType, DateTime previousDate, int targetShiftNumber, bool hadTargetShift) { var shiftDisplayName = GetShiftDisplayName(targetShiftNumber); var dayOfWeek = previousDate.ToString("dddd", new System.Globalization.CultureInfo("zh-CN")); // 【生成详细的成功验证信息】:清楚说明验证通过的原因 return ruleType switch { 8 => $"三班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}({dayOfWeek})无{shiftDisplayName}安排," + $"员工无需强制休息,可正常分配班次。验证条件:检查前日是否有夜班(编号3)作业", 9 => $"二班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}({dayOfWeek})无{shiftDisplayName}安排," + $"员工无需强制休息,可正常分配班次。验证条件:检查前日是否有中班(编号2)作业", _ => $"次日休息规则验证通过:规则类型{ruleType},前一天{previousDate:yyyy-MM-dd}({dayOfWeek})无{shiftDisplayName},符合规则要求" }; } /// /// 获取班次显示名称 /// 业务逻辑:将班次编号转换为易读的显示名称 /// /// 班次编号 /// 班次显示名称 private string GetShiftDisplayName(int shiftNumber) { return shiftNumber switch { 1 => "早班", 2 => "中班", 3 => "晚班", _ => $"{shiftNumber}班" }; } /// /// 获取日期的显示名称 /// 业务逻辑:根据规则类型和日期位置生成易读的日期描述 /// /// 日期 /// 规则类型 /// 是否为第一个日期 /// 日期显示名称 private string GetDateDisplayName(DateTime date, int ruleType, bool isFirst) { return ruleType switch { 5 => isFirst ? "本周日" : "下周六", 6 => isFirst ? "本周六" : "本周日", _ => $"{(isFirst ? "第一日期" : "第二日期")}({date:yyyy-MM-dd})" }; } #endregion #endregion #endregion #endregion #endregion #region 执行第三层:优化决策处理 /// /// 计算优化评分 /// private async Task CalculateOptimizationScoreAsync(PersonnelCandidate candidate, WorkOrderEntity workOrder, AllocationContext context) { try { var result = new OptimizationScoreResult { Candidate = candidate, DimensionScores = new Dictionary(), RiskAssessment = new RiskAssessment() }; // 指定人员优先评分 var assignedScore = workOrder.AssignedPersonnelId.HasValue && workOrder.AssignedPersonnelId.Value == candidate.PersonnelId ? 100.0 : 0.0; result.DimensionScores["AssignedPersonnel"] = assignedScore; // 项目FL人员优先评分 var projectFLScore = await CalculateProjectFLScoreAsync(candidate.PersonnelId, workOrder.ProjectNumber); result.DimensionScores["ProjectFL"] = projectFLScore; // 负载均衡评分 var workloadScore = await CalculateWorkloadBalanceScoreAsync(candidate.PersonnelId); result.DimensionScores["WorkloadBalance"] = workloadScore; // 班次连续性评分 var continuityScore = await CalculateShiftContinuityScoreAsync(candidate.PersonnelId, workOrder); result.DimensionScores["ShiftContinuity"] = continuityScore; // 技能匹配评分 var skillScore = candidate.QualificationScore.MatchScore; result.DimensionScores["SkillMatch"] = skillScore; // 协作历史评分 var collaborationScore = await CalculateCollaborationScoreAsync(candidate.PersonnelId, workOrder.ProjectNumber); result.DimensionScores["CollaborationHistory"] = collaborationScore; // 计算加权总分 var weightedScore = 0.0; foreach (var kvp in result.DimensionScores) { var weight = context.WeightConfiguration.GetValueOrDefault(kvp.Key, 0.0); weightedScore += kvp.Value * weight; } result.OptimizationScore = Math.Min(100, weightedScore); result.Confidence = CalculateConfidence(result.DimensionScores, candidate.ConstraintScore.ComplianceScore); result.RecommendationLevel = DetermineRecommendationLevel(result.OptimizationScore, result.Confidence); result.OptimizationReason = GenerateOptimizationReason(result.DimensionScores, context.WeightConfiguration); // 风险评估 result.RiskAssessment = await CalculateRiskAssessmentAsync(candidate, workOrder, result); return result; } catch (Exception ex) { _logger.LogError(ex, "计算优化评分异常,候选人:{PersonnelId}", candidate.PersonnelId); return new OptimizationScoreResult { Candidate = candidate, OptimizationScore = 0, Confidence = 0, RecommendationLevel = RecommendationLevel.NotFeasible, OptimizationReason = $"评分计算异常:{ex.Message}" }; } } /// /// 计算项目FL评分 /// private async Task CalculateProjectFLScoreAsync(long personnelId, string projectNumber) { try { // 查询该人员是否为项目内FL人员 var isProjectFL = await _workOrderRepository.Select .AnyAsync(w => w.ProjectNumber == projectNumber && w.WorkOrderFLPersonnels.Any(fp => fp.FLPersonnelId == personnelId)); return isProjectFL ? 100.0 : 0.0; } catch (Exception ex) { _logger.LogError(ex, "计算项目FL评分异常,人员ID:{PersonnelId},项目:{ProjectNumber}", personnelId, projectNumber); return 0.0; } } /// /// 计算工作负载均衡评分 /// private async Task CalculateWorkloadBalanceScoreAsync(long personnelId) { try { var dateRange = new DateRange { StartDate = DateTime.Today, EndDate = DateTime.Today.AddDays(30) }; var workloadAnalysis = await AnalyzePersonnelWorkloadAsync(new List { personnelId }, dateRange); var personalWorkload = workloadAnalysis.FirstOrDefault(); if (personalWorkload == null) { return 100.0; // 无工作负载,最高分 } // 基于工作负载百分比计算平衡评分(负载越低,评分越高) return personalWorkload.WorkloadPercentage switch { < 30 => 100.0, < 50 => 90.0, < 70 => 80.0, < 90 => 60.0, < 100 => 40.0, _ => 20.0 }; } catch (Exception ex) { _logger.LogError(ex, "计算工作负载均衡评分异常,人员ID:{PersonnelId}", personnelId); return 50.0; // 异常时返回中等评分 } } /// /// 计算班次连续性评分 /// private async Task CalculateShiftContinuityScoreAsync(long personnelId, WorkOrderEntity workOrder) { try { // 获取前一天的班次 var previousDay = workOrder.WorkOrderDate.AddDays(-1); var previousShift = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == previousDay.Date) .FirstAsync(); if (previousShift == null) { return 80.0; // 无前一天班次,给予较高分 } // 获取后一天的班次 var nextDay = workOrder.WorkOrderDate.AddDays(1); var nextShift = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == nextDay.Date) .FirstAsync(); // 评估班次连续性(相同班次的连续性更好) var continuityScore = 60.0; // 基础分 if (previousShift?.ShiftId == workOrder.ShiftId) { continuityScore += 20.0; } if (nextShift?.ShiftId == workOrder.ShiftId) { continuityScore += 20.0; } return Math.Min(100.0, continuityScore); } catch (Exception ex) { _logger.LogError(ex, "计算班次连续性评分异常,人员ID:{PersonnelId}", personnelId); return 60.0; // 异常时返回中等评分 } } /// /// 计算协作历史评分 /// private async Task CalculateCollaborationScoreAsync(long personnelId, string projectNumber) { try { // 查询历史协作次数 var collaborationCount = await _workOrderRepository.Select .Where(w => w.ProjectNumber == projectNumber && w.AssignedPersonnelId == personnelId && w.Status == (int)WorkOrderStatusEnum.Completed) .CountAsync(); // 基于协作次数计算评分 return collaborationCount switch { 0 => 50.0, // 无历史协作 1 => 60.0, // 少量协作 <= 3 => 75.0, // 适度协作 <= 5 => 85.0, // 良好协作 _ => 95.0 // 丰富协作经验 }; } catch (Exception ex) { _logger.LogError(ex, "计算协作历史评分异常,人员ID:{PersonnelId},项目:{ProjectNumber}", personnelId, projectNumber); return 50.0; } } #endregion #region 计算和统计方法 /// /// 计算综合总分 /// private double CalculateTotalScore(PersonnelCandidate candidate, Dictionary weights) { var scores = new Dictionary { ["Qualification"] = candidate.QualificationScore.MatchScore, ["Constraint"] = candidate.ConstraintScore.ComplianceScore, ["Optimization"] = candidate.OptimizationScore?.TotalOptimizationScore ?? 0 }; var weightedSum = 0.0; var totalWeight = 0.0; // 资格和约束是必须项,权重固定 weightedSum += scores["Qualification"] * 0.3; // 资格权重30% weightedSum += scores["Constraint"] * 0.4; // 约束权重40% weightedSum += scores["Optimization"] * 0.3; // 优化权重30% return Math.Min(100, weightedSum); } /// /// 计算连续工作天数 /// 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; } /// /// 异步计算连续工作天数 /// private async Task CalculateContinuousWorkDaysAsync(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, "计算连续工作天数异常,人员ID:{PersonnelId}", personnelId); return 0; } } /// /// 计算周班次数 /// private async Task CalculateWeekShiftCountAsync(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, "计算周班次数异常,人员ID:{PersonnelId}", personnelId); return 1; } } /// /// 计算月工作小时数 /// private async Task CalculateMonthlyWorkHoursAsync(long personnelId, DateTime targetDate) { try { var monthStart = new DateTime(targetDate.Year, targetDate.Month, 1); var monthEnd = monthStart.AddMonths(1).AddDays(-1); var totalHours = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= monthStart && w.WorkOrderDate <= monthEnd && w.Status > (int)WorkOrderStatusEnum.Assigned) .SumAsync(w => w.EstimatedHours ?? 8.0m); return totalHours + 8.0m; // 包含目标日期的估算工时 } catch (Exception ex) { _logger.LogError(ex, "计算月工作小时数异常,人员ID:{PersonnelId}", personnelId); return 8.0m; } } #endregion #region 辅助计算方法 /// /// 计算置信度 /// private double CalculateConfidence(Dictionary dimensionScores, double constraintScore) { var scores = dimensionScores.Values.ToList(); scores.Add(constraintScore); if (!scores.Any()) return 0; var average = scores.Average(); var variance = scores.Sum(s => Math.Pow(s - average, 2)) / scores.Count; var standardDeviation = Math.Sqrt(variance); // 置信度与平均分正相关,与标准差负相关 var confidence = average * (1 - standardDeviation / 100); return Math.Max(0, Math.Min(100, confidence)); } /// /// 确定推荐等级 /// private RecommendationLevel DetermineRecommendationLevel(double optimizationScore, double confidence) { var combinedScore = (optimizationScore + confidence) / 2; return combinedScore switch { >= 90 => RecommendationLevel.HighlyRecommended, >= 75 => RecommendationLevel.Recommended, >= 60 => RecommendationLevel.Acceptable, >= 40 => RecommendationLevel.NotRecommended, _ => RecommendationLevel.NotFeasible }; } /// /// 生成优化理由 /// private string GenerateOptimizationReason(Dictionary dimensionScores, Dictionary weights) { var reasons = new List(); foreach (var kvp in dimensionScores.OrderByDescending(x => x.Value * weights.GetValueOrDefault(x.Key, 0))) { var weight = weights.GetValueOrDefault(kvp.Key, 0); var weightedScore = kvp.Value * weight; if (weightedScore > 50) // 只显示较好的评分 { var dimensionName = kvp.Key switch { "AssignedPersonnel" => "指定人员匹配", "ProjectFL" => "项目FL人员", "WorkloadBalance" => "负载均衡", "ShiftContinuity" => "班次连续性", "SkillMatch" => "技能匹配", "CollaborationHistory" => "协作历史", _ => kvp.Key }; reasons.Add($"{dimensionName}({kvp.Value:F1}分,权重{weight:P0})"); } } return reasons.Any() ? $"优势:{string.Join(";", reasons)}" : "综合评分一般"; } /// /// 计算风险评估 /// private async Task CalculateRiskAssessmentAsync(PersonnelCandidate candidate, WorkOrderEntity workOrder, OptimizationScoreResult scoreResult) { var riskAssessment = new RiskAssessment { RiskFactors = new List() }; try { // 约束风险 if (candidate.ConstraintScore.ComplianceScore < 80) { riskAssessment.RiskFactors.Add(new RiskFactor { RiskType = "约束合规风险", Description = $"约束符合度较低({candidate.ConstraintScore.ComplianceScore:F1}分)", Level = candidate.ConstraintScore.ComplianceScore < 60 ? RiskLevel.High : RiskLevel.Medium, Probability = 100 - candidate.ConstraintScore.ComplianceScore, Impact = 70 }); } // 资质风险 if (candidate.QualificationScore.MatchScore < 90) { riskAssessment.RiskFactors.Add(new NPP.SmartSchedue.Api.Contracts.Services.Integration.Models.RiskFactor { RiskType = "资质匹配风险", Description = $"资质匹配度不完全({candidate.QualificationScore.MatchScore:F1}分)", Level = candidate.QualificationScore.MatchScore < 60 ? RiskLevel.High : RiskLevel.Medium, Probability = 100 - candidate.QualificationScore.MatchScore, Impact = 60 }); } // 工作负载风险 var workloadScore = scoreResult.DimensionScores.GetValueOrDefault("WorkloadBalance", 100); if (workloadScore < 50) { riskAssessment.RiskFactors.Add(new NPP.SmartSchedue.Api.Contracts.Services.Integration.Models.RiskFactor { RiskType = "工作负载风险", Description = "人员工作负载较重,可能影响工作质量", Level = workloadScore < 30 ? RiskLevel.High : RiskLevel.Medium, Probability = 100 - workloadScore, Impact = 50 }); } // 计算总体风险 if (riskAssessment.RiskFactors.Any()) { var avgProbability = riskAssessment.RiskFactors.Average(rf => rf.Probability); var avgImpact = riskAssessment.RiskFactors.Average(rf => rf.Impact); riskAssessment.RiskScore = (avgProbability + avgImpact) / 2; riskAssessment.OverallRiskLevel = riskAssessment.RiskScore switch { < 30 => RiskLevel.Low, < 60 => RiskLevel.Medium, < 80 => RiskLevel.High, _ => RiskLevel.Critical }; } else { riskAssessment.OverallRiskLevel = RiskLevel.Low; riskAssessment.RiskScore = 10; } riskAssessment.RiskDescription = GenerateRiskDescription(riskAssessment); riskAssessment.MitigationSuggestions = GenerateMitigationSuggestions(riskAssessment.RiskFactors); return riskAssessment; } catch (Exception ex) { _logger.LogError(ex, "计算风险评估异常,候选人:{PersonnelId}", candidate.PersonnelId); return new RiskAssessment { OverallRiskLevel = RiskLevel.Medium, RiskScore = 50, RiskDescription = $"风险评估异常:{ex.Message}" }; } } /// /// 生成风险描述 /// private string GenerateRiskDescription(RiskAssessment riskAssessment) { if (!riskAssessment.RiskFactors.Any()) { return "风险较低,可以安全执行"; } var highRiskFactors = riskAssessment.RiskFactors.Where(rf => rf.Level >= RiskLevel.High).ToList(); if (highRiskFactors.Any()) { return $"存在{highRiskFactors.Count}个高风险因子,需要特别关注:{string.Join(";", highRiskFactors.Select(rf => rf.RiskType))}"; } return $"存在{riskAssessment.RiskFactors.Count}个中低风险因子,建议采取预防措施"; } /// /// 生成缓解建议 /// private List GenerateMitigationSuggestions(List riskFactors) { var suggestions = new List(); foreach (var riskFactor in riskFactors) { var suggestion = riskFactor.RiskType switch { "约束合规风险" => "建议调整任务时间或寻找替代人员", "资质匹配风险" => "建议提供必要的培训或技术支持", "工作负载风险" => "建议减少该人员的其他任务或增加协助人员", _ => "建议进一步评估和监控" }; suggestions.Add(suggestion); } return suggestions.Distinct().ToList(); } #endregion #region 结果计算和生成方法 /// /// 计算验证评分 /// private double CalculateValidationScore(List violations, List warnings, int totalAssignments) { if (totalAssignments == 0) return 100; var criticalCount = violations.Count(v => v.Severity == ViolationSeverity.Critical); var errorCount = violations.Count(v => v.Severity == ViolationSeverity.Error); var warningCount = violations.Count(v => v.Severity == ViolationSeverity.Warning) + warnings.Count; // 计算扣分 var deductions = criticalCount * 30 + errorCount * 20 + warningCount * 5; var baseScore = Math.Max(0, 100 - (deductions / (double)totalAssignments)); return Math.Round(baseScore, 1); } /// /// 生成验证报告 /// private string GenerateValidationReport(PersonnelAllocationValidationResult result) { var report = $"验证完成:{result.PassedAssignmentCount}个通过,{result.FailedAssignmentCount}个失败,评分:{result.ValidationScore}"; if (result.Violations.Any()) { var groupedViolations = result.Violations.GroupBy(v => v.Severity); var violationSummary = string.Join(";", groupedViolations.Select(g => $"{g.Key}级别{g.Count()}个")); report += $";违规分布:{violationSummary}"; } return report; } /// /// 根据策略排序候选人员 /// private List SortCandidatesByStrategy(List candidates, PersonnelAllocationStrategy strategy) { return strategy switch { _ => candidates.OrderByDescending(c => c.TotalScore).ToList() }; } /// /// 计算推荐置信度 /// private double CalculateRecommendationConfidence(IEnumerable optimalAssignments) { var candidates = optimalAssignments.ToList(); if (!candidates.Any()) return 0; var averageScore = candidates.Average(c => c.TotalScore); var minScore = candidates.Min(c => c.TotalScore); // 置信度基于平均分和最低分 var confidence = (averageScore + minScore) / 2; return Math.Round(confidence, 1); } #endregion #region 其他辅助方法 /// /// 创建失败结果 /// private PersonnelAllocationResult CreateFailureResult(string errorMessage) { return new PersonnelAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), WorkloadAnalysis = new List(), FairnessScore = 0, AllocationSummary = $"分配失败:{errorMessage}", ProcessingDetails = errorMessage, AllocationStrategy = "Failed", ValidationResult = new { OverallScore = 0, ProcessingMetrics = new Dictionary(), ValidationPassed = false, ErrorMessage = errorMessage } }; } /// /// 创建部分失败结果 /// private PersonnelAllocationResult CreatePartialFailureResult(AllocationContext context, string errorMessage) { var result = new PersonnelAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), WorkloadAnalysis = new List(), FairnessScore = 0, AllocationSummary = $"部分失败:{errorMessage}", ProcessingDetails = errorMessage, AllocationStrategy = context.Strategy.ToString(), ValidationResult = new { OverallScore = 0, ProcessingMetrics = context.Metrics, ValidationPassed = false, ErrorMessage = errorMessage } }; return result; } /// /// 创建空结果 /// private PersonnelAllocationResult CreateEmptyResult() { return new PersonnelAllocationResult { SuccessfulMatches = new List(), FailedAllocations = new List(), WorkloadAnalysis = new List(), FairnessScore = 100, // 没有分配时公平性为满分 AllocationSummary = "无任务需要分配", ProcessingDetails = "输入的任务列表为空", AllocationStrategy = "Empty", ValidationResult = new { OverallScore = 100, ProcessingMetrics = new Dictionary(), ValidationPassed = true, ErrorMessage = "" } }; } /// /// 计算整体质量评分 /// private double CalculateOverallQualityScore(AllocationGenerationResult result) { if (!result.SuccessfulAllocations.Any()) return 0; var averageScore = result.SuccessfulAllocations.Values.Average(c => c.TotalScore); var successRate = (double)result.SuccessfulAllocations.Count / (result.SuccessfulAllocations.Count + result.FailedAllocations.Count) * 100; return Math.Round((averageScore + successRate) / 2, 1); } /// /// 生成分配摘要 /// private string GenerateAllocationSummary(AllocationGenerationResult result, AllocationContext context) { var totalTasks = context.WorkOrders.Count; var successCount = result.SuccessfulAllocations.Count; var failureCount = result.FailedAllocations.Count; return $"分配完成:{totalTasks}个任务,成功{successCount}个,失败{failureCount}个,质量评分{result.OverallQualityScore:F1}"; } /// /// 生成工作负载分析 /// private async Task> GenerateWorkloadAnalysisAsync( Dictionary successfulAllocations) { var workloadAnalysis = new List(); var personnelGroups = successfulAllocations.Values.GroupBy(c => c.PersonnelId); foreach (var group in personnelGroups) { var personnel = group.First(); workloadAnalysis.Add(new PersonnelWorkloadAnalysis { PersonnelId = personnel.PersonnelId, PersonnelName = personnel.PersonnelName, AssignedTaskCount = group.Count(), WorkloadPercentage = Math.Min(100, group.Count() * 25), // 假设每个任务25%工作负载 WorkloadStatus = group.Count() switch { 1 => "轻松", 2 => "适中", 3 => "繁重", _ => "超负荷" } }); } return workloadAnalysis.OrderByDescending(w => w.WorkloadPercentage).ToList(); } /// /// 计算公平性评分 /// private int CalculateFairnessScore(List workloadAnalysis) { if (!workloadAnalysis.Any()) return 100; var workloads = workloadAnalysis.Select(w => w.WorkloadPercentage).ToList(); var average = workloads.Average(); var variance = workloads.Sum(w => Math.Pow(w - average, 2)) / workloads.Count; var standardDeviation = Math.Sqrt(variance); // 公平性评分:标准差越小,公平性越高 var fairnessScore = Math.Max(0, 100 - standardDeviation); return (int)Math.Round(fairnessScore); } /// /// 计算预估效率 /// private decimal CalculateEstimatedEfficiency(PersonnelCandidate candidate) { // 基于各项评分计算预估效率 var qualificationFactor = candidate.QualificationScore.MatchScore / 100; var constraintFactor = candidate.ConstraintScore.ComplianceScore / 100; var optimizationFactor = candidate.OptimizationScore?.TotalOptimizationScore / 100 ?? 0.8; var efficiency = (qualificationFactor + constraintFactor + optimizationFactor) / 3; return Math.Round((decimal)efficiency, 2); } #endregion #region 辅助数据结构 /// /// 人员状态检查结果 /// private class PersonnelStatusCheck { public bool IsActive { get; set; } public string Reason { get; set; } public string StatusCode { get; set; } } /// /// 项目授权检查结果 /// private class ProjectAuthorizationCheck { public bool IsAuthorized { get; set; } public double AuthorizationScore { get; set; } public string Reason { get; set; } public string AuthorizationLevel { get; set; } } /// /// 设备可用性检查结果 /// private class EquipmentAvailabilityCheck { public bool IsAvailable { get; set; } public double AvailabilityScore { get; set; } public string Reason { get; set; } public int ConflictCount { get; set; } } /// /// 地理位置约束检查结果 /// private class LocationConstraintCheck { public bool IsAccessible { get; set; } public double AccessibilityScore { get; set; } public string Reason { get; set; } public TimeSpan EstimatedTravelTime { get; set; } public decimal TravelCost { get; set; } } /// /// 规则验证结果 /// private class RuleValidationResult { public bool IsValid { get; set; } public double ComplianceScore { get; set; } public bool IsCritical { get; set; } public string ViolationMessage { get; set; } } /// /// 时间可用性结果 /// private class TimeAvailabilityResult { public bool IsAvailable { get; set; } public double Score { get; set; } public string Reason { get; set; } } /// /// 工作限制结果 /// private class WorkLimitResult { public bool IsCompliant { get; set; } public double Score { get; set; } public string Reason { get; set; } public string Severity { get; set; } } /// /// 班次规则结果 /// private class ShiftRuleResult { public bool IsCompliant { get; set; } public double Score { get; set; } public string Reason { get; set; } public bool IsCritical { get; set; } } /// /// 班次时间冲突结果 /// private class ShiftTimeConflictResult { public bool HasConflict { get; set; } public string ConflictDescription { get; set; } } /// /// 资质匹配结果 /// private class QualificationMatchResult { public bool IsMatched { get; set; } public bool IsValid { get; set; } public double MatchScore { get; set; } public List MatchedQualifications { get; set; } = new(); public List MissingQualifications { get; set; } = new(); public string Details { get; set; } } /// /// 输入验证结果 /// private class InputValidationResult { public bool IsValid { get; set; } public string ErrorMessage { get; set; } public List WorkOrders { get; set; } } /// /// 人员基础资格检查结果 /// private class PersonnelEligibilityResult { public long PersonnelId { get; set; } public bool IsEligible { get; set; } public List EligibilityReasons { get; set; } = new(); } #region 规则10:项目FL优先分配相关辅助方法 /// /// 从当前分配上下文中获取工作任务信息 /// 【业务逻辑】:在规则验证过程中获取当前正在分配的工作任务 /// 深度思考:考虑多种获取任务信息的方式,确保数据准确性 /// /// 人员ID /// 工作日期 /// 班次ID /// 当前工作任务实体,如果获取失败返回null private async Task GetCurrentWorkOrderFromContextAsync(long personnelId, DateTime workDate, long shiftId) { try { // 方法一:从当前分配上下文获取(如果有的话) // 这里需要根据实际的分配上下文实现 // 方法二:查询最近创建的、尚未分配FL的工作任务 var recentWorkOrders = await _workOrderRepository.Select .Where(w => w.WorkOrderDate.Date == workDate.Date && w.ShiftId == shiftId && w.Status == (int)WorkOrderStatusEnum.PendingAssignment) .OrderByDescending(w => w.CreatedTime) .Take(10) .ToListAsync(); if (recentWorkOrders.Any()) { // 优先返回项目号相关的任务(如果人员已有项目关联) var personnelProjects = await GetPersonnelAllProjectFLsAsync(personnelId); if (personnelProjects.Any()) { var matchedOrder = recentWorkOrders.FirstOrDefault(w => personnelProjects.Any(p => p.ProjectNumber == w.ProjectNumber)); if (matchedOrder != null) { return matchedOrder; } } // 否则返回第一个符合条件的任务 return recentWorkOrders.First(); } // 方法三:如果没有待分配的任务,查找相似的已分配任务作为参考 var similarWorkOrders = await _workOrderRepository.Select .Where(w => w.WorkOrderDate.Date == workDate.Date && w.ShiftId == shiftId) .OrderByDescending(w => w.CreatedTime) .Take(1) .ToListAsync(); return similarWorkOrders.FirstOrDefault(); } catch (Exception ex) { _logger.LogError(ex, "获取当前工作任务上下文异常 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); return null; } } /// /// 检查人员是否为指定项目的FL成员 /// 【业务逻辑】:查询WorkOrderFLPersonnelEntity表,确定人员与项目的FL关联关系 /// 深度思考:考虑FL资格的时效性、状态有效性等因素 /// /// 人员ID /// 项目号 /// true表示是该项目的FL,false表示不是 private async Task CheckPersonnelIsProjectFLAsync(long personnelId, string projectNumber) { try { // 查询该人员在指定项目中的FL记录 var flRecords = await _workOrderRepository.Orm.Select() .From() .Where((flp, wo) => flp.FLPersonnelId == personnelId && wo.ProjectNumber == projectNumber && flp.WorkOrderId == wo.Id) .ToListAsync(); bool isProjectFL = flRecords.Any(); _logger.LogDebug("检查人员项目FL关联 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, 是否FL:{IsFL}, FL记录数:{Count}", personnelId, projectNumber, isProjectFL, flRecords.Count); return isProjectFL; } catch (Exception ex) { _logger.LogError(ex, "检查人员项目FL关联异常 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); return false; } } /// /// 获取人员所有项目FL关联情况 /// 【业务逻辑】:查询人员参与的所有项目FL记录,用于综合评分 /// 深度思考:考虑项目活跃度、历史记录权重等因素 /// /// 人员ID /// 人员所有项目FL关联列表 private async Task> GetPersonnelAllProjectFLsAsync(long personnelId) { try { // 查询该人员的所有项目FL记录,并统计每个项目的任务数量 var projectFLRecords = await _workOrderRepository.Orm.Select() .From() .Where((flp, wo) => flp.FLPersonnelId == personnelId && flp.WorkOrderId == wo.Id) .ToListAsync((flp, wo) => new { flp.FLPersonnelId, wo.ProjectNumber, wo.CreatedTime }); var projectFLStats = projectFLRecords .GroupBy(x => x.ProjectNumber) .Select(g => new ProjectFLInfo { ProjectNumber = g.Key, TaskCount = g.Count(), LatestAssignmentDate = g.Max(x => x.CreatedTime) ?? DateTime.MinValue }) .ToList(); _logger.LogDebug("获取人员所有项目FL关联 - 人员ID:{PersonnelId}, 项目数:{ProjectCount}", personnelId, projectFLStats.Count); return projectFLStats; } catch (Exception ex) { _logger.LogError(ex, "获取人员所有项目FL关联异常 - 人员ID:{PersonnelId}", personnelId); return new List(); } } /// /// 计算项目FL优先级评分 /// 【业务逻辑】:基于FL关联情况进行多维度评分 /// 评分策略: /// - 本项目FL:100分(最高优先级) /// - 有其他项目FL经验:85分(有经验,但需要适应) /// - 无FL经验:70分(可以培养,但优先级较低) /// - 多项目FL:95分(经验丰富,适应性强) /// /// 人员ID /// 当前项目号 /// 是否为当前项目FL /// 所有项目FL关联情况 /// 规则实体(可能包含评分权重配置) /// 评分结果,包含分数和详细原因 private ProjectFLScoringResult CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber, bool isProjectFL, List allProjectFLs, ShiftRuleEntity rule) { try { // 基础评分逻辑 if (isProjectFL) { // 本项目FL获得最高优先级 return new ProjectFLScoringResult { Score = 100, Reason = $"本项目FL成员,具有专业经验和项目知识,优先分配" }; } // 非本项目FL的评分策略 if (allProjectFLs.Any()) { // 有其他项目FL经验 int projectCount = allProjectFLs.Count; DateTime latestAssignment = allProjectFLs.Max(p => p.LatestAssignmentDate); 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; // 长期未参与FL工作 } // 确保评分在合理范围内 score = Math.Min(95, Math.Max(70, score)); return new ProjectFLScoringResult { Score = score, Reason = $"有{projectCount}个项目FL经验,最近参与时间:{latestAssignment:yyyy-MM-dd},具有一定项目经验" }; } else { // 无FL经验,但仍可培养 return new ProjectFLScoringResult { Score = 70, Reason = "暂无项目FL经验,可作为培养对象适当分配" }; } } catch (Exception ex) { _logger.LogError(ex, "计算项目FL优先级评分异常 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, currentProjectNumber); return new ProjectFLScoringResult { Score = 75, Reason = $"评分计算异常,采用默认中等评分:{ex.Message}" }; } } /// /// 生成项目FL验证信息 /// 【业务逻辑】:生成详细的验证结果说明,帮助理解分配决策 /// /// 人员ID /// 项目号 /// 是否为项目FL /// 评分 /// 评分原因 /// 详细的验证信息 private string GenerateProjectFLValidationMessage(long personnelId, string projectNumber, bool isProjectFL, double score, string reason) { string priorityLevel = score switch { >= 95 => "最高优先级", >= 85 => "高优先级", >= 75 => "中等优先级", _ => "低优先级" }; return $"【项目FL优先分配验证】" + Environment.NewLine + $"• 人员ID:{personnelId}" + Environment.NewLine + $"• 目标项目:{projectNumber}" + Environment.NewLine + $"• 是否本项目FL:{(isProjectFL ? "是" : "否")}" + Environment.NewLine + $"• 优先级评分:{score:F1}分({priorityLevel})" + Environment.NewLine + $"• 评分依据:{reason}" + Environment.NewLine + $"• 分配建议:{GetAllocationRecommendation(score, isProjectFL)}"; } /// /// 获取分配建议 /// 【业务逻辑】:基于评分提供分配建议 /// private string GetAllocationRecommendation(double score, bool isProjectFL) { if (isProjectFL) { return "强烈推荐分配,本项目FL具有最佳匹配度"; } else if (score >= 85) { return "推荐分配,有相关项目经验,适应性较强"; } else if (score >= 75) { return "可以分配,需要适当培训和指导"; } else { return "谨慎分配,建议优先考虑其他候选人"; } } /// /// 项目FL信息 /// private class ProjectFLInfo { public string ProjectNumber { get; set; } public int TaskCount { get; set; } public DateTime LatestAssignmentDate { get; set; } } /// /// 项目FL评分结果 /// private class ProjectFLScoringResult { public double Score { get; set; } public string Reason { get; set; } } #endregion #endregion } }