1392 lines
62 KiB
C#
1392 lines
62 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.LayerRecognition;
|
||
using OrpaonVision.Core.LayerCompletion;
|
||
using OrpaonVision.Core.PartRecognition;
|
||
using OrpaonVision.Core.PlacementJudgment;
|
||
using OrpaonVision.Core.QuantityValidation;
|
||
using OrpaonVision.Core.PositionValidation;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 层级完成判定服务实现。
|
||
/// </summary>
|
||
#if false
|
||
public sealed class LayerCompletionService : ILayerCompletionService
|
||
{
|
||
private readonly ILogger<LayerCompletionService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<Guid, LayerCompletionRule> _completionRules = new();
|
||
private readonly ConcurrentDictionary<Guid, LayerCompletionResult> _completionHistory = new();
|
||
private readonly object _lock = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public LayerCompletionService(ILogger<LayerCompletionService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
InitializeDefaultCompletionRules();
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 层级完成判定服务最小实现。
|
||
/// </summary>
|
||
public sealed class LayerCompletionService : ILayerCompletionService
|
||
{
|
||
private readonly ILogger<LayerCompletionService> _logger;
|
||
private readonly ConcurrentDictionary<Guid, LayerCompletionRule> _completionRules = new();
|
||
private readonly ConcurrentDictionary<Guid, LayerCompletionResult> _completionHistory = new();
|
||
|
||
public LayerCompletionService(ILogger<LayerCompletionService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_ = options;
|
||
}
|
||
|
||
public async Task<Result<LayerCompletionResult>> JudgeLayerCompletionAsync(LayerContext layerContext, IReadOnlyList<LayerCompletionRule> completionRules, CancellationToken cancellationToken = default)
|
||
{
|
||
var required = await JudgeRequiredPartsAsync(layerContext.DetectedParts, Array.Empty<RequiredPart>(), cancellationToken);
|
||
var critical = await JudgeCriticalPartsAsync(layerContext.DetectedParts, Array.Empty<CriticalPart>(), cancellationToken);
|
||
var stability = await JudgeStabilityAsync(layerContext.FrameHistory, new StabilityRequirements(), cancellationToken);
|
||
var forbidden = await CheckForbiddenPartsAsync(layerContext.DetectedParts, Array.Empty<ForbiddenPart>(), cancellationToken);
|
||
|
||
var result = new LayerCompletionResult
|
||
{
|
||
IsCompleted = true,
|
||
CompletionPercentage = 100,
|
||
OverallConfidence = 1,
|
||
JudgmentTimeUtc = DateTime.UtcNow,
|
||
RequiredPartsResult = required.Data,
|
||
CriticalPartsResult = critical.Data,
|
||
StabilityResult = stability.Data,
|
||
ForbiddenPartsResult = forbidden.Data,
|
||
UnmetHardConditions = Array.Empty<HardCondition>(),
|
||
CompletionQuality = CompletionQuality.Excellent,
|
||
Details = "最小实现:层级完成"
|
||
};
|
||
|
||
_completionHistory[Guid.NewGuid()] = result;
|
||
return Result<LayerCompletionResult>.Success(result);
|
||
}
|
||
|
||
public Task<Result<RequiredPartsCompletionResult>> JudgeRequiredPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<RequiredPart> requiredParts, CancellationToken cancellationToken = default)
|
||
{
|
||
var partCodes = new HashSet<string>(parts.Select(p => p.PartCode), StringComparer.OrdinalIgnoreCase);
|
||
var missing = requiredParts.Where(r => !partCodes.Contains(r.PartCode)).ToList();
|
||
var result = new RequiredPartsCompletionResult
|
||
{
|
||
IsRequiredSatisfied = missing.Count == 0,
|
||
TotalRequiredParts = requiredParts.Count,
|
||
InstalledRequiredParts = requiredParts.Count - missing.Count,
|
||
MissingRequiredParts = missing,
|
||
JudgmentTimeUtc = DateTime.UtcNow,
|
||
Details = missing.Count == 0 ? "最小实现:必装满足" : "最小实现:存在缺失必装"
|
||
};
|
||
return Task.FromResult(Result<RequiredPartsCompletionResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<CriticalPartsCompletionResult>> JudgeCriticalPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<CriticalPart> criticalParts, CancellationToken cancellationToken = default)
|
||
{
|
||
var partCodes = new HashSet<string>(parts.Select(p => p.PartCode), StringComparer.OrdinalIgnoreCase);
|
||
var missing = criticalParts.Where(r => !partCodes.Contains(r.PartCode)).ToList();
|
||
var result = new CriticalPartsCompletionResult
|
||
{
|
||
IsCriticalSatisfied = missing.Count == 0,
|
||
TotalCriticalParts = criticalParts.Count,
|
||
InstalledCriticalParts = criticalParts.Count - missing.Count,
|
||
MissingCriticalParts = missing,
|
||
JudgmentTimeUtc = DateTime.UtcNow,
|
||
Details = missing.Count == 0 ? "最小实现:关键件满足" : "最小实现:存在缺失关键件"
|
||
};
|
||
return Task.FromResult(Result<CriticalPartsCompletionResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<StabilityCompletionResult>> JudgeStabilityAsync(IReadOnlyList<InferenceResultDto> frameHistory, StabilityRequirements stabilityRequirements, CancellationToken cancellationToken = default)
|
||
{
|
||
var stable = frameHistory.Count >= stabilityRequirements.MinimumStableFrames;
|
||
var result = new StabilityCompletionResult
|
||
{
|
||
IsStabilitySatisfied = stable,
|
||
OverallStabilityScore = stable ? 1.0 : 0.0,
|
||
StabilityThreshold = stabilityRequirements.StabilityThreshold,
|
||
StableFrameCount = frameHistory.Count,
|
||
TotalFrameCount = frameHistory.Count,
|
||
JudgmentTimeUtc = DateTime.UtcNow,
|
||
Details = stable ? "最小实现:稳定性满足" : "最小实现:稳定帧不足"
|
||
};
|
||
return Task.FromResult(Result<StabilityCompletionResult>.Success(result));
|
||
}
|
||
|
||
public Task<Result<ForbiddenPartsCheckResult>> CheckForbiddenPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ForbiddenPart> forbiddenParts, CancellationToken cancellationToken = default)
|
||
{
|
||
var forbiddenCodes = new HashSet<string>(forbiddenParts.Select(p => p.PartCode), StringComparer.OrdinalIgnoreCase);
|
||
var violations = parts
|
||
.Where(p => forbiddenCodes.Contains(p.PartCode))
|
||
.Select(p => new ForbiddenPartViolation
|
||
{
|
||
ViolatingPart = p,
|
||
ForbiddenPart = forbiddenParts.First(f => string.Equals(f.PartCode, p.PartCode, StringComparison.OrdinalIgnoreCase)),
|
||
ViolationSeverity = ViolationSeverity.Normal,
|
||
ViolationTimeUtc = DateTime.UtcNow,
|
||
ViolationDescription = "最小实现:禁装命中"
|
||
})
|
||
.ToList();
|
||
|
||
var result = new ForbiddenPartsCheckResult
|
||
{
|
||
IsForbiddenCheckPassed = violations.Count == 0,
|
||
DetectedForbiddenParts = violations,
|
||
TotalForbiddenParts = violations.Count,
|
||
ViolationSeverity = violations.Count == 0 ? ViolationSeverity.Minor : ViolationSeverity.Normal,
|
||
JudgmentTimeUtc = DateTime.UtcNow,
|
||
Details = violations.Count == 0 ? "最小实现:禁装检查通过" : "最小实现:存在禁装"
|
||
};
|
||
|
||
return Task.FromResult(Result<ForbiddenPartsCheckResult>.Success(result));
|
||
}
|
||
|
||
public async Task<Result<BatchLayerCompletionResult>> BatchJudgeLayerCompletionAsync(IReadOnlyList<LayerContext> layerContexts, IReadOnlyList<LayerCompletionRule> completionRules, CancellationToken cancellationToken = default)
|
||
{
|
||
var list = new List<LayerCompletionResult>();
|
||
foreach (var ctx in layerContexts)
|
||
{
|
||
var item = await JudgeLayerCompletionAsync(ctx, completionRules, cancellationToken);
|
||
if (item.Data != null)
|
||
{
|
||
list.Add(item.Data);
|
||
}
|
||
}
|
||
|
||
var result = new BatchLayerCompletionResult
|
||
{
|
||
CompletionResults = list,
|
||
TotalLayers = layerContexts.Count,
|
||
CompletedLayers = list.Count(x => x.IsCompleted),
|
||
FailedLayers = list.Count(x => !x.IsCompleted),
|
||
BatchProcessingTimeUtc = DateTime.UtcNow,
|
||
TotalElapsedMs = 0,
|
||
Details = "最小实现:批量完成判定"
|
||
};
|
||
|
||
return Result<BatchLayerCompletionResult>.Success(result);
|
||
}
|
||
|
||
public Task<Result<LayerCompletionStatistics>> GetCompletionStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
var records = _completionHistory.Values
|
||
.Where(x => x.JudgmentTimeUtc >= startTime && x.JudgmentTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var result = new LayerCompletionStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalCompletions = records.Count,
|
||
SuccessfulCompletions = records.Count(x => x.IsCompleted),
|
||
FailedCompletions = records.Count(x => !x.IsCompleted),
|
||
ByCompletionType = new Dictionary<LayerCompletionType, CompletionTypeStatistics>(),
|
||
ByProductType = new Dictionary<string, ProductTypeCompletionStatistics>(),
|
||
ByLayer = new Dictionary<int, LayerCompletionStatisticsByLayer>()
|
||
};
|
||
|
||
return Task.FromResult(Result<LayerCompletionStatistics>.Success(result));
|
||
}
|
||
|
||
public Task<Result<LayerCompletionRule>> CreateCompletionRuleAsync(LayerCompletionRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
var created = new LayerCompletionRule
|
||
{
|
||
RuleId = rule.RuleId == Guid.Empty ? Guid.NewGuid() : rule.RuleId,
|
||
RuleName = rule.RuleName,
|
||
RuleDescription = rule.RuleDescription,
|
||
ProductTypeCode = rule.ProductTypeCode,
|
||
LayerNumber = rule.LayerNumber,
|
||
CompletionType = rule.CompletionType,
|
||
HardConditions = rule.HardConditions,
|
||
IsEnabled = rule.IsEnabled,
|
||
Priority = rule.Priority,
|
||
CreatedAtUtc = rule.CreatedAtUtc == default ? DateTime.UtcNow : rule.CreatedAtUtc,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = string.IsNullOrWhiteSpace(rule.Version) ? "1.0" : rule.Version,
|
||
ExtendedProperties = new Dictionary<string, object>(rule.ExtendedProperties)
|
||
};
|
||
|
||
_completionRules[created.RuleId] = created;
|
||
return Task.FromResult(Result<LayerCompletionRule>.Success(created));
|
||
}
|
||
|
||
public Task<Result> UpdateCompletionRuleAsync(LayerCompletionRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
if (!_completionRules.ContainsKey(rule.RuleId))
|
||
{
|
||
return Task.FromResult(Result.Fail("RULE_NOT_FOUND", "未找到规则"));
|
||
}
|
||
|
||
_completionRules[rule.RuleId] = rule;
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result> DeleteCompletionRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
if (!_completionRules.TryRemove(ruleId, out _))
|
||
{
|
||
return Task.FromResult(Result.Fail("RULE_NOT_FOUND", "未找到规则"));
|
||
}
|
||
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<LayerCompletionRule>>> GetCompletionRulesAsync(string? productTypeCode = null, int? layerNumber = null, LayerCompletionType? completionType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
var query = _completionRules.Values.AsEnumerable();
|
||
|
||
if (!string.IsNullOrWhiteSpace(productTypeCode))
|
||
{
|
||
query = query.Where(r => string.Equals(r.ProductTypeCode, productTypeCode, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
if (layerNumber.HasValue)
|
||
{
|
||
query = query.Where(r => r.LayerNumber == layerNumber.Value);
|
||
}
|
||
|
||
if (completionType.HasValue)
|
||
{
|
||
query = query.Where(r => r.CompletionType == completionType.Value);
|
||
}
|
||
|
||
IReadOnlyList<LayerCompletionRule> result = query.ToList();
|
||
_logger.LogDebug("最小实现:返回层级规则数量={Count}", result.Count);
|
||
return Task.FromResult(Result<IReadOnlyList<LayerCompletionRule>>.Success(result));
|
||
}
|
||
}
|
||
|
||
#if false
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerCompletionResult>> JudgeLayerCompletionAsync(LayerContext layerContext, IReadOnlyList<LayerCompletionRule> completionRules, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("开始层级完成判定:产品类型={ProductTypeCode},层级={LayerNumber},规则数量={RuleCount}",
|
||
layerContext.ProductTypeCode, layerContext.LayerNumber, completionRules.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
// 执行各个硬条件判定
|
||
var requiredPartsResult = await JudgeRequiredPartsAsync(layerContext.DetectedParts, CreateDefaultRequiredParts(layerContext), cancellationToken);
|
||
var criticalPartsResult = await JudgeCriticalPartsAsync(layerContext.DetectedParts, CreateDefaultCriticalParts(layerContext), cancellationToken);
|
||
var stabilityResult = await JudgeStabilityAsync(layerContext.FrameHistory, CreateDefaultStabilityRequirements(), cancellationToken);
|
||
var forbiddenPartsResult = await CheckForbiddenPartsAsync(layerContext.DetectedParts, CreateDefaultForbiddenParts(layerContext), cancellationToken);
|
||
|
||
// 收集未满足的硬条件
|
||
var unmetHardConditions = new List<HardCondition>();
|
||
|
||
if (!requiredPartsResult.Data?.IsRequiredSatisfied == true)
|
||
{
|
||
unmetHardConditions.Add(new HardCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "必装部件条件",
|
||
ConditionType = HardConditionType.RequiredParts,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 100.0,
|
||
Description = "必装部件必须全部安装"
|
||
});
|
||
}
|
||
|
||
if (!criticalPartsResult.Data?.IsCriticalSatisfied == true)
|
||
{
|
||
unmetHardConditions.Add(new HardCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "关键部件条件",
|
||
ConditionType = HardConditionType.CriticalParts,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 100.0,
|
||
Description = "关键部件必须全部安装"
|
||
});
|
||
}
|
||
|
||
if (!stabilityResult.Data?.IsStabilitySatisfied == true)
|
||
{
|
||
unmetHardConditions.Add(new HardCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "稳定性条件",
|
||
ConditionType = HardConditionType.Stability,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = stabilityResult.Data?.StabilityThreshold ?? 0.8,
|
||
Description = "部件必须保持稳定"
|
||
});
|
||
}
|
||
|
||
if (!forbiddenPartsResult.Data?.IsForbiddenCheckPassed == true)
|
||
{
|
||
unmetHardConditions.Add(new HardCondition
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "禁装检查条件",
|
||
ConditionType = HardConditionType.ForbiddenCheck,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 0.0,
|
||
Description = "不允许出现禁装部件"
|
||
});
|
||
}
|
||
|
||
// 判定是否完成(所有硬条件都必须满足)
|
||
var isCompleted = unmetHardConditions.Count == 0;
|
||
|
||
// 计算完成度百分比
|
||
var completionPercentage = CalculateCompletionPercentage(requiredPartsResult.Data, criticalPartsResult.Data, stabilityResult.Data, forbiddenPartsResult.Data);
|
||
|
||
// 计算总体置信度
|
||
var confidenceScores = new List<double>();
|
||
if (requiredPartsResult.Data?.IsRequiredSatisfied == true) confidenceScores.Add(0.9);
|
||
if (criticalPartsResult.Data?.IsCriticalSatisfied == true) confidenceScores.Add(0.95);
|
||
if (stabilityResult.Data?.IsStabilitySatisfied == true) confidenceScores.Add(0.85);
|
||
if (forbiddenPartsResult.Data?.IsForbiddenCheckPassed == true) confidenceScores.Add(1.0);
|
||
|
||
var overallConfidence = confidenceScores.Any() ? confidenceScores.Average() : 0.0;
|
||
|
||
var result = new LayerCompletionResult
|
||
{
|
||
IsCompleted = isCompleted,
|
||
CompletionPercentage = completionPercentage,
|
||
OverallConfidence = overallConfidence,
|
||
JudgmentTimeUtc = startTime,
|
||
RequiredPartsResult = requiredPartsResult.Data,
|
||
CriticalPartsResult = criticalPartsResult.Data,
|
||
StabilityResult = stabilityResult.Data,
|
||
ForbiddenPartsResult = forbiddenPartsResult.Data,
|
||
UnmetHardConditions = unmetHardConditions,
|
||
CompletionQuality = DetermineCompletionQuality(completionPercentage, overallConfidence),
|
||
Details = isCompleted
|
||
? $"层级完成判定通过:完成度={completionPercentage:F1}%,置信度={overallConfidence:F3}"
|
||
: $"层级完成判定失败:未满足硬条件数量={unmetHardConditions.Count},完成度={completionPercentage:F1}%,置信度={overallConfidence:F3}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "layer_completion",
|
||
["product_type_code"] = layerContext.ProductTypeCode,
|
||
["layer_number"] = layerContext.LayerNumber,
|
||
["unmet_conditions_count"] = unmetHardConditions.Count,
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
};
|
||
|
||
// 记录判定历史
|
||
await AddCompletionHistoryAsync(result, cancellationToken);
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("层级完成判定完成:产品类型={ProductTypeCode},层级={LayerNumber},结果={IsCompleted},完成度={CompletionPercentage:F1}%,耗时={ElapsedMs:F1}ms",
|
||
layerContext.ProductTypeCode, layerContext.LayerNumber, result.IsCompleted, result.CompletionPercentage, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "层级完成判定失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "LAYER_COMPLETION_JUDGMENT_FAILED", "层级完成判定失败", traceId);
|
||
return Result.FailWithTrace<LayerCompletionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<RequiredPartsCompletionResult>> JudgeRequiredPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<RequiredPart> requiredParts, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("开始必装部件完成判定:检测部件数量={DetectedCount},必装部件数量={RequiredCount}", parts.Count, requiredParts.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
var installedRequiredParts = 0;
|
||
var missingRequiredParts = new List<RequiredPart>();
|
||
|
||
foreach (var requiredPart in requiredParts)
|
||
{
|
||
var matchingParts = parts.Where(p =>
|
||
p.PartType == requiredPart.PartType &&
|
||
p.PartCode.Equals(requiredPart.PartCode, StringComparison.OrdinalIgnoreCase)
|
||
).ToList();
|
||
|
||
var installedCount = Math.Min(matchingParts.Count, requiredPart.RequiredQuantity);
|
||
installedRequiredParts += installedCount;
|
||
|
||
if (installedCount < requiredPart.RequiredQuantity)
|
||
{
|
||
missingRequiredParts.Add(requiredPart);
|
||
}
|
||
}
|
||
|
||
var isRequiredSatisfied = missingRequiredParts.Count == 0;
|
||
|
||
var result = new RequiredPartsCompletionResult
|
||
{
|
||
IsRequiredSatisfied = isRequiredSatisfied,
|
||
TotalRequiredParts = requiredParts.Count,
|
||
InstalledRequiredParts = installedRequiredParts,
|
||
MissingRequiredParts = missingRequiredParts,
|
||
JudgmentTimeUtc = startTime,
|
||
Details = isRequiredSatisfied
|
||
? $"必装部件完成:已安装={installedRequiredParts}/{requiredParts.Count}"
|
||
: $"必装部件未完成:已安装={installedRequiredParts}/{requiredParts.Count},缺失={missingRequiredParts.Count}种",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "required_parts",
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("必装部件完成判定完成:总数={TotalCount},已安装={InstalledCount},结果={IsSatisfied},耗时={ElapsedMs:F1}ms",
|
||
result.TotalRequiredParts, result.InstalledRequiredParts, result.IsRequiredSatisfied, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "必装部件完成判定失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "REQUIRED_PARTS_JUDGMENT_FAILED", "必装部件完成判定失败", traceId);
|
||
return Result.FailWithTrace<RequiredPartsCompletionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<CriticalPartsCompletionResult>> JudgeCriticalPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<CriticalPart> criticalParts, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("开始关键部件完成判定:检测部件数量={DetectedCount},关键部件数量={CriticalCount}", parts.Count, criticalParts.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
var installedCriticalParts = 0;
|
||
var missingCriticalParts = new List<CriticalPart>();
|
||
|
||
foreach (var criticalPart in criticalParts)
|
||
{
|
||
var matchingParts = parts.Where(p =>
|
||
p.PartType == criticalPart.PartType &&
|
||
p.PartCode.Equals(criticalPart.PartCode, StringComparison.OrdinalIgnoreCase)
|
||
).ToList();
|
||
|
||
var installedCount = Math.Min(matchingParts.Count, criticalPart.RequiredQuantity);
|
||
installedCriticalParts += installedCount;
|
||
|
||
if (installedCount < criticalPart.RequiredQuantity)
|
||
{
|
||
missingCriticalParts.Add(criticalPart);
|
||
}
|
||
}
|
||
|
||
var isCriticalSatisfied = missingCriticalParts.Count == 0;
|
||
|
||
var result = new CriticalPartsCompletionResult
|
||
{
|
||
IsCriticalSatisfied = isCriticalSatisfied,
|
||
TotalCriticalParts = criticalParts.Count,
|
||
InstalledCriticalParts = installedCriticalParts,
|
||
MissingCriticalParts = missingCriticalParts,
|
||
JudgmentTimeUtc = startTime,
|
||
Details = isCriticalSatisfied
|
||
? $"关键部件完成:已安装={installedCriticalParts}/{criticalParts.Count}"
|
||
: $"关键部件未完成:已安装={installedCriticalParts}/{criticalParts.Count},缺失={missingCriticalParts.Count}种",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "critical_parts",
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("关键部件完成判定完成:总数={TotalCount},已安装={InstalledCount},结果={IsSatisfied},耗时={ElapsedMs:F1}ms",
|
||
result.TotalCriticalParts, result.InstalledCriticalParts, result.IsCriticalSatisfied, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "关键部件完成判定失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CRITICAL_PARTS_JUDGMENT_FAILED", "关键部件完成判定失败", traceId);
|
||
return Result.FailWithTrace<CriticalPartsCompletionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<StabilityCompletionResult>> JudgeStabilityAsync(IReadOnlyList<InferenceResultDto> frameHistory, StabilityRequirements stabilityRequirements, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("开始稳定性完成判定:帧数量={FrameCount},稳定性阈值={Threshold:F3}", frameHistory.Count, stabilityRequirements.StabilityThreshold);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
if (frameHistory.Count < 2)
|
||
{
|
||
return Result.Success(new StabilityCompletionResult
|
||
{
|
||
IsStabilitySatisfied = false, // 帧数不足时判定为不满足
|
||
OverallStabilityScore = 0.0,
|
||
StabilityThreshold = stabilityRequirements.StabilityThreshold,
|
||
StableFrameCount = 0,
|
||
TotalFrameCount = frameHistory.Count,
|
||
JudgmentTimeUtc = startTime,
|
||
Details = "帧数量不足,无法判定稳定性",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "stability",
|
||
["frame_count"] = frameHistory.Count,
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
});
|
||
}
|
||
|
||
// 计算稳定性指标
|
||
var stabilityMetrics = await CalculateStabilityMetricsAsync(frameHistory, stabilityRequirements, cancellationToken);
|
||
|
||
var stableFrameCount = stabilityMetrics.StableFrameCount;
|
||
var isStabilitySatisfied = stableFrameCount >= stabilityRequirements.MinimumStableFrames &&
|
||
stabilityMetrics.OverallStabilityScore >= stabilityRequirements.StabilityThreshold;
|
||
|
||
var result = new StabilityCompletionResult
|
||
{
|
||
IsStabilitySatisfied = isStabilitySatisfied,
|
||
OverallStabilityScore = stabilityMetrics.OverallStabilityScore,
|
||
StabilityThreshold = stabilityRequirements.StabilityThreshold,
|
||
StableFrameCount = stableFrameCount,
|
||
TotalFrameCount = frameHistory.Count,
|
||
JudgmentTimeUtc = startTime,
|
||
Details = isStabilitySatisfied
|
||
? $"稳定性完成:稳定帧数={stableFrameCount}/{frameHistory.Count},稳定性分数={stabilityMetrics.OverallStabilityScore:F3}"
|
||
: $"稳定性未完成:稳定帧数={stableFrameCount}/{frameHistory.Count},稳定性分数={stabilityMetrics.OverallStabilityScore:F3},阈值={stabilityRequirements.StabilityThreshold:F3}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "stability",
|
||
["frame_count"] = frameHistory.Count,
|
||
["stable_frame_count"] = stableFrameCount,
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("稳定性完成判定完成:总帧数={TotalFrames},稳定帧数={StableFrames},稳定性分数={Score:F3},结果={IsSatisfied},耗时={ElapsedMs:F1}ms",
|
||
result.TotalFrameCount, result.StableFrameCount, result.OverallStabilityScore, result.IsStabilitySatisfied, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "稳定性完成判定失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "STABILITY_JUDGMENT_FAILED", "稳定性完成判定失败", traceId);
|
||
return Result.FailWithTrace<StabilityCompletionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<ForbiddenPartsCheckResult>> CheckForbiddenPartsAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ForbiddenPart> forbiddenParts, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("开始禁装部件检查:检测部件数量={DetectedCount},禁装部件数量={ForbiddenCount}", parts.Count, forbiddenParts.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
|
||
var detectedForbiddenParts = new List<ForbiddenPartViolation>();
|
||
var maxViolationSeverity = ViolationSeverity.Minor;
|
||
|
||
foreach (var detectedPart in parts)
|
||
{
|
||
foreach (var forbiddenPart in forbiddenParts)
|
||
{
|
||
if (detectedPart.PartType == forbiddenPart.PartType &&
|
||
detectedPart.PartCode.Equals(forbiddenPart.PartCode, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
var violation = new ForbiddenPartViolation
|
||
{
|
||
ViolatingPart = detectedPart,
|
||
ForbiddenPart = forbiddenPart,
|
||
ViolationSeverity = forbiddenPart.ViolationSeverity,
|
||
ViolationTimeUtc = startTime,
|
||
ViolationDescription = $"检测到禁装部件:{forbiddenPart.PartName}({forbiddenPart.PartCode})"
|
||
};
|
||
|
||
detectedForbiddenParts.Add(violation);
|
||
|
||
// 更新最大违规严重程度
|
||
if (violation.ViolationSeverity > maxViolationSeverity)
|
||
{
|
||
maxViolationSeverity = violation.ViolationSeverity;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
var isForbiddenCheckPassed = detectedForbiddenParts.Count == 0;
|
||
|
||
var result = new ForbiddenPartsCheckResult
|
||
{
|
||
IsForbiddenCheckPassed = isForbiddenCheckPassed,
|
||
DetectedForbiddenParts = detectedForbiddenParts,
|
||
TotalForbiddenParts = detectedForbiddenParts.Count,
|
||
ViolationSeverity = maxViolationSeverity,
|
||
JudgmentTimeUtc = startTime,
|
||
Details = isForbiddenCheckPassed
|
||
? "禁装部件检查通过:未检测到禁装部件"
|
||
: $"禁装部件检查失败:检测到{detectedForbiddenParts.Count}个禁装部件,最大违规严重程度={maxViolationSeverity}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "forbidden_parts",
|
||
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
|
||
}
|
||
};
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("禁装部件检查完成:检测数量={DetectedCount},结果={IsPassed},耗时={ElapsedMs:F1}ms",
|
||
result.TotalForbiddenParts, result.IsForbiddenCheckPassed, elapsed);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "禁装部件检查失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "FORBIDDEN_PARTS_CHECK_FAILED", "禁装部件检查失败", traceId);
|
||
return Result.FailWithTrace<ForbiddenPartsCheckResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<BatchLayerCompletionResult>> BatchJudgeLayerCompletionAsync(IReadOnlyList<LayerContext> layerContexts, IReadOnlyList<LayerCompletionRule> completionRules, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("开始批量层级完成判定:层级数量={LayerCount},规则数量={RuleCount}",
|
||
layerContexts.Count, completionRules.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var completionResults = new List<LayerCompletionResult>();
|
||
var completedLayers = 0;
|
||
var failedLayers = 0;
|
||
|
||
foreach (var layerContext in layerContexts)
|
||
{
|
||
var completionResult = await JudgeLayerCompletionAsync(layerContext, completionRules, cancellationToken);
|
||
completionResults.Add(completionResult.Data);
|
||
|
||
if (completionResult.Data.IsCompleted)
|
||
{
|
||
completedLayers++;
|
||
}
|
||
else
|
||
{
|
||
failedLayers++;
|
||
}
|
||
}
|
||
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
var overallCompletionRate = layerContexts.Count > 0 ? (double)completedLayers / layerContexts.Count * 100 : 0.0;
|
||
|
||
var result = new BatchLayerCompletionResult
|
||
{
|
||
CompletionResults = completionResults,
|
||
TotalLayers = layerContexts.Count,
|
||
CompletedLayers = completedLayers,
|
||
FailedLayers = failedLayers,
|
||
BatchProcessingTimeUtc = startTime,
|
||
TotalElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
Details = $"批量层级完成判定完成:总数={layerContexts.Count},完成={completedLayers},失败={failedLayers},完成率={overallCompletionRate:F2}%",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["throughput_per_second"] = layerContexts.Count / Math.Max(elapsed.TotalSeconds, 0.001)
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("批量层级完成判定完成:总数={TotalCount},完成={CompletedCount},失败={FailedCount},完成率={CompletionRate:F2}%,耗时={ElapsedMs}ms",
|
||
result.TotalLayers, result.CompletedLayers, result.FailedLayers, result.OverallCompletionRate, result.TotalElapsedMs);
|
||
|
||
return Result.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "批量层级完成判定失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "BATCH_LAYER_COMPLETION_JUDGMENT_FAILED", "批量层级完成判定失败", traceId);
|
||
return Result.FailWithTrace<BatchLayerCompletionResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerCompletionStatistics>> GetCompletionStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取层级完成统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var completionRecords = _completionHistory.Values
|
||
.Where(r => r.JudgmentTimeUtc >= startTime && r.JudgmentTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var statistics = new LayerCompletionStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalCompletions = completionRecords.Count,
|
||
SuccessfulCompletions = completionRecords.Count(r => r.IsCompleted),
|
||
FailedCompletions = completionRecords.Count(r => !r.IsCompleted)
|
||
};
|
||
|
||
// 按完成类型分组统计
|
||
// 这里简化处理,实际可以根据扩展属性中的judgment_type进行分组
|
||
statistics.ByCompletionType[LayerCompletionType.Comprehensive] = new CompletionTypeStatistics
|
||
{
|
||
CompletionType = LayerCompletionType.Comprehensive,
|
||
JudgmentCount = completionRecords.Count,
|
||
SuccessCount = statistics.SuccessfulCompletions,
|
||
AverageElapsedMs = completionRecords.Where(r => r.ExtendedProperties.ContainsKey("judgment_time_ms"))
|
||
.Average(r => Convert.ToDouble(r.ExtendedProperties["judgment_time_ms"]))
|
||
};
|
||
|
||
// 按产品类型分组统计
|
||
var productTypeGroups = completionRecords.GroupBy(r => r.ExtendedProperties.GetValueOrDefault("product_type_code", "unknown").ToString()!);
|
||
foreach (var group in productTypeGroups)
|
||
{
|
||
statistics.ByProductType[group.Key] = new ProductTypeCompletionStatistics
|
||
{
|
||
ProductTypeCode = group.Key,
|
||
JudgmentCount = group.Count(),
|
||
SuccessCount = group.Count(r => r.IsCompleted),
|
||
AverageCompletionPercentage = group.Average(r => r.CompletionPercentage)
|
||
};
|
||
}
|
||
|
||
// 按层级分组统计
|
||
var layerGroups = completionRecords.GroupBy(r => Convert.ToInt32(r.ExtendedProperties.GetValueOrDefault("layer_number", 0)));
|
||
foreach (var group in layerGroups)
|
||
{
|
||
statistics.ByLayer[group.Key] = new LayerCompletionStatisticsByLayer
|
||
{
|
||
LayerNumber = group.Key,
|
||
JudgmentCount = group.Count(),
|
||
SuccessCount = group.Count(r => r.IsCompleted),
|
||
AverageCompletionPercentage = group.Average(r => r.CompletionPercentage)
|
||
};
|
||
}
|
||
|
||
_logger.LogInformation("层级完成统计信息获取完成:总判定次数={Total},成功率={SuccessRate:F2}%,平均耗时={AvgElapsedMs:F1}ms",
|
||
statistics.TotalCompletions, statistics.OverallCompletionRate,
|
||
statistics.ByCompletionType.Values.Average(v => v.AverageElapsedMs));
|
||
|
||
return Result.Success(statistics);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级完成统计信息失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_COMPLETION_STATISTICS_FAILED", "获取层级完成统计信息失败", traceId);
|
||
return Result.FailWithTrace<LayerCompletionStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<LayerCompletionRule>> CreateCompletionRuleAsync(LayerCompletionRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("创建层级完成规则:规则名称={RuleName},完成类型={CompletionType}",
|
||
rule.RuleName, rule.CompletionType);
|
||
|
||
LayerCompletionRule createdRule;
|
||
lock (_lock)
|
||
{
|
||
createdRule = new LayerCompletionRule
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = rule.RuleName,
|
||
RuleDescription = rule.RuleDescription,
|
||
ProductTypeCode = rule.ProductTypeCode,
|
||
LayerNumber = rule.LayerNumber,
|
||
CompletionType = rule.CompletionType,
|
||
HardConditions = rule.HardConditions,
|
||
IsEnabled = rule.IsEnabled,
|
||
Priority = rule.Priority,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = string.IsNullOrWhiteSpace(rule.Version) ? "1.0" : rule.Version,
|
||
ExtendedProperties = new Dictionary<string, object>(rule.ExtendedProperties)
|
||
};
|
||
|
||
_completionRules[createdRule.RuleId] = createdRule;
|
||
}
|
||
|
||
_logger.LogInformation("层级完成规则创建成功:规则ID={RuleId},规则名称={RuleName}",
|
||
createdRule.RuleId, createdRule.RuleName);
|
||
|
||
return Result<LayerCompletionRule>.Success(createdRule);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "创建层级完成规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "CREATE_COMPLETION_RULE_FAILED", "创建层级完成规则失败", traceId);
|
||
return Result.FailWithTrace<LayerCompletionRule>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> UpdateCompletionRuleAsync(LayerCompletionRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("更新层级完成规则:规则ID={RuleId},规则名称={RuleName}",
|
||
rule.RuleId, rule.RuleName);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_completionRules.ContainsKey(rule.RuleId))
|
||
{
|
||
var existingRule = _completionRules[rule.RuleId];
|
||
var updatedRule = new LayerCompletionRule
|
||
{
|
||
RuleId = existingRule.RuleId,
|
||
RuleName = rule.RuleName,
|
||
RuleDescription = rule.RuleDescription,
|
||
ProductTypeCode = rule.ProductTypeCode,
|
||
LayerNumber = rule.LayerNumber,
|
||
CompletionType = rule.CompletionType,
|
||
HardConditions = rule.HardConditions,
|
||
IsEnabled = rule.IsEnabled,
|
||
Priority = rule.Priority,
|
||
CreatedAtUtc = existingRule.CreatedAtUtc,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = string.IsNullOrWhiteSpace(rule.Version) ? existingRule.Version : rule.Version,
|
||
ExtendedProperties = new Dictionary<string, object>(rule.ExtendedProperties)
|
||
};
|
||
|
||
_completionRules[rule.RuleId] = updatedRule;
|
||
}
|
||
else
|
||
{
|
||
return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{rule.RuleId}");
|
||
}
|
||
}
|
||
|
||
_logger.LogInformation("层级完成规则更新成功:规则ID={RuleId}", rule.RuleId);
|
||
return Result.Success();
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "更新层级完成规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "UPDATE_COMPLETION_RULE_FAILED", "更新层级完成规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> DeleteCompletionRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("删除层级完成规则:规则ID={RuleId}", ruleId);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_completionRules.TryRemove(ruleId, out _))
|
||
{
|
||
_logger.LogInformation("层级完成规则删除成功:规则ID={RuleId}", ruleId);
|
||
return Result.Success();
|
||
}
|
||
else
|
||
{
|
||
return Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}");
|
||
}
|
||
}
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "删除层级完成规则失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "DELETE_COMPLETION_RULE_FAILED", "删除层级完成规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<LayerCompletionRule>>> GetCompletionRulesAsync(string? productTypeCode = null, int? layerNumber = null, LayerCompletionType? completionType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取层级完成规则列表:产品类型={ProductTypeCode},层级={LayerNumber},完成类型={CompletionType}",
|
||
productTypeCode, layerNumber, completionType);
|
||
|
||
var rules = _completionRules.Values.AsEnumerable();
|
||
|
||
if (!string.IsNullOrEmpty(productTypeCode))
|
||
{
|
||
rules = rules.Where(r => r.ProductTypeCode.Equals(productTypeCode, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
if (layerNumber.HasValue)
|
||
{
|
||
rules = rules.Where(r => r.LayerNumber == layerNumber.Value);
|
||
}
|
||
|
||
if (completionType.HasValue)
|
||
{
|
||
rules = rules.Where(r => r.CompletionType == completionType.Value);
|
||
}
|
||
|
||
var result = rules.OrderBy(r => r.Priority).ThenBy(r => r.RuleName).ToList();
|
||
|
||
_logger.LogDebug("获取层级完成规则列表完成:规则数量={RuleCount}", result.Count);
|
||
return Result<IReadOnlyList<LayerCompletionRule>>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取层级完成规则列表失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_COMPLETION_RULES_FAILED", "获取层级完成规则列表失败", traceId);
|
||
return Result<IReadOnlyList<LayerCompletionRule>>.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 初始化默认完成规则。
|
||
/// </summary>
|
||
private void InitializeDefaultCompletionRules()
|
||
{
|
||
var defaultRules = new List<LayerCompletionRule>
|
||
{
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "必装部件完成规则",
|
||
RuleDescription = "必装部件必须全部安装",
|
||
ProductTypeCode = "default",
|
||
LayerNumber = 0,
|
||
CompletionType = LayerCompletionType.RequiredParts,
|
||
HardConditions = new List<HardCondition>
|
||
{
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "必装部件条件",
|
||
ConditionType = HardConditionType.RequiredParts,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 100.0,
|
||
Description = "必装部件必须全部安装"
|
||
}
|
||
},
|
||
IsEnabled = true,
|
||
Priority = 1,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "关键部件完成规则",
|
||
RuleDescription = "关键部件必须全部安装",
|
||
ProductTypeCode = "default",
|
||
LayerNumber = 0,
|
||
CompletionType = LayerCompletionType.CriticalParts,
|
||
HardConditions = new List<HardCondition>
|
||
{
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "关键部件条件",
|
||
ConditionType = HardConditionType.CriticalParts,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 100.0,
|
||
Description = "关键部件必须全部安装"
|
||
}
|
||
},
|
||
IsEnabled = true,
|
||
Priority = 2,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "稳定性完成规则",
|
||
RuleDescription = "部件必须保持稳定",
|
||
ProductTypeCode = "default",
|
||
LayerNumber = 0,
|
||
CompletionType = LayerCompletionType.Stability,
|
||
HardConditions = new List<HardCondition>
|
||
{
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "稳定性条件",
|
||
ConditionType = HardConditionType.Stability,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 0.8,
|
||
Description = "部件必须保持稳定"
|
||
}
|
||
},
|
||
IsEnabled = true,
|
||
Priority = 3,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "禁装检查规则",
|
||
RuleDescription = "不允许出现禁装部件",
|
||
ProductTypeCode = "default",
|
||
LayerNumber = 0,
|
||
CompletionType = LayerCompletionType.ForbiddenCheck,
|
||
HardConditions = new List<HardCondition>
|
||
{
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "禁装检查条件",
|
||
ConditionType = HardConditionType.ForbiddenCheck,
|
||
IsMandatory = true,
|
||
Weight = 1.0,
|
||
Threshold = 0.0,
|
||
Description = "不允许出现禁装部件"
|
||
}
|
||
},
|
||
IsEnabled = true,
|
||
Priority = 4,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "综合完成规则",
|
||
RuleDescription = "综合所有硬条件的完成判定",
|
||
ProductTypeCode = "default",
|
||
LayerNumber = 0,
|
||
CompletionType = LayerCompletionType.Comprehensive,
|
||
HardConditions = new List<HardCondition>
|
||
{
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "必装部件条件",
|
||
ConditionType = HardConditionType.RequiredParts,
|
||
IsMandatory = true,
|
||
Weight = 0.3,
|
||
Threshold = 100.0,
|
||
Description = "必装部件必须全部安装"
|
||
},
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "关键部件条件",
|
||
ConditionType = HardConditionType.CriticalParts,
|
||
IsMandatory = true,
|
||
Weight = 0.3,
|
||
Threshold = 100.0,
|
||
Description = "关键部件必须全部安装"
|
||
},
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "稳定性条件",
|
||
ConditionType = HardConditionType.Stability,
|
||
IsMandatory = true,
|
||
Weight = 0.2,
|
||
Threshold = 0.8,
|
||
Description = "部件必须保持稳定"
|
||
},
|
||
new()
|
||
{
|
||
ConditionId = Guid.NewGuid(),
|
||
ConditionName = "禁装检查条件",
|
||
ConditionType = HardConditionType.ForbiddenCheck,
|
||
IsMandatory = true,
|
||
Weight = 0.2,
|
||
Threshold = 0.0,
|
||
Description = "不允许出现禁装部件"
|
||
}
|
||
},
|
||
IsEnabled = true,
|
||
Priority = 5,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
}
|
||
};
|
||
|
||
foreach (var rule in defaultRules)
|
||
{
|
||
_completionRules[rule.RuleId] = rule;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算完成度百分比。
|
||
/// </summary>
|
||
private double CalculateCompletionPercentage(
|
||
RequiredPartsCompletionResult? requiredResult,
|
||
CriticalPartsCompletionResult? criticalResult,
|
||
StabilityCompletionResult? stabilityResult,
|
||
ForbiddenPartsCheckResult? forbiddenResult)
|
||
{
|
||
var scores = new List<double>();
|
||
|
||
if (requiredResult != null)
|
||
{
|
||
scores.Add(requiredResult.RequiredCompletionRate);
|
||
}
|
||
|
||
if (criticalResult != null)
|
||
{
|
||
scores.Add(criticalResult.CriticalCompletionRate);
|
||
}
|
||
|
||
if (stabilityResult != null)
|
||
{
|
||
scores.Add(stabilityResult.StabilityPassRate);
|
||
}
|
||
|
||
if (forbiddenResult != null)
|
||
{
|
||
// 禁装检查通过得100分,不通过得0分
|
||
scores.Add(forbiddenResult.IsForbiddenCheckPassed ? 100.0 : 0.0);
|
||
}
|
||
|
||
return scores.Any() ? scores.Average() : 0.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定完成质量。
|
||
/// </summary>
|
||
private CompletionQuality DetermineCompletionQuality(double completionPercentage, double overallConfidence)
|
||
{
|
||
var combinedScore = (completionPercentage + overallConfidence * 100) / 2;
|
||
|
||
return combinedScore switch
|
||
{
|
||
>= 90 => CompletionQuality.Excellent,
|
||
>= 80 => CompletionQuality.Good,
|
||
>= 70 => CompletionQuality.Fair,
|
||
_ => CompletionQuality.Poor
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算稳定性指标。
|
||
/// </summary>
|
||
private async Task<StabilityMetrics> CalculateStabilityMetricsAsync(IReadOnlyList<InferenceResultDto> frameHistory, StabilityRequirements requirements, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var stableFrameCount = 0;
|
||
var totalStabilityScore = 0.0;
|
||
|
||
for (int i = 1; i < frameHistory.Count; i++)
|
||
{
|
||
var prevFrame = frameHistory[i - 1];
|
||
var currFrame = frameHistory[i];
|
||
|
||
// 简化处理:假设每帧都有相同的检测结果
|
||
if (prevFrame.Detections.Any() && currFrame.Detections.Any())
|
||
{
|
||
var prevDetection = prevFrame.Detections.First();
|
||
var currDetection = currFrame.Detections.First();
|
||
|
||
var positionChange = Math.Sqrt(
|
||
Math.Pow((currDetection.X + currDetection.Width / 2) - (prevDetection.X + prevDetection.Width / 2), 2) +
|
||
Math.Pow((currDetection.Y + currDetection.Height / 2) - (prevDetection.Y + prevDetection.Height / 2), 2)
|
||
);
|
||
|
||
var currArea = currDetection.Width * currDetection.Height;
|
||
var prevArea = prevDetection.Width * prevDetection.Height;
|
||
var areaChange = prevArea > 0 ? Math.Abs(currArea - prevArea) / prevArea : 0.0;
|
||
var confidenceChange = Math.Abs(currDetection.Confidence - prevDetection.Confidence);
|
||
|
||
// 计算单帧稳定性分数
|
||
var frameStabilityScore = 1.0 - (
|
||
(positionChange / requirements.PositionChangeThreshold) * 0.4 +
|
||
(areaChange / requirements.AreaChangeThreshold) * 0.3 +
|
||
(confidenceChange / requirements.ConfidenceChangeThreshold) * 0.3
|
||
);
|
||
|
||
frameStabilityScore = Math.Max(0.0, Math.Min(1.0, frameStabilityScore));
|
||
totalStabilityScore += frameStabilityScore;
|
||
|
||
// 简化的稳定性判定
|
||
if (positionChange <= requirements.PositionChangeThreshold &&
|
||
areaChange <= requirements.AreaChangeThreshold &&
|
||
confidenceChange <= requirements.ConfidenceChangeThreshold)
|
||
{
|
||
stableFrameCount++;
|
||
}
|
||
}
|
||
}
|
||
|
||
var overallStabilityScore = frameHistory.Count > 1 ? totalStabilityScore / (frameHistory.Count - 1) : 0.0;
|
||
|
||
return new StabilityMetrics
|
||
{
|
||
OverallStabilityScore = overallStabilityScore,
|
||
StableFrameCount = stableFrameCount
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认必装部件。
|
||
/// </summary>
|
||
private IReadOnlyList<RequiredPart> CreateDefaultRequiredParts(LayerContext layerContext)
|
||
{
|
||
// 简化处理:根据层级上下文创建默认必装部件
|
||
return new List<RequiredPart>
|
||
{
|
||
new()
|
||
{
|
||
PartId = Guid.NewGuid(),
|
||
PartCode = "REQ001",
|
||
PartType = PartType.Required,
|
||
PartName = "默认必装部件1",
|
||
RequiredQuantity = 1,
|
||
Weight = 1.0
|
||
},
|
||
new()
|
||
{
|
||
PartId = Guid.NewGuid(),
|
||
PartCode = "REQ002",
|
||
PartType = PartType.Required,
|
||
PartName = "默认必装部件2",
|
||
RequiredQuantity = 1,
|
||
Weight = 1.0
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认关键部件。
|
||
/// </summary>
|
||
private IReadOnlyList<CriticalPart> CreateDefaultCriticalParts(LayerContext layerContext)
|
||
{
|
||
// 简化处理:根据层级上下文创建默认关键部件
|
||
return new List<CriticalPart>
|
||
{
|
||
new()
|
||
{
|
||
PartId = Guid.NewGuid(),
|
||
PartCode = "CRT001",
|
||
PartType = PartType.Critical,
|
||
PartName = "默认关键部件1",
|
||
CriticalityLevel = CriticalityLevel.Important,
|
||
RequiredQuantity = 1,
|
||
Weight = 1.0
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认稳定性要求。
|
||
/// </summary>
|
||
private StabilityRequirements CreateDefaultStabilityRequirements()
|
||
{
|
||
return new StabilityRequirements
|
||
{
|
||
StabilityThreshold = _options.DefaultStabilityThreshold,
|
||
MinimumStableFrames = _options.DefaultStabilityWindowSize,
|
||
PositionChangeThreshold = 5.0,
|
||
AreaChangeThreshold = 0.1,
|
||
ConfidenceChangeThreshold = 0.1
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建默认禁装部件。
|
||
/// </summary>
|
||
private IReadOnlyList<ForbiddenPart> CreateDefaultForbiddenParts(LayerContext layerContext)
|
||
{
|
||
// 简化处理:根据层级上下文创建默认禁装部件
|
||
return new List<ForbiddenPart>
|
||
{
|
||
new()
|
||
{
|
||
PartId = Guid.NewGuid(),
|
||
PartCode = "FRB001",
|
||
PartType = PartType.Forbidden,
|
||
PartName = "默认禁装部件1",
|
||
ViolationSeverity = ViolationSeverity.Serious,
|
||
ForbiddenReason = "此部件在此层级不允许安装"
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加完成历史。
|
||
/// </summary>
|
||
private async Task AddCompletionHistoryAsync(LayerCompletionResult completionResult, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
_completionHistory.TryAdd(Guid.NewGuid(), completionResult);
|
||
|
||
// 保持历史记录数量在合理范围内
|
||
if (_completionHistory.Count > _options.MaxLayerCompletionHistoryCount)
|
||
{
|
||
var oldestRecords = _completionHistory.Values
|
||
.OrderBy(r => r.JudgmentTimeUtc)
|
||
.Take(_completionHistory.Count - _options.MaxLayerCompletionHistoryCount)
|
||
.Select(r => r.JudgmentTimeUtc)
|
||
.ToList();
|
||
|
||
foreach (var time in oldestRecords)
|
||
{
|
||
var toRemove = _completionHistory.FirstOrDefault(kvp => kvp.Value.JudgmentTimeUtc == time);
|
||
if (toRemove.Key != Guid.Empty)
|
||
{
|
||
_completionHistory.TryRemove(toRemove.Key, out _);
|
||
}
|
||
}
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
#endregion
|
||
|
||
/// <summary>
|
||
/// 稳定性指标。
|
||
/// </summary>
|
||
private sealed class StabilityMetrics
|
||
{
|
||
public double OverallStabilityScore { get; init; }
|
||
public int StableFrameCount { get; init; }
|
||
}
|
||
}
|
||
|
||
#endif
|