paiban/apply_fairness.ps1
Developer 7c80f73079 1111
2025-09-23 08:33:50 +08:00

314 lines
15 KiB
PowerShell
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.

$path = "NPP.SmartSchedue.Api/Services/Integration/Algorithms/LinearProgrammingEngine.cs"
$lines = [System.Collections.Generic.List[string]]::new()
$lines.AddRange([System.IO.File]::ReadAllLines($path))
function FindExactIndexInRange($lines, $value, $start, $end) {
for ($i = $start; $i -le $end; $i++) {
if ($lines[$i] -eq $value) { return $i }
}
return -1
}
function FindContainsIndexInRange($lines, $value, $start, $end) {
for ($i = $start; $i -le $end; $i++) {
if ($lines[$i].Contains($value)) { return $i }
}
return -1
}
# BuildAuxiliaryVariables
$index = $lines.IndexOf(' var auxiliaryVars = new Dictionary<string, Variable>();')
if ($index -ge 0) {
$lines.InsertRange($index + 1, [string[]]@(
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalTasks);',
' var avgWorkload = context.AvailablePersonnel.Count > 0',
' ? (double)(context.Tasks.Count + totalHistoricalTasks) / context.AvailablePersonnel.Count',
' : 0.0;'
))
}
$index = $lines.IndexOf(' var avgWorkload = (double)context.Tasks.Count / context.AvailablePersonnel.Count;')
if ($index -ge 0) { $lines.RemoveAt($index) }
$index = $lines.IndexOf(' var negDeviationVar = solver.MakeNumVar(0, avgWorkload, $"neg_dev_{personnel.Id}");')
if ($index -ge 0) { $lines[$index] = ' var negDeviationVar = solver.MakeNumVar(0, Math.Max(0.0, avgWorkload), $"neg_dev_{personnel.Id}");' }
# AddFairnessConstraints
$start = $lines.IndexOf(' private void AddFairnessConstraints(')
if ($start -ge 0) {
$brace = 0
for ($end = $start; $end -lt $lines.Count; $end++) {
if ($lines[$end].Contains('{')) { $brace++ }
if ($lines[$end].Contains('}')) { $brace-- }
if ($brace -eq 0) { break }
}
$lines.RemoveRange($start, $end - $start + 1)
$newFairness = [string[]]@(
' private void AddFairnessConstraints(',
' Solver solver,',
' Dictionary<(long TaskId, long PersonnelId), Variable> decisionVars,',
' Dictionary<string, Variable> auxiliaryVars,',
' GlobalAllocationContext context)',
' {',
' var constraintCount = 0;',
'',
' _logger.LogInformation("⚖️ 开始添加公平性约束...");',
'',
' var totalTasks = context.Tasks.Count;',
' var totalPersonnel = context.AvailablePersonnel.Count;',
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalWorkload);',
' var totalWorkload = totalTasks + totalHistoricalWorkload;',
' var avgWorkload = totalPersonnel > 0 ? (double)totalWorkload / totalPersonnel : 0.0;',
'',
' // 公平性约束1负载均衡偏差计算',
' foreach (var personnel in context.AvailablePersonnel)',
' {',
' var workloadVarKey = $"workload_{personnel.Id}";',
' var posDevVarKey = $"pos_dev_{personnel.Id}";',
' var negDevVarKey = $"neg_dev_{personnel.Id}";',
' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);',
'',
' if (auxiliaryVars.ContainsKey(workloadVarKey) &&',
' auxiliaryVars.ContainsKey(posDevVarKey) &&',
' auxiliaryVars.ContainsKey(negDevVarKey))',
' {',
' // 约束currentWorkload + 历史工作量偏移 = avgWorkload + pos_deviation - neg_deviation',
' var targetWorkload = avgWorkload - historicalWorkload;',
' var balanceConstraint = solver.MakeConstraint(targetWorkload, targetWorkload,',
' $"load_balance_{personnel.Id}");',
'',
' balanceConstraint.SetCoefficient(auxiliaryVars[workloadVarKey], 1);',
' balanceConstraint.SetCoefficient(auxiliaryVars[posDevVarKey], -1);',
' balanceConstraint.SetCoefficient(auxiliaryVars[negDevVarKey], 1);',
' constraintCount++;',
' }',
' }',
'',
' // 公平性约束2最大负载限制 - 防止极端不均衡',
' var maxTotalWorkload = Math.Ceiling(avgWorkload * 1.5);',
' foreach (var personnel in context.AvailablePersonnel)',
' {',
' var workloadVarKey = $"workload_{personnel.Id}";',
' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);',
' if (auxiliaryVars.ContainsKey(workloadVarKey))',
' {',
' var constraint = solver.MakeConstraint(0, Math.Max(0.0, maxTotalWorkload - historicalWorkload),',
' $"max_workload_{personnel.Id}");',
' constraint.SetCoefficient(auxiliaryVars[workloadVarKey], 1);',
' constraintCount++;',
' }',
' }',
'',
' // 公平性约束3最小人员利用率约束 - 确保不会过度集中',
' var minPersonnelUtilization = totalPersonnel == 0',
' ? 0',
' : (totalTasks > 10 ? Math.Max(3, Math.Ceiling(totalPersonnel * 0.4)) : Math.Min((double)totalPersonnel, 2));',
'',
' // 创建人员参与指示变量',
' var participationVars = new List<Variable>();',
' foreach (var personnel in context.AvailablePersonnel)',
' {',
' var participationVar = solver.MakeBoolVar($"participation_{personnel.Id}");',
' participationVars.Add(participationVar);',
'',
' // 如果人员参与则至少分配1个任务',
' var constraint = solver.MakeConstraint(0, double.PositiveInfinity,',
' $"participation_min_{personnel.Id}");',
'',
' constraint.SetCoefficient(participationVar, -1);',
'',
' var personnelTasks = decisionVars.Where(v => v.Key.PersonnelId == personnel.Id);',
' foreach (var kvp in personnelTasks)',
' {',
' constraint.SetCoefficient(kvp.Value, 1);',
' }',
' constraintCount++;',
' }',
'',
' // 最小参与人数约束',
' var minParticipationConstraint = solver.MakeConstraint(minPersonnelUtilization, double.PositiveInfinity,',
' "min_personnel_utilization");',
'',
' foreach (var participationVar in participationVars)',
' {',
' minParticipationConstraint.SetCoefficient(participationVar, 1);',
' }',
' constraintCount++;',
'',
' _logger.LogDebug("✅ 公平性约束添加完成 - 约束数量:{ConstraintCount},平均负载:{AvgWorkload:F2},最大负载:{MaxWorkload:F2},最小参与人数:{MinParticipation}",',
' constraintCount, avgWorkload, maxTotalWorkload, minPersonnelUtilization);',
' }'
)
$lines.InsertRange($start, $newFairness)
}
# Update LogWorkloadAnalysis call
$index = $lines.IndexOf(' LogWorkloadAnalysis(workloadDistribution, context.Tasks.Count, context.AvailablePersonnel.Count);')
if ($index -ge 0) { $lines[$index] = ' LogWorkloadAnalysis(workloadDistribution, context.Tasks.Count, context.AvailablePersonnel.Count, context);' }
# ValidateSolutionQuality
$index = $lines.IndexOf(' var totalChecks = 0;')
if ($index -ge 0) { $lines.Insert($index + 1, ' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out _);') }
$start = $lines.IndexOf(' // 检查3负载均衡评估')
if ($start -ge 0) {
$lines.RemoveRange($start, 8)
$lines.InsertRange($start, [string[]]@(
' // 检查3负载均衡评估纳入历史任务',
' var currentWorkloadMap = assignments',
' .GroupBy(a => a.Value)',
' .ToDictionary(g => g.Key, g => g.Count());',
'',
' var combinedWorkloads = context.AvailablePersonnel.ToDictionary(',
' p => p.Id,',
' p => historicalWorkloads.GetValueOrDefault(p.Id) + currentWorkloadMap.GetValueOrDefault(p.Id));',
'',
' if (combinedWorkloads.Any())',
' {',
' var workloadValues = combinedWorkloads.Values.Select(v => (decimal)v).ToList();',
' var giniCoefficient = CalculateGiniCoefficient(workloadValues);',
' metrics.LoadBalanceScore = Math.Max(0.0, 1.0 - giniCoefficient);',
'',
' var utilizedCount = combinedWorkloads.Count(kv => kv.Value > 0);',
' metrics.PersonnelUtilizationRate = context.AvailablePersonnel.Count > 0',
' ? (double)utilizedCount / context.AvailablePersonnel.Count',
' : 0.0;',
' }'
))
}
# LogWorkloadAnalysis replacement
$start = $lines.IndexOf(' private void LogWorkloadAnalysis(Dictionary<long, decimal> workloadDistribution, int totalTasks, int totalPersonnel)')
if ($start -ge 0) {
$brace = 0
for ($end = $start; $end -lt $lines.Count; $end++) {
if ($lines[$end].Contains('{')) { $brace++ }
if ($lines[$end].Contains('}')) { $brace-- }
if ($brace -eq 0) { break }
}
$lines.RemoveRange($start, $end - $start + 1)
$newLog = [string[]]@(
' private void LogWorkloadAnalysis(',
' Dictionary<long, decimal> workloadDistribution,',
' int totalTasks,',
' int totalPersonnel,',
' GlobalAllocationContext context)',
' {',
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out _);',
'',
' if (!workloadDistribution.Any() && historicalWorkloads.Values.All(value => value == 0))',
' {',
' return;',
' }',
'',
' var combinedWorkloads = context.AvailablePersonnel.ToDictionary(',
' p => p.Id,',
' p => workloadDistribution.GetValueOrDefault(p.Id) + historicalWorkloads.GetValueOrDefault(p.Id));',
'',
' if (!combinedWorkloads.Any())',
' {',
' return;',
' }',
'',
' var combinedValues = combinedWorkloads.Values.Select(value => (decimal)value).ToList();',
' var avgWorkload = combinedValues.Average(value => (double)value);',
' var maxWorkload = combinedValues.Max();',
' var minWorkload = combinedValues.Min();',
' var utilizedCount = combinedWorkloads.Count(kv => kv.Value > 0);',
' var utilizationRate = totalPersonnel > 0 ? (double)utilizedCount / totalPersonnel : 0.0;',
'',
' _logger.LogInformation("📈 工作负载分析(含历史) - 平均:{Avg:F2},最大:{Max},最小:{Min},当前批次任务数:{CurrentTasks},人员利用率:{Util:P2},基尼系数:{Gini:F4}",',
' avgWorkload, maxWorkload, minWorkload, totalTasks, utilizationRate, CalculateGiniCoefficient(combinedValues));',
'',
' var currentDistributionLog = string.Join(", ",',
' workloadDistribution.OrderBy(kv => kv.Key)',
' .Select(kv => $"人员{kv.Key}:{kv.Value}任务"));',
' if (!string.IsNullOrWhiteSpace(currentDistributionLog))',
' {',
' _logger.LogDebug("📊 本次任务负载:{Distribution}", currentDistributionLog);',
' }',
'',
' var historicalDistributionLog = string.Join(", ",',
' historicalWorkloads.OrderBy(kv => kv.Key)',
' .Select(kv => $"人员{kv.Key}:{kv.Value}历史任务"));',
' if (!string.IsNullOrWhiteSpace(historicalDistributionLog))',
' {',
' _logger.LogDebug("🕰️ 历史任务负载:{Distribution}", historicalDistributionLog);',
' }',
'',
' var cumulativeDistributionLog = string.Join(", ",',
' combinedWorkloads.OrderBy(kv => kv.Key)',
' .Select(kv => $"人员{kv.Key}:{kv.Value}累计任务"));',
' _logger.LogDebug("⚖️ 累计任务负载:{Distribution}", cumulativeDistributionLog);',
' }'
)
$lines.InsertRange($start, $newLog)
}
# Insert helper method
$index = $lines.IndexOf(' #region 辅助方法')
if ($index -ge 0) {
$lines.InsertRange($index + 1, [string[]]@(
'',
' private Dictionary<long, int> BuildHistoricalWorkloadMap(GlobalAllocationContext context, out int totalHistoricalTasks)',
' {',
' var historicalWorkloads = new Dictionary<long, int>();',
' totalHistoricalTasks = 0;',
'',
' foreach (var personnel in context.AvailablePersonnel)',
' {',
' var historyCount = 0;',
' if (context.PersonnelHistoryTasks.TryGetValue(personnel.Id, out var historyTasks) && historyTasks != null)',
' {',
' historyCount = historyTasks.Count;',
' }',
'',
' historicalWorkloads[personnel.Id] = historyCount;',
' totalHistoricalTasks += historyCount;',
' }',
'',
' return historicalWorkloads;',
' }',
''
))
}
# CreateBatchIntegrityConstraints
$start = $lines.IndexOf(' private int CreateBatchIntegrityConstraints(')
if ($start -ge 0) {
$brace = 0
for ($end = $start; $end -lt $lines.Count; $end++) {
if ($lines[$end].Contains('{')) { $brace++ }
if ($lines[$end].Contains('}')) { $brace-- }
if ($brace -eq 0) { break }
}
$idx = FindExactIndexInRange $lines ' var totalPersonnel = context.AvailablePersonnel.Count;' $start $end
if ($idx -ge 0) {
$lines.InsertRange($idx + 1, [string[]]@(
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalWorkload);',
' var totalWorkload = totalTasks + totalHistoricalWorkload;',
' var avgWorkload = totalPersonnel > 0 ? (double)totalWorkload / totalPersonnel : 0.0;'
))
$origIdx = FindExactIndexInRange $lines ' var avgWorkload = (double)totalTasks / totalPersonnel;' $start ($end + 3)
if ($origIdx -ge 0) { $lines.RemoveAt($origIdx) }
}
$toListIdx = FindExactIndexInRange $lines ' .ToList();' $start $end
if ($toListIdx -ge 0) {
$lines.Insert($toListIdx + 1, ' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);')
}
$constraintIdx = FindExactIndexInRange $lines ' var constraint = solver.MakeConstraint(' $start $end
if ($constraintIdx -ge 0) {
$lines.RemoveRange($constraintIdx, 3)
$lines.InsertRange($constraintIdx, [string[]]@(
' var lowerBound = Math.Max(0.0, (avgWorkload - maxDeviation) - historicalWorkload);',
' var upperBound = Math.Max(lowerBound, (avgWorkload + maxDeviation) - historicalWorkload);',
' var constraint = solver.MakeConstraint(',
' lowerBound,',
' upperBound,',
' $"workload_balance_{personnel.Id}");'
))
}
}
[System.IO.File]::WriteAllLines($path, $lines)