using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Caching.Memory; using ZhonTai.Admin.Services; using ZhonTai.DynamicApi.Attributes; using NPP.SmartSchedue.Api.Contracts.Services.Integration; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Input; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Output; using NPP.SmartSchedue.Api.Repositories.Work; using NPP.SmartSchedue.Api.Contracts.Services.Personnel; using NPP.SmartSchedue.Api.Contracts.Services.Time; using NPP.SmartSchedue.Api.Contracts.Domain.Work; using NPP.SmartSchedue.Api.Contracts.Domain.Time; using NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal; using NPP.SmartSchedue.Api.Services.Integration.Algorithms; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Services.Personnel.Output; using NPP.SmartSchedue.Api.Contracts.Services.Time.Output; using NPP.SmartSchedue.Api.Contracts.Services.Work; using NPP.SmartSchedue.Api.Contracts.Services.Work.Output; using ShiftRuleValidationResult = NPP.SmartSchedue.Api.Contracts.Services.Integration.Internal.ShiftRuleValidationResult; namespace NPP.SmartSchedue.Api.Services.Integration { /// /// 全局优化人员分配服务 /// 基于遗传算法和基尼系数的智能全局优化分配 /// [DynamicApi(Area = "app")] public partial class GlobalPersonnelAllocationService : BaseService, IGlobalPersonnelAllocationService { private readonly WorkOrderRepository _workOrderRepository; private readonly IPersonService _personService; private readonly IPersonnelWorkLimitService _personnelWorkLimitService; private readonly IPersonnelQualificationService _personnelQualificationService; private readonly IProcessService _processService; private readonly IQualificationService _qualificationService; private readonly IShiftService _shiftService; private readonly IShiftRuleService _shiftRuleService; private readonly IEmployeeLeaveService _employeeLeaveService; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; private readonly ActivitySource _activitySource; /// /// 输入验证引擎 - 专门处理第一阶段的输入验证和数据准备 /// private readonly InputValidationEngine _inputValidationEngine; /// /// 上下文构建引擎 - 专门处理第二阶段的上下文构建和性能优化 /// private readonly ContextBuilderEngine _contextBuilderEngine; /// /// 全局优化引擎 - 专门处理第三阶段的遗传算法优化和结果处理 /// private readonly GlobalOptimizationEngine _optimizationEngine; /// /// 人员ID到姓名的映射缓存 - 避免重复查询,提高性能 /// private readonly Dictionary _personnelNameCache = new(); /// /// 当前全局分配上下文 - 存储预加载的数据,避免重复数据库查询 /// 业务用途:在分配过程中提供缓存的班次规则、人员历史等数据 /// 生命周期:在AllocatePersonnelGloballyAsync方法执行期间有效 /// private GlobalAllocationContext? _currentAllocationContext; public GlobalPersonnelAllocationService( WorkOrderRepository workOrderRepository, IPersonService personService, IPersonnelWorkLimitService personnelWorkLimitService, IPersonnelQualificationService personnelQualificationService, IProcessService processService, IShiftService shiftService, IShiftRuleService shiftRuleService, IEmployeeLeaveService employeeLeaveService, ILogger logger, IQualificationService qualificationService, IMemoryCache memoryCache, InputValidationEngine inputValidationEngine, ContextBuilderEngine contextBuilderEngine, GlobalOptimizationEngine optimizationEngine) { _workOrderRepository = workOrderRepository; _personService = personService; _personnelWorkLimitService = personnelWorkLimitService; _personnelQualificationService = personnelQualificationService; _processService = processService; _shiftService = shiftService; _shiftRuleService = shiftRuleService; _employeeLeaveService = employeeLeaveService; _logger = logger; _qualificationService = qualificationService; _memoryCache = memoryCache; _inputValidationEngine = inputValidationEngine ?? throw new ArgumentNullException(nameof(inputValidationEngine)); _contextBuilderEngine = contextBuilderEngine ?? throw new ArgumentNullException(nameof(contextBuilderEngine)); _optimizationEngine = optimizationEngine ?? throw new ArgumentNullException(nameof(optimizationEngine)); _activitySource = new ActivitySource("NPP.SmartSchedule.GlobalAllocation"); } /// /// 全局优化人员分配 - 核心业务方法 /// 业务逻辑:使用遗传算法对未分配人员的任务进行全局优化分配 /// 算法流程:输入验证 → 构建分配上下文 → 执行遗传算法优化 → 智能协商处理冲突 → 返回优化结果 /// 性能目标:30秒内完成分配,基尼系数小于0.3,约束满足率大于90% /// /// 全局分配输入参数,包含待分配任务、排除人员、优化配置 /// 全局分配结果,包含成功匹配、失败项、公平性分析、协商操作等 [HttpPost] public async Task AllocatePersonnelGloballyAsync(GlobalAllocationInput input) { using var activity = _activitySource?.StartActivity("GlobalAllocation"); var stopwatch = Stopwatch.StartNew(); try { var taskCount = input.Tasks?.Count ?? input.TaskIds?.Count ?? 0; _logger.LogInformation("🚀 开始全局优化人员分配,任务数量:{TaskCount},排除人员:{ExcludedCount}", taskCount, input.ExcludedPersonnelIds?.Count ?? 0); // 【重构完成】第一阶段:使用专门的输入验证引擎处理输入验证和数据准备 _logger.LogInformation("📋 阶段1:开始输入验证和数据准备(使用InputValidationEngine)"); var validationResult = await _inputValidationEngine.ValidateAndPrepareAsync(input); if (!validationResult.IsValid) { _logger.LogError("❌ 输入验证失败:{ErrorMessage}", validationResult.ErrorMessage); return CreateFailureResult(validationResult.ErrorMessage); } _logger.LogInformation("✅ 输入验证通过,有效任务数:{ValidTaskCount}(Engine处理)", validationResult.WorkOrders.Count); // 【重构完成】第二阶段:使用专门的上下文构建引擎构建全局分配上下文 _logger.LogInformation("🏗️ 阶段2:开始构建全局分配上下文(使用ContextBuilderEngine)"); var context = await _contextBuilderEngine.BuildContextAsync(input, validationResult.WorkOrders); _logger.LogInformation( "📊 分配上下文构建完成 - 任务数:{TaskCount},可用人员:{PersonnelCount},预筛选结果:{PrefilterCount}(Engine处理)", context.Tasks.Count, context.AvailablePersonnel.Count, context.PrefilterResults.Count); // 【性能关键修复】:设置当前分配上下文,供班次规则查询优化使用 _currentAllocationContext = context; activity?.SetTag("task.count", context.Tasks.Count); activity?.SetTag("personnel.pool.size", context.AvailablePersonnel.Count); activity?.SetTag("algorithm.population.size", input.OptimizationConfig.PopulationSize); // 第三阶段:执行全局优化算法 _logger.LogInformation("🧬 阶段3:开始执行全局优化算法"); var optimizationResult = await _optimizationEngine.ExecuteOptimizationAsync(context); stopwatch.Stop(); activity?.SetTag("execution.time.ms", stopwatch.ElapsedMilliseconds); activity?.SetTag("allocation.success", optimizationResult.IsSuccess); if (optimizationResult.IsSuccess) { _logger.LogInformation("🎉 全局优化分配成功!耗时:{ElapsedMs}ms,成功分配:{SuccessCount}个任务,失败:{FailCount}个任务", stopwatch.ElapsedMilliseconds, optimizationResult.SuccessfulMatches?.Count ?? 0, optimizationResult.FailedAllocations?.Count ?? 0); } else { _logger.LogWarning("⚠️ 全局优化分配未完全成功,耗时:{ElapsedMs}ms,原因:{Reason}", stopwatch.ElapsedMilliseconds, optimizationResult.AllocationSummary ?? "未知原因"); } return optimizationResult; } catch (Exception ex) { stopwatch.Stop(); if (activity != null) { activity.SetStatus(ActivityStatusCode.Error, ex.Message); } _logger.LogError(ex, "全局优化人员分配异常,耗时:{ElapsedMs}ms", stopwatch.ElapsedMilliseconds); return CreateFailureResult($"系统异常:{ex.Message}"); } finally { // 【内存管理】:清理当前分配上下文,避免内存泄漏 _currentAllocationContext = null; } } #region 核心算法实现 /// /// 获取可用人员数量 /// private async Task GetAvailablePersonnelCountAsync() { try { var personnel = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false); return personnel?.Count(p => p.IsActive) ?? 0; } catch (Exception ex) { _logger.LogWarning(ex, "获取人员数量异常,使用默认值"); return 10; // 默认人员数量 } } #endregion #region 辅助方法 /// /// 创建失败结果 - 错误处理工具方法 /// 业务逻辑:当输入验证或系统异常时,统一创建格式化的失败响应 /// 设计目的:保证API响应的一致性,提供明确的错误信息 /// /// 错误消息 /// 失败的全局分配结果 private GlobalAllocationResult CreateFailureResult(string errorMessage) { return new GlobalAllocationResult { IsSuccess = false, AllocationSummary = $"全局分配失败:{errorMessage}", ProcessingDetails = errorMessage }; } #endregion #region 生产环境大规模优化方法 /// /// 根据任务规模确定最优并发度 /// 业务逻辑:生产环境(100任务)需要平衡性能和系统资源占用 /// private int DetermineConcurrencyLevel(int taskCount, int personnelCount) { var coreCount = Environment.ProcessorCount; if (taskCount <= 2) { // 小规模:充分利用CPU核心 return Math.Min(coreCount, taskCount); } else if (taskCount <= 10) { // 中规模:适度并行,避免过度竞争 return Math.Min(coreCount * 2, taskCount / 2); } else { // 大规模(生产环境):保守策略,避免内存压力 var optimalLevel = Math.Max(coreCount / 2, 4); // 最少4个并发 return Math.Min(optimalLevel, 12); // 最多12个并发,避免过度并发 } } /// /// 根据任务数量确定最优批次大小 /// 业务逻辑:分批处理可以降低内存峰值占用,提高稳定性 /// private int DetermineOptimalBatchSize(int taskCount) { if (taskCount <= 2) { return taskCount; // 小规模一次性处理 } else if (taskCount <= 10) { return 15; // 中规模分批处理 } else { return 20; // 大规模小批次高频率处理 } } /// /// 针对大规模任务的特别优化配置 /// 适用7天100任务的生产场景 /// private void OptimizeForLargeScale(GlobalAllocationContext context) { _logger.LogInformation("【大规模优化】启动生产环境优化模式"); // 1. 调整缓存策略:增加缓存容量以应对大规模数据 context.CacheManager.L1MaxSize = 200; // 从100增加到200 context.CacheManager.L2MaxSize = 1000; // 从500增加到1000 context.CacheManager.L3MaxSize = 3000; // 从2000增加到3000 // 2. 调整收敛检测参数:适应大规模任务的复杂度 context.ConvergenceDetector.WindowSize = 15; // 从10增加到15 context.ConvergenceDetector.MaxPlateauGenerations = 25; // 从15增加到25 context.ConvergenceDetector.MinGenerationsBeforeCheck = 30; // 从20增加到30 // 3. 调整遗传算法参数:保证质量的同时控制性能 if (context.Config.PopulationSize > 120) { _logger.LogWarning("【大规模优化】种群大小过大({PopulationSize}),调整为120以控制内存占用", context.Config.PopulationSize); context.Config.PopulationSize = 120; } // 4. 设置大规模性能监控 context.Metrics["LargeScaleMode"] = true; context.Metrics["OptimizationTarget"] = "ProductionScale100Tasks"; _logger.LogInformation("【大规模优化】优化配置完成 - 缓存L1:{L1},L2:{L2},收敛窗口:{Window},种群大小:{PopSize}", context.CacheManager.L1MaxSize, context.CacheManager.L2MaxSize, context.ConvergenceDetector.WindowSize, context.Config.PopulationSize); } /// /// 初始化性能优化组件 /// private void InitializePerformanceComponents(GlobalAllocationContext context) { // 初始化收敛检测器 context.ConvergenceDetector = new ConvergenceDetector(); // 初始化智能缓存管理器 context.CacheManager = new IntelligentCacheManager(); // 初始化其他组件... context.ParallelPartitions = new List(); context.PrefilterResults = new Dictionary(); context.HighPriorityTaskPersonnelMapping = new Dictionary>(); } #endregion #region 占位符方法(待后续实现) #region 人员替换辅助方法 #region 辅助计算方法 /// /// 计算连续工作天数 - 连续性统计算法 /// 业务逻辑:从指定日期向前统计连续有任务分配的天数 /// private async Task CalculateContinuousWorkDaysAsync(long personnelId, DateTime workDate) { var continuousDays = 0; var checkDate = workDate.AddDays(-1); // 从前一天开始检查 // 向前检查连续工作天数,最多检查30天防止无限循环 for (int i = 0; i < 30; i++) { var dayTaskCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == checkDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); if (dayTaskCount > 0) { continuousDays++; checkDate = checkDate.AddDays(-1); } else { break; // 遇到无任务的天数,停止统计 } } return continuousDays; } /// /// 计算周班次数 - 周期性统计算法 /// 业务逻辑:统计指定日期所在周的班次总数 /// private async Task CalculateWeekShiftCountAsync(long personnelId, DateTime workDate) { // 计算当前日期所在周的开始和结束日期 var dayOfWeek = (int)workDate.DayOfWeek; var startOfWeek = workDate.AddDays(-dayOfWeek); // 周日为一周开始 var endOfWeek = startOfWeek.AddDays(6); var weekShiftCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= startOfWeek && w.WorkOrderDate <= endOfWeek && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); return (int)weekShiftCount; } #endregion #endregion #endregion /// /// 项目FL经验 - 人员项目FL参与经验数据结构 /// 业务用途:记录人员在特定项目中的FL经验和参与情况 /// public class ProjectFLExperience { /// /// 项目编号 /// public string ProjectNumber { get; set; } = string.Empty; /// /// 参与任务数量 /// public int TaskCount { get; set; } /// /// 最早参与日期 /// public DateTime EarliestAssignmentDate { get; set; } /// /// 最近参与日期 /// public DateTime LatestAssignmentDate { get; set; } /// /// 总经验月数 /// public int TotalExperienceMonths { get; set; } } /// /// 项目FL评分结果 - FL优先评分算法的输出结果 /// 业务用途:封装FL优先评分的分数和详细原因说明 /// public class ProjectFLScoringResult { /// /// FL优先评分(0-100分) /// public double Score { get; set; } /// /// 评分原因和详细说明 /// public string Reason { get; set; } = string.Empty; } #region 班次规则验证辅助方法 - 基于PersonnelAllocationService完整逻辑 /// /// 获取班次关联的规则列表 - 性能优化版本 /// 【核心性能优化】:优先使用预加载的班次规则映射数据,避免重复数据库查询 /// 【业务逻辑】:查询指定班次ID关联的所有规则配置,支持两级数据获取策略 /// 技术策略:预加载缓存 → 数据库查询 → 异常降级 /// 性能提升:将每次2个数据库查询优化为0个查询(缓存命中时) /// /// 班次ID /// 班次规则列表 private async Task> GetShiftRulesAsync(long shiftId) { try { // 第一级:尝试从预加载的班次规则映射数据中获取 // 【性能关键修复】:直接使用CreateOptimizedAllocationContextAsync预加载的数据 if (_currentAllocationContext?.ShiftRulesMapping.Any() == true) { var preloadedRules = _currentAllocationContext.ShiftRulesMapping; var convertedRules = preloadedRules.Select(rule => new ShiftRuleEntity { Id = rule.Id, RuleType = rule.RuleType, RuleName = rule.RuleName, IsEnabled = rule.IsEnabled, }).ToList(); _logger.LogDebug("从预加载缓存获取班次规则成功 - 班次ID:{ShiftId}, 规则数量:{RuleCount}", shiftId, convertedRules.Count); return convertedRules; } // 第二级:预加载数据不可用时,回退到原始数据库查询逻辑 _logger.LogDebug("预加载缓存未命中,回退到数据库查询 - 班次ID:{ShiftId}", shiftId); return await _shiftService.GetShiftRulesAsync(shiftId); } catch (Exception ex) { _logger.LogError(ex, "获取班次规则异常 - 班次ID:{ShiftId}", shiftId); // 第三级:异常降级处理,返回空规则列表,避免阻断业务流程 return new List(); } } /// /// 验证个人班次规则 - 完整规则验证引擎 /// 【深度业务思考】:基于规则类型、参数和时间有效性进行全面验证 /// 参考PersonnelAllocationService.ValidateIndividualShiftRuleAsync的完整实现 /// /// 班次规则实体 /// 人员ID /// 工作日期 /// 班次ID /// 规则验证结果 private async Task ValidateIndividualShiftRuleAsync(ShiftRuleEntity rule, long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context = null) { try { if (!rule.IsEnabled) { return new RuleValidationResult { IsValid = true, // 规则不生效,视为通过 ComplianceScore = 100.0, IsCritical = false, ViolationMessage = $"规则'{rule.RuleName}'在此时间段不生效" }; } // 根据规则类型进行详细验证 var result = rule.RuleType switch { "1" => await ValidateAssignedPersonnelPriorityRuleAsync(personnelId, workDate), // 指定人员优先 "2" => await ValidateWeeklyTaskLimitRuleAsync(personnelId, workDate), "3" => await ValidateShiftContinuityRuleAsync(personnelId, workDate, shiftId, 3), // 1->2班 "4" => await ValidateShiftContinuityRuleAsync(personnelId, workDate, shiftId, 4), // 2->3班 "5" => await ValidateCrossWeekendContinuityRuleAsync(personnelId, workDate, shiftId, 5), // 不能这周日/下周六连 "6" => await ValidateCrossWeekendContinuityRuleAsync(personnelId, workDate, shiftId, 6), // 不能本周六/本周日连 "7" => await ValidateContinuousWorkDaysRuleAsync(personnelId, workDate), // 连续工作天数 "8" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 8), // 三班后一天不排班 "9" => await ValidateNextDayRestRuleAsync(personnelId, workDate, shiftId, 9), // 二班后一天不排班 "10" => await ValidateProjectFLPriorityRuleAsync(personnelId, workDate, shiftId, rule, context, context.CurrentTask), // 优先分配本项目FL _ => await ValidateDefaultRuleAsync(personnelId, workDate, rule) // 默认规则验证 }; // 记录规则验证详情 _logger.LogDebug("规则验证完成 - 规则:{RuleName}({RuleType}),人员:{PersonnelId},结果:{IsValid},评分:{Score}", rule.RuleName, rule.RuleType, personnelId, result.IsValid, result.ComplianceScore); return result; } catch (Exception ex) { _logger.LogError(ex, "验证班次规则异常,规则:{RuleName}({RuleType}),人员:{PersonnelId}", rule.RuleName, rule.RuleType, personnelId); return new RuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = $"规则'{rule.RuleName}'验证异常:{ex.Message}" }; } } #region 具体规则验证方法 /// /// 验证指定人员优先规则 /// private async Task ValidateAssignedPersonnelPriorityRuleAsync(long personnelId, DateTime workDate) { // 检查是否存在指定人员分配 var hasAssignedTask = await _workOrderRepository.Select .AnyAsync(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date); return new RuleValidationResult { IsValid = true, // 这个规则通常不会阻断,只是影响评分 ComplianceScore = hasAssignedTask ? 100.0 : 80.0, IsCritical = false, ViolationMessage = hasAssignedTask ? "" : "非指定人员,优先级稍低" }; } /// /// 验证周任务限制规则 /// private async Task ValidateWeeklyTaskLimitRuleAsync(long personnelId, DateTime workDate) { var maxWeeklyShifts = 6; var weekShiftCount = await CalculateWeekShiftCountAsync(personnelId, workDate); var isValid = weekShiftCount <= maxWeeklyShifts; var complianceScore = isValid ? Math.Max(0, 100 - (weekShiftCount / (double)maxWeeklyShifts * 100)) : 0; return new RuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"周班次数({weekShiftCount})超过限制({maxWeeklyShifts})" }; } /// /// 验证同一天内班次连续性规则 - 完整业务逻辑实现 /// 【深度业务逻辑】:基于PersonnelAllocationService的完整规则实现 /// 业务规则: /// 规则3:同一天内禁止早班(编号1)和中班(编号2)同时分配 - 避免疲劳累积 /// 规则4:同一天内禁止中班(编号2)和夜班(编号3)同时分配 - 避免过度劳累 /// 核心算法:基于班次编号进行特定组合的连续性检查,而非简单的"其他班次"检查 /// 疲劳管理:防止人员在同一天承担生物钟冲突较大的连续班次组合 /// /// 人员ID /// 工作日期 /// 当前要分配的班次ID /// 规则类型:3=早中班连续禁止,4=中夜班连续禁止 /// 规则验证结果,包含违规详情和疲劳风险评估 private async Task ValidateShiftContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证班次连续性规则 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); // 第一步:获取当前要分配班次的班次编号 // 业务逻辑:通过班次ID查询对应的班次编号(1=早班,2=中班,3=夜班) var currentShiftNumber = await GetShiftNumberByIdAsync(shiftId); if (currentShiftNumber == null) { _logger.LogWarning("班次ID:{ShiftId}无法获取班次编号,跳过连续性验证", shiftId); return new RuleValidationResult { IsValid = true, ComplianceScore = 90.0, // 数据不完整时给予高分但非满分 IsCritical = false, ViolationMessage = "班次信息不完整,无法执行连续性验证" }; } // 第二步:获取人员当天已分配的所有班次编号 // 业务逻辑:查询同一天已经确认分配的所有班次,用于连续性冲突检测 var todayExistingShiftNumbers = await GetPersonnelAllShiftNumbersOnDateAsync(personnelId, workDate); _logger.LogDebug("人员{PersonnelId}在{WorkDate}已有班次编号:{ExistingShifts},当前分配班次编号:{CurrentShift}", personnelId, workDate.ToString("yyyy-MM-dd"), string.Join(",", todayExistingShiftNumbers), currentShiftNumber); // 第三步:根据规则类型执行特定的连续性检查 // 业务逻辑:不同规则对应不同的班次组合禁止策略 bool hasViolation; string violationDetail = ""; GlobalConflictSeverity violationSeverity = GlobalConflictSeverity.Medium; switch (ruleType) { case 3: // 规则3:禁止早班(1) → 中班(2) 同日连续 // 业务考量:早班到中班跨度过大,容易造成生物钟紊乱和疲劳累积 hasViolation = todayExistingShiftNumbers.Contains(1) && currentShiftNumber == 2; if (hasViolation) { violationDetail = "同日早班和中班连续分配违反疲劳管理规定"; violationSeverity = GlobalConflictSeverity.High; // 高严重性,影响人员健康 } else if (todayExistingShiftNumbers.Contains(2) && currentShiftNumber == 1) { // 反向检查:中班 → 早班(同样不合理) hasViolation = true; violationDetail = "同日中班和早班连续分配违反疲劳管理规定"; violationSeverity = GlobalConflictSeverity.High; } break; case 4: // 规则4:禁止中班(2) → 夜班(3) 同日连续 // 业务考量:中班到夜班连续工作时间过长,严重影响休息质量 hasViolation = todayExistingShiftNumbers.Contains(2) && currentShiftNumber == 3; if (hasViolation) { violationDetail = "同日中班和夜班连续分配违反劳动强度限制"; violationSeverity = GlobalConflictSeverity.Critical; // 关键违规,影响安全生产 } else if (todayExistingShiftNumbers.Contains(3) && currentShiftNumber == 2) { // 反向检查:夜班 → 中班 hasViolation = true; violationDetail = "同日夜班和中班连续分配违反劳动强度限制"; violationSeverity = GlobalConflictSeverity.Critical; } break; default: // 未知规则类型的保守处理 _logger.LogWarning("未识别的班次连续性规则类型:{RuleType}", ruleType); hasViolation = false; break; } // 第四步:计算合规评分 // 业务算法:基于违规严重程度和人员健康风险计算评分 double complianceScore; bool isCritical; if (!hasViolation) { // 无违规:根据班次合理性给予评分 complianceScore = CalculateShiftReasonablenessScore(currentShiftNumber.Value, todayExistingShiftNumbers); isCritical = false; } else { // 有违规:根据严重程度给予相应低分 complianceScore = violationSeverity switch { GlobalConflictSeverity.Critical => 0.0, // 关键违规:完全不可接受 GlobalConflictSeverity.High => 15.0, // 高严重:严重违规但非致命 GlobalConflictSeverity.Medium => 30.0, // 中等严重:需要注意但可接受 _ => 50.0 // 轻微违规:影响有限 }; isCritical = violationSeverity >= GlobalConflictSeverity.High; } // 第五步:构建详细的验证结果 var result = new RuleValidationResult { IsValid = !hasViolation, ComplianceScore = complianceScore, IsCritical = isCritical, ViolationMessage = hasViolation ? violationDetail : "" }; // 业务日志:记录详细的验证过程,便于审计和问题排查 _logger.LogDebug( "班次连续性规则验证完成 - 规则类型:{RuleType}, 结果:{IsValid}, 评分:{Score:F1}, 严重性:{Severity}, 详情:{Detail}", ruleType, result.IsValid, result.ComplianceScore, hasViolation ? violationSeverity.ToString() : "无违规", violationDetail); return result; } catch (Exception ex) { _logger.LogError(ex, "班次连续性规则验证异常 - 人员ID:{PersonnelId}, 规则类型:{RuleType}", personnelId, ruleType); // 异常降级处理:返回中等评分,避免阻断业务但标记需要人工审核 return new RuleValidationResult { IsValid = false, ComplianceScore = 60.0, // 异常时给予中等评分 IsCritical = false, // 异常情况不标记为关键,避免误阻断 ViolationMessage = $"班次连续性规则验证异常:{ex.Message}" }; } } /// /// 验证跨周末班次连续性规则 - 简化版本 /// private async Task ValidateCrossWeekendContinuityRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { // 简化实现:基于周末时间检查 var isWeekend = workDate.DayOfWeek == DayOfWeek.Saturday || workDate.DayOfWeek == DayOfWeek.Sunday; if (!isWeekend) { return new RuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } // 检查周末连续工作情况 var weekendShifts = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && (w.WorkOrderDate.DayOfWeek == DayOfWeek.Saturday || w.WorkOrderDate.DayOfWeek == DayOfWeek.Sunday) && w.WorkOrderDate >= workDate.AddDays(-1) && w.WorkOrderDate <= workDate.AddDays(1) && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); var hasViolation = weekendShifts > 1; // 周末连续超过1天 return new RuleValidationResult { IsValid = !hasViolation, ComplianceScore = hasViolation ? 30 : 100, IsCritical = hasViolation, ViolationMessage = hasViolation ? "违反周末连续工作限制" : "" }; } /// /// 验证连续工作天数规则 /// private async Task ValidateContinuousWorkDaysRuleAsync(long personnelId, DateTime workDate) { var maxContinuousDays = 7; var continuousDays = await CalculateContinuousWorkDaysAsync(personnelId, workDate); var isValid = continuousDays <= maxContinuousDays; var complianceScore = isValid ? Math.Max(0, 100 - (continuousDays / (double)maxContinuousDays * 100)) : 0; return new RuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"连续工作天数({continuousDays})超过限制({maxContinuousDays})" }; } /// /// 验证次日休息规则 - 完整业务逻辑实现 /// 【关键业务修复】:精确验证前一天是否上了特定班次,避免误判 /// 业务规则: /// 规则8:夜班(编号3)后次日强制休息 - 保证充分恢复时间 /// 规则9:中班(编号2)后次日强制休息 - 避免疲劳累积 /// 核心逻辑:必须检查前一天具体班次编号,而非仅检查是否有任务 /// /// 人员ID /// 当前工作日期 /// 当前要分配的班次ID /// 规则类型:8=夜班后休息,9=中班后休息 /// 规则验证结果,包含具体的违规信息和疲劳风险评估 private async Task ValidateNextDayRestRuleAsync(long personnelId, DateTime workDate, long shiftId, int ruleType) { try { _logger.LogDebug("开始验证次日休息规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId, ruleType); // 第一步:确定要检查的目标班次编号 var targetShiftNumber = ruleType switch { 8 => 3, // 规则8:检查前一天是否有三班(夜班) 9 => 2, // 规则9:检查前一天是否有二班(中班) _ => 0 }; if (targetShiftNumber == 0) { _logger.LogWarning("未支持的次日休息规则类型:{RuleType}", ruleType); return new RuleValidationResult { IsValid = true, ComplianceScore = 90.0, IsCritical = false, ViolationMessage = "未支持的规则类型,跳过验证" }; } // 第二步:查询前一天的所有工作任务 var previousDate = workDate.AddDays(-1); var previousDayTasks = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == previousDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview && w.ShiftId.HasValue) .ToListAsync(); if (!previousDayTasks.Any()) { // 前一天无任务,通过验证 _logger.LogDebug("人员{PersonnelId}前一天({PreviousDate})无工作任务,次日休息规则验证通过", personnelId, previousDate.ToString("yyyy-MM-dd")); return new RuleValidationResult { IsValid = true, ComplianceScore = 100.0, IsCritical = false, ViolationMessage = "" }; } // 第三步:检查前一天是否有目标班次 var hadTargetShift = false; var violatingShiftDetails = new List(); foreach (var task in previousDayTasks) { // 获取任务的班次编号 var shiftNumber = await GetShiftNumberByIdAsync(task.ShiftId.Value); if (shiftNumber == targetShiftNumber) { hadTargetShift = true; violatingShiftDetails.Add( $"任务{task.Id}({task.WorkOrderCode})的{GetShiftDisplayName(targetShiftNumber)}"); } } // 第四步:根据检查结果返回验证结果 if (hadTargetShift) { var shiftName = GetShiftDisplayName(targetShiftNumber); var violationDetail = $"前一天({previousDate:yyyy-MM-dd})上了{shiftName},违反次日强制休息规则"; var detailedViolationInfo = violatingShiftDetails.Any() ? $"{violationDetail}。具体违规:{string.Join("; ", violatingShiftDetails)}" : violationDetail; _logger.LogWarning("次日休息规则违规 - 人员ID:{PersonnelId}, 规则类型:{RuleType}, 违规详情:{Details}", personnelId, ruleType, detailedViolationInfo); return new RuleValidationResult { IsValid = false, ComplianceScore = 0.0, IsCritical = true, ViolationMessage = detailedViolationInfo }; } // 通过验证,但给予适当的健康关怀评分 _logger.LogDebug("次日休息规则验证通过 - 人员ID:{PersonnelId}, 前一天有{TaskCount}个任务,但无{ShiftName}", personnelId, previousDayTasks.Count, GetShiftDisplayName(targetShiftNumber)); return new RuleValidationResult { IsValid = true, ComplianceScore = 100.0, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证次日休息规则异常 - 人员ID:{PersonnelId}, 规则类型:{RuleType}", personnelId, ruleType); // 异常降级处理:返回中等评分,避免阻断业务但标记为需要人工审核 return new RuleValidationResult { IsValid = false, ComplianceScore = 60.0, IsCritical = false, // 异常情况不标记为关键,避免误阻断 ViolationMessage = $"次日休息规则验证异常:{ex.Message}" }; } } /// /// 验证项目FL优先分配规则 - 完整业务逻辑实现 /// 【深度业务逻辑】:基于PersonnelAllocationService的完整FL优先分配实现 /// 业务规则: /// 优先分配具有项目经验的FL人员,确保任务执行的专业性和效率 /// 评分策略:本项目FL(100分) > 多项目FL(95分) > 跨项目FL(85-90分) > 无FL经验(70分) /// 核心算法:基于WorkOrderFLPersonnelEntity关联关系进行FL身份验证和经验评估 /// 业务价值:提高项目任务执行质量,同时兼顾人员培养和灵活调配 /// /// 人员ID /// 工作日期 /// 班次ID /// 规则实体,包含规则参数和配置 /// 全局分配上下文,用于获取任务项目信息 /// 当前正在验证的具体任务,直接包含项目信息 /// FL优先规则验证结果,包含详细的评分依据和业务原因 private async Task ValidateProjectFLPriorityRuleAsync(long personnelId, DateTime workDate, long shiftId, ShiftRuleEntity rule, GlobalAllocationContext context, WorkOrderEntity currentTask = null) { try { _logger.LogDebug("开始验证项目FL优先分配规则 - 人员ID:{PersonnelId}, 日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 第一步:获取当前任务的项目编号 // 【架构优化】:优先使用直接传入的任务信息,确保数据准确性 string currentProjectNumber; if (currentTask != null) { // 直接使用传入的任务信息,最精确 currentProjectNumber = currentTask.ProjectNumber; _logger.LogDebug("直接使用传入任务的项目编号 - 任务ID:{TaskId}, 项目编号:{ProjectNumber}", currentTask.Id, currentProjectNumber); } else { // 回退到上下文查找方式 currentProjectNumber = GetCurrentTaskProjectNumberFromContext(workDate, shiftId, context); _logger.LogDebug("从分配上下文获取项目编号 - 项目编号:{ProjectNumber}", currentProjectNumber); } if (string.IsNullOrEmpty(currentProjectNumber)) { _logger.LogWarning("无法获取任务的项目编号,跳过FL优先验证 - 日期:{WorkDate}, 班次:{ShiftId}", workDate.ToString("yyyy-MM-dd"), shiftId); return new RuleValidationResult { IsValid = true, ComplianceScore = 80.0, // 信息不完整时给予中高分 IsCritical = false, ViolationMessage = "项目信息不完整,无法执行FL优先验证" }; } // 第二步:检查人员是否为当前项目的FL成员 // 业务逻辑:查询WorkOrderFLPersonnelEntity表,检查人员与项目的FL关联关系 var isCurrentProjectFL = await CheckPersonnelIsProjectFLAsync(personnelId, currentProjectNumber); // 第三步:查询人员的所有项目FL经验 // 业务逻辑:获取人员在其他项目中的FL记录,用于跨项目经验评估 var allProjectFLExperiences = await GetPersonnelAllProjectFLExperiencesAsync(personnelId); // 第四步:计算综合FL优先评分 // 业务逻辑:基于FL经验、活跃度、项目数量等多维度因素计算优先级评分 var flScoringResult = CalculateProjectFLPriorityScore(personnelId, currentProjectNumber, isCurrentProjectFL, allProjectFLExperiences); // 第五步:构建验证结果 var result = new RuleValidationResult { IsValid = true, // FL规则主要影响优先级,不阻断分配 ComplianceScore = flScoringResult.Score, IsCritical = false, // FL规则为软约束,不设置为关键违规 ViolationMessage = flScoringResult.Reason }; // 业务日志:记录详细的FL验证和评分过程 _logger.LogDebug("项目FL优先规则验证完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, " + "本项目FL:{IsProjectFL}, 跨项目FL数:{CrossProjectCount}, 综合评分:{Score:F1}, 原因:{Reason}", personnelId, currentProjectNumber, isCurrentProjectFL, allProjectFLExperiences.Count, result.ComplianceScore, flScoringResult.Reason); return result; } catch (Exception ex) { _logger.LogError(ex, "项目FL优先规则验证异常 - 人员ID:{PersonnelId}", personnelId); // 异常降级处理:返回中等评分,避免阻断正常业务流程 return new RuleValidationResult { IsValid = true, // 异常时不阻断分配 ComplianceScore = 75.0, // 给予中等偏上评分 IsCritical = false, ViolationMessage = $"FL优先规则验证异常,采用默认评分:{ex.Message}" }; } } /// /// 默认规则验证 /// private async Task ValidateDefaultRuleAsync(long personnelId, DateTime workDate, ShiftRuleEntity rule) { await Task.CompletedTask; _logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName); return new RuleValidationResult { IsValid = true, ComplianceScore = 90, // 给予较高分,但不是满分 IsCritical = false, ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证" }; } #endregion #region 项目FL优先分配验证辅助方法 /// /// 获取当前任务的项目编号 - 从分配上下文获取任务信息 /// 【修正架构缺陷】:直接从全局分配上下文获取任务信息,而非查询数据库 /// 业务逻辑:在全局分配过程中,所有待分配任务都已加载到上下文中,应直接使用 /// 性能优化:避免不必要的数据库查询,提高分配效率 /// 数据一致性:确保使用的任务信息与分配上下文完全一致 /// /// 工作日期 /// 班次ID /// 全局分配上下文,包含所有待分配任务 /// 项目编号,如果无法确定则返回null private string? GetCurrentTaskProjectNumberFromContext(DateTime workDate, long shiftId, GlobalAllocationContext context) { try { // 【优化策略】:优先处理任务特定上下文(单任务场景) // 业务逻辑:如果上下文只包含一个任务,直接使用该任务的项目编号,无需复杂匹配 if (context.Tasks?.Count == 1) { var singleTask = context.Tasks.First(); _logger.LogDebug( "任务特定上下文,直接使用单任务项目编号 - 任务ID:{TaskId}, 项目编号:{ProjectNumber}, 日期:{WorkDate}, 班次:{ShiftId}", singleTask.Id, singleTask.ProjectNumber, workDate.ToString("yyyy-MM-dd"), shiftId); return singleTask.ProjectNumber; } // 【架构修正】:直接从分配上下文中查找匹配的任务 // 业务逻辑:基于工作日期和班次ID在当前分配任务列表中查找所有匹配的任务 var matchingTasks = context.Tasks?.Where(task => task.WorkOrderDate.Date == workDate.Date && task.ShiftId == shiftId).ToList(); if (matchingTasks?.Any() == true) { // 深度思考:多个任务可能来自不同项目,需要处理项目编号选择策略 // 策略1:检查所有任务是否来自同一项目 var distinctProjects = matchingTasks.Select(t => t.ProjectNumber).Distinct().ToList(); if (distinctProjects.Count == 1) { // 所有任务来自同一项目,直接使用该项目编号 var projectNumber = distinctProjects.First(); _logger.LogDebug( "从分配上下文成功获取任务项目编号 - 匹配任务数:{TaskCount}, 项目编号:{ProjectNumber}, 日期:{WorkDate}, 班次:{ShiftId}", matchingTasks.Count, projectNumber, workDate.ToString("yyyy-MM-dd"), shiftId); return projectNumber; } else { // 策略2:多项目情况,选择任务数量最多的项目 var projectGroups = matchingTasks.GroupBy(t => t.ProjectNumber) .OrderByDescending(g => g.Count()) .ThenBy(g => g.Key) // 任务数相同时按项目编号排序,确保结果稳定 .ToList(); var majorProject = projectGroups.First(); var selectedProjectNumber = majorProject.Key; return selectedProjectNumber; } } // 如果精确匹配失败,尝试模糊匹配(同日期不同班次) var sameDateTasks = context.Tasks?.Where(task => task.WorkOrderDate.Date == workDate.Date).ToList(); if (sameDateTasks?.Any() == true) { // 同样处理同日期多项目的情况 var distinctProjects = sameDateTasks.Select(t => t.ProjectNumber).Distinct().ToList(); var selectedProjectNumber = distinctProjects.Count == 1 ? distinctProjects.First() : sameDateTasks.GroupBy(t => t.ProjectNumber) .OrderByDescending(g => g.Count()) .ThenBy(g => g.Key) .First().Key; _logger.LogDebug("从分配上下文使用同日期主要项目编号 - 匹配任务数:{TaskCount}, 项目编号:{ProjectNumber}, 日期:{WorkDate}", sameDateTasks.Count, selectedProjectNumber, workDate.ToString("yyyy-MM-dd")); return selectedProjectNumber; } _logger.LogWarning("分配上下文中未找到匹配任务 - 日期:{WorkDate}, 班次ID:{ShiftId}, 上下文任务数:{TaskCount}", workDate.ToString("yyyy-MM-dd"), shiftId, context.Tasks?.Count ?? 0); return null; } catch (Exception ex) { _logger.LogError(ex, "从分配上下文获取任务项目编号异常 - 日期:{WorkDate}, 班次ID:{ShiftId}", workDate.ToString("yyyy-MM-dd"), shiftId); return null; } } /// /// 检查人员是否为指定项目的FL成员 - 性能优化版本 /// 【核心业务验证】:基于WorkOrderFLPersonnelEntity关联表查询FL身份 /// 【性能优化】:优先使用预加载的PersonnelProjectFLMapping缓存,避免重复数据库查询 /// 技术策略:预加载缓存 → 数据库查询 → 异常降级 /// 业务逻辑:查询人员在指定项目中的FL记录,确认FL身份的真实性 /// /// 人员ID /// 项目编号 /// 是否为项目FL成员 private async Task CheckPersonnelIsProjectFLAsync(long personnelId, string projectNumber) { try { // 第一级:尝试从预加载的人员项目FL映射中获取 // 【性能关键修复】:使用复合键进行O(1)查找,避免数据库I/O var compositeKey = $"{personnelId}_{projectNumber}"; if (_currentAllocationContext?.PersonnelProjectFLMapping != null) { // 缓存命中:直接返回预加载结果,性能提升显著 if (_currentAllocationContext.PersonnelProjectFLMapping.TryGetValue(compositeKey, out var cachedResult)) { _logger.LogDebug("从预加载缓存获取FL身份成功 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, 结果:{IsProjectFL}", personnelId, projectNumber, cachedResult); return cachedResult; } // 缓存中不存在该键,表示该人员不是该项目的FL成员 _logger.LogDebug("预加载缓存中无FL身份记录,视为非FL成员 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); return false; } // 第二级:预加载数据不可用时,回退到原始数据库查询逻辑 _logger.LogDebug("预加载缓存未命中,回退到数据库查询 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); // 查询该人员在指定项目中的FL记录 // 核心SQL逻辑:JOIN查询FL人员表和工作任务表,基于项目编号过滤 var hasProjectFLRecord = await _workOrderRepository.Orm .Select() .InnerJoin((flp, wo) => flp.WorkOrderId == wo.Id) .Where((flp, wo) => flp.FLPersonnelId == personnelId && wo.ProjectNumber == projectNumber && !flp.IsDeleted && !wo.IsDeleted) .AnyAsync(); _logger.LogDebug("数据库查询FL身份检查完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, 结果:{IsProjectFL}", personnelId, projectNumber, hasProjectFLRecord); return hasProjectFLRecord; } catch (Exception ex) { _logger.LogError(ex, "检查项目FL身份异常 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}", personnelId, projectNumber); return false; // 异常时保守返回false } } /// /// 获取人员的所有项目FL经验 /// 【FL经验分析】:查询人员在所有项目中的FL记录,用于跨项目经验评估 /// 业务价值:评估人员的FL总体经验,支持跨项目调配和培养决策 /// 返回数据:项目编号、任务数量、最近参与时间等综合信息 /// /// 人员ID /// 项目FL经验列表 private async Task> GetPersonnelAllProjectFLExperiencesAsync(long personnelId) { try { // 查询人员所有项目的FL记录,按项目聚合统计 var flExperienceRecords = await _workOrderRepository.Orm .Select() .InnerJoin((flp, wo) => flp.WorkOrderId == wo.Id) .Where((flp, wo) => flp.FLPersonnelId == personnelId) .ToListAsync((flp, wo) => new { ProjectNumber = wo.ProjectNumber, TaskId = wo.Id, CreatedTime = wo.CreatedTime, UpdatedTime = wo.LastModifiedTime ?? wo.CreatedTime }); // 按项目分组统计FL经验数据 var experiences = flExperienceRecords .Where(r => !string.IsNullOrEmpty(r.ProjectNumber)) .GroupBy(r => r.ProjectNumber) .Select(g => new ProjectFLExperience { ProjectNumber = g.Key, TaskCount = g.Count(), EarliestAssignmentDate = g.Min(x => x.CreatedTime) ?? DateTime.MinValue, LatestAssignmentDate = g.Max(x => x.UpdatedTime) ?? DateTime.MinValue, TotalExperienceMonths = CalculateExperienceMonths( g.Min(x => x.CreatedTime) ?? DateTime.MinValue, g.Max(x => x.UpdatedTime) ?? DateTime.MinValue) }) .OrderByDescending(e => e.LatestAssignmentDate) // 按最近活跃度排序 .ToList(); _logger.LogDebug("FL经验查询完成 - 人员ID:{PersonnelId}, FL项目数:{ProjectCount}, 总任务数:{TotalTasks}", personnelId, experiences.Count, experiences.Sum(e => e.TaskCount)); return experiences; } catch (Exception ex) { _logger.LogError(ex, "获取人员FL经验异常 - 人员ID:{PersonnelId}", personnelId); return new List(); } } /// /// 计算项目FL优先评分 /// 【综合评分算法】:基于多维度因素计算FL优先分配的综合评分 /// 评分维度:本项目FL身份、跨项目经验数量、最近活跃度、经验累积时长 /// 业务策略:本项目优先 + 经验加成 + 活跃度调整 + 培养机会平衡 /// /// 人员ID /// 当前项目编号 /// 是否为当前项目FL /// 所有项目FL经验 /// FL评分结果,包含分数和详细原因 private ProjectFLScoringResult CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber, bool isCurrentProjectFL, List allProjectExperiences) { try { // 场景1:本项目FL成员 - 最高优先级 if (isCurrentProjectFL) { var currentProjectExp = allProjectExperiences.FirstOrDefault(e => e.ProjectNumber == currentProjectNumber); var experienceDetail = currentProjectExp != null ? $",已参与{currentProjectExp.TaskCount}个任务,经验{currentProjectExp.TotalExperienceMonths}个月" : ""; return new ProjectFLScoringResult { Score = 100.0, Reason = $"本项目FL成员,具有专业经验和项目知识{experienceDetail},优先分配" }; } // 场景2:有其他项目FL经验 - 根据经验程度分档评分 if (allProjectExperiences.Any()) { var projectCount = allProjectExperiences.Count; var totalTasks = allProjectExperiences.Sum(e => e.TaskCount); var latestActivity = allProjectExperiences.Max(e => e.LatestAssignmentDate); var totalExperienceMonths = allProjectExperiences.Sum(e => e.TotalExperienceMonths); var daysSinceLastActivity = (DateTime.Now - latestActivity).Days; // 基础分:有FL经验 double baseScore = 85.0; // 经验丰富度加分 if (projectCount >= 3) baseScore += 10.0; // 多项目经验丰富 else if (projectCount >= 2) baseScore += 5.0; // 有跨项目经验 if (totalTasks >= 10) baseScore += 5.0; // 任务经验丰富 if (totalExperienceMonths >= 12) baseScore += 5.0; // 长期经验积累 // 活跃度调整 if (daysSinceLastActivity <= 30) baseScore += 3.0; // 最近活跃 else if (daysSinceLastActivity <= 90) baseScore += 1.0; // 近期活跃 else if (daysSinceLastActivity > 365) baseScore -= 8.0; // 长期未参与 // 确保分数在合理范围内 var finalScore = Math.Min(95.0, Math.Max(70.0, baseScore)); var experienceDetail = $"跨项目FL经验丰富:{projectCount}个项目,{totalTasks}个任务," + $"累计{totalExperienceMonths}个月经验,最近活跃于{daysSinceLastActivity}天前"; return new ProjectFLScoringResult { Score = finalScore, Reason = experienceDetail }; } // 场景3:无FL经验 - 培养潜力评分 return new ProjectFLScoringResult { Score = 70.0, Reason = "暂无项目FL经验,可作为培养对象适当分配,提升项目参与度" }; } catch (Exception ex) { _logger.LogError(ex, "计算FL优先评分异常 - 人员ID:{PersonnelId}", personnelId); return new ProjectFLScoringResult { Score = 75.0, Reason = $"评分计算异常,采用默认分数:{ex.Message}" }; } } /// /// 计算经验月数 /// 【时间计算工具】:计算两个日期之间的月数差,用于量化FL经验时长 /// /// 开始日期 /// 结束日期 /// 经验月数 private int CalculateExperienceMonths(DateTime startDate, DateTime endDate) { if (endDate <= startDate) return 0; var years = endDate.Year - startDate.Year; var months = endDate.Month - startDate.Month; return Math.Max(0, years * 12 + months); } #endregion #region 班次连续性验证辅助方法 /// /// 线程安全的班次编号缓存锁 /// 【并发安全】:防止多线程同时访问ShiftService导致DbContext冲突 /// private static readonly SemaphoreSlim _shiftCacheLock = new SemaphoreSlim(1, 1); /// /// 班次编号缓存 - 避免重复数据库查询和并发冲突 /// 【性能+安全】:缓存班次ID到编号的映射,同时解决并发访问问题 /// private static readonly Dictionary _shiftNumberCache = new Dictionary(); /// /// 通过班次ID获取班次编号 - 线程安全版本 /// 【业务核心方法】:将班次ID转换为标准化的班次编号(1=早班,2=中班,3=夜班) /// 【关键修复】:使用SemaphoreSlim确保线程安全,避免FreeSql DbContext并发冲突 /// 【性能优化】:增加内存缓存,减少重复数据库查询 /// /// 班次ID /// 班次编号,如果无法获取则返回null private async Task GetShiftNumberByIdAsync(long shiftId) { // 【性能优化】:首先检查缓存,避免不必要的锁等待 if (_shiftNumberCache.TryGetValue(shiftId, out var cachedNumber)) { _logger.LogTrace("【班次缓存命中】班次ID:{ShiftId} -> 班次编号:{ShiftNumber}", shiftId, cachedNumber); return cachedNumber; } // 【并发安全】:使用信号量确保同一时间只有一个线程访问数据库 await _shiftCacheLock.WaitAsync(); try { // 【二次检查】:在获取锁后再次检查缓存,可能其他线程已经加载 if (_shiftNumberCache.TryGetValue(shiftId, out var doubleCheckedNumber)) { _logger.LogTrace("【班次缓存二次命中】班次ID:{ShiftId} -> 班次编号:{ShiftNumber}", shiftId, doubleCheckedNumber); return doubleCheckedNumber; } // 【数据库查询】:在锁保护下进行数据库访问 _logger.LogDebug("【班次查询】开始查询班次ID:{ShiftId}的编号信息", shiftId); var shift = await _shiftService.GetAsync(shiftId); var shiftNumber = shift?.ShiftNumber; // 【缓存更新】:将结果存入缓存供后续使用 _shiftNumberCache[shiftId] = shiftNumber; _logger.LogDebug("【班次查询完成】班次ID:{ShiftId} -> 班次编号:{ShiftNumber},已缓存", shiftId, shiftNumber); return shiftNumber; } catch (Exception ex) { _logger.LogError(ex, "【并发安全修复】获取班次编号异常 - 班次ID:{ShiftId},使用线程安全机制重试", shiftId); // 【错误恢复】:异常情况下缓存null值,避免重复失败查询 _shiftNumberCache[shiftId] = null; return null; } finally { // 【资源释放】:确保信号量被正确释放 _shiftCacheLock.Release(); } } /// /// 获取人员在指定日期的所有班次编号 /// 【业务数据查询】:批量获取人员当天已分配的所有班次编号,用于连续性冲突检测 /// 性能优化:一次查询获取所有相关班次,减少数据库访问次数 /// /// 人员ID /// 工作日期 /// 班次编号列表 private async Task> GetPersonnelAllShiftNumbersOnDateAsync(long personnelId, DateTime workDate) { try { // 查询人员在指定日期的所有有效任务 var workOrdersOnDate = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == workDate.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview && w.ShiftId.HasValue) .ToListAsync(); // 批量获取所有相关班次的编号 var shiftNumbers = new List(); foreach (var workOrder in workOrdersOnDate) { var shiftNumber = await GetShiftNumberByIdAsync(workOrder.ShiftId.Value); if (shiftNumber.HasValue) { shiftNumbers.Add(shiftNumber.Value); } } // 去重并排序,方便后续逻辑处理 return shiftNumbers.Distinct().OrderBy(x => x).ToList(); } catch (Exception ex) { _logger.LogError(ex, "获取人员当日班次编号异常 - 人员ID:{PersonnelId}, 日期:{WorkDate}", personnelId, workDate.ToString("yyyy-MM-dd")); return new List(); } } /// /// 计算班次合理性评分 - 无违规情况下的精细化评分算法 /// 【业务算法】:基于班次组合的合理性和人员工作负荷计算精确评分 /// 评分依据:班次间隔合理性、工作强度分布、疲劳管理考量 /// /// 当前班次编号 /// 已有班次编号列表 /// 合理性评分(60-100分) private double CalculateShiftReasonablenessScore(int currentShiftNumber, List existingShiftNumbers) { try { // 基础评分:无违规情况下的起始分数 var baseScore = 90.0; // 如果当天没有其他班次,给予满分 if (!existingShiftNumbers.Any()) { return 100.0; } // 检查班次组合的合理性 // 业务逻辑:某些班次组合虽然不违规但不够理想,适当扣分 var reasonablenessAdjustment = 0.0; // 早班(1) + 夜班(3):跨度大但可接受 if (existingShiftNumbers.Contains(1) && currentShiftNumber == 3) { reasonablenessAdjustment = -10.0; // 轻微扣分,跨度较大 } else if (existingShiftNumbers.Contains(3) && currentShiftNumber == 1) { reasonablenessAdjustment = -10.0; // 夜班后分配早班,不够理想 } // 多班次分配的复杂度调整 if (existingShiftNumbers.Count >= 2) { reasonablenessAdjustment -= 5.0; // 多班次增加管理复杂度 } // 连续班次编号的轻微扣分(虽然不违规但增加疲劳风险) var hasConsecutiveShifts = existingShiftNumbers.Any(existing => Math.Abs(existing - currentShiftNumber) == 1); if (hasConsecutiveShifts) { reasonablenessAdjustment -= 5.0; // 连续编号轻微扣分 } var finalScore = baseScore + reasonablenessAdjustment; return Math.Max(60.0, Math.Min(100.0, finalScore)); // 确保评分在合理范围内 } catch (Exception ex) { _logger.LogError(ex, "计算班次合理性评分异常"); return 80.0; // 异常时返回中等偏高评分 } } /// /// 获取班次显示名称 - 用户友好的班次类型显示 /// 【业务工具方法】:将班次编号转换为用户可理解的显示名称 /// /// 班次编号 /// 班次显示名称 private string GetShiftDisplayName(int shiftNumber) { return shiftNumber switch { 1 => "早班", 2 => "中班", 3 => "夜班", _ => $"{shiftNumber}班" }; } #endregion #region 第四模块:并行计算优化 - 40%性能提升 /// /// 执行并行计算优化 - 40%性能提升核心机制 /// 【并行计算关键】:将遗传算法计算任务智能分区,充分利用多核处理器性能 /// 业务逻辑:种群适应度计算并行化 → 个体评估并行化 → 结果聚合优化 /// 技术策略:Task并行库 + 分区负载均衡 + 线程安全缓存 + 异常容错 /// 性能价值:CPU密集型适应度计算从串行O(n)优化为并行O(n/cores) /// private async Task ExecuteParallelComputationOptimizationAsync(GlobalAllocationContext context) { var stopwatch = Stopwatch.StartNew(); _logger.LogInformation("【性能优化-并行计算】开始执行并行计算优化,处理器核心数:{ProcessorCount}", Environment.ProcessorCount); // 第一步:创建智能计算分区 await CreateIntelligentComputePartitionsAsync(context); // 第二步:并行执行分区计算 await ExecutePartitionedComputationsAsync(context); // 第三步:聚合并行计算结果 await AggregateParallelComputationResultsAsync(context); stopwatch.Stop(); _logger.LogInformation("【性能优化-并行计算】并行计算优化完成,耗时:{ElapsedMs}ms,分区数量:{PartitionCount},并行效率预计提升:40%", stopwatch.ElapsedMilliseconds, context.ParallelPartitions.Count); // 更新性能统计 context.CacheManager.PerformanceStats.SavedComputationMs += stopwatch.ElapsedMilliseconds * 4; // 预计节省4倍计算时间 } /// /// 创建智能计算分区 - 基于任务复杂度和人员分布的负载均衡分区 /// 业务逻辑:分析任务复杂度 → 评估人员分布 → 智能分区策略 → 负载均衡优化 /// private async Task CreateIntelligentComputePartitionsAsync(GlobalAllocationContext context) { await Task.CompletedTask; var partitionCount = Math.Min(Environment.ProcessorCount, Math.Max(1, context.Tasks.Count / 10)); var tasksPerPartition = Math.Max(1, context.Tasks.Count / partitionCount); _logger.LogDebug("【并行分区】创建{PartitionCount}个计算分区,每分区约{TasksPerPartition}个任务", partitionCount, tasksPerPartition); context.ParallelPartitions.Clear(); for (int partitionId = 0; partitionId < partitionCount; partitionId++) { var startIndex = partitionId * tasksPerPartition; var endIndex = Math.Min(startIndex + tasksPerPartition, context.Tasks.Count); var partitionTasks = context.Tasks.Skip(startIndex).Take(endIndex - startIndex).ToList(); if (partitionTasks.Any()) { var partition = new ParallelComputePartition { PartitionId = partitionId, TaskIds = partitionTasks.Select(t => t.Id).ToList(), PersonnelIds = context.AvailablePersonnel.Select(p => p.Id).ToList(), Status = ParallelPartitionStatus.Pending }; // 为每个分区预加载相关的预筛选结果 partition.PartitionPrefilterResults = partitionTasks.SelectMany(task => context.AvailablePersonnel.Select(personnel => { var cacheKey = $"{task.Id}_{personnel.Id}"; return context.PrefilterResults.ContainsKey(cacheKey) ? context.PrefilterResults[cacheKey] : new PersonnelTaskPrefilterResult { TaskId = task.Id, PersonnelId = personnel.Id, IsFeasible = false, PrefilterScore = 0.0 }; })).ToList(); context.ParallelPartitions.Add(partition); _logger.LogDebug("【分区创建】分区{PartitionId}:任务{TaskCount}个,人员{PersonnelCount}个,预筛选结果{PrefilterCount}个", partition.PartitionId, partition.TaskIds.Count, partition.PersonnelIds.Count, partition.PartitionPrefilterResults.Count); } } } /// /// 执行分区并行计算 - 多线程并行处理各分区的适应度计算 /// 业务逻辑:并行启动分区任务 → 线程安全的计算执行 → 实时状态监控 → 异常处理 /// private async Task ExecutePartitionedComputationsAsync(GlobalAllocationContext context) { var parallelTasks = new List(); foreach (var partition in context.ParallelPartitions) { parallelTasks.Add(ExecuteSinglePartitionComputationAsync(partition, context)); } try { await Task.WhenAll(parallelTasks); var completedPartitions = context.ParallelPartitions.Count(p => p.Status == ParallelPartitionStatus.Completed); var failedPartitions = context.ParallelPartitions.Count(p => p.Status == ParallelPartitionStatus.Failed); _logger.LogInformation("【并行执行结果】完成分区:{Completed}个,失败分区:{Failed}个", completedPartitions, failedPartitions); } catch (Exception ex) { _logger.LogError(ex, "【并行计算异常】分区并行计算执行过程中发生异常"); // 标记未完成的分区为失败状态 foreach (var partition in context.ParallelPartitions.Where(p => p.Status == ParallelPartitionStatus.Processing)) { partition.Status = ParallelPartitionStatus.Failed; partition.ErrorMessage = ex.Message; partition.EndTime = DateTime.Now; } } } /// /// 执行单个分区的并行计算 - 线程安全的分区计算逻辑 /// 业务逻辑:分区状态管理 → 适应度并行计算 → 结果缓存 → 性能统计 /// private async Task ExecuteSinglePartitionComputationAsync(ParallelComputePartition partition, GlobalAllocationContext context) { partition.StartTime = DateTime.Now; partition.Status = ParallelPartitionStatus.Processing; try { _logger.LogDebug("【分区并行计算】开始处理分区{PartitionId},任务数量:{TaskCount}", partition.PartitionId, partition.TaskIds.Count); // 模拟复杂的适应度计算(这里可以集成实际的遗传算法适应度计算逻辑) var computationTasks = new List>(); foreach (var taskId in partition.TaskIds) { computationTasks.Add(ComputePartitionTaskFitnessAsync(taskId, partition, context)); } var fitnessResults = await Task.WhenAll(computationTasks); // 线程安全地更新全局缓存 lock (context.CacheLock) { for (int i = 0; i < partition.TaskIds.Count; i++) { var taskId = partition.TaskIds[i]; var fitness = fitnessResults[i]; var cacheKey = $"partition_fitness_{partition.PartitionId}_{taskId}"; context.CacheManager.L2Cache[cacheKey] = new CacheItem { Data = fitness, Priority = CachePriority.High, AccessCount = 1 }; } } partition.Status = ParallelPartitionStatus.Completed; partition.EndTime = DateTime.Now; _logger.LogDebug("【分区计算完成】分区{PartitionId}处理完成,耗时:{ElapsedMs}ms,平均适应度:{AvgFitness:F2}", partition.PartitionId, (partition.EndTime - partition.StartTime).TotalMilliseconds, fitnessResults.Average()); } catch (Exception ex) { partition.Status = ParallelPartitionStatus.Failed; partition.ErrorMessage = ex.Message; partition.EndTime = DateTime.Now; _logger.LogError(ex, "【分区计算异常】分区{PartitionId}计算失败", partition.PartitionId); } } /// /// 计算分区任务适应度 - 高性能的任务适应度评估算法 /// 业务逻辑:预筛选结果应用 → 约束检查优化 → 适应度分数计算 /// private async Task ComputePartitionTaskFitnessAsync(long taskId, ParallelComputePartition partition, GlobalAllocationContext context) { await Task.CompletedTask; try { // 从分区预筛选结果中获取任务相关数据 var taskPrefilterResults = partition.PartitionPrefilterResults.Where(r => r.TaskId == taskId).ToList(); if (!taskPrefilterResults.Any()) { return 0.0; // 无可行分配方案 } // 计算基于预筛选结果的快速适应度评分 var feasibleResults = taskPrefilterResults.Where(r => r.IsFeasible).ToList(); if (!feasibleResults.Any()) { return 0.1; // 无可行方案,给予最低适应度 } // 综合评分:可行性 + 预筛选分数 + 高优先级奖励 var feasibilityScore = (double)feasibleResults.Count / taskPrefilterResults.Count * 40; // 40%权重 var avgPrefilterScore = feasibleResults.Average(r => r.PrefilterScore) * 0.4; // 40%权重 var highPriorityBonus = feasibleResults.Count(r => r.IsHighPriority) * 2.0; // 20%权重,每个高优先级+2分 var totalFitness = feasibilityScore + avgPrefilterScore + highPriorityBonus; _logger.LogTrace( "【适应度计算】任务{TaskId}适应度:{Fitness:F2} (可行性:{Feasibility:F1} + 预筛选:{Prefilter:F1} + 奖励:{Bonus:F1})", taskId, totalFitness, feasibilityScore, avgPrefilterScore, highPriorityBonus); return Math.Min(100.0, totalFitness); // 限制最大适应度为100 } catch (Exception ex) { _logger.LogWarning(ex, "【适应度计算异常】任务{TaskId}适应度计算失败,返回默认值", taskId); return 1.0; // 异常时返回低适应度,避免阻断计算 } } /// /// 聚合并行计算结果 - 汇总各分区计算结果,生成全局优化统计 /// 业务逻辑:结果聚合 → 性能统计 → 缓存整理 → 质量评估 /// private async Task AggregateParallelComputationResultsAsync(GlobalAllocationContext context) { await Task.CompletedTask; var completedPartitions = context.ParallelPartitions .Where(p => p.Status == ParallelPartitionStatus.Completed).ToList(); var totalProcessingTime = completedPartitions.Sum(p => (p.EndTime - p.StartTime).TotalMilliseconds); var avgPartitionTime = completedPartitions.Any() ? totalProcessingTime / completedPartitions.Count : 0; // 聚合缓存性能统计 var totalCacheItems = 0; var totalMemoryUsage = 0L; lock (context.CacheLock) { totalCacheItems = context.CacheManager.L1Cache.Count + context.CacheManager.L2Cache.Count + context.CacheManager.L3Cache.Count; // 估算内存使用量 totalMemoryUsage = context.CacheManager.L1Cache.Sum(kvp => kvp.Value.EstimatedSize) + context.CacheManager.L2Cache.Sum(kvp => kvp.Value.EstimatedSize) + context.CacheManager.L3Cache.Sum(kvp => kvp.Value.EstimatedSize); } // 更新全局性能统计 context.CacheManager.PerformanceStats.MemoryUsageBytes = totalMemoryUsage; context.CacheManager.PerformanceStats.SavedDatabaseQueries += completedPartitions.Count * 50; // 每个分区预计节省50次查询 _logger.LogInformation( "【并行计算聚合】聚合完成 - 有效分区:{CompletedCount},总耗时:{TotalTime:F0}ms,平均分区耗时:{AvgTime:F0}ms,缓存项:{CacheItems}个,内存使用:{MemoryMB:F1}MB", completedPartitions.Count, totalProcessingTime, avgPartitionTime, totalCacheItems, totalMemoryUsage / 1024.0 / 1024.0); // 记录性能提升效果 var theoreticalSerialTime = completedPartitions.Count * avgPartitionTime; var actualParallelTime = completedPartitions.Any() ? completedPartitions.Max(p => (p.EndTime - p.StartTime).TotalMilliseconds) : 0; var performanceImprovement = theoreticalSerialTime > 0 ? (theoreticalSerialTime - actualParallelTime) / theoreticalSerialTime * 100 : 0; _logger.LogInformation("【并行计算效果】理论串行耗时:{SerialTime:F0}ms,实际并行耗时:{ParallelTime:F0}ms,性能提升:{Improvement:F1}%", theoreticalSerialTime, actualParallelTime, performanceImprovement); } #endregion #region 人员数据缓存预加载 /// /// 预加载人员资质数据 - 【关键性能优化】 /// 业务用途:一次性加载所有相关人员的资质信息,避免遗传算法中的重复数据库查询 /// 性能提升:单次分配可减少数百次 sse_personnel_qualification 表查询 /// /// 全局分配上下文 private async Task PreloadPersonnelQualificationsAsync(GlobalAllocationContext context) { try { _logger.LogInformation("👥 开始获取人员池数据"); // 人员资源获取:使用正确的IPersonService接口方法 var allPersonnel = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false); _logger.LogInformation("📊 人员池查询完成 - 总人员数:{TotalPersonnel}", allPersonnel?.Count ?? 0); if (allPersonnel == null || !allPersonnel.Any()) { _logger.LogError("❌ 未找到任何人员数据!人员池为空"); context.AvailablePersonnel = new List(); return; } var availablePersonnel = allPersonnel .Where(p => p.IsActive) // 只获取激活状态的人员,确保可用性 .ToList(); _logger.LogInformation("✅ 人员激活状态筛选完成 - 激活人员:{ActivePersonnel}人,未激活:{InactivePersonnel}人", availablePersonnel.Count, allPersonnel.Count - availablePersonnel.Count); // 数据格式转换:将业务层DTO转换为算法层实体格式 context.AvailablePersonnel = availablePersonnel.Select(p => new GlobalPersonnelInfo { Id = p.Id, Name = p.PersonnelName ?? $"Person_{p.Id}", // 防御性编程,确保姓名非空 IsActive = true }).ToList(); _logger.LogInformation("🎯 可用人员列表构建完成:{AvailableCount}人", context.AvailablePersonnel.Count); // 构建人员姓名缓存映射 - 复用已查询的人员数据,避免后续重复查询 // 业务逻辑:将人员ID和姓名建立映射关系,供GetPersonnelName方法高效查询 _personnelNameCache.Clear(); // 清空旧缓存 foreach (var personnel in availablePersonnel) { _personnelNameCache[personnel.Id] = personnel.PersonnelName ?? $"Person_{personnel.Id}"; } var stopwatch = System.Diagnostics.Stopwatch.StartNew(); var personnelIds = context.AvailablePersonnel?.Select(p => p.Id).ToList() ?? new List(); if (!personnelIds.Any()) { _logger.LogWarning("没有可用人员ID,跳过资质缓存预加载"); return; } _logger.LogDebug("开始预加载人员资质数据,人员数量:{PersonnelCount}", personnelIds.Count); var allQualifications = new List(); foreach (var personnelId in personnelIds) { var personnelQualifications = await _personnelQualificationService.GetByPersonnelIdAsync(personnelId); allQualifications.AddRange(personnelQualifications); } // 转换为缓存格式并建立索引 foreach (var personnelId in personnelIds) { var personnelQualifications = allQualifications .Where(q => q.PersonnelId == personnelId) .Select(q => new PersonnelQualificationCacheItem { Id = q.Id, PersonnelId = q.PersonnelId, PersonnelName = q.PersonnelName ?? string.Empty, QualificationId = q.QualificationId, HighLevel = q.QualificationLevel == "岗位负责人" ? true : false, ExpiryDate = q.ExpiryDate, ExpiryWarningDays = q.ExpiryWarningDays, RenewalDate = q.RenewalDate, IsActive = q.IsActive }) .ToList(); context.PersonnelQualificationsCache[personnelId] = personnelQualifications; } stopwatch.Stop(); _logger.LogInformation("【性能优化】人员资质缓存预加载完成 - 人员:{PersonnelCount}人,资质记录:{RecordCount}条,耗时:{ElapsedMs}ms", personnelIds.Count, allQualifications.Count(), stopwatch.ElapsedMilliseconds); } catch (Exception ex) { _logger.LogError(ex, "预加载人员资质数据异常"); // 异常情况下确保缓存不为空,避免后续逻辑异常 context.PersonnelQualificationsCache = new Dictionary>(); } } /// /// 从缓存中获取人员资质配置 - 【性能优化核心方法】 /// 业务逻辑:替代直接数据库查询,从预加载的缓存中快速获取资质信息 /// /// 人员ID /// 资质配置列表,格式兼容原有GetByPersonnelIdAsync方法 private List GetPersonnelQualificationsFromCache(long personnelId) { try { // 从当前分配上下文缓存中查找 if (_currentAllocationContext?.PersonnelQualificationsCache?.ContainsKey(personnelId) == true) { var cacheItems = _currentAllocationContext.PersonnelQualificationsCache[personnelId]; return cacheItems.Select(item => new PersonnelQualificationGetPageOutput { Id = item.Id, PersonnelId = item.PersonnelId, PersonnelName = item.PersonnelName, QualificationId = item.QualificationId, ExpiryDate = item.ExpiryDate, ExpiryWarningDays = item.ExpiryWarningDays, RenewalDate = item.RenewalDate, IsActive = item.IsActive }).ToList(); } _logger.LogDebug("人员{PersonnelId}不在资质缓存中,返回空列表", personnelId); return new List(); } catch (Exception ex) { _logger.LogError(ex, "从缓存获取人员{PersonnelId}资质信息异常,返回空列表", personnelId); return new List(); } } #endregion /// /// 规则验证结果 /// private class RuleValidationResult { public bool IsValid { get; set; } public double ComplianceScore { get; set; } public bool IsCritical { get; set; } public string ViolationMessage { get; set; } } #endregion #region 遗传算法性能优化专用公共方法 /// /// 【性能优化】公共的人员可用性计算方法 - 专为遗传算法优化 /// 替代原有的反射调用CalculatePersonnelAvailabilityAsync方法,消除15-20%的性能开销 /// /// 人员信息 /// 待分配任务 /// 当前上下文中已分配的任务列表 /// 人员对该任务的可用性评分(0-100分) public async Task CalculatePersonnelAvailabilityForGeneticAsync( GlobalPersonnelInfo personnel, WorkOrderEntity task, List contextTasks) { try { // 调用私有方法进行计算(保持业务逻辑一致性) var methodInfo = this.GetType().GetMethod("CalculatePersonnelAvailabilityAsync", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); if (methodInfo != null) { var parameters = new object[] { personnel, task, contextTasks ?? new List() }; return await (Task)methodInfo.Invoke(this, parameters); } else { _logger.LogWarning("【遗传算法优化】无法找到CalculatePersonnelAvailabilityAsync方法,使用简化计算"); return await CalculateFallbackAvailabilityAsync(personnel.Id, task, contextTasks); } } catch (Exception ex) { _logger.LogError(ex, "【遗传算法优化】人员可用性计算异常 - 人员:{PersonnelId}, 任务:{TaskId}", personnel.Id, task.Id); return await CalculateFallbackAvailabilityAsync(personnel.Id, task, contextTasks); } } /// /// 【性能优化】公共的班次规则合规性评分方法 - 专为遗传算法优化 /// 替代原有的反射调用CalculateShiftRuleComplianceScoreAsync方法,提升约束检查性能 /// /// 人员ID /// 工作日期 /// 班次ID /// 全局分配上下文 /// 班次规则合规性评分(0-100分) public async Task CalculateShiftRuleComplianceForGeneticAsync( long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { // 【性能优化关键】:直接使用最新的缓存优化班次规则验证引擎 _logger.LogDebug("【遗传算法优化】使用缓存优化班次规则验证 - 人员:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate, shiftId); var shiftRulesValidation = await CheckShiftRulesWithCacheAsync(personnelId, workDate, shiftId, context); // 将0-100评分转换为遗传算法需要的格式 var geneticScore = shiftRulesValidation.OverallScore; _logger.LogDebug( "【遗传算法-班次验证】人员{PersonnelId}在{WorkDate:yyyy-MM-dd}班次{ShiftId}的合规评分:{Score:F2}, 合规状态:{IsCompliant}, 关键违规:{HasCritical}", personnelId, workDate, shiftId, geneticScore, shiftRulesValidation.IsCompliant, shiftRulesValidation.HasCriticalViolations); return geneticScore; } catch (Exception ex) { _logger.LogError(ex, "【遗传算法优化】缓存版班次规则验证异常,回退到简化验证 - 人员:{PersonnelId}, 日期:{WorkDate}, 班次:{ShiftId}", personnelId, workDate, shiftId); return await CalculateFallbackShiftRuleComplianceAsync(personnelId, workDate, shiftId); } } /// /// 简化版人员可用性评分 - 遗传算法回退方案 /// private async Task CalculateFallbackAvailabilityAsync(long personnelId, WorkOrderEntity task, List contextTasks) { await Task.CompletedTask; try { // 基本时间冲突检查 var hasTimeConflict = contextTasks?.Any(ct => ct.AssignedPersonnelId == personnelId && ct.WorkOrderDate.Date == task.WorkOrderDate.Date && ct.ShiftId == task.ShiftId) ?? false; if (hasTimeConflict) return 0.0; // 时间冲突,完全不可用 // 工作负载检查 var dailyTaskCount = contextTasks?.Count(ct => ct.AssignedPersonnelId == personnelId && ct.WorkOrderDate.Date == task.WorkOrderDate.Date) ?? 0; if (dailyTaskCount >= 3) return 25.0; // 过载但可用 else if (dailyTaskCount >= 2) return 60.0; // 适度负载 else return 85.0; // 轻度负载,高可用性 } catch (Exception ex) { _logger.LogError(ex, "简化版人员可用性评分计算异常"); return 50.0; // 异常时返回中等评分 } } /// /// 简化版班次规则合规性评分 - 遗传算法回退方案 /// private async Task CalculateFallbackShiftRuleComplianceAsync(long personnelId, DateTime workDate, long shiftId) { await Task.CompletedTask; try { // 基本约束检查 var constraintChecks = new List(); // 检查1:基本时间规则(假设通过) constraintChecks.Add(1.0); // 检查2:简化的二班/三班后休息规则检查 var previousDate = workDate.AddDays(-1); var hadRestrictedShiftYesterday = false; // 简化实现,实际需要查询历史数据 constraintChecks.Add(hadRestrictedShiftYesterday ? 0.0 : 1.0); // 检查3:周工作限制(假设合规) constraintChecks.Add(0.9); var averageCompliance = constraintChecks.Average(); return averageCompliance * 100.0; // 转换为0-100分制 } catch (Exception ex) { _logger.LogError(ex, "简化版班次规则合规评分计算异常"); return 70.0; // 异常时返回较好的合规评分 } } #endregion #region 班次规则验证方法 - 从PersonnelAllocationService迁移并优化 /// /// 【核心方法】带缓存优化的班次规则验证 - 迁移自PersonnelAllocationService /// 业务逻辑:使用GlobalAllocationContext缓存数据,支持按需预加载、回退机制、优先级短路验证 /// 性能优化:避免重复数据库查询,15天时间窗口,线程安全缓存访问 /// /// 人员ID /// 工作日期 /// 班次ID /// 全局分配上下文 - 包含预加载的缓存数据 /// 班次规则验证汇总结果 private async Task CheckShiftRulesWithCacheAsync( long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { _logger.LogDebug("开始班次规则验证 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 班次ID:{ShiftId}", personnelId, workDate.ToString("yyyy-MM-dd"), shiftId); // 确保缓存数据已加载 await EnsurePersonnelCacheLoadedAsync(personnelId, context); // 从缓存获取班次规则 var shiftRules = context.ShiftRulesMapping .Where(r => r.IsEnabled) .OrderBy(r => ShiftRulePriorityConfig.Rules.GetValueOrDefault(r.RuleType, new ShiftRuleConfig()).Priority) .ToList(); if (!shiftRules.Any()) { _logger.LogDebug("未找到启用的班次规则,验证通过 - 班次ID:{ShiftId}", shiftId); return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 100, ValidationReason = "无班次规则配置", HasCriticalViolations = false, IndividualResults = new List() }; } var individualResults = new List(); var scores = new List(); var criticalViolations = new List(); // 按优先级验证各项规则 foreach (var rule in shiftRules) { var ruleResult = await ValidateIndividualShiftRuleWithCacheAsync(rule, personnelId, workDate, shiftId, context); individualResults.Add(ruleResult); if (!ruleResult.IsValid) { var ruleConfig = ShiftRulePriorityConfig.Rules.GetValueOrDefault(rule.RuleType, new ShiftRuleConfig()); // 关键规则短路验证 if (ruleResult.IsCritical && ruleConfig.IsCritical) { _logger.LogWarning( "关键规则违规,立即停止验证 - 人员:{PersonnelId}, 规则:{RuleType}({RuleName}), 原因:{Reason}", personnelId, rule.RuleType, rule.RuleName, ruleResult.ViolationMessage); return new ShiftRulesValidationSummary { IsCompliant = false, OverallScore = 0, ValidationReason = $"关键规则违规:{ruleResult.ViolationMessage}", HasCriticalViolations = true, IndividualResults = individualResults }; } if (ruleResult.IsCritical) { criticalViolations.Add($"规则{rule.RuleName}:{ruleResult.ViolationMessage}"); } } scores.Add(ruleResult.ComplianceScore); } // 计算综合结果 var averageScore = scores.Any() ? scores.Average() : 100; var hasCriticalViolation = criticalViolations.Any(); var isCompliant = !hasCriticalViolation && averageScore >= 50; var validationReason = hasCriticalViolation ? $"关键违规:{string.Join("; ", criticalViolations)}" : individualResults.Any(r => !r.IsValid) ? $"规则违规:{string.Join("; ", individualResults.Where(r => !r.IsValid).Select(r => r.ViolationMessage))}" : "符合所有班次规则"; _logger.LogDebug("班次规则验证完成 - 人员:{PersonnelId}, 合规:{IsCompliant}, 评分:{Score:F1}, 关键违规:{CriticalCount}", personnelId, isCompliant, averageScore, criticalViolations.Count); return new ShiftRulesValidationSummary { IsCompliant = isCompliant, OverallScore = averageScore, ValidationReason = validationReason, HasCriticalViolations = hasCriticalViolation, IndividualResults = individualResults }; } catch (Exception ex) { _logger.LogError(ex, "班次规则验证异常,回退到传统验证 - PersonnelId: {PersonnelId}", personnelId); // 缓存失败时回退到数据库查询 return await FallbackToDirectShiftRuleValidation(personnelId, workDate, shiftId); } } /// /// 验证单个班次规则 - 支持所有10种规则类型 /// private async Task ValidateIndividualShiftRuleWithCacheAsync( ShiftRuleGetPageOutput rule, long personnelId, DateTime workDate, long shiftId, GlobalAllocationContext context) { try { var result = rule.RuleType switch { "1" => await ValidateAssignedPersonnelPriorityRuleWithCacheAsync(personnelId, workDate, context), "2" => await ValidateWeeklyTaskLimitRuleWithCacheAsync(personnelId, workDate, context), "3" => await ValidateShiftContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 3, context), "4" => await ValidateShiftContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 4, context), "5" => await ValidateCrossWeekendContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 5, context), "6" => await ValidateCrossWeekendContinuityRuleWithCacheAsync(personnelId, workDate, shiftId, 6, context), "7" => await ValidateContinuousWorkDaysRuleWithCacheAsync(personnelId, workDate, context), "8" => await ValidateNextDayRestRuleWithCacheAsync(personnelId, workDate, shiftId, 8, context), "9" => await ValidateNextDayRestRuleWithCacheAsync(personnelId, workDate, shiftId, 9, context), "10" => await ValidateProjectFLPriorityRuleWithCacheAsync(personnelId, workDate, context), _ => await ValidateDefaultRuleWithCacheAsync(rule, personnelId, workDate, context) }; _logger.LogTrace("规则验证完成 - 规则:{RuleName}({RuleType}), 人员:{PersonnelId}, 结果:{IsValid}, 评分:{Score}", rule.RuleName, rule.RuleType, personnelId, result.IsValid, result.ComplianceScore); return result; } catch (Exception ex) { _logger.LogError(ex, "验证单个班次规则异常,规则:{RuleName}({RuleType}),人员:{PersonnelId}", rule.RuleName, rule.RuleType, personnelId); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = $"规则'{rule.RuleName}'验证异常:{ex.Message}" }; } } #region 10种规则的具体实现 - 优化版本 /// /// 规则1:指定人员优先验证 - 缓存优化版本 /// private async Task ValidateAssignedPersonnelPriorityRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { // 从缓存获取该人员当天的任务分配 var workDates = context.PersonnelWorkDatesCache.GetValueOrDefault(personnelId, new List()); bool hasAssignedTask = workDates.Contains(workDate.Date); return new ShiftRuleValidationResult { IsValid = true, // 这个规则通常不会阻断,只是影响评分 ComplianceScore = hasAssignedTask ? 100.0 : 80.0, IsCritical = false, ViolationMessage = hasAssignedTask ? "" : "非指定人员,优先级稍低" }; } catch (Exception ex) { _logger.LogError(ex, "验证指定人员优先规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("AssignedPersonnelPriority", personnelId, workDate); } } /// /// 规则2:周任务限制验证 - 缓存优化版本 /// private async Task ValidateWeeklyTaskLimitRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { var maxWeeklyShifts = 6; // 从缓存获取工作限制 if (context.PersonnelWorkLimitsRuleCache.TryGetValue(personnelId, out var workLimit) && workLimit.MaxShiftsPerWeek.HasValue) { maxWeeklyShifts = workLimit.MaxShiftsPerWeek.Value; } // 计算该周的班次数 var weekShiftCount = await CalculateWeekShiftCountFromCache(personnelId, workDate, context); var isValid = weekShiftCount <= maxWeeklyShifts; var complianceScore = isValid ? Math.Max(0, 100 - (weekShiftCount / (double)maxWeeklyShifts * 100)) : 0; return new ShiftRuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"周班次数({weekShiftCount})超过限制({maxWeeklyShifts})" }; } catch (Exception ex) { _logger.LogError(ex, "验证周任务限制规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("WeeklyTaskLimit", personnelId, workDate); } } /// /// 规则8&9:次日休息规则验证 - 缓存优化版本 /// 最高优先级关键规则,夜班/中班后次日必须休息 /// private async Task ValidateNextDayRestRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证次日休息规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 确定需要检查的特定班次编号 int targetShiftNumber = ruleType switch { 8 => 3, // 规则8:检查前一天是否有三班(夜班) 9 => 2, // 规则9:检查前一天是否有二班(中班) _ => 0 // 未知规则类型 }; if (targetShiftNumber == 0) { _logger.LogWarning("未支持的次日休息规则类型:{RuleType},跳过验证", ruleType); return CreateValidResult("未支持的规则类型,跳过验证", 90); } // 计算前一天日期 var previousDate = workDate.AddDays(-1); // 从缓存获取前一天的班次信息 var previousDayShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, previousDate, context); // 检查前一天是否有目标班次 bool hadTargetShiftPreviousDay = previousDayShifts.Contains(targetShiftNumber); _logger.LogTrace("前一天班次查询结果 - 人员ID:{PersonnelId}, 日期:{PreviousDate}, " + "所有班次:{AllShifts}, 目标班次:{TargetShift}, 是否存在目标班次:{HasTarget}", personnelId, previousDate.ToString("yyyy-MM-dd"), string.Join(",", previousDayShifts), targetShiftNumber, hadTargetShiftPreviousDay); if (!hadTargetShiftPreviousDay) { // 前一天没有目标班次,验证通过 var successMessage = GenerateNextDayRestSuccessMessage(ruleType, previousDate, targetShiftNumber); _logger.LogTrace("次日休息规则验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } // 前一天有目标班次,违反次日休息规则 var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); var currentShiftName = GetShiftDisplayName(currentShiftNumber); var violationDetail = GenerateNextDayRestViolationDetail(ruleType, previousDate, workDate, targetShiftNumber, previousDayShifts, currentShiftName); _logger.LogWarning("次日休息规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, // 违反休息规则是关键风险 ViolationMessage = violationDetail }; } catch (Exception ex) { _logger.LogError(ex, "验证次日休息规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"NextDayRest_{ruleType}", personnelId, workDate); } } /// /// 规则3&4:班次连续性验证 - 缓存优化版本 /// 关键规则,禁止同一天内特定班次组合 /// private async Task ValidateShiftContinuityRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证班次连续性规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 检查当天是否已有相同班次的任务分配(防重复分配) var todayShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, workDate, context); var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); if (todayShifts.Contains(currentShiftNumber)) { _logger.LogWarning("班次重复分配违规 - 人员ID:{PersonnelId}, 日期:{Date}, 班次编号:{ShiftNumber}", personnelId, workDate.ToString("yyyy-MM-dd"), currentShiftNumber); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = "该人员当天已有相同班次的任务分配,不能重复分配" }; } // 如果当天无其他班次记录,直接通过验证 if (!todayShifts.Any()) { _logger.LogTrace("同天班次连续性验证:当天无其他班次记录,通过验证 - 人员ID:{PersonnelId}, 当前班次编号:{ShiftNumber}", personnelId, currentShiftNumber); return CreateValidResult("当天无其他班次记录,通过连续性验证", 100); } // 根据规则类型检查同一天内班次连续性违规情况 bool hasViolation = ruleType switch { 3 => todayShifts.Contains(1) && currentShiftNumber == 2, // 同一天禁止早班(1)和中班(2)同时存在 4 => todayShifts.Contains(2) && currentShiftNumber == 3, // 同一天禁止中班(2)和晚班(3)同时存在 _ => false // 未定义的规则类型默认不违规 }; if (hasViolation) { var existingShift = todayShifts.First(s => (ruleType == 3 && s == 1) || (ruleType == 4 && s == 2)); var violationDetail = GenerateSameDayViolationDetail(ruleType, existingShift, currentShiftNumber); _logger.LogWarning("同天班次连续性规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = violationDetail }; } var successMessage = GenerateSameDaySuccessMessage(ruleType, currentShiftNumber); _logger.LogTrace("同天班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证班次连续性规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"ShiftContinuity_{ruleType}", personnelId, workDate); } } /// /// 规则10:项目FL优先分配验证 - 缓存优化版本 /// private async Task ValidateProjectFLPriorityRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证项目FL优先分配规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}", personnelId, workDate.ToString("yyyy-MM-dd")); // 从当前任务上下文获取项目信息 var currentTask = context.CurrentTask; if (currentTask == null || string.IsNullOrEmpty(currentTask.ProjectNumber)) { _logger.LogWarning("无法获取当前任务项目信息,采用中性评分 - 人员ID:{PersonnelId}", personnelId); return CreateValidResult("无法确定工作任务上下文,采用中性评分", 85); } // 从缓存检查人员是否为当前项目的FL成员 var projectFLKey = $"{personnelId}_{currentTask.ProjectNumber}"; var isProjectFL = context.PersonnelProjectFLCache.GetValueOrDefault(projectFLKey, false); // 从缓存获取人员的其他项目FL关联情况 var allProjectFLs = context.PersonnelAllProjectFLsCache.GetValueOrDefault(personnelId, new List()); // 计算项目FL优先级评分 var scoringResult = CalculateProjectFLPriorityScore(personnelId, currentTask.ProjectNumber, isProjectFL, allProjectFLs); var resultMessage = GenerateProjectFLValidationMessage(personnelId, currentTask.ProjectNumber, isProjectFL, scoringResult.Score, scoringResult.Reason); _logger.LogTrace("项目FL优先规则验证完成 - 人员ID:{PersonnelId}, 项目:{ProjectNumber}, " + "是否本项目FL:{IsProjectFL}, 评分:{Score}", personnelId, currentTask.ProjectNumber, isProjectFL, scoringResult.Score); return new ShiftRuleValidationResult { IsValid = true, // 此规则主要影响优先级,不阻断分配 ComplianceScore = scoringResult.Score, IsCritical = false, ViolationMessage = resultMessage }; } catch (Exception ex) { _logger.LogError(ex, "验证项目FL优先规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("ProjectFLPriority", personnelId, workDate); } } /// /// 规则7:连续工作天数验证 - 缓存优化版本 /// private async Task ValidateContinuousWorkDaysRuleWithCacheAsync( long personnelId, DateTime workDate, GlobalAllocationContext context) { try { var maxContinuousDays = 7; // 从缓存获取工作限制 if (context.PersonnelWorkLimitsRuleCache.TryGetValue(personnelId, out var workLimit) && workLimit.MaxContinuousWorkDays.HasValue) { maxContinuousDays = workLimit.MaxContinuousWorkDays.Value; } // 从缓存计算连续工作天数 var continuousDays = await CalculateContinuousWorkDaysFromCache(personnelId, workDate, context); var isValid = continuousDays <= maxContinuousDays; var complianceScore = isValid ? Math.Max(0, 100 - (continuousDays / (double)maxContinuousDays * 100)) : 0; return new ShiftRuleValidationResult { IsValid = isValid, ComplianceScore = complianceScore, IsCritical = !isValid, ViolationMessage = isValid ? "" : $"连续工作天数({continuousDays})超过限制({maxContinuousDays})" }; } catch (Exception ex) { _logger.LogError(ex, "验证连续工作天数规则异常 - PersonnelId: {PersonnelId}", personnelId); return await FallbackToDirectRuleValidation("ContinuousWorkDays", personnelId, workDate); } } /// /// 规则5&6:跨周末连续性验证 - 缓存优化版本 /// private async Task ValidateCrossWeekendContinuityRuleWithCacheAsync( long personnelId, DateTime workDate, long shiftId, int ruleType, GlobalAllocationContext context) { try { _logger.LogTrace("开始验证跨周末班次连续性规则 - 人员ID:{PersonnelId}, 工作日期:{WorkDate}, 规则类型:{RuleType}", personnelId, workDate.ToString("yyyy-MM-dd"), ruleType); // 计算相关的日期 var (firstDate, secondDate) = CalculateWeekendDates(workDate, ruleType); // 检查当前工作日期是否匹配需要验证的日期 bool isTargetDate = workDate.Date == firstDate.Date || workDate.Date == secondDate.Date; if (!isTargetDate) { _logger.LogTrace("当前日期不在验证范围内,跳过验证 - 当前日期:{CurrentDate}", workDate.ToString("yyyy-MM-dd")); return CreateValidResult("非目标验证日期,通过验证", 100); } // 获取两个日期的班次分配情况 var firstDateShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, firstDate, context); var secondDateShifts = await GetPersonnelShiftNumbersOnDateFromCache(personnelId, secondDate, context); // 如果当前要分配的日期需要将当前班次加入到检查中 var currentShiftNumber = context.ShiftNumberMapping.GetValueOrDefault(shiftId, 0); if (workDate.Date == secondDate.Date && !secondDateShifts.Contains(currentShiftNumber)) { secondDateShifts.Add(currentShiftNumber); } else if (workDate.Date == firstDate.Date && !firstDateShifts.Contains(currentShiftNumber)) { firstDateShifts.Add(currentShiftNumber); } // 检查是否存在跨周末连续性违规 bool hasViolation = firstDateShifts.Any() && secondDateShifts.Any(); if (hasViolation) { var violationDetail = GenerateCrossWeekendViolationDetail(ruleType, firstDate, secondDate, firstDateShifts, secondDateShifts); _logger.LogWarning("跨周末班次连续性规则违规 - 人员ID:{PersonnelId}, {ViolationDetail}", personnelId, violationDetail); return new ShiftRuleValidationResult { IsValid = false, ComplianceScore = 0, IsCritical = true, ViolationMessage = violationDetail }; } var successMessage = GenerateCrossWeekendSuccessMessage(ruleType, firstDate, secondDate); _logger.LogTrace("跨周末班次连续性验证通过 - 人员ID:{PersonnelId}, {SuccessMessage}", personnelId, successMessage); return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 100, IsCritical = false, ViolationMessage = "" }; } catch (Exception ex) { _logger.LogError(ex, "验证跨周末连续性规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return await FallbackToDirectRuleValidation($"CrossWeekendContinuity_{ruleType}", personnelId, workDate); } } /// /// 默认规则验证 - 缓存优化版本 /// private async Task ValidateDefaultRuleWithCacheAsync( ShiftRuleGetPageOutput rule, long personnelId, DateTime workDate, GlobalAllocationContext context) { _logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName); return await Task.FromResult(new ShiftRuleValidationResult { IsValid = true, ComplianceScore = 90, // 给予较高分,但不是满分 IsCritical = false, ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证" }); } #endregion #region 缓存数据加载和辅助方法 /// /// 确保人员缓存数据已加载 - 按需预加载策略 /// private async Task EnsurePersonnelCacheLoadedAsync(long personnelId, GlobalAllocationContext context) { if (context.CachedPersonnelIds.Contains(personnelId)) { return; // 已加载过,直接返回 } try { _logger.LogDebug("开始预加载人员缓存数据 - PersonnelId: {PersonnelId}", personnelId); // 加载15天时间窗口内的工作历史 var workOrderHistory = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= context.TimeWindowStartDate && w.WorkOrderDate <= context.TimeWindowEndDate) .Include(w => w.ShiftEntity) .ToListAsync(); // 提取工作日期列表 context.PersonnelWorkDatesCache[personnelId] = workOrderHistory .Select(w => w.WorkOrderDate.Date) .Distinct() .ToList(); // 构建历史任务缓存 var historyTasks = workOrderHistory.Select(w => new WorkOrderHistoryItem { TaskId = w.Id, WorkDate = w.WorkOrderDate, ShiftId = w.ShiftId, ShiftNumber = w.ShiftEntity?.ShiftNumber, TaskCode = w.WorkOrderCode, ProjectNumber = w.ProjectNumber, Status = w.Status }).ToList(); if (context.PersonnelHistoryTasks.ContainsKey(personnelId)) { context.PersonnelHistoryTasks[personnelId].AddRange(historyTasks); } else { context.PersonnelHistoryTasks[personnelId] = historyTasks; } // 标记为已加载 context.CachedPersonnelIds.Add(personnelId); _logger.LogDebug("人员缓存数据预加载完成 - PersonnelId: {PersonnelId}, 历史任务: {TaskCount}, 工作日期: {DateCount}", personnelId, historyTasks.Count, context.PersonnelWorkDatesCache[personnelId].Count); } catch (Exception ex) { _logger.LogWarning(ex, "预加载人员缓存数据失败,将在验证时回退到数据库查询 - PersonnelId: {PersonnelId}", personnelId); } } /// /// 从缓存获取人员在指定日期的班次编号列表 /// private async Task> GetPersonnelShiftNumbersOnDateFromCache( long personnelId, DateTime date, GlobalAllocationContext context) { try { // 首先尝试从缓存获取 if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var shiftsOnDate = historyTasks .Where(t => t.WorkDate.Date == date.Date && t.ShiftNumber.HasValue) .Select(t => t.ShiftNumber.Value) .Distinct() .ToList(); _logger.LogTrace("从缓存获取人员当日班次编号 - 人员ID:{PersonnelId}, 日期:{Date}, 班次编号:{ShiftNumbers}", personnelId, date.ToString("yyyy-MM-dd"), string.Join(",", shiftsOnDate)); return shiftsOnDate; } // 缓存未命中,回退到数据库查询 _logger.LogDebug("缓存未命中,回退到数据库查询 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return await GetPersonnelShiftNumbersOnDateFromDatabase(personnelId, date); } catch (Exception ex) { _logger.LogError(ex, "获取人员当日班次编号异常 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return new List(); } } /// /// 从缓存计算周班次数量 /// private async Task CalculateWeekShiftCountFromCache( long personnelId, DateTime targetDate, GlobalAllocationContext context) { try { // 获取目标日期所在周的开始和结束时间 var weekStart = targetDate.AddDays(-(int)targetDate.DayOfWeek); var weekEnd = weekStart.AddDays(6); if (context.PersonnelHistoryTasks.TryGetValue(personnelId, out var historyTasks)) { var weekShiftCount = historyTasks .Where(t => t.WorkDate.Date >= weekStart.Date && t.WorkDate.Date <= weekEnd.Date) .Count(); return weekShiftCount + 1; // 包含目标日期 } // 缓存未命中,回退到数据库查询 return await CalculateWeekShiftCountFromDatabase(personnelId, targetDate); } catch (Exception ex) { _logger.LogError(ex, "从缓存计算周班次数异常 - PersonnelId: {PersonnelId}", personnelId); return 1; } } /// /// 从缓存计算连续工作天数 /// private async Task CalculateContinuousWorkDaysFromCache( long personnelId, DateTime targetDate, GlobalAllocationContext context) { try { if (context.PersonnelWorkDatesCache.TryGetValue(personnelId, out var workDates)) { // 包含目标日期 var allWorkDates = workDates.ToList(); if (!allWorkDates.Contains(targetDate.Date)) { allWorkDates.Add(targetDate.Date); } return CalculateContinuousWorkDays(allWorkDates); } // 缓存未命中,回退到数据库查询 return await CalculateContinuousWorkDaysFromDatabase(personnelId, targetDate); } catch (Exception ex) { _logger.LogError(ex, "从缓存计算连续工作天数异常 - PersonnelId: {PersonnelId}", personnelId); return 0; } } /// /// 计算连续工作天数 - 辅助算法 /// private int CalculateContinuousWorkDays(List workDates) { if (workDates == null || !workDates.Any()) return 0; var sortedDates = workDates.OrderBy(d => d.Date).ToList(); var maxContinuous = 1; var currentContinuous = 1; for (int i = 1; i < sortedDates.Count; i++) { if ((sortedDates[i].Date - sortedDates[i - 1].Date).Days == 1) { currentContinuous++; maxContinuous = Math.Max(maxContinuous, currentContinuous); } else { currentContinuous = 1; } } return maxContinuous; } #endregion #region 回退机制 - 缓存失败时的数据库查询 /// /// 回退到直接的班次规则验证 - 缓存失败时使用 /// private async Task FallbackToDirectShiftRuleValidation( long personnelId, DateTime workDate, long shiftId) { try { _logger.LogInformation("执行回退班次规则验证 - PersonnelId: {PersonnelId}", personnelId); // 使用简化的直接数据库查询验证 var shiftRules = await _shiftService.GetShiftRulesAsync(shiftId); if (shiftRules?.Any() != true) { return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 100, ValidationReason = "无班次规则配置(回退验证)", HasCriticalViolations = false }; } // 简化验证:只检查最关键的规则 var criticalRules = shiftRules.Where(r => r.IsEnabled && ShiftRulePriorityConfig.Rules .GetValueOrDefault(r.RuleType, new ShiftRuleConfig()) .IsCritical); foreach (var rule in criticalRules) { if (rule.RuleType == "8" || rule.RuleType == "9") // 次日休息规则 { var hasViolation = await CheckNextDayRestRuleDirectly(personnelId, workDate, rule.RuleType); if (hasViolation) { return new ShiftRulesValidationSummary { IsCompliant = false, OverallScore = 0, ValidationReason = $"关键规则违规:{rule.RuleName}(回退验证)", HasCriticalViolations = true }; } } } return new ShiftRulesValidationSummary { IsCompliant = true, OverallScore = 80, // 回退验证给予中等评分 ValidationReason = "回退验证通过", HasCriticalViolations = false }; } catch (Exception ex) { _logger.LogError(ex, "回退班次规则验证异常 - PersonnelId: {PersonnelId}", personnelId); return new ShiftRulesValidationSummary { IsCompliant = true, // 异常时采用保守策略,不阻断业务 OverallScore = 60, ValidationReason = $"回退验证异常,建议人工审核:{ex.Message}", HasCriticalViolations = false }; } } /// /// 回退到直接的单个规则验证 /// private async Task FallbackToDirectRuleValidation( string ruleName, long personnelId, DateTime workDate) { _logger.LogDebug("执行单个规则回退验证 - Rule: {RuleName}, PersonnelId: {PersonnelId}", ruleName, personnelId); return await Task.FromResult(new ShiftRuleValidationResult { IsValid = true, // 异常时采用保守策略 ComplianceScore = 70, // 给予中等评分 IsCritical = false, ViolationMessage = $"规则'{ruleName}'回退验证,建议人工审核" }); } #endregion #region 数据库查询回退方法 /// /// 直接数据库查询:获取人员在指定日期的班次编号 /// private async Task> GetPersonnelShiftNumbersOnDateFromDatabase(long personnelId, DateTime date) { try { var workOrdersWithShifts = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate.Date == date.Date && w.Status > (int)WorkOrderStatusEnum.PendingReview) .Include(w => w.ShiftEntity) .ToListAsync(); return workOrdersWithShifts .Where(w => w.ShiftEntity != null) .Select(w => w.ShiftEntity.ShiftNumber) .Distinct() .ToList(); } catch (Exception ex) { _logger.LogError(ex, "数据库查询人员当日班次编号异常 - PersonnelId: {PersonnelId}, Date: {Date}", personnelId, date.ToString("yyyy-MM-dd")); return new List(); } } /// /// 直接数据库查询:计算周班次数量 /// private async Task CalculateWeekShiftCountFromDatabase(long personnelId, DateTime targetDate) { try { var weekStart = targetDate.AddDays(-(int)targetDate.DayOfWeek); var weekEnd = weekStart.AddDays(6); var shiftCount = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= weekStart && w.WorkOrderDate <= weekEnd && w.Status > (int)WorkOrderStatusEnum.PendingReview) .CountAsync(); return (int)shiftCount + 1; // 包含目标日期 } catch (Exception ex) { _logger.LogError(ex, "数据库查询周班次数异常 - PersonnelId: {PersonnelId}", personnelId); return 1; } } /// /// 直接数据库查询:计算连续工作天数 /// private async Task CalculateContinuousWorkDaysFromDatabase(long personnelId, DateTime targetDate) { try { var startDate = targetDate.AddDays(-14); var endDate = targetDate.AddDays(14); var workDates = await _workOrderRepository.Select .Where(w => w.AssignedPersonnelId == personnelId && w.WorkOrderDate >= startDate && w.WorkOrderDate <= endDate && w.Status > (int)WorkOrderStatusEnum.PendingReview) .ToListAsync(w => w.WorkOrderDate); workDates.Add(targetDate); return CalculateContinuousWorkDays(workDates); } catch (Exception ex) { _logger.LogError(ex, "数据库查询连续工作天数异常 - PersonnelId: {PersonnelId}", personnelId); return 0; } } /// /// 直接检查次日休息规则 /// private async Task CheckNextDayRestRuleDirectly(long personnelId, DateTime workDate, string ruleType) { try { int targetShiftNumber = ruleType switch { "8" => 3, // 夜班 "9" => 2, // 中班 _ => 0 }; if (targetShiftNumber == 0) return false; var previousDate = workDate.AddDays(-1); var previousDayShifts = await GetPersonnelShiftNumbersOnDateFromDatabase(personnelId, previousDate); return previousDayShifts.Contains(targetShiftNumber); } catch (Exception ex) { _logger.LogError(ex, "直接检查次日休息规则异常 - PersonnelId: {PersonnelId}, RuleType: {RuleType}", personnelId, ruleType); return false; } } #endregion #region 辅助方法和消息生成 - 与PersonnelAllocationService保持一致 /// /// 创建验证通过结果的辅助方法 /// private ShiftRuleValidationResult CreateValidResult(string reason, double score = 100) { return new ShiftRuleValidationResult { IsValid = true, ComplianceScore = score, IsCritical = false, ViolationMessage = "" }; } /// /// 计算项目FL优先级评分 - 与PersonnelAllocationService保持一致的算法 /// private (double Score, string Reason) CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber, bool isProjectFL, List allProjectFLs) { try { if (isProjectFL) { return (100, $"本项目FL成员,具有专业经验和项目知识,优先分配"); } if (allProjectFLs.Any()) { int projectCount = allProjectFLs.Count; DateTime latestAssignment = allProjectFLs.Max(p => p.LastAssignmentDate); int daysSinceLastAssignment = (DateTime.Now - latestAssignment).Days; double score = 85; // 基础分 // 多项目经验加分 if (projectCount >= 3) score += 10; else if (projectCount == 2) score += 5; // 最近活跃度调整 if (daysSinceLastAssignment <= 30) score += 5; else if (daysSinceLastAssignment > 180) score -= 10; score = Math.Min(95, Math.Max(70, score)); return (score, $"有{projectCount}个项目FL经验,最近参与时间:{latestAssignment:yyyy-MM-dd},具有一定项目经验"); } else { return (70, "暂无项目FL经验,可作为培养对象适当分配"); } } catch (Exception ex) { _logger.LogError(ex, "计算项目FL优先级评分异常 - PersonnelId: {PersonnelId}, Project: {ProjectNumber}", personnelId, currentProjectNumber); return (75, $"评分计算异常,采用默认中等评分:{ex.Message}"); } } /// /// 生成项目FL验证信息 /// private string GenerateProjectFLValidationMessage(long personnelId, string projectNumber, bool isProjectFL, double score, string reason) { string priorityLevel = score switch { >= 95 => "最高优先级", >= 85 => "高优先级", >= 75 => "中等优先级", _ => "低优先级" }; return $"【项目FL优先分配验证】人员ID:{personnelId},目标项目:{projectNumber}," + $"是否本项目FL:{(isProjectFL ? "是" : "否")},优先级评分:{score:F1}分({priorityLevel}),评分依据:{reason}"; } /// /// 生成次日休息规则违规详细信息 /// private string GenerateNextDayRestViolationDetail(int ruleType, DateTime previousDate, DateTime currentDate, int targetShiftNumber, List previousDayShifts, string currentShiftName = "未知班次") { var targetShiftDisplayName = GetShiftDisplayName(targetShiftNumber); var previousShiftsStr = string.Join(",", previousDayShifts); return ruleType switch { 8 => $"【三班后次日强制休息违规】前一天:{previousDate:yyyy-MM-dd} 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})," + $"当前日期:{currentDate:yyyy-MM-dd} 试图分配{currentShiftName},违反休息规则", 9 => $"【二班后次日强制休息违规】前一天:{previousDate:yyyy-MM-dd} 已安排{targetShiftDisplayName}(班次编号{targetShiftNumber})," + $"当前日期:{currentDate:yyyy-MM-dd} 试图分配{currentShiftName},违反休息规则", _ => $"【次日休息规则违规】规则类型{ruleType},前一天({previousDate:yyyy-MM-dd})有{targetShiftDisplayName}(班次{previousShiftsStr})," + $"当天({currentDate:yyyy-MM-dd})试图分配{currentShiftName},违反休息规则" }; } /// /// 生成次日休息规则验证成功信息 /// private string GenerateNextDayRestSuccessMessage(int ruleType, DateTime previousDate, int targetShiftNumber) { var shiftDisplayName = GetShiftDisplayName(targetShiftNumber); return ruleType switch { 8 => $"三班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName}安排,员工无需强制休息", 9 => $"二班后次日休息规则验证通过:前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName}安排,员工无需强制休息", _ => $"次日休息规则验证通过:规则类型{ruleType},前一天{previousDate:yyyy-MM-dd}无{shiftDisplayName},符合规则要求" }; } /// /// 生成同天班次违规详细信息 /// private string GenerateSameDayViolationDetail(int ruleType, int existingShift, int currentShift) { return ruleType switch { 3 => $"同天早中班连续性违规:违反早中班同天禁止规则", 4 => $"同天中夜班连续性违规:违反中夜班同天禁止规则", _ => $"同天班次连续性违规:规则类型{ruleType},已有班次{existingShift}与当前班次{currentShift}冲突" }; } /// /// 生成同天班次验证成功信息 /// private string GenerateSameDaySuccessMessage(int ruleType, int currentShift) { return ruleType switch { 3 => $"同天早中班连续性验证通过:符合早中班同天禁止规则", 4 => $"同天中夜班连续性验证通过:符合中夜班同天禁止规则", _ => $"同天班次连续性验证通过:规则类型{ruleType},班次{currentShift}符合要求" }; } /// /// 计算跨周末规则验证的相关日期 /// private (DateTime firstDate, DateTime secondDate) CalculateWeekendDates(DateTime workDate, int ruleType) { try { var currentDate = workDate.Date; if (ruleType == 5) // 不能这周日/下周六连续 { var daysFromSunday = (int)currentDate.DayOfWeek; var thisWeekSunday = currentDate.AddDays(-daysFromSunday); var nextWeekSaturday = thisWeekSunday.AddDays(13); return (thisWeekSunday, nextWeekSaturday); } else if (ruleType == 6) // 不能本周六/本周日连续 { var daysFromSunday = (int)currentDate.DayOfWeek; var thisWeekSunday = currentDate.AddDays(-daysFromSunday); var thisWeekSaturday = thisWeekSunday.AddDays(-1); return (thisWeekSaturday, thisWeekSunday); } else { _logger.LogWarning("未支持的跨周末规则类型:{RuleType}", ruleType); return (currentDate, currentDate); } } catch (Exception ex) { _logger.LogError(ex, "计算跨周末日期异常 - 工作日期:{WorkDate}, 规则类型:{RuleType}", workDate.ToString("yyyy-MM-dd"), ruleType); return (workDate, workDate); } } /// /// 生成跨周末班次违规详细信息 /// private string GenerateCrossWeekendViolationDetail(int ruleType, DateTime firstDate, DateTime secondDate, List firstDateShifts, List secondDateShifts) { var firstShiftsStr = string.Join(",", firstDateShifts); var secondShiftsStr = string.Join(",", secondDateShifts); return ruleType switch { 5 => $"跨周末连续性违规:违反周日/下周六禁止连续工作规则,{firstDate:yyyy-MM-dd}有班次{firstShiftsStr},{secondDate:yyyy-MM-dd}有班次{secondShiftsStr}", 6 => $"周末连续性违规:违反周六/周日禁止连续工作规则,{firstDate:yyyy-MM-dd}有班次{firstShiftsStr},{secondDate:yyyy-MM-dd}有班次{secondShiftsStr}", _ => $"跨周末班次连续性违规:规则类型{ruleType},{firstDate:yyyy-MM-dd}和{secondDate:yyyy-MM-dd}存在班次冲突" }; } /// /// 生成跨周末班次验证成功信息 /// private string GenerateCrossWeekendSuccessMessage(int ruleType, DateTime firstDate, DateTime secondDate) { return ruleType switch { 5 => $"跨周末连续性验证通过:符合周日/下周六禁止连续工作规则", 6 => $"周末连续性验证通过:符合周六/周日禁止连续工作规则", _ => $"跨周末班次连续性验证通过:规则类型{ruleType},符合要求" }; } #endregion #endregion } }