paiban/NPP.SmartSchedue.Api/Services/Integration/PersonnelAllocationService.cs
Asoka.Wang 21f044712c 1
2025-08-27 18:39:19 +08:00

4241 lines
188 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// 智能人员分配服务
/// 实现基于五层决策模型的智能分配算法:资格过滤→约束评估→优化决策→结果生成→统一验证
/// 深度业务思考:支持复杂的多维度约束和智能优化,确保分配结果的合理性和公平性
/// </summary>
[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<PersonnelAllocationService> _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<PersonnelAllocationService> 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");
}
/// <summary>
/// 智能人员分配核心方法
/// 基于五层决策模型的完整实现:资格过滤→约束评估→优化决策→结果生成→验证
/// 深度业务思考:考虑所有约束条件,确保分配结果既满足业务规则又实现最优化
/// </summary>
[HttpPost]
public async Task<PersonnelAllocationResult> 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}");
}
}
/// <summary>
/// 获取任务的候选人员列表(带评分和推理)
/// </summary>
public async Task<List<PersonnelCandidate>> GetCandidatesAsync(long workOrderId)
{
try
{
var workOrder = await _workOrderRepository.GetAsync(workOrderId);
if (workOrder == null)
{
_logger.LogWarning("工作任务不存在:{WorkOrderId}", workOrderId);
return new List<PersonnelCandidate>();
}
// 构建单任务分配上下文
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<PersonnelCandidate>();
}
catch (Exception ex)
{
_logger.LogError(ex, "获取候选人员异常任务ID{WorkOrderId}", workOrderId);
return new List<PersonnelCandidate>();
}
}
/// <summary>
/// 批量获取任务的候选人员映射(性能优化)
/// </summary>
public async Task<Dictionary<long, List<PersonnelCandidate>>> GetBatchCandidatesAsync(List<long> workOrderIds)
{
try
{
if (workOrderIds == null || !workOrderIds.Any())
{
return new Dictionary<long, List<PersonnelCandidate>>();
}
// 批量获取工作任务
var workOrders = await _workOrderRepository.Select
.Where(w => workOrderIds.Contains(w.Id))
.Include(w => w.ProcessEntity)
.ToListAsync();
if (!workOrders.Any())
{
return new Dictionary<long, List<PersonnelCandidate>>();
}
// 构建批量分配上下文
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<long, List<PersonnelCandidate>>();
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<long, List<PersonnelCandidate>>();
}
}
/// <summary>
/// 验证人员分配的可行性
/// </summary>
public async Task<PersonnelAllocationValidationResult> ValidateAllocationAsync(
PersonnelAllocationValidationInput validationInput)
{
try
{
var result = new PersonnelAllocationValidationResult();
var violations = new List<ValidationViolation>();
var warnings = new List<ValidationWarning>();
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}"
};
}
}
/// <summary>
/// 获取人员工作负载分析
/// </summary>
public async Task<List<PersonnelWorkloadAnalysis>> AnalyzePersonnelWorkloadAsync(List<long> personnelIds,
DateRange dateRange)
{
try
{
var result = new List<PersonnelWorkloadAnalysis>();
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<PersonnelWorkloadAnalysis>();
}
}
#region
/// <summary>
/// 验证和准备输入数据
/// 深度业务思考:确保输入数据的完整性和有效性,避免后续处理出现异常
/// </summary>
private async Task<InputValidationResult> ValidateAndPrepareInputAsync(PersonnelAllocationInput input)
{
var result = new InputValidationResult();
try
{
// 基础验证
if (input == null)
{
result.IsValid = false;
result.ErrorMessage = "输入参数不能为空";
return result;
}
// 获取工作任务
List<WorkOrderEntity> 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;
}
}
/// <summary>
/// 构建分配上下文
/// 深度架构思考:集中管理分配过程中的所有状态和数据,支持复杂的多任务分配场景
/// </summary>
private async Task<AllocationContext> BuildAllocationContextAsync(PersonnelAllocationInput input,
List<WorkOrderEntity> 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);
}
}
/// <summary>
/// 构建单任务分配上下文用于GetCandidatesAsync
/// </summary>
private async Task<AllocationContext> BuildSingleTaskContextAsync(WorkOrderEntity workOrder)
{
try
{
var context = new AllocationContext
{
ContextId = Guid.NewGuid().ToString(),
CreatedAt = DateTime.UtcNow,
WorkOrders = new List<WorkOrderEntity> { 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;
}
}
/// <summary>
/// 获取完整人员池
/// 深度架构思考:严格遵循五层决策模型,获取全量人员池,由各决策层负责筛选
/// 业务逻辑:根据资质表获取所有人员并去重,确保决策模型的完整性和公正性
/// </summary>
private async Task<List<PersonnelEntity>> GetAvailablePersonnelPoolAsync()
{
try
{
// 获取所有人员池(从资质表关联获取并去重)
// 五层决策模型要求:人员池应该是完整的,筛选工作由各决策层完成
var personnelPoolOutputs = await _personService.GetAllPersonnelPoolAsync(includeQualifications: false);
if (personnelPoolOutputs == null || !personnelPoolOutputs.Any())
{
_logger.LogWarning("系统中无任何人员数据");
return new List<PersonnelEntity>();
}
// 转换为PersonnelEntity格式只进行最基础的数据完整性过滤
var personnelEntities = new List<PersonnelEntity>();
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<PersonnelEntity>();
}
}
#endregion
#region
/// <summary>
/// 执行第一层:资格过滤处理
/// 🎯 架构说明:这是一个可插拔的处理器架构
/// 📌 设计理念:既要开箱即用(内置实现),又要支持企业定制(外部处理器)
/// </summary>
private async Task<ProcessorResult> ExecuteQualificationFilterAsync(AllocationContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
context.ProcessingLog.Add("开始资格过滤处理");
// ⚡ 内置资格过滤实现默认实现覆盖99%的业务场景)
// 📈 技术特点:高性能、业务完整、可靠稳定
var filteredCandidates = new Dictionary<long, List<PersonnelCandidate>>();
var totalProcessed = 0;
var totalQualified = 0;
var totalBasicEligible = 0;
foreach (var workOrder in context.WorkOrders)
{
var candidates = new List<PersonnelCandidate>();
// 获取工序资质要求
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}"
};
}
}
/// <summary>
/// 执行第二层:约束评估处理
/// </summary>
private async Task<ProcessorResult> ExecuteConstraintEvaluationAsync(AllocationContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
context.ProcessingLog.Add("开始约束评估处理");
// ⚡ 内置约束评估实现默认实现包含完整的11种班次规则验证
var constraintResults = new Dictionary<long, List<ConstraintEvaluationResult>>();
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<ConstraintEvaluationResult>();
// 人员循环
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<string>(),
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<ConstraintViolation>(),
PassedConstraints = constraintCheck.PassedConstraints ?? new List<string>(),
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}"
};
}
}
/// <summary>
/// 执行第三层:优化决策处理
/// </summary>
private async Task<ProcessorResult> ExecuteOptimizationDecisionAsync(AllocationContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
context.ProcessingLog.Add("开始优化决策处理");
// 内置优化决策实现
var optimizationResults = new Dictionary<long, List<OptimizationScoreResult>>();
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<OptimizationScoreResult>();
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}"
};
}
}
/// <summary>
/// 执行第四层:结果生成处理
/// </summary>
private async Task<ProcessorResult> ExecuteResultGenerationAsync(AllocationContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
context.ProcessingLog.Add("开始结果生成处理");
// 内置结果生成实现
var allocationResult = new AllocationGenerationResult
{
SuccessfulAllocations = new Dictionary<long, PersonnelCandidate>(),
FailedAllocations = new List<FailedAllocation>(),
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<string, object>(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}"
};
}
}
/// <summary>
/// 执行第五层:最终验证和质量评估
/// </summary>
private async Task<PersonnelAllocationResult> ExecuteFinalValidationAsync(AllocationContext context,
AllocationGenerationResult generationResult)
{
try
{
context.ProcessingLog.Add("开始最终验证和质量评估");
var finalResult = new PersonnelAllocationResult
{
SuccessfulMatches = new List<TaskPersonnelMatch>(),
FailedAllocations = new List<FailedPersonnelAllocation>(),
WorkloadAnalysis = new List<PersonnelWorkloadAnalysis>()
};
// 转换成功分配
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
/// <summary>
/// 获取工序资质要求
/// </summary>
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();
}
/// <summary>
/// 生成分配理由
/// </summary>
private string GenerateAllocationReason(PersonnelCandidate candidate)
{
var reasons = new List<string>();
// 资质匹配
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);
}
/// <summary>
/// 生成失败建议
/// </summary>
private List<string> GenerateFailureSuggestions(WorkOrderEntity workOrder)
{
var suggestions = new List<string>();
// 基于工序特点给出建议
if (!string.IsNullOrEmpty(workOrder.ProcessEntity?.QualificationRequirements))
{
suggestions.Add("考虑放宽资质要求或安排人员培训");
}
suggestions.Add("调整任务时间安排");
suggestions.Add("增加临时人员或外包");
suggestions.Add("与其他项目协调人员调配");
return suggestions;
}
/// <summary>
/// 生成详细报告
/// </summary>
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;
}
/// <summary>
/// 检查人员基础资格(第一层决策的基础筛选)
/// 深度架构思考:在资格过滤层进行人员基础状态检查,符合五层决策模型设计
/// </summary>
private async Task<PersonnelEligibilityResult> IsPersonnelBasicallyEligibleAsync(PersonnelEntity personnel)
{
try
{
var result = new PersonnelEligibilityResult
{
PersonnelId = personnel.Id,
IsEligible = true,
EligibilityReasons = new List<string>()
};
// 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<string> { $"基础资格检查异常:{ex.Message}" }
};
}
}
/// <summary>
/// 检查资质匹配
/// </summary>
private async Task<QualificationMatchResult> CheckQualificationMatchAsync(long personnelId,
string[] requiredQualifications)
{
try
{
if (requiredQualifications == null || !requiredQualifications.Any())
{
return new QualificationMatchResult
{
IsMatched = true,
IsValid = true,
MatchScore = 100,
MatchedQualifications = new List<string>(),
MissingQualifications = new List<string>(),
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<string>(),
MissingQualifications = requiredQualifications?.ToList() ?? new List<string>(),
Details = $"资质检查异常:{ex.Message}"
};
}
}
/// <summary>
/// 获取人员资质列表
/// </summary>
private async Task<List<NPP.SmartSchedue.Api.Contracts.Domain.Personnel.PersonnelQualificationEntity>>
GetPersonnelQualificationsAsync(long personnelId)
{
try
{
return await _personnelQualificationService.GetPersonnelQualificationsAsync(personnelId);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取人员资质异常人员ID{PersonnelId}", personnelId);
return new List<NPP.SmartSchedue.Api.Contracts.Domain.Personnel.PersonnelQualificationEntity>();
}
}
#endregion
#region
/// <summary>
/// 评估候选人约束条件
/// 深度业务思考:全面检查时间可用性、工作限制、班次规则等多维度约束
/// </summary>
private async Task<ConstraintEvaluationResult> EvaluateCandidateConstraintsAsync(PersonnelCandidate candidate,
WorkOrderEntity workOrder)
{
var result = new ConstraintEvaluationResult
{
Candidate = candidate,
IsValid = true,
PassedConstraints = new List<string>(),
Violations = new List<ConstraintViolation>()
};
try
{
var scores = new List<double>();
// 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;
}
}
/// <summary>
/// 生成约束评估详情
/// </summary>
private string GenerateConstraintEvaluationDetails(ConstraintEvaluationResult result, List<double> scores)
{
var details = new List<string>
{
$"约束评分:{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
/// <summary>
/// 检查时间可用性
/// </summary>
private async Task<TimeAvailabilityResult> 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}"
};
}
}
/// <summary>
/// 检查工作限制
/// </summary>
private async Task<WorkLimitResult> 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<string>();
var scores = new List<double>();
// 检查连续工作天数限制
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"
};
}
}
/// <summary>
/// 检查班次规则
/// </summary>
private async Task<ShiftRuleResult> 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<string>();
var scores = new List<double>();
var criticalViolations = new List<string>();
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 ?????
/// <summary>
/// 验证人员资质
/// </summary>
private async Task<NPP.SmartSchedue.Api.Contracts.Services.Integration.Output.ValidationResult> 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<ValidationViolation>()
};
}
var requiredQualifications = workOrder.ProcessEntity.QualificationRequirements.Split(',');
var qualificationMatch = await CheckQualificationMatchAsync(personnelId, requiredQualifications);
if (qualificationMatch.IsMatched)
{
return new ValidationResult
{
IsValid = true,
Violations = new List<ValidationViolation>()
};
}
return new ValidationResult
{
IsValid = false,
Violations = new List<ValidationViolation>
{
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<ValidationViolation>
{
new ValidationViolation
{
ViolationType = ViolationType.QualificationMismatch.ToString(),
Severity = ViolationSeverity.Critical,
Description = $"资质验证异常:{ex.Message}"
}
}
};
}
}
/// <summary>
/// 验证时间冲突
/// </summary>
private async Task<NPP.SmartSchedue.Api.Contracts.Services.Integration.Output.ValidationResult> 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<ValidationViolation>()
};
}
return new ValidationResult
{
IsValid = false,
Violations = new List<ValidationViolation>
{
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<ValidationViolation>
{
new ValidationViolation
{
ViolationType = ViolationType.TimeConflict.ToString(),
Severity = ViolationSeverity.Critical,
Description = $"时间冲突验证异常:{ex.Message}"
}
}
};
}
}
/// <summary>
/// 验证工作限制
/// </summary>
private async Task<NPP.SmartSchedue.Api.Contracts.Services.Integration.Output.ValidationResult> ValidateWorkLimitAsync(long personnelId, DateTime assignmentDate)
{
try
{
var workLimitResult = await CheckWorkLimitAsync(personnelId, assignmentDate);
if (workLimitResult.IsCompliant)
{
return new ValidationResult
{
IsValid = true,
Violations = new List<ValidationViolation>()
};
}
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<ValidationViolation>
{
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<ValidationViolation>
{
new ValidationViolation
{
ViolationType = ViolationType.WorkloadExceeded.ToString(),
Severity = ViolationSeverity.Critical,
Description = $"工作限制验证异常:{ex.Message}"
}
}
};
}
}
/// <summary>
/// 验证班次规则
/// </summary>
private async Task<NPP.SmartSchedue.Api.Contracts.Services.Integration.Output.ValidationResult> 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<ValidationViolation>()
};
}
var severity = shiftRuleResult.IsCritical ? ViolationSeverity.Critical : ViolationSeverity.Warning;
return new ValidationResult
{
IsValid = !shiftRuleResult.IsCritical,
Violations = new List<ValidationViolation>
{
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<ValidationViolation>
{
new ValidationViolation
{
ViolationType = ViolationType.ShiftRuleViolation.ToString(),
Severity = ViolationSeverity.Critical,
Description = $"班次规则验证异常:{ex.Message}"
}
}
};
}
}
#endregion
#region
/// <summary>
/// 验证个人班次规则
/// 深度业务思考:基于规则类型、参数和时间有效性进行全面验证
/// </summary>
private async Task<RuleValidationResult> 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<string, object>()), // 连续工作天数
"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<string, object>()) // 默认规则验证
};
// 记录规则验证详情
_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
/// <summary>
/// 验证指定人员优先规则
/// </summary>
private async Task<RuleValidationResult> 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 ? "" : "非指定人员,优先级稍低"
};
}
/// <summary>
/// 验证周任务限制规则
/// </summary>
private async Task<RuleValidationResult> 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})"
};
}
/// <summary>
/// 验证同一天内班次连续性规则
/// 深度业务思考:检查同一天内是否存在不合理的班次连续安排
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">当前工作日期</param>
/// <param name="shiftId">当前班次ID</param>
/// <param name="ruleType">规则类型3同天早中班连续性4同天中晚班连续性</param>
/// <returns>规则验证结果,包含合规状态、评分和详细说明</returns>
private async Task<RuleValidationResult> 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}"
};
}
}
/// <summary>
/// 验证连续工作天数规则
/// </summary>
private async Task<RuleValidationResult> ValidateContinuousWorkDaysRuleAsync(long personnelId,
DateTime workDate, Dictionary<string, object> 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})"
};
}
/// <summary>
/// 验证跨周末班次连续性规则
/// 深度业务思考:检查跨周末时期是否存在违反休息间隔的班次连续安排
/// 规则5不能这周日/下周六连续工作
/// 规则6不能本周六/本周日连续工作
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">当前工作日期</param>
/// <param name="shiftId">当前班次ID</param>
/// <param name="ruleType">规则类型5周日/下周六禁止连续6周六/周日禁止连续)</param>
/// <returns>规则验证结果,包含合规状态、评分和详细说明</returns>
private async Task<RuleValidationResult> 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}"
};
}
}
/// <summary>
/// 验证项目FL优先分配规则
/// 【深度业务思考】:确保项目专业性和工作效率的关键规则
/// 业务背景:
/// - FLFront Line一线人员是项目的核心执行者
/// - 本项目FL具有专业知识和操作经验优势
/// - 优先分配本项目FL有助于提高工作质量和效率
/// - 降低项目间人员切换的学习成本和沟通成本
///
/// 规则10业务逻辑
/// - 检查人员是否为当前工作任务所属项目的FL成员
/// - 本项目FL获得优先分配机会高评分
/// - 非本项目FL仍可分配但评分较低
/// - 确保项目专业性与人员灵活调配的平衡
///
/// 【边界情况深度分析】:
/// 1. 跨项目FL人员同时是多个项目的FL按关联度评分
/// 2. 项目FL不足当本项目FL都不可用时允许跨项目分配
/// 3. 新项目场景项目初期可能还没有指定FL采用宽松策略
/// 4. FL资质变更人员FL资格变更后的历史数据处理
/// 5. 项目暂停/结束项目状态变更对FL优先级的影响
/// 6. 紧急任务紧急情况下可适当放宽FL限制
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">工作日期</param>
/// <param name="shiftId">班次ID</param>
/// <param name="rule">规则实体(包含规则参数配置)</param>
/// <returns>规则验证结果,包含优先级评分和详细说明</returns>
private async Task<RuleValidationResult> 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}"
};
}
}
/// <summary>
/// 默认规则验证
/// </summary>
private async Task<RuleValidationResult> ValidateDefaultRuleAsync(long personnelId, DateTime workDate,
ShiftRuleEntity rule, Dictionary<string, object> ruleParams)
{
// 对于未识别的规则类型,进行基础验证
_logger.LogWarning("未识别的规则类型:{RuleType},规则名称:{RuleName}", rule.RuleType, rule.RuleName);
return new RuleValidationResult
{
IsValid = true,
ComplianceScore = 90, // 给予较高分,但不是满分
IsCritical = false,
ViolationMessage = $"未识别的规则类型({rule.RuleType}),采用默认验证"
};
}
#region
/// <summary>
/// 检查指定人员在同一天是否已有相同班次的任务分配
/// 业务逻辑:防止同一人员在同一天被分配多个相同班次的任务
/// 深度思考:考虑任务状态、班次类型、数据一致性等因素
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">工作日期</param>
/// <param name="shiftId">班次ID</param>
/// <returns>true表示存在重复班次分配false表示无重复</returns>
private async Task<bool> 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;
}
}
/// <summary>
/// 线程安全的班次编号缓存锁 - 与GlobalPersonnelAllocationService共享
/// 【并发安全】防止多个服务同时访问ShiftService导致DbContext冲突
/// </summary>
private static readonly SemaphoreSlim _shiftCacheLock = new SemaphoreSlim(1, 1);
/// <summary>
/// 班次编号缓存 - 与GlobalPersonnelAllocationService共享缓存
/// 【性能+安全】:跨服务共享班次缓存,避免重复查询和并发冲突
/// </summary>
private static readonly Dictionary<long, int?> _shiftNumberCache = new Dictionary<long, int?>();
/// <summary>
/// 根据班次ID获取班次编号 - 线程安全版本
/// 业务逻辑将班次ID转换为班次编号用于班次连续性规则计算
/// 【关键修复】使用SemaphoreSlim确保线程安全避免FreeSql DbContext并发冲突
/// 深度思考:处理班次不存在、数据异常、并发访问等所有边界情况
/// </summary>
/// <param name="shiftId">班次ID</param>
/// <returns>班次编号1,2,3等null表示班次不存在或异常</returns>
private async Task<int?> 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();
}
}
/// <summary>
/// 获取人员在指定日期的所有班次编号
/// 业务逻辑:查询人员在特定日期的所有已分配班次编号,用于班次冲突检查
/// 深度思考:处理多班次分配、任务状态过滤、数据完整性等复杂场景
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="date">查询日期</param>
/// <returns>班次编号列表,空列表表示该日期无班次分配</returns>
private async Task<List<int>> 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<int>();
}
}
/// <summary>
/// 创建验证通过结果的辅助方法
/// </summary>
private RuleValidationResult CreateValidResult(string reason, double score = 100)
{
return new RuleValidationResult
{
IsValid = true,
ComplianceScore = score,
IsCritical = false,
ViolationMessage = ""
};
}
/// <summary>
/// 生成同天班次违规详细信息
/// 业务逻辑:为同天班次连续性违规生成详细的错误说明
/// </summary>
/// <param name="ruleType">规则类型</param>
/// <param name="existingShift">已存在的班次编号</param>
/// <param name="currentShift">当前要分配的班次编号</param>
/// <returns>详细的违规说明</returns>
private string GenerateSameDayViolationDetail(int ruleType, int existingShift, int currentShift)
{
return ruleType switch
{
3 => $"同天早中班连续性违规:违反早中班同天禁止规则",
4 => $"同天中夜班连续性违规:违反中夜班同天禁止规则",
_ => $"同天班次连续性违规:规则类型{ruleType},已有班次{existingShift}与当前班次{currentShift}冲突"
};
}
/// <summary>
/// 生成同天班次验证成功信息
/// 业务逻辑:为同天班次连续性验证通过生成详细的成功说明
/// </summary>
/// <param name="ruleType">规则类型</param>
/// <param name="currentShift">当前班次编号</param>
/// <returns>成功验证的详细说明</returns>
private string GenerateSameDaySuccessMessage(int ruleType, int currentShift)
{
return ruleType switch
{
3 => $"同天早中班连续性验证通过:符合早中班同天禁止规则",
4 => $"同天中夜班连续性验证通过:符合中夜班同天禁止规则",
_ => $"同天班次连续性验证通过:规则类型{ruleType},班次{currentShift}符合要求"
};
}
/// <summary>
/// 计算跨周末规则验证的相关日期
/// 业务逻辑:根据规则类型和当前工作日期,计算需要检查的两个关键日期
/// 深度思考:考虑周历边界、时区、月份跨越等复杂场景
/// </summary>
/// <param name="workDate">当前工作日期</param>
/// <param name="ruleType">规则类型5周日/下周六6周六/周日)</param>
/// <returns>需要检查的两个日期(第一日期,第二日期)</returns>
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); // 异常情况返回相同日期
}
}
/// <summary>
/// 生成跨周末班次违规详细信息
/// 业务逻辑:为跨周末班次连续性违规生成详细的错误说明
/// </summary>
/// <param name="ruleType">规则类型</param>
/// <param name="firstDate">第一个日期</param>
/// <param name="secondDate">第二个日期</param>
/// <param name="firstDateShifts">第一个日期的班次编号列表</param>
/// <param name="secondDateShifts">第二个日期的班次编号列表</param>
/// <returns>详细的违规说明</returns>
private string GenerateCrossWeekendViolationDetail(int ruleType, DateTime firstDate, DateTime secondDate,
List<int> firstDateShifts, List<int> 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})存在班次冲突"
};
}
/// <summary>
/// 生成跨周末班次验证成功信息
/// 业务逻辑:为跨周末班次连续性验证通过生成详细的成功说明
/// </summary>
/// <param name="ruleType">规则类型</param>
/// <param name="firstDate">第一个日期</param>
/// <param name="secondDate">第二个日期</param>
/// <returns>成功验证的详细说明</returns>
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}符合要求"
};
}
/// <summary>
/// 验证次日休息规则
/// 【深度业务思考】:该规则确保员工在高强度班次(二班/三班)工作后获得充分的休息时间
/// 业务背景:
/// - 二班(中班)通常是下午14:00-22:00工作强度较大
/// - 三班(夜班)通常是晚上22:00-次日6:00对身体负担最重
/// - 劳动保护要求夜班工作后必须有24小时的休息时间
/// - 生产效率考虑:疲劳工作会导致操作失误和安全隐患
///
/// 规则8三班后一天不排班 - 检查前一天是否有三班(班次编号3),如有则当天不能排班
/// 规则9二班后一天不排班 - 检查前一天是否有二班(班次编号2),如有则当天不能排班
///
/// 【边界情况深度分析】:
/// 1. 跨周末场景:周六三班→周日必须休息
/// 2. 跨月场景:月末最后一天三班→次月第一天必须休息
/// 3. 节假日场景:节假日前的二班/三班→节假日当天必须休息
/// 4. 紧急任务场景:即使有紧急任务,也不能违反休息规则
/// 5. 多班次冲突:前一天有多个班次时,只要包含目标班次就触发规则
/// 6. 任务状态过滤:只考虑已确认和执行中的任务,草稿状态不算
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">当前工作日期(即将分配班次的日期)</param>
/// <param name="shiftId">当前班次ID即将分配的班次</param>
/// <param name="ruleType">规则类型8三班后次日休息9二班后次日休息</param>
/// <returns>规则验证结果,包含合规状态、评分和详细说明</returns>
private async Task<RuleValidationResult> 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}"
};
}
}
/// <summary>
/// 生成次日休息规则违规详细信息
/// 【业务逻辑深度思考】:生成详细的违规说明,帮助管理人员理解违规原因和影响
/// 错误信息设计原则:
/// 1. 明确指出违规的具体规则
/// 2. 详细说明前一天的班次情况
/// 3. 明确当前试图分配的班次信息
/// 4. 提供易于理解的业务背景说明
/// </summary>
/// <param name="ruleType">规则类型8三班后休息9二班后休息</param>
/// <param name="previousDate">前一天日期</param>
/// <param name="currentDate">当前日期(即将分配班次的日期)</param>
/// <param name="targetShiftNumber">触发规则的班次编号2或3</param>
/// <param name="previousDayShifts">前一天的所有班次编号列表</param>
/// <param name="currentShiftName">当前试图分配的班次名称</param>
/// <returns>详细的违规说明,包含业务上下文和建议</returns>
private string GenerateNextDayRestViolationDetail(int ruleType, DateTime previousDate, DateTime currentDate,
int targetShiftNumber, List<int> 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},违反休息规则"
};
}
/// <summary>
/// 生成次日休息规则验证成功信息
/// 【业务逻辑思考】:为次日休息规则验证通过生成详细的成功说明
/// 成功信息的价值:
/// 1. 确认规则检查已正确执行
/// 2. 记录验证的具体条件和结果
/// 3. 为系统审计提供完整的决策轨迹
/// </summary>
/// <param name="ruleType">规则类型8三班后休息9二班后休息</param>
/// <param name="previousDate">前一天日期</param>
/// <param name="targetShiftNumber">目标班次编号2或3</param>
/// <param name="hadTargetShift">前一天是否有目标班次当前总是false为扩展保留</param>
/// <returns>成功验证的详细说明,包含验证条件和结论</returns>
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},符合规则要求"
};
}
/// <summary>
/// 获取班次显示名称
/// 业务逻辑:将班次编号转换为易读的显示名称
/// </summary>
/// <param name="shiftNumber">班次编号</param>
/// <returns>班次显示名称</returns>
private string GetShiftDisplayName(int shiftNumber)
{
return shiftNumber switch
{
1 => "早班",
2 => "中班",
3 => "晚班",
_ => $"{shiftNumber}班"
};
}
/// <summary>
/// 获取日期的显示名称
/// 业务逻辑:根据规则类型和日期位置生成易读的日期描述
/// </summary>
/// <param name="date">日期</param>
/// <param name="ruleType">规则类型</param>
/// <param name="isFirst">是否为第一个日期</param>
/// <returns>日期显示名称</returns>
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
/// <summary>
/// 计算优化评分
/// </summary>
private async Task<OptimizationScoreResult> CalculateOptimizationScoreAsync(PersonnelCandidate candidate,
WorkOrderEntity workOrder, AllocationContext context)
{
try
{
var result = new OptimizationScoreResult
{
Candidate = candidate,
DimensionScores = new Dictionary<string, double>(),
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}"
};
}
}
/// <summary>
/// 计算项目FL评分
/// </summary>
private async Task<double> 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;
}
}
/// <summary>
/// 计算工作负载均衡评分
/// </summary>
private async Task<double> CalculateWorkloadBalanceScoreAsync(long personnelId)
{
try
{
var dateRange = new DateRange
{
StartDate = DateTime.Today,
EndDate = DateTime.Today.AddDays(30)
};
var workloadAnalysis = await AnalyzePersonnelWorkloadAsync(new List<long> { 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; // 异常时返回中等评分
}
}
/// <summary>
/// 计算班次连续性评分
/// </summary>
private async Task<double> 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; // 异常时返回中等评分
}
}
/// <summary>
/// 计算协作历史评分
/// </summary>
private async Task<double> 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
/// <summary>
/// 计算综合总分
/// </summary>
private double CalculateTotalScore(PersonnelCandidate candidate, Dictionary<string, double> weights)
{
var scores = new Dictionary<string, double>
{
["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);
}
/// <summary>
/// 计算连续工作天数
/// </summary>
private int CalculateContinuousWorkDays(List<DateTime> 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;
}
/// <summary>
/// 异步计算连续工作天数
/// </summary>
private async Task<int> 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;
}
}
/// <summary>
/// 计算周班次数
/// </summary>
private async Task<int> 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;
}
}
/// <summary>
/// 计算月工作小时数
/// </summary>
private async Task<decimal> 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
/// <summary>
/// 计算置信度
/// </summary>
private double CalculateConfidence(Dictionary<string, double> 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));
}
/// <summary>
/// 确定推荐等级
/// </summary>
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
};
}
/// <summary>
/// 生成优化理由
/// </summary>
private string GenerateOptimizationReason(Dictionary<string, double> dimensionScores,
Dictionary<string, double> weights)
{
var reasons = new List<string>();
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)}" : "综合评分一般";
}
/// <summary>
/// 计算风险评估
/// </summary>
private async Task<RiskAssessment> CalculateRiskAssessmentAsync(PersonnelCandidate candidate,
WorkOrderEntity workOrder, OptimizationScoreResult scoreResult)
{
var riskAssessment = new RiskAssessment
{
RiskFactors = new List<RiskFactor>()
};
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}"
};
}
}
/// <summary>
/// 生成风险描述
/// </summary>
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}个中低风险因子,建议采取预防措施";
}
/// <summary>
/// 生成缓解建议
/// </summary>
private List<string> GenerateMitigationSuggestions(List<RiskFactor> riskFactors)
{
var suggestions = new List<string>();
foreach (var riskFactor in riskFactors)
{
var suggestion = riskFactor.RiskType switch
{
"约束合规风险" => "建议调整任务时间或寻找替代人员",
"资质匹配风险" => "建议提供必要的培训或技术支持",
"工作负载风险" => "建议减少该人员的其他任务或增加协助人员",
_ => "建议进一步评估和监控"
};
suggestions.Add(suggestion);
}
return suggestions.Distinct().ToList();
}
#endregion
#region
/// <summary>
/// 计算验证评分
/// </summary>
private double CalculateValidationScore(List<ValidationViolation> violations,
List<NPP.SmartSchedue.Api.Contracts.Services.Integration.Models.ValidationWarning> 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);
}
/// <summary>
/// 生成验证报告
/// </summary>
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;
}
/// <summary>
/// 根据策略排序候选人员
/// </summary>
private List<PersonnelCandidate> SortCandidatesByStrategy(List<PersonnelCandidate> candidates,
PersonnelAllocationStrategy strategy)
{
return strategy switch
{
_ => candidates.OrderByDescending(c => c.TotalScore).ToList()
};
}
/// <summary>
/// 计算推荐置信度
/// </summary>
private double CalculateRecommendationConfidence(IEnumerable<PersonnelCandidate> 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
/// <summary>
/// 创建失败结果
/// </summary>
private PersonnelAllocationResult CreateFailureResult(string errorMessage)
{
return new PersonnelAllocationResult
{
SuccessfulMatches = new List<TaskPersonnelMatch>(),
FailedAllocations = new List<FailedPersonnelAllocation>(),
WorkloadAnalysis = new List<PersonnelWorkloadAnalysis>(),
FairnessScore = 0,
AllocationSummary = $"分配失败:{errorMessage}",
ProcessingDetails = errorMessage,
AllocationStrategy = "Failed",
ValidationResult = new
{
OverallScore = 0,
ProcessingMetrics = new Dictionary<string, object>(),
ValidationPassed = false,
ErrorMessage = errorMessage
}
};
}
/// <summary>
/// 创建部分失败结果
/// </summary>
private PersonnelAllocationResult CreatePartialFailureResult(AllocationContext context, string errorMessage)
{
var result = new PersonnelAllocationResult
{
SuccessfulMatches = new List<TaskPersonnelMatch>(),
FailedAllocations = new List<FailedPersonnelAllocation>(),
WorkloadAnalysis = new List<PersonnelWorkloadAnalysis>(),
FairnessScore = 0,
AllocationSummary = $"部分失败:{errorMessage}",
ProcessingDetails = errorMessage,
AllocationStrategy = context.Strategy.ToString(),
ValidationResult = new
{
OverallScore = 0,
ProcessingMetrics = context.Metrics,
ValidationPassed = false,
ErrorMessage = errorMessage
}
};
return result;
}
/// <summary>
/// 创建空结果
/// </summary>
private PersonnelAllocationResult CreateEmptyResult()
{
return new PersonnelAllocationResult
{
SuccessfulMatches = new List<TaskPersonnelMatch>(),
FailedAllocations = new List<FailedPersonnelAllocation>(),
WorkloadAnalysis = new List<PersonnelWorkloadAnalysis>(),
FairnessScore = 100, // 没有分配时公平性为满分
AllocationSummary = "无任务需要分配",
ProcessingDetails = "输入的任务列表为空",
AllocationStrategy = "Empty",
ValidationResult = new
{
OverallScore = 100,
ProcessingMetrics = new Dictionary<string, object>(),
ValidationPassed = true,
ErrorMessage = ""
}
};
}
/// <summary>
/// 计算整体质量评分
/// </summary>
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);
}
/// <summary>
/// 生成分配摘要
/// </summary>
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}";
}
/// <summary>
/// 生成工作负载分析
/// </summary>
private async Task<List<PersonnelWorkloadAnalysis>> GenerateWorkloadAnalysisAsync(
Dictionary<long, PersonnelCandidate> successfulAllocations)
{
var workloadAnalysis = new List<PersonnelWorkloadAnalysis>();
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();
}
/// <summary>
/// 计算公平性评分
/// </summary>
private int CalculateFairnessScore(List<PersonnelWorkloadAnalysis> 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);
}
/// <summary>
/// 计算预估效率
/// </summary>
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
/// <summary>
/// 人员状态检查结果
/// </summary>
private class PersonnelStatusCheck
{
public bool IsActive { get; set; }
public string Reason { get; set; }
public string StatusCode { get; set; }
}
/// <summary>
/// 项目授权检查结果
/// </summary>
private class ProjectAuthorizationCheck
{
public bool IsAuthorized { get; set; }
public double AuthorizationScore { get; set; }
public string Reason { get; set; }
public string AuthorizationLevel { get; set; }
}
/// <summary>
/// 设备可用性检查结果
/// </summary>
private class EquipmentAvailabilityCheck
{
public bool IsAvailable { get; set; }
public double AvailabilityScore { get; set; }
public string Reason { get; set; }
public int ConflictCount { get; set; }
}
/// <summary>
/// 地理位置约束检查结果
/// </summary>
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; }
}
/// <summary>
/// 规则验证结果
/// </summary>
private class RuleValidationResult
{
public bool IsValid { get; set; }
public double ComplianceScore { get; set; }
public bool IsCritical { get; set; }
public string ViolationMessage { get; set; }
}
/// <summary>
/// 时间可用性结果
/// </summary>
private class TimeAvailabilityResult
{
public bool IsAvailable { get; set; }
public double Score { get; set; }
public string Reason { get; set; }
}
/// <summary>
/// 工作限制结果
/// </summary>
private class WorkLimitResult
{
public bool IsCompliant { get; set; }
public double Score { get; set; }
public string Reason { get; set; }
public string Severity { get; set; }
}
/// <summary>
/// 班次规则结果
/// </summary>
private class ShiftRuleResult
{
public bool IsCompliant { get; set; }
public double Score { get; set; }
public string Reason { get; set; }
public bool IsCritical { get; set; }
}
/// <summary>
/// 班次时间冲突结果
/// </summary>
private class ShiftTimeConflictResult
{
public bool HasConflict { get; set; }
public string ConflictDescription { get; set; }
}
/// <summary>
/// 资质匹配结果
/// </summary>
private class QualificationMatchResult
{
public bool IsMatched { get; set; }
public bool IsValid { get; set; }
public double MatchScore { get; set; }
public List<string> MatchedQualifications { get; set; } = new();
public List<string> MissingQualifications { get; set; } = new();
public string Details { get; set; }
}
/// <summary>
/// 输入验证结果
/// </summary>
private class InputValidationResult
{
public bool IsValid { get; set; }
public string ErrorMessage { get; set; }
public List<WorkOrderEntity> WorkOrders { get; set; }
}
/// <summary>
/// 人员基础资格检查结果
/// </summary>
private class PersonnelEligibilityResult
{
public long PersonnelId { get; set; }
public bool IsEligible { get; set; }
public List<string> EligibilityReasons { get; set; } = new();
}
#region 10FL优先分配相关辅助方法
/// <summary>
/// 从当前分配上下文中获取工作任务信息
/// 【业务逻辑】:在规则验证过程中获取当前正在分配的工作任务
/// 深度思考:考虑多种获取任务信息的方式,确保数据准确性
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="workDate">工作日期</param>
/// <param name="shiftId">班次ID</param>
/// <returns>当前工作任务实体如果获取失败返回null</returns>
private async Task<WorkOrderEntity> 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;
}
}
/// <summary>
/// 检查人员是否为指定项目的FL成员
/// 【业务逻辑】查询WorkOrderFLPersonnelEntity表确定人员与项目的FL关联关系
/// 深度思考考虑FL资格的时效性、状态有效性等因素
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="projectNumber">项目号</param>
/// <returns>true表示是该项目的FLfalse表示不是</returns>
private async Task<bool> CheckPersonnelIsProjectFLAsync(long personnelId, string projectNumber)
{
try
{
// 查询该人员在指定项目中的FL记录
var flRecords = await _workOrderRepository.Orm.Select<WorkOrderFLPersonnelEntity>()
.From<WorkOrderEntity>()
.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;
}
}
/// <summary>
/// 获取人员所有项目FL关联情况
/// 【业务逻辑】查询人员参与的所有项目FL记录用于综合评分
/// 深度思考:考虑项目活跃度、历史记录权重等因素
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <returns>人员所有项目FL关联列表</returns>
private async Task<List<ProjectFLInfo>> GetPersonnelAllProjectFLsAsync(long personnelId)
{
try
{
// 查询该人员的所有项目FL记录并统计每个项目的任务数量
var projectFLRecords = await _workOrderRepository.Orm.Select<WorkOrderFLPersonnelEntity>()
.From<WorkOrderEntity>()
.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<ProjectFLInfo>();
}
}
/// <summary>
/// 计算项目FL优先级评分
/// 【业务逻辑】基于FL关联情况进行多维度评分
/// 评分策略:
/// - 本项目FL100分最高优先级
/// - 有其他项目FL经验85分有经验但需要适应
/// - 无FL经验70分可以培养但优先级较低
/// - 多项目FL95分经验丰富适应性强
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="currentProjectNumber">当前项目号</param>
/// <param name="isProjectFL">是否为当前项目FL</param>
/// <param name="allProjectFLs">所有项目FL关联情况</param>
/// <param name="rule">规则实体(可能包含评分权重配置)</param>
/// <returns>评分结果,包含分数和详细原因</returns>
private ProjectFLScoringResult CalculateProjectFLPriorityScore(long personnelId, string currentProjectNumber,
bool isProjectFL, List<ProjectFLInfo> 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}"
};
}
}
/// <summary>
/// 生成项目FL验证信息
/// 【业务逻辑】:生成详细的验证结果说明,帮助理解分配决策
/// </summary>
/// <param name="personnelId">人员ID</param>
/// <param name="projectNumber">项目号</param>
/// <param name="isProjectFL">是否为项目FL</param>
/// <param name="score">评分</param>
/// <param name="reason">评分原因</param>
/// <returns>详细的验证信息</returns>
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)}";
}
/// <summary>
/// 获取分配建议
/// 【业务逻辑】:基于评分提供分配建议
/// </summary>
private string GetAllocationRecommendation(double score, bool isProjectFL)
{
if (isProjectFL)
{
return "强烈推荐分配本项目FL具有最佳匹配度";
}
else if (score >= 85)
{
return "推荐分配,有相关项目经验,适应性较强";
}
else if (score >= 75)
{
return "可以分配,需要适当培训和指导";
}
else
{
return "谨慎分配,建议优先考虑其他候选人";
}
}
/// <summary>
/// 项目FL信息
/// </summary>
private class ProjectFLInfo
{
public string ProjectNumber { get; set; }
public int TaskCount { get; set; }
public DateTime LatestAssignmentDate { get; set; }
}
/// <summary>
/// 项目FL评分结果
/// </summary>
private class ProjectFLScoringResult
{
public double Score { get; set; }
public string Reason { get; set; }
}
#endregion
#endregion
}
}