feat(templateMatcher): Добавлена проверка принадлежности связанного ЭК к КИИ, который имеет больший приоритет.
This commit is contained in:
@@ -6,7 +6,6 @@ using PARR.Core.Repositories.Interfaces.Job;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
using PARR.Core.Services.MatchingStatusService;
|
||||
using PARR.Core.Services.UnitFilterService;
|
||||
using PARR.Core.Services.UnitFilterService.Models;
|
||||
using PARR.Domain.Cache.Models;
|
||||
using PARR.Domain.Common.Rabbit.Messages;
|
||||
using PARR.Domain.Entities;
|
||||
@@ -38,7 +37,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||
private readonly IMatchingStatusService matchingStatusService;
|
||||
|
||||
private readonly IUnitInTemplateConflictMapper unitInTemplateConflictMapper;
|
||||
|
||||
public GroupedTemplateSynchronizer(
|
||||
ILogger<GroupedTemplateSynchronizer> logger,
|
||||
@@ -55,7 +54,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
ITemplateDeactivator templateDeactivator,
|
||||
ITemplateNameNormalizer templateNameNormalizer,
|
||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||
IMatchingStatusService matchingStatusService
|
||||
IMatchingStatusService matchingStatusService,
|
||||
IUnitInTemplateConflictMapper unitInTemplateConflictMapper
|
||||
)
|
||||
{
|
||||
this.logger = logger;
|
||||
@@ -73,6 +73,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
this.templateNameNormalizer = templateNameNormalizer;
|
||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
this.unitInTemplateConflictMapper = unitInTemplateConflictMapper;
|
||||
}
|
||||
|
||||
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||
@@ -236,16 +237,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
}
|
||||
|
||||
logger.LogDebug("Построение обратного отображения: связанные юниты -> юниты, связанные с ними (после фильтрации).");
|
||||
var reverseMapping = await BuildReverseMappingAsync(finalFilteredUnitFilterResults, maxJob);
|
||||
|
||||
logger.LogDebug("Построено {Count} записей в обратном отображении.", reverseMapping.Count);
|
||||
|
||||
if (!reverseMapping.Any())
|
||||
{
|
||||
logger.LogInformation("После построения обратного отображения в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после построения обратного отображения");
|
||||
return;
|
||||
}
|
||||
var reverseMapping = await unitInTemplateConflictMapper.BuildMappingAsync(finalFilteredUnitFilterResults, maxJob);
|
||||
|
||||
logger.LogDebug("Построено {Count} записей в обратном отображении.", reverseMapping.Count);
|
||||
|
||||
@@ -620,148 +612,5 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
return field.Id;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Строит обратное отображение: UnitId шаблона -> [UnitsInTemplate]
|
||||
/// Решает конфликты, когда юнит из UnitsInTemplate может быть связан с несколькими UnitId шаблона.
|
||||
/// </summary>
|
||||
/// <param name="unitFilterResults">Результаты фильтрации, содержащие связи.</param>
|
||||
/// <param name="maxJob">Job, используемый для определения направления связей (IsParentRelationships).</param>
|
||||
/// <returns>Словарь, где ключ - это UnitId шаблона, а значение - список юнитов, входящих в него (UnitsInTemplate).</returns>
|
||||
private async Task<Dictionary<Guid, List<Guid>>> BuildReverseMappingAsync(IEnumerable<UnitFilterResultDto> unitFilterResults, Job maxJob)
|
||||
{
|
||||
logger.LogDebug("Разрешение конфликта: определение, для какого UnitId выбрать каждый юнит из UnitsInTemplate.");
|
||||
|
||||
// 1. Собираем все потенциальные пары (relatedUnitId, unitInTemplateId)
|
||||
var potentialAssignments = new Dictionary<Guid, List<Guid>>(); // relatedUnitId -> [unitInTemplateId, ...]
|
||||
var allPotentialRelatedUnitIds = new HashSet<Guid>();
|
||||
var allUnitInTemplateIds = new HashSet<Guid>();
|
||||
|
||||
foreach (var dto in unitFilterResults)
|
||||
{
|
||||
List<Guid> relatedUnitIds;
|
||||
if (maxJob.IsParentRelationships == true)
|
||||
{
|
||||
relatedUnitIds = dto.Children.Select(c => c.UnitId).ToList(); // relatedUnitIds - это ChildUnitIds, которые станут UnitId шаблона
|
||||
}
|
||||
else
|
||||
{
|
||||
relatedUnitIds = dto.Parents.Select(p => p.UnitId).ToList(); // relatedUnitIds - это ParentUnitIds, которые станут UnitId шаблона
|
||||
}
|
||||
|
||||
var unitInTemplateId = dto.Id; // dto.Id - это юнит, который прошел фильтры, он будет в UnitsInTemplate
|
||||
|
||||
allUnitInTemplateIds.Add(unitInTemplateId);
|
||||
allPotentialRelatedUnitIds.UnionWith(relatedUnitIds); // Собираем все unique relatedUnitId
|
||||
|
||||
foreach (var relatedUnitId in relatedUnitIds)
|
||||
{
|
||||
if (!potentialAssignments.ContainsKey(relatedUnitId))
|
||||
{
|
||||
potentialAssignments[relatedUnitId] = new List<Guid>();
|
||||
}
|
||||
potentialAssignments[relatedUnitId].Add(unitInTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Загрузим имена всех potentialRelatedUnitIds для сортировки по алфавиту при равенстве связей
|
||||
var relatedUnitNames = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allPotentialRelatedUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
||||
|
||||
// 3. Для каждого unitInTemplateId, найти лучший relatedUnitId
|
||||
var unitInTemplateToBestRelatedUnit = new Dictionary<Guid, Guid>(); // unitInTemplateId -> bestRelatedUnitId
|
||||
|
||||
foreach (var unitInTemplateId in allUnitInTemplateIds)
|
||||
{
|
||||
var candidates = potentialAssignments
|
||||
.Where(kvp => kvp.Value.Contains(unitInTemplateId))
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
|
||||
if (candidates.Count == 1)
|
||||
{
|
||||
// Только один кандидат, просто назначаем
|
||||
unitInTemplateToBestRelatedUnit[unitInTemplateId] = candidates[0];
|
||||
}
|
||||
else if (candidates.Count > 1)
|
||||
{
|
||||
// Несколько кандидатов, применяем правила: 1. Больше связей -> лучше. 2. По алфавиту.
|
||||
Guid bestCandidate = candidates[0]; // Инициализируем первым кандидатом
|
||||
|
||||
// Загрузим количество связей для каждого кандидата
|
||||
// Количество связей - это общее число юнитов (в Parents или Children) в UnitFilterResultDto, связанном с *этим* relatedUnitId
|
||||
var candidateRelationshipCounts = new Dictionary<Guid, int>();
|
||||
foreach (var candidateId in candidates)
|
||||
{
|
||||
// Найдем все dto, которые привели к этому candidateId
|
||||
// Это dto.Id, у которых candidateId был в Parents (если IsParentRelationships) или в Children (если !IsParentRelationships)
|
||||
var relevantDtos = unitFilterResults.Where(dto =>
|
||||
{
|
||||
if (maxJob.IsParentRelationships == true)
|
||||
{
|
||||
return dto.Children.Any(c => c.UnitId == candidateId);
|
||||
}
|
||||
else
|
||||
{
|
||||
return dto.Parents.Any(p => p.UnitId == candidateId);
|
||||
}
|
||||
}).ToList();
|
||||
|
||||
// Общее количество связей для этого candidateId - это сумма связей (Parents.Count или Children.Count) из *всех* relevantDtos
|
||||
int totalRelationships = 0;
|
||||
foreach (var relevantDto in relevantDtos)
|
||||
{
|
||||
if (maxJob.IsParentRelationships == true)
|
||||
{
|
||||
totalRelationships += relevantDto.Children.Count;
|
||||
}
|
||||
else
|
||||
{
|
||||
totalRelationships += relevantDto.Parents.Count;
|
||||
}
|
||||
}
|
||||
|
||||
candidateRelationshipCounts[candidateId] = totalRelationships;
|
||||
}
|
||||
|
||||
// Применяем правило 1: больше связей -> лучше
|
||||
int bestCount = candidateRelationshipCounts[bestCandidate];
|
||||
foreach (var candidateId in candidates.Skip(1))
|
||||
{
|
||||
int candidateCount = candidateRelationshipCounts[candidateId];
|
||||
if (candidateCount > bestCount ||
|
||||
(candidateCount == bestCount && string.Compare(relatedUnitNames.GetValueOrDefault(candidateId, candidateId.ToString()), relatedUnitNames.GetValueOrDefault(bestCandidate, bestCandidate.ToString()), StringComparison.OrdinalIgnoreCase) < 0))
|
||||
{
|
||||
bestCandidate = candidateId;
|
||||
bestCount = candidateCount;
|
||||
}
|
||||
}
|
||||
|
||||
unitInTemplateToBestRelatedUnit[unitInTemplateId] = bestCandidate;
|
||||
}
|
||||
// else: если candidates.Count == 0 (что маловероятно, если dto.Id гарантированно связан), то unitInTemplateId не будет в unitInTemplateToBestRelatedUnit
|
||||
}
|
||||
|
||||
logger.LogDebug("Разрешение конфликта завершено. Найдено {Count} однозначных назначений.", unitInTemplateToBestRelatedUnit.Count);
|
||||
|
||||
// 4. Построение reverseMapping на основе решённых конфликтов
|
||||
var reverseMapping = new Dictionary<Guid, List<Guid>>();
|
||||
foreach (var assignmentKvp in unitInTemplateToBestRelatedUnit)
|
||||
{
|
||||
var unitInTemplateId = assignmentKvp.Key;
|
||||
var bestRelatedUnitId = assignmentKvp.Value;
|
||||
|
||||
if (!reverseMapping.ContainsKey(bestRelatedUnitId))
|
||||
{
|
||||
reverseMapping[bestRelatedUnitId] = new List<Guid>();
|
||||
}
|
||||
reverseMapping[bestRelatedUnitId].Add(unitInTemplateId);
|
||||
}
|
||||
|
||||
return reverseMapping;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user