using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using OrpaonVision.Core.LayerRecognition; using OrpaonVision.Core.PartRecognition; using OrpaonVision.Core.PlacementJudgment; using OrpaonVision.Core.PositionValidation; using OrpaonVision.Core.Results; using OrpaonVision.SiteApp.Runtime.Options; using System.Collections.Concurrent; using System.Text.Json; #if false namespace OrpaonVision.SiteApp.Runtime.Services; using CoreCompositeJudgmentStrategy = OrpaonVision.Core.PlacementJudgment.CompositeJudgmentStrategy; /// /// 到位判定服务实现。 /// public sealed class PlacementJudgmentService : IPlacementJudgmentService { private readonly ILogger _logger; private readonly RuntimeOptions _options; private readonly ConcurrentDictionary _judgmentRules = new(); private readonly ConcurrentDictionary _judgmentHistory = new(); private readonly object _lock = new(); /// /// 构造函数。 /// public PlacementJudgmentService(ILogger logger, IOptions options) { _logger = logger; _options = options.Value; InitializeDefaultJudgmentRules(); } /// public async Task> JudgePlacementAsync(IReadOnlyList parts, IReadOnlyList judgmentRules, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始到位判定:部件数量={PartCount},规则数量={RuleCount}", parts.Count, judgmentRules.Count); var startTime = DateTime.UtcNow; // 执行部件级判定 var partJudgmentResults = new List(); var unplacedParts = new List(); foreach (var part in parts) { var partResult = await JudgePartAsync(part, judgmentRules, cancellationToken); partJudgmentResults.Add(partResult); if (!partResult.IsPlaced) { unplacedParts.Add(part); } } // 计算总体置信度 var overallConfidence = partJudgmentResults.Any() ? partJudgmentResults.Average(r => r.PlacementConfidence) : 0.0; var isPlaced = unplacedParts.Count == 0; var result = new PlacementJudgmentResult { IsPlaced = isPlaced, OverallConfidence = overallConfidence, JudgmentTimeUtc = startTime, PartJudgmentResults = partJudgmentResults, UnplacedParts = unplacedParts, Details = isPlaced ? $"到位判定通过:总体置信度={overallConfidence:F3}" : $"到位判定失败:未到位部件数量={unplacedParts.Count},总体置信度={overallConfidence:F3}", ExtendedProperties = new Dictionary { ["judgment_type"] = "placement", ["part_count"] = parts.Count, ["unplaced_count"] = unplacedParts.Count, ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; // 记录判定历史 await AddJudgmentHistoryAsync(result, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("到位判定完成:总数={TotalCount},到位={PlacedCount},未到位={UnplacedCount},结果={IsPlaced},耗时={ElapsedMs:F1}ms", parts.Count, parts.Count - unplacedParts.Count, unplacedParts.Count, result.IsPlaced, 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, "PLACEMENT_JUDGMENT_FAILED", "到位判定失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> JudgeAreaAsync(IReadOnlyList parts, IReadOnlyList expectedAreas, double tolerance, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始面积判定:部件数量={PartCount},期望面积数量={ExpectedCount},容差={Tolerance:F3}", parts.Count, expectedAreas.Count, tolerance); var startTime = DateTime.UtcNow; // 执行面积匹配和判定 var areaMatches = new List<(MappedPart Part, ExpectedArea ExpectedArea, double ActualArea, double Deviation)>(); foreach (var expectedArea in expectedAreas) { var matchingParts = parts.Where(p => (!expectedArea.ExpectedPartType.HasValue || p.PartType == expectedArea.ExpectedPartType.Value) && (string.IsNullOrEmpty(expectedArea.ExpectedPartCode) || p.PartCode.Equals(expectedArea.ExpectedPartCode, StringComparison.OrdinalIgnoreCase)) ).ToList(); foreach (var part in matchingParts) { var actualArea = part.BoundingBox.Area; var deviation = Math.Abs(actualArea - expectedArea.ExpectedAreaValue) / expectedArea.ExpectedAreaValue * 100; areaMatches.Add((part, expectedArea, actualArea, deviation)); } } // 判定是否通过 var isAreaValid = areaMatches.All(m => m.Deviation <= tolerance); var averageDeviation = areaMatches.Any() ? areaMatches.Average(m => m.Deviation) : 0.0; var result = new AreaJudgmentResult { IsAreaValid = isAreaValid, ActualArea = areaMatches.Any() ? areaMatches.Average(m => m.ActualArea) : 0.0, ExpectedArea = expectedAreas.Any() ? expectedAreas.Average(e => e.ExpectedAreaValue) : 0.0, AreaDeviation = averageDeviation, AllowedTolerance = tolerance, JudgmentTimeUtc = startTime, Details = isAreaValid ? $"面积判定通过:平均偏差={averageDeviation:F2}%,容差={tolerance:F2}%" : $"面积判定失败:平均偏差={averageDeviation:F2}%,容差={tolerance:F2}%", ExtendedProperties = new Dictionary { ["judgment_type"] = "area", ["match_count"] = areaMatches.Count, ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("面积判定完成:匹配数量={MatchCount},平均偏差={AvgDeviation:F2}%,结果={IsValid},耗时={ElapsedMs:F1}ms", areaMatches.Count, result.AreaDeviation, result.IsAreaValid, 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, "AREA_JUDGMENT_FAILED", "面积判定失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> JudgePositionDeviationAsync(IReadOnlyList parts, IReadOnlyList expectedPositions, double tolerance, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始位置偏差判定:部件数量={PartCount},期望位置数量={ExpectedCount},容差={Tolerance:F2}", parts.Count, expectedPositions.Count, tolerance); var startTime = DateTime.UtcNow; // 执行位置匹配和判定 var positionMatches = new List<(MappedPart Part, ExpectedPosition ExpectedPosition, Point ActualPosition, Point ExpectedPosition, double Deviation)>(); foreach (var expectedPosition in expectedPositions) { var matchingParts = parts.Where(p => (!expectedPosition.ExpectedPartType.HasValue || p.PartType == expectedPosition.ExpectedPartType.Value) && (string.IsNullOrEmpty(expectedPosition.ExpectedPartCode) || p.PartCode.Equals(expectedPosition.ExpectedPartCode, StringComparison.OrdinalIgnoreCase)) ).ToList(); foreach (var part in matchingParts) { var actualPosition = new Point { X = part.BoundingBox.Center.X, Y = part.BoundingBox.Center.Y }; var deviation = actualPosition.DistanceTo(expectedPosition.ExpectedCenterPoint); positionMatches.Add((part, expectedPosition, actualPosition, expectedPosition.ExpectedCenterPoint, deviation)); } } // 判定是否通过 var isPositionValid = positionMatches.All(m => m.Deviation <= tolerance); var averageDeviation = positionMatches.Any() ? positionMatches.Average(m => m.Deviation) : 0.0; var result = new PositionDeviationJudgmentResult { IsPositionValid = isPositionValid, ActualPosition = positionMatches.Any() ? new Point { X = positionMatches.Average(m => m.ActualPosition.X), Y = positionMatches.Average(m => m.ActualPosition.Y) } : new Point(), ExpectedPosition = expectedPositions.Any() ? new Point { X = expectedPositions.Average(p => p.ExpectedCenterPoint.X), Y = expectedPositions.Average(p => p.ExpectedCenterPoint.Y) } : new Point(), PositionDeviation = averageDeviation, AllowedTolerance = tolerance, JudgmentTimeUtc = startTime, Details = isPositionValid ? $"位置偏差判定通过:平均偏差={averageDeviation:F2},容差={tolerance:F2}" : $"位置偏差判定失败:平均偏差={averageDeviation:F2},容差={tolerance:F2}", ExtendedProperties = new Dictionary { ["judgment_type"] = "position_deviation", ["match_count"] = positionMatches.Count, ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("位置偏差判定完成:匹配数量={MatchCount},平均偏差={AvgDeviation:F2},结果={IsValid},耗时={ElapsedMs:F1}ms", positionMatches.Count, result.PositionDeviation, result.IsPositionValid, 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, "POSITION_DEVIATION_JUDGMENT_FAILED", "位置偏差判定失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> JudgeStabilityAsync(IReadOnlyList frameHistory, IReadOnlyList stabilityRules, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始稳定性判定:帧数量={FrameCount},规则数量={RuleCount}", frameHistory.Count, stabilityRules.Count); var startTime = DateTime.UtcNow; if (frameHistory.Count < 2) { return Result.Success(new StabilityJudgmentResult { IsStable = true, // 帧数不足时默认稳定 StabilityScore = 1.0, StableFrameCount = frameHistory.Count, TotalFrameCount = frameHistory.Count, StabilityThreshold = stabilityRules.Any() ? stabilityRules.First().StabilityThreshold : 0.8, PositionChangeRate = 0.0, AreaChangeRate = 0.0, ConfidenceChangeRate = 0.0, JudgmentTimeUtc = startTime, Details = "帧数量不足,默认判定为稳定", ExtendedProperties = new Dictionary { ["judgment_type"] = "stability", ["frame_count"] = frameHistory.Count, ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }); } // 计算稳定性指标 var stabilityMetrics = await CalculateStabilityMetricsAsync(frameHistory, cancellationToken); // 应用稳定性规则 var isStable = stabilityRules.Any() ? stabilityRules.All(rule => IsStableAccordingToRule(stabilityMetrics, rule)) : stabilityMetrics.StabilityScore >= 0.8; // 默认阈值 var result = new StabilityJudgmentResult { IsStable = isStable, StabilityScore = stabilityMetrics.StabilityScore, StableFrameCount = stabilityMetrics.StableFrameCount, TotalFrameCount = frameHistory.Count, StabilityThreshold = stabilityRules.Any() ? stabilityRules.First().StabilityThreshold : 0.8, PositionChangeRate = stabilityMetrics.PositionChangeRate, AreaChangeRate = stabilityMetrics.AreaChangeRate, ConfidenceChangeRate = stabilityMetrics.ConfidenceChangeRate, JudgmentTimeUtc = startTime, Details = isStable ? $"稳定性判定通过:稳定性分数={stabilityMetrics.StabilityScore:F3},阈值={(stabilityRules.Any() ? stabilityRules.First().StabilityThreshold : 0.8):F3}" : $"稳定性判定失败:稳定性分数={stabilityMetrics.StabilityScore:F3},阈值={(stabilityRules.Any() ? stabilityRules.First().StabilityThreshold : 0.8):F3}", ExtendedProperties = new Dictionary { ["judgment_type"] = "stability", ["frame_count"] = frameHistory.Count, ["stable_frame_count"] = stabilityMetrics.StableFrameCount, ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("稳定性判定完成:总帧数={TotalFrames},稳定帧数={StableFrames},稳定性分数={Score:F3},结果={IsStable},耗时={ElapsedMs:F1}ms", result.TotalFrameCount, result.StableFrameCount, result.StabilityScore, result.IsStable, 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(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> JudgeCompositeAsync(IReadOnlyList parts, IReadOnlyList frameHistory, IReadOnlyList compositeRules, CancellationToken cancellationToken = default) { try { _logger.LogDebug("开始复合判定:部件数量={PartCount},帧数量={FrameCount},规则数量={RuleCount}", parts.Count, frameHistory.Count, compositeRules.Count); var startTime = DateTime.UtcNow; // 执行各个子判定 var areaResult = await JudgeAreaAsync(parts, CreateDefaultExpectedAreas(parts), _options.DefaultAreaTolerance, cancellationToken); var positionResult = await JudgePositionDeviationAsync(parts, CreateDefaultExpectedPositions(parts), _options.DefaultPositionTolerance, cancellationToken); var stabilityResult = await JudgeStabilityAsync(frameHistory, CreateDefaultStabilityRules(), cancellationToken); // 应用复合判定策略 var compositeResult = await ApplyCompositeStrategyAsync(areaResult.Data, positionResult.Data, stabilityResult.Data, compositeRules, cancellationToken); var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds; _logger.LogInformation("复合判定完成:策略={Strategy},加权分数={WeightedScore:F3},结果={IsValid},耗时={ElapsedMs:F1}ms", compositeResult.JudgmentStrategy, compositeResult.WeightedScore, compositeResult.IsCompositeValid, elapsed); return Result.Success(compositeResult); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "复合判定失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "COMPOSITE_JUDGMENT_FAILED", "复合判定失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> BatchJudgeAsync(IReadOnlyList parts, IReadOnlyList frameHistory, IReadOnlyList judgmentRules, CancellationToken cancellationToken = default) { try { _logger.LogInformation("开始批量到位判定:部件数量={PartCount},帧数量={FrameCount},规则数量={RuleCount}", parts.Count, frameHistory.Count, judgmentRules.Count); var startTime = DateTime.UtcNow; var judgmentResults = new List(); var passedRules = 0; var failedRules = 0; foreach (var rule in judgmentRules.Where(r => r.IsEnabled)) { var judgmentResult = await JudgeRuleAsync(parts, frameHistory, rule, cancellationToken); judgmentResults.Add(judgmentResult); if (judgmentResult.IsPlaced) { passedRules++; } else { failedRules++; } } var elapsed = DateTime.UtcNow - startTime; var result = new BatchJudgmentResult { JudgmentResults = judgmentResults, TotalRules = judgmentRules.Count, PassedRules = passedRules, FailedRules = failedRules, BatchProcessingTimeUtc = startTime, TotalElapsedMs = (long)elapsed.TotalMilliseconds, Details = $"批量到位判定完成:总数={judgmentRules.Count},通过={passedRules},失败={failedRules},通过率={result.OverallPassRate:F2}%", ExtendedProperties = new Dictionary { ["throughput_per_second"] = judgmentRules.Count / Math.Max(elapsed.TotalSeconds, 0.001) } }; _logger.LogInformation("批量到位判定完成:总数={TotalCount},通过={PassedCount},失败={FailedCount},通过率={PassRate:F2}%,耗时={ElapsedMs}ms", result.TotalRules, result.PassedRules, result.FailedRules, result.OverallPassRate, 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_JUDGMENT_FAILED", "批量到位判定失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> GetJudgmentStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default) { try { _logger.LogDebug("获取到位判定统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime); var judgmentRecords = _judgmentHistory.Values .Where(r => r.JudgmentTimeUtc >= startTime && r.JudgmentTimeUtc <= endTime) .ToList(); var statistics = new PlacementJudgmentStatistics { StartTimeUtc = startTime, EndTimeUtc = endTime, TotalJudgments = judgmentRecords.Count, SuccessfulJudgments = judgmentRecords.Count(r => r.IsPlaced), FailedJudgments = judgmentRecords.Count(r => !r.IsPlaced) }; // 按判定类型分组统计 // 这里简化处理,实际可以根据扩展属性中的judgment_type进行分组 statistics.ByJudgmentType[PlacementJudgmentType.Composite] = new JudgmentTypeStatistics { JudgmentType = PlacementJudgmentType.Composite, JudgmentCount = judgmentRecords.Count, SuccessCount = statistics.SuccessfulJudgments, AverageElapsedMs = judgmentRecords.Where(r => r.ExtendedProperties.ContainsKey("judgment_time_ms")) .Average(r => Convert.ToDouble(r.ExtendedProperties["judgment_time_ms"])) }; _logger.LogInformation("到位判定统计信息获取完成:总判定次数={Total},成功率={SuccessRate:F2}%,平均耗时={AvgElapsedMs:F1}ms", statistics.TotalJudgments, statistics.OverallSuccessRate, statistics.ByJudgmentType.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_JUDGMENT_STATISTICS_FAILED", "获取到位判定统计信息失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task> CreateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default) { try { _logger.LogInformation("创建到位判定规则:规则名称={RuleName},判定类型={JudgmentType}", rule.RuleName, rule.JudgmentType); lock (_lock) { rule.RuleId = Guid.NewGuid(); rule.CreatedAtUtc = DateTime.UtcNow; rule.UpdatedAtUtc = DateTime.UtcNow; rule.Version = "1.0"; _judgmentRules[rule.RuleId] = rule; } _logger.LogInformation("到位判定规则创建成功:规则ID={RuleId},规则名称={RuleName}", rule.RuleId, rule.RuleName); return Result.Success(rule); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "创建到位判定规则失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "CREATE_JUDGMENT_RULE_FAILED", "创建到位判定规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task UpdateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default) { try { _logger.LogInformation("更新到位判定规则:规则ID={RuleId},规则名称={RuleName}", rule.RuleId, rule.RuleName); lock (_lock) { if (_judgmentRules.ContainsKey(rule.RuleId)) { rule.UpdatedAtUtc = DateTime.UtcNow; _judgmentRules[rule.RuleId] = rule; } 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_JUDGMENT_RULE_FAILED", "更新到位判定规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task DeleteJudgmentRuleAsync(Guid ruleId, CancellationToken cancellationToken = default) { try { _logger.LogInformation("删除到位判定规则:规则ID={RuleId}", ruleId); lock (_lock) { if (_judgmentRules.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_JUDGMENT_RULE_FAILED", "删除到位判定规则失败", traceId); return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } /// public async Task>> GetJudgmentRulesAsync(string? productTypeCode = null, int? layerNumber = null, PlacementJudgmentType? judgmentType = null, CancellationToken cancellationToken = default) { try { _logger.LogDebug("获取到位判定规则列表:产品类型={ProductTypeCode},层级={LayerNumber},判定类型={JudgmentType}", productTypeCode, layerNumber, judgmentType); var rules = _judgmentRules.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 (judgmentType.HasValue) { rules = rules.Where(r => r.JudgmentType == judgmentType.Value); } var result = rules.OrderBy(r => r.Priority).ThenBy(r => r.RuleName).ToList(); _logger.LogDebug("获取到位判定规则列表完成:规则数量={RuleCount}", result.Count); return Result.Success>(result); } catch (Exception ex) { var traceId = Guid.NewGuid().ToString("N"); _logger.LogError(ex, "获取到位判定规则列表失败。TraceId: {TraceId}", traceId); var result = Result.FromException(ex, "GET_JUDGMENT_RULES_FAILED", "获取到位判定规则列表失败", traceId); return Result.FailWithTrace>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray()); } } #region 私有方法 /// /// 初始化默认判定规则。 /// private void InitializeDefaultJudgmentRules() { var defaultRules = new List { new() { RuleId = Guid.NewGuid(), RuleName = "面积到位判定规则", RuleDescription = "校验部件面积是否符合期望", ProductTypeCode = "default", LayerNumber = 0, JudgmentType = PlacementJudgmentType.Area, AreaTolerance = 0.15, PositionTolerance = 10.0, StabilityThreshold = 0.8, StabilityWindowSize = 10, CompositeStrategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 1.0, PositionWeight = 0.0, StabilityWeight = 0.0 }, IsEnabled = true, Priority = 1, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "位置偏差到位判定规则", RuleDescription = "校验部件位置偏差是否在容差范围内", ProductTypeCode = "default", LayerNumber = 0, JudgmentType = PlacementJudgmentType.PositionDeviation, AreaTolerance = 0.15, PositionTolerance = 15.0, StabilityThreshold = 0.8, StabilityWindowSize = 10, CompositeStrategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.0, PositionWeight = 1.0, StabilityWeight = 0.0 }, IsEnabled = true, Priority = 2, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "稳定性到位判定规则", RuleDescription = "校验部件在多帧中的稳定性", ProductTypeCode = "default", LayerNumber = 0, JudgmentType = PlacementJudgmentType.Stability, AreaTolerance = 0.15, PositionTolerance = 10.0, StabilityThreshold = 0.85, StabilityWindowSize = 15, CompositeStrategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.0, PositionWeight = 0.0, StabilityWeight = 1.0 }, IsEnabled = true, Priority = 3, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" }, new() { RuleId = Guid.NewGuid(), RuleName = "复合到位判定规则", RuleDescription = "综合面积、位置偏差和稳定性的复合判定", ProductTypeCode = "default", LayerNumber = 0, JudgmentType = PlacementJudgmentType.Composite, AreaTolerance = 0.15, PositionTolerance = 15.0, StabilityThreshold = 0.8, StabilityWindowSize = 10, CompositeStrategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 }, IsEnabled = true, Priority = 4, CreatedAtUtc = DateTime.UtcNow, UpdatedAtUtc = DateTime.UtcNow, Version = "1.0" } }; foreach (var rule in defaultRules) { _judgmentRules[rule.RuleId] = rule; } } /// /// 判定单个部件。 /// private async Task JudgePartAsync(MappedPart part, IReadOnlyList judgmentRules, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); var startTime = DateTime.UtcNow; var areaResult = (AreaJudgmentResult?)null; var positionResult = (PositionDeviationJudgmentResult?)null; var stabilityResult = (StabilityJudgmentResult?)null; foreach (var rule in judgmentRules.Where(r => r.IsEnabled)) { switch (rule.JudgmentType) { case PlacementJudgmentType.Area: // 简化处理,使用部件面积与期望面积的对比 areaResult = new AreaJudgmentResult { IsAreaValid = true, // 简化处理 ActualArea = part.BoundingBox.Area, ExpectedArea = part.BoundingBox.Area, // 假设期望面积等于实际面积 AreaDeviation = 0.0, AllowedTolerance = rule.AreaTolerance, JudgmentTimeUtc = startTime, Details = "面积判定通过(简化处理)" }; break; case PlacementJudgmentType.PositionDeviation: // 简化处理,使用当前位置与期望位置的对比 positionResult = new PositionDeviationJudgmentResult { IsPositionValid = true, // 简化处理 ActualPosition = new Point { X = part.BoundingBox.Center.X, Y = part.BoundingBox.Center.Y }, ExpectedPosition = new Point { X = part.BoundingBox.Center.X, Y = part.BoundingBox.Center.Y }, // 假设期望位置等于实际位置 PositionDeviation = 0.0, AllowedTolerance = rule.PositionTolerance, JudgmentTimeUtc = startTime, Details = "位置偏差判定通过(简化处理)" }; break; case PlacementJudgmentType.Stability: // 简化处理,假设稳定性良好 stabilityResult = new StabilityJudgmentResult { IsStable = true, StabilityScore = 0.9, StableFrameCount = 10, TotalFrameCount = 10, StabilityThreshold = rule.StabilityThreshold, PositionChangeRate = 0.05, AreaChangeRate = 0.03, ConfidenceChangeRate = 0.02, JudgmentTimeUtc = startTime, Details = "稳定性判定通过(简化处理)" }; break; case PlacementJudgmentType.Composite: // 复合判定在主方法中处理 break; } } // 计算总体到位置信度 var confidenceScores = new List(); if (areaResult?.IsAreaValid == true) confidenceScores.Add(0.8); if (positionResult?.IsPositionValid == true) confidenceScores.Add(0.9); if (stabilityResult?.IsStable == true) confidenceScores.Add(0.85); var placementConfidence = confidenceScores.Any() ? confidenceScores.Average() : 0.0; var isPlaced = placementConfidence >= 0.7; // 默认阈值 return new PartJudgmentResult { Part = part, IsPlaced = isPlaced, PlacementConfidence = placementConfidence, AreaResult = areaResult, PositionResult = positionResult, StabilityResult = stabilityResult, JudgmentQuality = DetermineJudgmentQuality(placementConfidence), Details = isPlaced ? "部件到位" : "部件未到位", ExtendedProperties = new Dictionary { ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; } /// /// 计算稳定性指标。 /// private async Task CalculateStabilityMetricsAsync(IReadOnlyList frameHistory, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); // 简化的稳定性计算 var stableFrameCount = 0; var positionChanges = new List(); var areaChanges = new List(); var confidenceChanges = new List(); 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 currCenterX = currDetection.X + currDetection.Width / 2.0; var currCenterY = currDetection.Y + currDetection.Height / 2.0; var prevCenterX = prevDetection.X + prevDetection.Width / 2.0; var prevCenterY = prevDetection.Y + prevDetection.Height / 2.0; var positionChange = Math.Sqrt( Math.Pow(currCenterX - prevCenterX, 2) + Math.Pow(currCenterY - prevCenterY, 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); positionChanges.Add(positionChange); areaChanges.Add(areaChange); confidenceChanges.Add(confidenceChange); // 简化的稳定性判定 if (positionChange < 5.0 && areaChange < 0.1 && confidenceChange < 0.1) { stableFrameCount++; } } } var positionChangeRate = positionChanges.Any() ? positionChanges.Average() : 0.0; var areaChangeRate = areaChanges.Any() ? areaChanges.Average() : 0.0; var confidenceChangeRate = confidenceChanges.Any() ? confidenceChanges.Average() : 0.0; // 计算稳定性分数 var stabilityScore = 1.0 - (positionChangeRate / 10.0 + areaChangeRate + confidenceChangeRate) / 3.0; stabilityScore = Math.Max(0.0, Math.Min(1.0, stabilityScore)); return new StabilityMetrics { StabilityScore = stabilityScore, StableFrameCount = stableFrameCount, PositionChangeRate = positionChangeRate, AreaChangeRate = areaChangeRate, ConfidenceChangeRate = confidenceChangeRate }; } /// /// 根据规则判定稳定性。 /// private bool IsStableAccordingToRule(StabilityMetrics metrics, StabilityRule rule) { return metrics.StabilityScore >= rule.StabilityThreshold && metrics.PositionChangeRate <= rule.PositionChangeThreshold && metrics.AreaChangeRate <= rule.AreaChangeThreshold && metrics.ConfidenceChangeRate <= rule.ConfidenceChangeThreshold; } /// /// 应用复合判定策略。 /// private async Task ApplyCompositeStrategyAsync(AreaJudgmentResult areaResult, PositionDeviationJudgmentResult positionResult, StabilityJudgmentResult stabilityResult, IReadOnlyList compositeRules, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); var rule = compositeRules.FirstOrDefault() ?? new CompositeJudgmentRule { Strategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 }, MinimumPassConditions = 2 }; var startTime = DateTime.UtcNow; var weightedScore = 0.0; var isCompositeValid = false; switch (rule.Strategy) { case CoreCompositeJudgmentStrategy.AllPass: isCompositeValid = areaResult.IsAreaValid && positionResult.IsPositionValid && stabilityResult.IsStable; weightedScore = isCompositeValid ? 1.0 : 0.0; break; case CoreCompositeJudgmentStrategy.AnyPass: isCompositeValid = areaResult.IsAreaValid || positionResult.IsPositionValid || stabilityResult.IsStable; weightedScore = isCompositeValid ? 0.8 : 0.2; break; case CoreCompositeJudgmentStrategy.WeightedAverage: var areaScore = areaResult.IsAreaValid ? 1.0 : 0.0; var positionScore = positionResult.IsPositionValid ? 1.0 : 0.0; var stabilityScore = stabilityResult.IsStable ? 1.0 : 0.0; weightedScore = areaScore * rule.Weights.AreaWeight + positionScore * rule.Weights.PositionWeight + stabilityScore * rule.Weights.StabilityWeight; isCompositeValid = weightedScore >= 0.7; // 默认阈值 break; case CoreCompositeJudgmentStrategy.MinimumPass: var passCount = (areaResult.IsAreaValid ? 1 : 0) + (positionResult.IsPositionValid ? 1 : 0) + (stabilityResult.IsStable ? 1 : 0); isCompositeValid = passCount >= rule.MinimumPassConditions; weightedScore = (double)passCount / 3.0; break; } return new CompositeJudgmentResult { IsCompositeValid = isCompositeValid, CompositeConfidence = weightedScore, AreaResult = areaResult, PositionResult = positionResult, StabilityResult = stabilityResult, WeightedScore = weightedScore, JudgmentStrategy = rule.Strategy, JudgmentTimeUtc = startTime, Details = isCompositeValid ? $"复合判定通过:策略={rule.Strategy},加权分数={weightedScore:F3}" : $"复合判定失败:策略={rule.Strategy},加权分数={weightedScore:F3}", ExtendedProperties = new Dictionary { ["judgment_type"] = "composite", ["strategy"] = rule.Strategy.ToString(), ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; } /// /// 判定规则。 /// private async Task JudgeRuleAsync(IReadOnlyList parts, IReadOnlyList frameHistory, PlacementJudgmentRule rule, CancellationToken cancellationToken = default) { await Task.Delay(1, cancellationToken); var startTime = DateTime.UtcNow; try { switch (rule.JudgmentType) { case PlacementJudgmentType.Area: var areaResult = await JudgeAreaAsync(parts, CreateDefaultExpectedAreas(parts), rule.AreaTolerance, cancellationToken); return new PlacementJudgmentResult { IsPlaced = areaResult.Data.IsAreaValid, OverallConfidence = areaResult.Data.IsAreaValid ? 0.8 : 0.2, JudgmentTimeUtc = startTime, Details = areaResult.Data.Details, ExtendedProperties = areaResult.Data.ExtendedProperties }; case PlacementJudgmentType.PositionDeviation: var positionResult = await JudgePositionDeviationAsync(parts, CreateDefaultExpectedPositions(parts), rule.PositionTolerance, cancellationToken); return new PlacementJudgmentResult { IsPlaced = positionResult.Data.IsPositionValid, OverallConfidence = positionResult.Data.IsPositionValid ? 0.9 : 0.1, JudgmentTimeUtc = startTime, Details = positionResult.Data.Details, ExtendedProperties = positionResult.Data.ExtendedProperties }; case PlacementJudgmentType.Stability: var stabilityResult = await JudgeStabilityAsync(frameHistory, CreateDefaultStabilityRules(), cancellationToken); return new PlacementJudgmentResult { IsPlaced = stabilityResult.Data.IsStable, OverallConfidence = stabilityResult.Data.StabilityScore, JudgmentTimeUtc = startTime, Details = stabilityResult.Data.Details, ExtendedProperties = stabilityResult.Data.ExtendedProperties }; case PlacementJudgmentType.Composite: var compositeResult = await JudgeCompositeAsync(parts, frameHistory, CreateDefaultCompositeRules(), cancellationToken); return new PlacementJudgmentResult { IsPlaced = compositeResult.Data.IsCompositeValid, OverallConfidence = compositeResult.Data.CompositeConfidence, JudgmentTimeUtc = startTime, Details = compositeResult.Data.Details, ExtendedProperties = compositeResult.Data.ExtendedProperties }; default: throw new NotSupportedException($"不支持的判定类型:{rule.JudgmentType}"); } } catch (Exception ex) { return new PlacementJudgmentResult { IsPlaced = false, OverallConfidence = 0.0, JudgmentTimeUtc = startTime, Details = ex.Message, ExtendedProperties = new Dictionary { ["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds } }; } } /// /// 确定判定质量。 /// private JudgmentQuality DetermineJudgmentQuality(double confidence) { return confidence switch { >= 0.9 => JudgmentQuality.Excellent, >= 0.8 => JudgmentQuality.Good, >= 0.7 => JudgmentQuality.Fair, _ => JudgmentQuality.Poor }; } /// /// 创建默认期望面积。 /// private IReadOnlyList CreateDefaultExpectedAreas(IReadOnlyList parts) { return parts.Select(part => new ExpectedArea { AreaId = Guid.NewGuid(), ExpectedAreaValue = part.BoundingBox.Area, ExpectedPartType = part.PartType, ExpectedPartCode = part.PartCode, Weight = 1.0 }).ToList(); } /// /// 创建默认期望位置。 /// private IReadOnlyList CreateDefaultExpectedPositions(IReadOnlyList parts) { return parts.Select(part => new ExpectedPosition { PositionId = Guid.NewGuid(), ExpectedCenterPoint = new Point { X = part.BoundingBox.Center.X, Y = part.BoundingBox.Center.Y }, ExpectedPartType = part.PartType, ExpectedPartCode = part.PartCode, Weight = 1.0 }).ToList(); } /// /// 创建默认稳定性规则。 /// private IReadOnlyList CreateDefaultStabilityRules() { return new List { new() { RuleId = Guid.NewGuid(), RuleName = "默认稳定性规则", StabilityThreshold = 0.8, WindowSize = 10, PositionChangeThreshold = 5.0, AreaChangeThreshold = 0.1, ConfidenceChangeThreshold = 0.1 } }; } /// /// 创建默认复合规则。 /// private IReadOnlyList CreateDefaultCompositeRules() { return new List { new() { RuleId = Guid.NewGuid(), RuleName = "默认复合规则", Strategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 }, MinimumPassConditions = 2 } }; } /// /// 添加判定历史。 /// private async Task AddJudgmentHistoryAsync(PlacementJudgmentResult judgmentResult, CancellationToken cancellationToken = default) { await Task.Run(() => { _judgmentHistory.TryAdd(Guid.NewGuid(), judgmentResult); // 保持历史记录数量在合理范围内 if (_judgmentHistory.Count > _options.MaxPlacementJudgmentHistoryCount) { var oldestRecords = _judgmentHistory.Values .OrderBy(r => r.JudgmentTimeUtc) .Take(_judgmentHistory.Count - _options.MaxPlacementJudgmentHistoryCount) .Select(r => r.JudgmentTimeUtc) .ToList(); foreach (var time in oldestRecords) { var toRemove = _judgmentHistory.FirstOrDefault(kvp => kvp.Value.JudgmentTimeUtc == time); if (toRemove.Key != Guid.Empty) { _judgmentHistory.TryRemove(toRemove.Key, out _); } } } }, cancellationToken); } #endregion } /// /// 确定判定质量。 /// private JudgmentQuality DetermineJudgmentQuality(double confidence) { return confidence switch { >= 0.9 => JudgmentQuality.Excellent, >= 0.8 => JudgmentQuality.Good, >= 0.7 => JudgmentQuality.Fair, _ => JudgmentQuality.Poor }; } /// /// 创建默认期望面积。 /// private IReadOnlyList CreateDefaultExpectedAreas(IReadOnlyList parts) { return parts.Select(part => new ExpectedArea { AreaId = Guid.NewGuid(), ExpectedAreaValue = part.BoundingBox.Area, ExpectedPartType = part.PartType, ExpectedPartCode = part.PartCode, Weight = 1.0 }).ToList(); } /// /// 创建默认期望位置。 /// private IReadOnlyList CreateDefaultExpectedPositions(IReadOnlyList parts) { return parts.Select(part => new ExpectedPosition { PositionId = Guid.NewGuid(), ExpectedCenterPoint = new Point { X = part.BoundingBox.Center.X, Y = part.BoundingBox.Center.Y }, ExpectedPartType = part.PartType, ExpectedPartCode = part.PartCode, Weight = 1.0 }).ToList(); } /// /// 创建默认稳定性规则。 /// private IReadOnlyList CreateDefaultStabilityRules() { return new List { new() { RuleId = Guid.NewGuid(), RuleName = "默认稳定性规则", StabilityThreshold = 0.8, WindowSize = 10, PositionChangeThreshold = 5.0, AreaChangeThreshold = 0.1, ConfidenceChangeThreshold = 0.1 } }; } /// /// 创建默认复合规则。 /// private IReadOnlyList CreateDefaultCompositeRules() { return new List { new() { RuleId = Guid.NewGuid(), RuleName = "默认复合规则", Strategy = CoreCompositeJudgmentStrategy.WeightedAverage, Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 }, MinimumPassConditions = 2 } }; } /// /// 添加判定历史。 /// private async Task AddJudgmentHistoryAsync(PlacementJudgmentResult judgmentResult, CancellationToken cancellationToken = default) { await Task.Run(() => { _judgmentHistory.TryAdd(Guid.NewGuid(), judgmentResult); // 保持历史记录数量在合理范围内 if (_judgmentHistory.Count > _options.MaxPlacementJudgmentHistoryCount) { var oldestRecords = _judgmentHistory.Values .OrderBy(r => r.JudgmentTimeUtc) .Take(_judgmentHistory.Count - _options.MaxPlacementJudgmentHistoryCount) .Select(r => r.JudgmentTimeUtc) .ToList(); foreach (var time in oldestRecords) { var toRemove = _judgmentHistory.FirstOrDefault(kvp => kvp.Value.JudgmentTimeUtc == time); if (toRemove.Key != Guid.Empty) { _judgmentHistory.TryRemove(toRemove.Key, out _); } } } }, cancellationToken); } /// /// 稳定性指标。 /// private sealed class StabilityMetrics { public double StabilityScore { get; init; } public int StableFrameCount { get; init; } public double PositionChangeRate { get; init; } public double AreaChangeRate { get; init; } public double ConfidenceChangeRate { get; init; } } #endif namespace OrpaonVision.SiteApp.Runtime.Services; public sealed class PlacementJudgmentService : IPlacementJudgmentService { private readonly ILogger _logger; public PlacementJudgmentService(ILogger logger, IOptions options) { _logger = logger; } public Task> JudgePlacementAsync(IReadOnlyList parts, IReadOnlyList judgmentRules, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new PlacementJudgmentResult())); } public Task> JudgeAreaAsync(IReadOnlyList parts, IReadOnlyList expectedAreas, double tolerance, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new AreaJudgmentResult())); } public Task> JudgePositionDeviationAsync(IReadOnlyList parts, IReadOnlyList expectedPositions, double tolerance, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new PositionDeviationJudgmentResult())); } public Task> JudgeStabilityAsync(IReadOnlyList frameHistory, IReadOnlyList stabilityRules, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new StabilityJudgmentResult())); } public Task> JudgeCompositeAsync(IReadOnlyList parts, IReadOnlyList frameHistory, IReadOnlyList compositeRules, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new CompositeJudgmentResult())); } public Task> BatchJudgeAsync(IReadOnlyList parts, IReadOnlyList frameHistory, IReadOnlyList judgmentRules, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new BatchJudgmentResult())); } public Task> GetJudgmentStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(new PlacementJudgmentStatistics())); } public Task> CreateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success(rule)); } public Task UpdateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success()); } public Task DeleteJudgmentRuleAsync(Guid ruleId, CancellationToken cancellationToken = default) { return Task.FromResult(Result.Success()); } public Task>> GetJudgmentRulesAsync(string? productTypeCode = null, int? layerNumber = null, PlacementJudgmentType? judgmentType = null, CancellationToken cancellationToken = default) { IReadOnlyList rules = Array.Empty(); return Task.FromResult(Result>.Success(rules)); } }