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

1097 lines
50 KiB
C#
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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.Core.Enums;
using NPP.SmartSchedue.Api.Contracts.Domain.Work;
using NPP.SmartSchedue.Api.Repositories.Work;
namespace NPP.SmartSchedue.Api.Services.Integration
{
/// <summary>
/// 智能调度编排服务
/// 职责:协调各个专业服务,实现智能任务整合的核心流程编排
/// 架构思考:采用编排器模式,专注于流程控制而非具体业务逻辑实现
/// </summary>
[DynamicApi(Area = "app")]
public class SmartScheduleOrchestratorService : BaseService, ISmartScheduleOrchestratorService, IDynamicApi
{
private readonly WorkOrderRepository _workOrderRepository;
private readonly IGlobalPersonnelAllocationService _globalPersonnelAllocationService;
private readonly IEquipmentAllocationService _equipmentAllocationService;
private readonly IIntegrationRecordService _integrationRecordService;
private readonly ITaskValidationService _taskValidationService;
private readonly ILogger<SmartScheduleOrchestratorService> _logger;
public SmartScheduleOrchestratorService(
WorkOrderRepository workOrderRepository,
IGlobalPersonnelAllocationService globalPersonnelAllocationService,
IEquipmentAllocationService equipmentAllocationService,
IIntegrationRecordService integrationRecordService,
ITaskValidationService taskValidationService,
ILogger<SmartScheduleOrchestratorService> logger)
{
_workOrderRepository = workOrderRepository;
_globalPersonnelAllocationService = globalPersonnelAllocationService;
_equipmentAllocationService = equipmentAllocationService;
_integrationRecordService = integrationRecordService;
_taskValidationService = taskValidationService;
_logger = logger;
}
/// <summary>
/// 执行智能任务整合主流程
/// 深度业务思考:编排器负责协调各服务,确保流程的一致性和完整性
/// 架构优化:增加分组处理、降级策略和智能重试机制
/// </summary>
[HttpPost]
public async Task<SmartIntegrationResult> ExecuteSmartIntegrationAsync(SmartIntegrationInput input)
{
var stopwatch = Stopwatch.StartNew();
var batchCode = GenerateIntegrationBatchCode();
_logger.LogInformation("==================== 开始执行智能任务整合主流程 ====================");
_logger.LogInformation("🎯 整合批次号: {BatchCode}", batchCode);
_logger.LogInformation("📊 整合策略: 人员={PersonnelStrategy}, 设备={EquipmentStrategy}, 强制班次规则={EnforceShiftRules}",
input.Strategy?.PersonnelStrategy, input.Strategy?.EquipmentStrategy, input.Strategy?.EnforceShiftRules);
_logger.LogInformation("👤 操作员: {OperatorName} (ID: {OperatorUserId})", input.OperatorName, input.OperatorUserId);
var result = new SmartIntegrationResult
{
IntegrationBatchCode = batchCode,
CompletionTime = DateTime.Now
};
try
{
// 0. 获取待整合的任务列表
_logger.LogInformation("🔍 步骤0: 开始获取待整合的任务列表...");
var taskStopwatch = Stopwatch.StartNew();
var tasksToIntegrate = await GetTasksForIntegrationAsync(input);
taskStopwatch.Stop();
_logger.LogInformation("✅ 步骤0完成: 获取到 {TaskCount} 个待整合任务,耗时 {ElapsedMs}ms",
tasksToIntegrate.Count, taskStopwatch.ElapsedMilliseconds);
if (!tasksToIntegrate.Any())
{
_logger.LogWarning("⚠️ 未找到需要整合的任务,流程结束");
result.IsSuccess = false;
result.IntegrationDetails = "未找到需要整合的任务";
return result;
}
// 打印任务详细信息和日期分析
_logger.LogInformation("📋 待整合任务详情:");
var tasksByDate = GroupTasksByDate(tasksToIntegrate);
foreach (var task in tasksToIntegrate)
{
var hasPreAssigned = task.AssignedPersonnelId.HasValue && task.AssignedPersonnelId.Value > 0;
_logger.LogInformation(" 任务ID: {TaskId}, 编码: {TaskCode}, 状态: {Status}, 日期: {WorkDate}, 预分配: {PreAssigned}",
task.Id, task.WorkOrderCode, task.Status, task.WorkOrderDate.ToString("yyyy-MM-dd"),
hasPreAssigned ? $"人员{task.AssignedPersonnelId}" : "无");
}
_logger.LogInformation("📅 任务日期分组: {DateGroups} 个日期组,跨度 {DateSpan} 天",
tasksByDate.Count,
tasksByDate.Keys.Any() ? (tasksByDate.Keys.Max() - tasksByDate.Keys.Min()).Days + 1 : 0);
// 1. 任务需求汇总与验证
_logger.LogInformation("🔍 步骤1: 开始任务需求汇总与验证...");
var validationStopwatch = Stopwatch.StartNew();
_logger.LogInformation(" 1.1 分析任务需求...");
await _taskValidationService.AnalyzeTaskRequirementsAsync(tasksToIntegrate);
_logger.LogInformation(" 1.2 验证任务数据完整性...");
await _taskValidationService.ValidateTaskDataIntegrityAsync(tasksToIntegrate);
_logger.LogInformation(" 1.3 加载任务详细信息...");
await _taskValidationService.LoadTaskDetailedInfoAsync(tasksToIntegrate);
validationStopwatch.Stop();
_logger.LogInformation("✅ 步骤1完成: 任务验证完成,耗时 {ElapsedMs}ms", validationStopwatch.ElapsedMilliseconds);
// 2. 执行智能人员分配 - 分时段处理优化
_logger.LogInformation("🔍 步骤2: 开始执行智能人员分配...");
var personnelStopwatch = Stopwatch.StartNew();
// 检查是否需要分时段处理
bool useTimeSegmentedAllocation = tasksByDate.Count > 1 && !input.Strategy.EnforceShiftRules;
if (useTimeSegmentedAllocation)
{
_logger.LogInformation(" 2.1 采用分时段人员分配策略处理 {DateCount} 个日期组", tasksByDate.Count);
result.PersonnelAllocation = await ExecuteTimeSegmentedPersonnelAllocationAsync(
tasksByDate, input.Strategy, input.OperatorUserId, input.OperatorName);
}
else
{
_logger.LogInformation(" 2.1 采用统一人员分配策略: {Strategy}, 强制班次规则: {EnforceShiftRules}",
input.Strategy.PersonnelStrategy, input.Strategy.EnforceShiftRules);
var globalAllocationInput = new GlobalAllocationInput
{
Tasks = tasksToIntegrate,
ExcludedPersonnelIds = new List<long>()
};
var globalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(globalAllocationInput);
result.PersonnelAllocation = ConvertToPersonnelAllocationResult(globalResult);
}
personnelStopwatch.Stop();
_logger.LogInformation("✅ 步骤2完成: 人员分配完成,成功 {SuccessCount} 个,失败 {FailedCount} 个,耗时 {ElapsedMs}ms",
result.PersonnelAllocation?.SuccessfulMatches?.Count ?? 0,
result.PersonnelAllocation?.FailedTasks?.Count ?? 0,
personnelStopwatch.ElapsedMilliseconds);
// 2.2 智能重试机制:如果失败任务过多,尝试降级策略
if (result.PersonnelAllocation?.FailedTasks?.Count > 0)
{
var failedCount = result.PersonnelAllocation.FailedTasks.Count;
var totalCount = tasksToIntegrate.Count;
var failureRate = (double)failedCount / totalCount;
if (failureRate > 0.3) // 失败率超过30%时启动降级重试
{
_logger.LogWarning("⚠️ 人员分配失败率 {FailureRate:P1},启动降级重试策略...", failureRate);
await ExecuteFallbackPersonnelAllocationAsync(result, tasksToIntegrate, input.Strategy);
}
}
// 3. 执行智能设备分配
_logger.LogInformation("🔍 步骤3: 开始执行智能设备分配...");
var equipmentStopwatch = Stopwatch.StartNew();
// 深度架构优化直接传入Tasks对象避免重复数据库查询
var equipmentAllocationInput = new EquipmentAllocationInput
{
Tasks = tasksToIntegrate, // 性能优化直接使用已加载的task对象
TaskIds = tasksToIntegrate.Select(t => t.Id).ToList(), // 保持兼容性
Strategy = input.Strategy.EquipmentStrategy,
TargetUtilization = input.Strategy.TargetEquipmentUtilization,
ExcludedEquipmentIds = new List<long>(),
PreferredEquipmentIds = new List<long>()
};
_logger.LogInformation(" 3.1 设备分配策略: {Strategy}, 目标利用率: {TargetUtilization}%",
input.Strategy.EquipmentStrategy, input.Strategy.TargetEquipmentUtilization);
result.EquipmentAllocation =
await _equipmentAllocationService.AllocateEquipmentSmartlyAsync(equipmentAllocationInput);
equipmentStopwatch.Stop();
_logger.LogInformation("✅ 步骤3完成: 设备分配完成,成功 {SuccessCount} 个,失败 {FailedCount} 个,耗时 {ElapsedMs}ms",
result.EquipmentAllocation?.SuccessfulMatches?.Count ?? 0,
result.EquipmentAllocation?.FailedAllocations.Count ?? 0,
equipmentStopwatch.ElapsedMilliseconds);
// 4. 更新任务状态
await UpdateTaskStatusAsync(tasksToIntegrate, result);
// 5. 生成整合记录
var integrationRecordInput = new IntegrationRecordInput
{
TaskIds = tasksToIntegrate.Select(t => t.Id).ToList(),
PersonnelAllocation = result.PersonnelAllocation,
EquipmentAllocation = result.EquipmentAllocation,
Strategy = input.Strategy,
OperatorUserId = input.OperatorUserId,
OperatorName = input.OperatorName,
Remarks = input.Remarks
};
var integrationRecord =
await _integrationRecordService.GenerateIntegrationRecordAsync(integrationRecordInput);
result.IntegrationRecordId = integrationRecord.Id;
result.IntegrationBatchCode = integrationRecord.IntegrationBatchCode;
// 6. 最终结果汇总统计
GenerateFinalResultsSummary(result, tasksToIntegrate);
}
catch (Exception ex)
{
_logger.LogError(ex, "💥 智能整合过程中发生严重异常: {ExceptionMessage}", ex.Message);
result.IsSuccess = false;
result.IntegrationDetails = $"智能整合过程中发生异常: {ex.Message}";
// 详细的异常分析日志
_logger.LogError("🔍 异常详细信息:");
_logger.LogError(" 异常类型: {ExceptionType}", ex.GetType().Name);
_logger.LogError(" 异常消息: {ExceptionMessage}", ex.Message);
_logger.LogError(" 堆栈跟踪: {StackTrace}", ex.StackTrace);
if (ex.InnerException != null)
{
_logger.LogError(" 内部异常: {InnerExceptionType} - {InnerExceptionMessage}",
ex.InnerException.GetType().Name, ex.InnerException.Message);
}
result.FailedTasks.Add(new FailedTaskInfo
{
TaskId = 0,
TaskCode = "系统错误",
FailureReason = $"系统异常: {ex.Message}",
FailureType = FailureType.SystemError,
ConflictDetails = new List<string>
{
$"异常类型: {ex.GetType().Name}",
$"发生时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}",
$"整合批次: {batchCode}"
}
});
}
stopwatch.Stop();
result.ElapsedMilliseconds = stopwatch.ElapsedMilliseconds;
return result;
}
#region
/// <summary>
/// 获取待整合的任务列表
/// </summary>
private async Task<List<WorkOrderEntity>> GetTasksForIntegrationAsync(SmartIntegrationInput input)
{
var query = _workOrderRepository.Select
.Where(w => w.Status == (int)WorkOrderStatusEnum.PendingIntegration)
.Include(w => w.ProcessEntity)
.IncludeMany(w => w.WorkOrderFLPersonnels);
if (input.SelectedTaskIds?.Any() == true)
{
query = query.Where(t => input.SelectedTaskIds.Contains(t.Id));
}
if (input.SelectedProjectNumbers?.Any() == true)
{
query = query.Where(t => input.SelectedProjectNumbers.Contains(t.ProjectNumber));
}
var allTasks = await query.ToListAsync();
var tasks = allTasks
.OrderBy(w => w.Priority)
.ThenBy(w => w.UrgencyLevel)
.ThenBy(w => w.ComplexityLevel)
.ThenBy(w => w.PlannedStartTime)
.ToList();
// 项目分组优化
return tasks
.GroupBy(t => t.ProjectNumber)
.OrderBy(g => g.Min(t => t.Priority))
.SelectMany(g => g.OrderBy(t => t.PlannedStartTime))
.ToList();
}
/// <summary>
/// 更新任务状态
/// </summary>
private async Task UpdateTaskStatusAsync(List<WorkOrderEntity> tasks, SmartIntegrationResult result)
{
var successfulTaskIds = result.PersonnelAllocation.SuccessfulMatches.Select(m => m.TaskId).ToHashSet();
foreach (var task in tasks.Where(t => successfulTaskIds.Contains(t.Id)))
{
task.Status = (int)WorkOrderStatusEnum.Assigned;
task.AssignedTime = DateTime.Now;
task.LastModifiedTime = DateTime.Now;
var personnelMatch = result.PersonnelAllocation.SuccessfulMatches
.FirstOrDefault(m => m.TaskId == task.Id);
var equipmentMatch = result.EquipmentAllocation.SuccessfulMatches
.FirstOrDefault(m => m.TaskId == task.Id);
var allocationNotes = new List<string>();
if (personnelMatch != null)
{
task.AssignedPersonnelId = personnelMatch.PersonnelId;
task.AssignedPersonnelName = personnelMatch.PersonnelName;
allocationNotes.Add($"分配人员: {personnelMatch.PersonnelName ?? ""} (评分: {personnelMatch.MatchScore:F1})");
}
if (equipmentMatch != null)
{
task.AssignedEquipmentId = equipmentMatch.EquipmentId;
task.AssignedEquipmentName = equipmentMatch.EquipmentName;
allocationNotes.Add($"分配设备: {equipmentMatch.EquipmentCode ?? equipmentMatch.EquipmentId.ToString()}");
}
allocationNotes.Add($"智能分配完成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
task.Remarks = string.Join("; ", allocationNotes);
}
// 处理失败的任务
var failedTasks = tasks.Where(t => !successfulTaskIds.Contains(t.Id)).ToList();
foreach (var failedTask in failedTasks)
{
failedTask.Status = (int)WorkOrderStatusEnum.PendingIntegration;
failedTask.Remarks = $"分配失败,等待重新分配 - {DateTime.Now:yyyy-MM-dd HH:mm}";
}
if (tasks.Any())
{
await _workOrderRepository.UpdateAsync(tasks);
}
}
/// <summary>
/// 最终结果汇总统计
/// 优化:提供更详细的分析报告和改进建议
/// </summary>
private void GenerateFinalResultsSummary(SmartIntegrationResult result, List<WorkOrderEntity> tasksToIntegrate)
{
var successCount = result.PersonnelAllocation?.SuccessfulMatches?.Count ?? 0;
var totalTasks = tasksToIntegrate.Count;
result.SuccessfulTaskCount = successCount;
result.OverallSuccessRate = totalTasks > 0 ? (double)successCount / totalTasks : 0;
result.FailedTasks = CollectFailedTasksComprehensive(result);
var fairnessScore = result.PersonnelAllocation?.FairnessScore ?? 0;
var equipmentUtilizationRate = result.EquipmentAllocation?.OverallUtilizationRate ?? 0;
var equipmentSuccessCount = result.EquipmentAllocation?.SuccessfulMatches?.Count ?? 0;
// 生成详细的统计报告
_logger.LogInformation("📊 === 智能整合最终统计报告 ===");
_logger.LogInformation("🎯 总体成功率: {SuccessRate:P1} ({SuccessCount}/{TotalCount})",
result.OverallSuccessRate, successCount, totalTasks);
_logger.LogInformation("👥 人员分配: 成功 {PersonnelSuccess} 个,公平性评分 {FairnessScore:F1}",
successCount, fairnessScore);
_logger.LogInformation("🔧 设备分配: 成功 {EquipmentSuccess} 个,利用率 {UtilizationRate:F1}%",
equipmentSuccessCount, equipmentUtilizationRate);
_logger.LogInformation("⏱️ 总耗时: {ElapsedTime}ms", result.ElapsedMilliseconds);
// 按失败原因分类统计
if (result.FailedTasks?.Any() == true)
{
var failureStats = result.FailedTasks
.GroupBy(f => f.FailureType)
.Select(g => new { Type = g.Key, Count = g.Count() })
.OrderByDescending(x => x.Count);
_logger.LogWarning("⚠️ 失败任务分析:");
foreach (var stat in failureStats)
{
_logger.LogWarning(" {FailureType}: {Count} 个任务", stat.Type, stat.Count);
}
}
// 生成改进建议
var suggestions = GenerateImprovementSuggestions(result, tasksToIntegrate);
if (suggestions.Any())
{
_logger.LogInformation("💡 改进建议:");
foreach (var suggestion in suggestions)
{
_logger.LogInformation(" • {Suggestion}", suggestion);
}
}
result.IntegrationDetails = $"智能整合完成。" +
$"成功分配 {result.SuccessfulTaskCount} 个任务," +
$"失败 {result.FailedTasks.Count} 个任务。" +
$"人员分配公平性评分: {fairnessScore:F1}" +
$"设备利用率: {equipmentUtilizationRate:F1}%。" +
$"执行耗时: {result.ElapsedMilliseconds}ms。";
result.IsSuccess = result.SuccessfulTaskCount > 0;
result.CompletionTime = DateTime.Now;
if (string.IsNullOrEmpty(result.IntegrationBatchCode))
{
result.IntegrationBatchCode = GenerateIntegrationBatchCode();
}
}
/// <summary>
/// 生成改进建议
/// </summary>
private List<string> GenerateImprovementSuggestions(SmartIntegrationResult result, List<WorkOrderEntity> tasksToIntegrate)
{
var suggestions = new List<string>();
if (result.OverallSuccessRate < 0.7)
{
suggestions.Add("成功率偏低,建议增加人员池或调整任务优先级");
}
if (result.FailedTasks?.Any(f => f.FailureType == FailureType.PersonnelAllocationFailed) == true)
{
suggestions.Add("存在人员不可用问题,建议优化人员排班计划");
}
if (result.PersonnelAllocation?.FairnessScore < 70)
{
suggestions.Add("人员分配公平性有待提升,建议调整分配策略");
}
var dateSpan = tasksToIntegrate.Any() ?
(tasksToIntegrate.Max(t => t.WorkOrderDate) - tasksToIntegrate.Min(t => t.WorkOrderDate)).Days : 0;
if (dateSpan > 7)
{
suggestions.Add("任务时间跨度较大,建议采用分时段处理策略");
}
return suggestions;
}
/// <summary>
/// 收集失败任务信息
/// </summary>
private List<FailedTaskInfo> CollectFailedTasksComprehensive(SmartIntegrationResult result)
{
var failedTasks = new List<FailedTaskInfo>();
if (result.PersonnelAllocation?.FailedTasks != null)
{
failedTasks.AddRange(result.PersonnelAllocation.FailedTasks);
}
if (result.EquipmentAllocation?.FailedAllocations != null)
{
failedTasks.AddRange(result.EquipmentAllocation.FailedAllocations.Select(f => new FailedTaskInfo
{
TaskId = f.TaskId,
TaskCode = f.TaskCode,
FailureReason = f.FailureReason,
FailureType = FailureType.EquipmentUnavailable,
ConflictDetails = f.ConflictDetails ?? new List<string>()
}));
}
return failedTasks.GroupBy(f => f.TaskId).Select(g => g.First()).ToList();
}
/// <summary>
/// 生成整合批次编码
/// 格式INT + yyyyMMddHHmmss + XXX总长度23字符符合数据库30字符限制
/// 业务思考:优化编码长度,保持唯一性的同时确保数据库兼容性
/// </summary>
private string GenerateIntegrationBatchCode()
{
return $"INT{DateTime.Now:yyyyMMddHHmmss}{new Random().Next(100, 999)}";
}
/// <summary>
/// 按日期分组任务
/// 架构设计:将任务按工作日期分组,确保不同日期的任务可以分配给同一人员
/// 深度业务思考:这种分组方式解决了原算法中时间维度处理粗糙的问题
/// </summary>
private Dictionary<DateTime, List<WorkOrderEntity>> GroupTasksByDate(List<WorkOrderEntity> tasks)
{
return tasks
.Where(t => t.WorkOrderDate != default(DateTime))
.GroupBy(t => t.WorkOrderDate.Date)
.ToDictionary(g => g.Key, g => g.OrderBy(t => t.PlannedStartTime).ToList());
}
/// <summary>
/// 执行分时段人员分配
/// 核心算法优化:分别处理每个日期的任务,避免跨日期的时间冲突误判
/// 预分配优化:确保已指定人员的任务也进行最优性检测
/// 约束分离:独立处理时间约束与匹配算法
/// </summary>
private async Task<PersonnelAllocationResult> ExecuteTimeSegmentedPersonnelAllocationAsync(
Dictionary<DateTime, List<WorkOrderEntity>> taskGroups,
IntegrationStrategy strategy,
long operatorUserId,
string operatorName)
{
var allSuccessfulMatches = new List<TaskPersonnelMatch>();
var allFailedTasks = new List<FailedTaskInfo>();
var totalFairnessScore = 0.0;
var processedDates = 0;
foreach (var dateGroup in taskGroups.OrderBy(g => g.Key))
{
var date = dateGroup.Key;
var dailyTasks = dateGroup.Value;
_logger.LogInformation("🗓️ 处理 {Date} 的 {TaskCount} 个任务",
date.ToString("yyyy-MM-dd"), dailyTasks.Count);
// 为当日任务执行人员分配
var dailyGlobalInput = new GlobalAllocationInput
{
Tasks = dailyTasks,
ExcludedPersonnelIds = new List<long>()
};
// 执行当日分配,采用增强的预分配约束处理
var dailyGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(dailyGlobalInput);
var dailyResult = ConvertToPersonnelAllocationResult(dailyGlobalResult);
if (dailyResult?.SuccessfulMatches != null)
{
allSuccessfulMatches.AddRange(dailyResult.SuccessfulMatches);
_logger.LogInformation(" ✅ {Date} 分配成功 {SuccessCount} 个任务",
date.ToString("MM-dd"), dailyResult.SuccessfulMatches.Count);
}
if (dailyResult?.FailedTasks != null)
{
allFailedTasks.AddRange(dailyResult.FailedTasks);
if (dailyResult.FailedTasks.Any())
{
_logger.LogWarning(" ⚠️ {Date} 分配失败 {FailedCount} 个任务",
date.ToString("MM-dd"), dailyResult.FailedTasks.Count);
}
}
// 累计公平性评分
if (dailyResult?.FairnessScore > 0)
{
totalFairnessScore += dailyResult.FairnessScore;
processedDates++;
}
// 记录详细分配信息用于调试
LogDailyAllocationDetails(date, dailyResult);
}
// 执行跨日期约束验证
var crossDateValidationResult = await ValidateCrossDateConstraintsAsync(allSuccessfulMatches, taskGroups);
if (!crossDateValidationResult.IsValid)
{
_logger.LogWarning("⚠️ 跨日期约束验证失败: {ValidationDetails}",
string.Join("; ", crossDateValidationResult.ValidationErrors));
// 将约束验证失败的任务移到失败列表
foreach (var failedTaskId in crossDateValidationResult.FailedTaskIds)
{
var failedMatch = allSuccessfulMatches.FirstOrDefault(m => m.TaskId == failedTaskId);
if (failedMatch != null)
{
allSuccessfulMatches.Remove(failedMatch);
allFailedTasks.Add(new FailedTaskInfo
{
TaskId = failedTaskId,
TaskCode = failedMatch.TaskCode,
FailureReason = "跨日期约束验证失败",
FailureType = FailureType.TimeConflict,
ConflictDetails = crossDateValidationResult.ValidationErrors
});
}
}
}
return new PersonnelAllocationResult
{
SuccessfulMatches = allSuccessfulMatches,
FailedTasks = allFailedTasks,
FairnessScore = (int)(processedDates > 0 ? totalFairnessScore / processedDates : 0),
AllocationStrategy = strategy.PersonnelStrategy.ToString(),
ProcessingDetails = $"处理了 {taskGroups.Count} 个日期分组,成功分配 {allSuccessfulMatches.Count} 个任务"
};
}
/// <summary>
/// 验证跨日期约束
/// 约束分离设计:独立处理跨日期的人员工作负载、休息时间等约束
/// </summary>
private async Task<CrossDateValidationResult> ValidateCrossDateConstraintsAsync(
List<TaskPersonnelMatch> allMatches,
Dictionary<DateTime, List<WorkOrderEntity>> taskGroups)
{
var validationResult = new CrossDateValidationResult { IsValid = true };
var personnelWorkloadMap = new Dictionary<long, List<TaskPersonnelMatch>>();
// 按人员分组已分配的任务
foreach (var match in allMatches)
{
if (!personnelWorkloadMap.ContainsKey(match.PersonnelId))
personnelWorkloadMap[match.PersonnelId] = new List<TaskPersonnelMatch>();
personnelWorkloadMap[match.PersonnelId].Add(match);
}
// 检查每个人员的跨日期工作负载
foreach (var personnelGroup in personnelWorkloadMap)
{
var personnelId = personnelGroup.Key;
var personnelTasks = personnelGroup.Value;
// 检查连续工作天数限制放宽为8天限制
var workDates = personnelTasks
.Select(t => taskGroups.First(g => g.Value.Any(task => task.Id == t.TaskId)).Key)
.Distinct()
.OrderBy(d => d)
.ToList();
// 采用更宽松的连续工作天数检查
if (HasExcessiveContinuousWorkDays(workDates, maxContinuousDays: 8))
{
validationResult.IsValid = false;
validationResult.ValidationErrors.Add(
$"人员 {personnelId} 连续工作天数严重超过限制: {string.Join(", ", workDates.Select(d => d.ToString("MM-dd")))}");
// 仅将最后1天的任务标记为失败而不是最后2天
var lastWorkDate = workDates.LastOrDefault();
var tasksToFail = personnelTasks
.Where(t => taskGroups.Any(g => g.Key == lastWorkDate && g.Value.Any(task => task.Id == t.TaskId)))
.Select(t => t.TaskId);
validationResult.FailedTaskIds.AddRange(tasksToFail);
_logger.LogWarning("⚠️ 人员 {PersonnelId} 连续工作天数过多,仅限制最后一天 {LastDate} 的任务",
personnelId, lastWorkDate.ToString("MM-dd"));
}
else if (workDates.Count >= 6)
{
// 6-8天连续工作记录警告但不阻止
_logger.LogInformation(" 人员 {PersonnelId} 连续工作 {WorkDays} 天,在可接受范围内: {Dates}",
personnelId, workDates.Count, string.Join(", ", workDates.Select(d => d.ToString("MM-dd"))));
}
// 检查每日工作时长限制
foreach (var date in workDates)
{
var dailyTasks = personnelTasks
.Where(t => taskGroups[date].Any(task => task.Id == t.TaskId))
.ToList();
if (dailyTasks.Count > 0)
{
var dailyWorkHours = CalculateDailyWorkHours(dailyTasks, taskGroups[date]);
if (dailyWorkHours > 12) // 假设每日工作时长不超过12小时
{
validationResult.IsValid = false;
validationResult.ValidationErrors.Add(
$"人员 {personnelId} 在 {date:MM-dd} 的工作时长 {dailyWorkHours:F1} 小时超过限制");
// 将当日最后的任务标记为失败
var lastTask = dailyTasks.OrderByDescending(t => t.TaskId).First();
validationResult.FailedTaskIds.Add(lastTask.TaskId);
}
}
}
}
await Task.CompletedTask; // 保持异步方法契约
return validationResult;
}
/// <summary>
/// 检查是否有过度的连续工作天数
/// 优化策略:根据任务紧急程度和业务需求灵活调整限制
/// </summary>
private bool HasExcessiveContinuousWorkDays(List<DateTime> workDates, int maxContinuousDays = 6)
{
// 如果工作日期少于限制天数直接返回false
if (workDates.Count <= maxContinuousDays) return false;
// 排序工作日期
var sortedDates = workDates.OrderBy(d => d).ToList();
var consecutiveDays = 1;
var maxConsecutiveFound = 1;
for (int i = 1; i < sortedDates.Count; i++)
{
// 检查是否连续
if (sortedDates[i] == sortedDates[i - 1].AddDays(1))
{
consecutiveDays++;
maxConsecutiveFound = Math.Max(maxConsecutiveFound, consecutiveDays);
// 如果超过限制记录详细信息但不立即返回true
if (consecutiveDays > maxContinuousDays)
{
_logger.LogWarning("🚨 发现连续工作 {ConsecutiveDays} 天,超过限制 {MaxDays} 天,日期范围: {StartDate} 到 {EndDate}",
consecutiveDays, maxContinuousDays,
sortedDates[i - consecutiveDays + 1].ToString("MM-dd"),
sortedDates[i].ToString("MM-dd"));
}
}
else
{
consecutiveDays = 1;
}
}
// 基于业务优先级决定是否真的阻止分配
bool shouldBlock = maxConsecutiveFound > maxContinuousDays + 1; // 允许适度超出
if (shouldBlock)
{
_logger.LogError("❌ 连续工作天数 {MaxConsecutive} 严重超过限制 {MaxAllowed},阻止分配",
maxConsecutiveFound, maxContinuousDays + 1);
}
else if (maxConsecutiveFound > maxContinuousDays)
{
_logger.LogWarning("⚠️ 连续工作天数 {MaxConsecutive} 超过建议限制 {MaxRecommended},但允许分配",
maxConsecutiveFound, maxContinuousDays);
}
return shouldBlock;
}
/// <summary>
/// 计算每日工作时长
/// </summary>
private double CalculateDailyWorkHours(List<TaskPersonnelMatch> dailyTasks, List<WorkOrderEntity> dayTasks)
{
var totalMinutes = 0.0;
foreach (var match in dailyTasks)
{
var task = dayTasks.FirstOrDefault(t => t.Id == match.TaskId);
if (task != null)
{
var duration = (task.PlannedEndTime - task.PlannedStartTime).TotalMinutes;
totalMinutes += duration;
}
}
return totalMinutes / 60.0;
}
/// <summary>
/// 记录每日分配详情(用于调试)
/// </summary>
private void LogDailyAllocationDetails(DateTime date, PersonnelAllocationResult? dailyResult)
{
if (dailyResult?.SuccessfulMatches != null)
{
_logger.LogDebug("📊 {Date} 分配详情:", date.ToString("yyyy-MM-dd"));
foreach (var match in dailyResult.SuccessfulMatches)
{
_logger.LogDebug(" 👤 任务 {TaskCode} → 人员 {PersonnelName} (评分: {Score:F1})",
match.TaskCode, match.PersonnelName, match.MatchScore);
}
}
}
#endregion
#region
/// <summary>
/// 跨日期验证结果
/// </summary>
private class CrossDateValidationResult
{
public bool IsValid { get; set; }
public List<string> ValidationErrors { get; set; } = new List<string>();
public List<long> FailedTaskIds { get; set; } = new List<long>();
}
/// <summary>
/// 执行降级人员分配策略
/// 深度业务思考:当标准分配失败率过高时,采用更宽松的约束条件重新分配
/// </summary>
private async Task ExecuteFallbackPersonnelAllocationAsync(
SmartIntegrationResult result,
List<WorkOrderEntity> allTasks,
IntegrationStrategy strategy)
{
var failedTaskIds = result.PersonnelAllocation.FailedTasks.Select(f => f.TaskId).ToHashSet();
var failedTasks = allTasks.Where(t => failedTaskIds.Contains(t.Id)).ToList();
_logger.LogInformation("🔄 降级重试: 尝试为 {FailedCount} 个失败任务重新分配", failedTasks.Count);
// 降级策略1放松时间约束
var fallbackGlobalInput = new GlobalAllocationInput
{
Tasks = failedTasks,
ExcludedPersonnelIds = new List<long>()
// TODO: 未来版本支持更多降级策略
// AllowConstraintRelaxation = true,
// MaxContinuousWorkDays = 10,
// IgnoreDateConstraints = true
};
var fallbackGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(fallbackGlobalInput);
var fallbackResult = ConvertToPersonnelAllocationResult(fallbackGlobalResult);
if (fallbackResult?.SuccessfulMatches?.Any() == true)
{
// 合并降级成功的分配结果
result.PersonnelAllocation.SuccessfulMatches.AddRange(fallbackResult.SuccessfulMatches);
// 从失败列表中移除成功分配的任务
var recoveredTaskIds = fallbackResult.SuccessfulMatches.Select(m => m.TaskId).ToHashSet();
result.PersonnelAllocation.FailedTasks = result.PersonnelAllocation.FailedTasks
.Where(f => !recoveredTaskIds.Contains(f.TaskId))
.ToList();
_logger.LogInformation("✅ 降级重试成功: 额外分配了 {RecoveredCount} 个任务",
fallbackResult.SuccessfulMatches.Count);
// 更新分配摘要
result.PersonnelAllocation.ProcessingDetails += $"; 降级重试恢复了{fallbackResult.SuccessfulMatches.Count}个任务";
}
else
{
_logger.LogWarning("⚠️ 降级重试未能恢复任何任务");
}
}
/// <summary>
/// 优化的分时段人员分配方法
/// 架构设计:增强错误处理和智能重分配机制
/// </summary>
private async Task<PersonnelAllocationResult> ExecuteOptimizedTimeSegmentedAllocationAsync(
Dictionary<DateTime, List<WorkOrderEntity>> taskGroups,
IntegrationStrategy strategy,
long operatorUserId,
string operatorName)
{
var allSuccessfulMatches = new List<TaskPersonnelMatch>();
var allFailedTasks = new List<FailedTaskInfo>();
var totalFairnessScore = 0.0;
var processedDates = 0;
// 按日期优先级排序:预分配任务较多的日期优先处理
var sortedDateGroups = taskGroups
.OrderBy(g => g.Value.Count(t => !t.AssignedPersonnelId.HasValue || t.AssignedPersonnelId.Value == 0))
.ThenBy(g => g.Key);
foreach (var dateGroup in sortedDateGroups)
{
var date = dateGroup.Key;
var dailyTasks = dateGroup.Value;
_logger.LogInformation("🗓️ 处理 {Date} 的 {TaskCount} 个任务 (预分配: {PreAssignedCount})",
date.ToString("yyyy-MM-dd"), dailyTasks.Count,
dailyTasks.Count(t => t.AssignedPersonnelId.HasValue && t.AssignedPersonnelId.Value > 0));
// 为当日任务执行人员分配,采用逐步放松约束的策略
var dailyResult = await ExecuteDailyAllocationWithProgressiveRelaxationAsync(
dailyTasks, strategy, allSuccessfulMatches);
if (dailyResult?.SuccessfulMatches != null)
{
allSuccessfulMatches.AddRange(dailyResult.SuccessfulMatches);
_logger.LogInformation(" ✅ {Date} 分配成功 {SuccessCount} 个任务",
date.ToString("MM-dd"), dailyResult.SuccessfulMatches.Count);
}
if (dailyResult?.FailedTasks != null)
{
allFailedTasks.AddRange(dailyResult.FailedTasks);
if (dailyResult.FailedTasks.Any())
{
_logger.LogWarning(" ⚠️ {Date} 分配失败 {FailedCount} 个任务",
date.ToString("MM-dd"), dailyResult.FailedTasks.Count);
}
}
// 累计公平性评分
if (dailyResult?.FairnessScore > 0)
{
totalFairnessScore += dailyResult.FairnessScore;
processedDates++;
}
}
return new PersonnelAllocationResult
{
SuccessfulMatches = allSuccessfulMatches,
FailedTasks = allFailedTasks,
FairnessScore = (int)(processedDates > 0 ? totalFairnessScore / processedDates : 0),
AllocationStrategy = strategy.PersonnelStrategy.ToString(),
ProcessingDetails = $"优化分时段处理: {taskGroups.Count}个日期组,成功分配{allSuccessfulMatches.Count}个任务"
};
}
/// <summary>
/// 每日分配的渐进式约束放松策略
/// 深度业务思考:从严格约束开始,逐步放松直到找到可行解
/// </summary>
private async Task<PersonnelAllocationResult> ExecuteDailyAllocationWithProgressiveRelaxationAsync(
List<WorkOrderEntity> dailyTasks,
IntegrationStrategy strategy,
List<TaskPersonnelMatch> existingMatches)
{
// 策略1标准约束
var standardGlobalInput = new GlobalAllocationInput
{
Tasks = dailyTasks,
ExcludedPersonnelIds = new List<long>()
// TODO: 未来版本支持现有分配考虑
// ExistingAllocations = existingMatches
};
var standardGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(standardGlobalInput);
var result = ConvertToPersonnelAllocationResult(standardGlobalResult);
// 如果标准策略成功率较高,直接返回
if (result?.SuccessfulMatches?.Count > 0 &&
(result.FailedTasks?.Count ?? 0) <= dailyTasks.Count * 0.3)
{
return result;
}
// 策略2放松班次规则
_logger.LogInformation(" 🔄 标准策略效果不佳,尝试放松班次规则...");
var relaxedGlobalInput = new GlobalAllocationInput
{
Tasks = dailyTasks,
ExcludedPersonnelIds = new List<long>()
// TODO: 未来版本支持约束放松
// AllowConstraintRelaxation = true
};
var relaxedGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(relaxedGlobalInput);
var relaxedResult = ConvertToPersonnelAllocationResult(relaxedGlobalResult);
if (relaxedResult?.SuccessfulMatches?.Count > result?.SuccessfulMatches?.Count)
{
return relaxedResult;
}
// 策略3最宽松约束
_logger.LogInformation(" 🔄 尝试最宽松约束策略...");
var fallbackGlobalInput = new GlobalAllocationInput
{
Tasks = dailyTasks,
ExcludedPersonnelIds = new List<long>()
// TODO: 未来版本支持更多宽松约束
// MaxContinuousWorkDays = 10,
// IgnoreDateConstraints = true
};
var fallbackGlobalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(fallbackGlobalInput);
var fallbackResult = ConvertToPersonnelAllocationResult(fallbackGlobalResult);
// 返回效果最好的结果
return SelectBestResult(result, relaxedResult, fallbackResult);
}
/// <summary>
/// 选择最佳分配结果
/// </summary>
private PersonnelAllocationResult SelectBestResult(params PersonnelAllocationResult[] results)
{
return results
.Where(r => r != null)
.OrderByDescending(r => r.SuccessfulMatches?.Count ?? 0)
.ThenBy(r => r.FailedTasks?.Count ?? int.MaxValue)
.ThenByDescending(r => r.FairnessScore)
.FirstOrDefault() ?? new PersonnelAllocationResult
{
SuccessfulMatches = new List<TaskPersonnelMatch>(),
FailedTasks = new List<FailedTaskInfo>()
};
}
#endregion
#region ISmartTaskIntegrationService
/// <summary>
/// 转发到全局人员分配服务
/// </summary>
[HttpPost]
public async Task<PersonnelAllocationResult> AllocatePersonnelSmartlyAsync(PersonnelAllocationInput input)
{
// 将PersonnelAllocationInput转换为GlobalAllocationInput
var globalInput = new GlobalAllocationInput
{
Tasks = input.Tasks,
TaskIds = input.TaskIds,
ExcludedPersonnelIds = input.ExcludedPersonnelIds
};
var globalResult = await _globalPersonnelAllocationService.AllocatePersonnelGloballyAsync(globalInput);
return ConvertToPersonnelAllocationResult(globalResult);
}
/// <summary>
/// 转发到设备分配服务
/// </summary>
[HttpPost]
public async Task<EquipmentAllocationResult> AllocateEquipmentSmartlyAsync(EquipmentAllocationInput input)
{
return await _equipmentAllocationService.AllocateEquipmentSmartlyAsync(input);
}
/// <summary>
/// 转发到整合记录服务
/// </summary>
[HttpPost]
public async Task<IntegrationRecord> GenerateIntegrationRecordAsync(IntegrationRecordInput input)
{
return await _integrationRecordService.GenerateIntegrationRecordAsync(input);
}
/// <summary>
/// 获取整合历史记录
/// </summary>
[HttpPost]
public async Task<List<IntegrationRecordListOutput>> GetIntegrationRecordsAsync(IntegrationRecordGetListInput input)
{
return await _integrationRecordService.GetIntegrationRecordsAsync(input);
}
/// <summary>
/// 获取整合记录详细信息
/// </summary>
[HttpGet]
public async Task<IntegrationRecordWithTasksOutput?> GetIntegrationRecordDetailAsync(long integrationRecordId)
{
// 转发到整合记录服务或实现基本逻辑
return await _integrationRecordService.GetIntegrationRecordWithTasksAsync(integrationRecordId);
}
/// <summary>
/// 发布任务
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
public async Task<PublishIntegrationRecordResult> PublishIntegrationRecordAsync(PublishIntegrationRecordInput input)
{
return await _integrationRecordService.PublishIntegrationRecordAsync(input);
}
/// <summary>
/// 转换全局分配结果为人员分配结果
/// 业务逻辑:适配不同服务间的数据结构差异,确保接口兼容性
/// </summary>
private PersonnelAllocationResult ConvertToPersonnelAllocationResult(GlobalAllocationResult globalResult)
{
return new PersonnelAllocationResult
{
IsSuccess = globalResult.IsSuccess,
SuccessfulMatches = globalResult.SuccessfulMatches?.Select(gm => new TaskPersonnelMatch
{
TaskId = gm.TaskId,
PersonnelId = gm.PersonnelId,
PersonnelName = gm.PersonnelName,
MatchScore = (int)gm.MatchScore,
TaskCode = $"WO_{gm.TaskId}",
MatchReason = gm.MatchReason
}).ToList() ?? new List<TaskPersonnelMatch>(),
FailedTasks = globalResult.FailedAllocations?.Select(gf => new FailedTaskInfo
{
TaskId = gf.TaskId,
TaskCode = gf.TaskCode,
FailureReason = gf.FailureReason,
FailureType = FailureType.PersonnelAllocationFailed,
ConflictDetails = gf.ConflictDetails
}).ToList() ?? new List<FailedTaskInfo>(),
FairnessScore = globalResult.FairnessAnalysis?.GiniCoefficient != null
? (int)((1.0 - globalResult.FairnessAnalysis.GiniCoefficient) * 100)
: 85,
AllocationStrategy = "GlobalOptimization",
ProcessingDetails = globalResult.AllocationSummary ?? "全局优化分配完成"
};
}
#endregion
}
}