feat(aihitRelationshipsSyncer): Добавлена реализация MinRelationshipsThresholdPct для защиты базы данных от потери связей ЭК

This commit is contained in:
Mikhail Kuznetsov
2026-07-14 14:55:49 +10:00
parent 4c80e247f5
commit 497d241caf

View File

@@ -2,9 +2,9 @@
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using PARR.AIHITRelationshipsSyncer.Models; using PARR.AIHITRelationshipsSyncer.Models;
using PARR.AIHITRelationshipsSyncer.Services.Interfaces; using PARR.AIHITRelationshipsSyncer.Services.Interfaces;
using PARR.AIHITRelationshipsSyncer.Settings;
using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Domain.Entities.Unit; using PARR.Domain.Entities.Unit;
using PARR.Domain.Settings;
namespace PARR.AIHITRelationshipsSyncer.Services.Implementations namespace PARR.AIHITRelationshipsSyncer.Services.Implementations
{ {
@@ -12,25 +12,56 @@ namespace PARR.AIHITRelationshipsSyncer.Services.Implementations
{ {
private readonly ILogger<RelationshipsSyncService> logger; private readonly ILogger<RelationshipsSyncService> logger;
private readonly IUnitRepository unitService; private readonly IUnitRepository unitService;
private readonly WorkerSettings workerSettings; private readonly SettingsFromDb _settingsFromDb;
public RelationshipsSyncService( public RelationshipsSyncService(
ILogger<RelationshipsSyncService> logger, ILogger<RelationshipsSyncService> logger,
IUnitRepository unitService, IUnitRepository unitService,
WorkerSettings workerSettings SettingsFromDb settingsFromDb
) )
{ {
this.logger = logger; this.logger = logger;
this.unitService = unitService; this.unitService = unitService;
this.workerSettings = workerSettings; _settingsFromDb = settingsFromDb;
} }
public async Task SyncAsync(List<AihitData> aihitdata) public async Task SyncAsync(List<AihitData> aihitdata)
{ {
// 1. Сначала фильтруем и нормализуем входящий мусор
var validPairs = ValidateInput(aihitdata); var validPairs = ValidateInput(aihitdata);
// Если пришел пустой список, то и проверять порог нет смысла (лог уже записан внутри)
if (!validPairs.Any()) return; if (!validPairs.Any()) return;
// 2. FAIL-FAST: Быстро узнаем общее число связей в БД без выкачивания самих данных
int previousCount = await unitService.Get()
.SelectMany(u => u.ChildUnits)
.CountAsync();
// 3. SAFEGUARD COMPLIANCE: Проверяем защитный порог падения данных
if (previousCount > 0)
{
// 1. Считаем в decimal с абсолютной точностью
decimal exactPercentage = ((decimal)validPairs.Count / previousCount) * 100;
// 2. Округляем до 1 знака после запятой (например, 96.98% -> 97.0%)
// Это защитит от ложных срабатываний из-за пары недостающих связей на больших объемах
decimal currentPercentage = Math.Round(exactPercentage, 1, MidpointRounding.AwayFromZero);
// 3. Строгое сравнение (<) гарантирует пропуск при ровно 97% и работу "0" как выключателя
if (currentPercentage < _settingsFromDb.MinRelationshipsThresholdPct)
{
logger.LogWarning(
"Синхронизация отменена: количество полученных связей ниже порогового значения! " +
"Получено: {CurrentCount} ({CurrentPercentage:F1}%), ожидалось >= {Threshold}% от прошлого объема ({PreviousCount}).",
validPairs.Count, currentPercentage, _settingsFromDb.MinRelationshipsThresholdPct, previousCount);
return;
}
}
// 4. HAPPY PATH: Если проверка пройдена, выполняем тяжелую работу
var sourceUnitNames = ExtractUnitNames(validPairs); var sourceUnitNames = ExtractUnitNames(validPairs);
var existingUnits = await GetOrCreateUnitsAsync(sourceUnitNames); var existingUnits = await GetOrCreateUnitsAsync(sourceUnitNames);
@@ -42,6 +73,7 @@ namespace PARR.AIHITRelationshipsSyncer.Services.Implementations
await FinalCommitAsync(toRemove.Count, toAdd.Count); await FinalCommitAsync(toRemove.Count, toAdd.Count);
} }
private List<(string Parent, string Child)> ValidateInput(List<AihitData> data) private List<(string Parent, string Child)> ValidateInput(List<AihitData> data)
{ {
if (data == null || data.Count == 0) if (data == null || data.Count == 0)