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
}
}