Files
OrpaonVision/OrpaonVision.SiteApp/Runtime/Services/PlacementJudgmentService.cs
2026-04-06 22:04:05 +08:00

1309 lines
58 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
/// <summary>
/// 到位判定服务实现。
/// </summary>
public sealed class PlacementJudgmentService : IPlacementJudgmentService
{
private readonly ILogger<PlacementJudgmentService> _logger;
private readonly RuntimeOptions _options;
private readonly ConcurrentDictionary<Guid, PlacementJudgmentRule> _judgmentRules = new();
private readonly ConcurrentDictionary<Guid, PlacementJudgmentResult> _judgmentHistory = new();
private readonly object _lock = new();
/// <summary>
/// 构造函数。
/// </summary>
public PlacementJudgmentService(ILogger<PlacementJudgmentService> logger, IOptions<RuntimeOptions> options)
{
_logger = logger;
_options = options.Value;
InitializeDefaultJudgmentRules();
}
/// <inheritdoc />
public async Task<Result<PlacementJudgmentResult>> JudgePlacementAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<PlacementJudgmentRule> judgmentRules, CancellationToken cancellationToken = default)
{
try
{
_logger.LogDebug("开始到位判定:部件数量={PartCount},规则数量={RuleCount}", parts.Count, judgmentRules.Count);
var startTime = DateTime.UtcNow;
// 执行部件级判定
var partJudgmentResults = new List<PartJudgmentResult>();
var unplacedParts = new List<MappedPart>();
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<string, object>
{
["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<PlacementJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<AreaJudgmentResult>> JudgeAreaAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ExpectedArea> 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<string, object>
{
["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<AreaJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<PositionDeviationJudgmentResult>> JudgePositionDeviationAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ExpectedPosition> 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<string, object>
{
["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<PositionDeviationJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<StabilityJudgmentResult>> JudgeStabilityAsync(IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<StabilityRule> 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<string, object>
{
["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<string, object>
{
["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<StabilityJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<CompositeJudgmentResult>> JudgeCompositeAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<CompositeJudgmentRule> 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<CompositeJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<BatchJudgmentResult>> BatchJudgeAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<PlacementJudgmentRule> judgmentRules, CancellationToken cancellationToken = default)
{
try
{
_logger.LogInformation("开始批量到位判定:部件数量={PartCount},帧数量={FrameCount},规则数量={RuleCount}",
parts.Count, frameHistory.Count, judgmentRules.Count);
var startTime = DateTime.UtcNow;
var judgmentResults = new List<PlacementJudgmentResult>();
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<string, object>
{
["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<BatchJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<PlacementJudgmentStatistics>> 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<PlacementJudgmentStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result<PlacementJudgmentRule>> 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<PlacementJudgmentRule>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
/// <inheritdoc />
public async Task<Result> 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());
}
}
/// <inheritdoc />
public async Task<Result> 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());
}
}
/// <inheritdoc />
public async Task<Result<IReadOnlyList<PlacementJudgmentRule>>> 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<IReadOnlyList<PlacementJudgmentRule>>(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<IReadOnlyList<PlacementJudgmentRule>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
}
}
#region
/// <summary>
/// 初始化默认判定规则。
/// </summary>
private void InitializeDefaultJudgmentRules()
{
var defaultRules = new List<PlacementJudgmentRule>
{
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;
}
}
/// <summary>
/// 判定单个部件。
/// </summary>
private async Task<PartJudgmentResult> JudgePartAsync(MappedPart part, IReadOnlyList<PlacementJudgmentRule> 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<double>();
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<string, object>
{
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
}
};
}
/// <summary>
/// 计算稳定性指标。
/// </summary>
private async Task<StabilityMetrics> CalculateStabilityMetricsAsync(IReadOnlyList<InferenceResultDto> frameHistory, CancellationToken cancellationToken = default)
{
await Task.Delay(1, cancellationToken);
// 简化的稳定性计算
var stableFrameCount = 0;
var positionChanges = new List<double>();
var areaChanges = new List<double>();
var confidenceChanges = new List<double>();
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
};
}
/// <summary>
/// 根据规则判定稳定性。
/// </summary>
private bool IsStableAccordingToRule(StabilityMetrics metrics, StabilityRule rule)
{
return metrics.StabilityScore >= rule.StabilityThreshold &&
metrics.PositionChangeRate <= rule.PositionChangeThreshold &&
metrics.AreaChangeRate <= rule.AreaChangeThreshold &&
metrics.ConfidenceChangeRate <= rule.ConfidenceChangeThreshold;
}
/// <summary>
/// 应用复合判定策略。
/// </summary>
private async Task<CompositeJudgmentResult> ApplyCompositeStrategyAsync(AreaJudgmentResult areaResult, PositionDeviationJudgmentResult positionResult, StabilityJudgmentResult stabilityResult, IReadOnlyList<CompositeJudgmentRule> 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<string, object>
{
["judgment_type"] = "composite",
["strategy"] = rule.Strategy.ToString(),
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
}
};
}
/// <summary>
/// 判定规则。
/// </summary>
private async Task<PlacementJudgmentResult> JudgeRuleAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<InferenceResultDto> 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<string, object>
{
["judgment_time_ms"] = (DateTime.UtcNow - startTime).TotalMilliseconds
}
};
}
}
/// <summary>
/// 确定判定质量。
/// </summary>
private JudgmentQuality DetermineJudgmentQuality(double confidence)
{
return confidence switch
{
>= 0.9 => JudgmentQuality.Excellent,
>= 0.8 => JudgmentQuality.Good,
>= 0.7 => JudgmentQuality.Fair,
_ => JudgmentQuality.Poor
};
}
/// <summary>
/// 创建默认期望面积。
/// </summary>
private IReadOnlyList<ExpectedArea> CreateDefaultExpectedAreas(IReadOnlyList<MappedPart> parts)
{
return parts.Select(part => new ExpectedArea
{
AreaId = Guid.NewGuid(),
ExpectedAreaValue = part.BoundingBox.Area,
ExpectedPartType = part.PartType,
ExpectedPartCode = part.PartCode,
Weight = 1.0
}).ToList();
}
/// <summary>
/// 创建默认期望位置。
/// </summary>
private IReadOnlyList<ExpectedPosition> CreateDefaultExpectedPositions(IReadOnlyList<MappedPart> 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();
}
/// <summary>
/// 创建默认稳定性规则。
/// </summary>
private IReadOnlyList<StabilityRule> CreateDefaultStabilityRules()
{
return new List<StabilityRule>
{
new()
{
RuleId = Guid.NewGuid(),
RuleName = "默认稳定性规则",
StabilityThreshold = 0.8,
WindowSize = 10,
PositionChangeThreshold = 5.0,
AreaChangeThreshold = 0.1,
ConfidenceChangeThreshold = 0.1
}
};
}
/// <summary>
/// 创建默认复合规则。
/// </summary>
private IReadOnlyList<CompositeJudgmentRule> CreateDefaultCompositeRules()
{
return new List<CompositeJudgmentRule>
{
new()
{
RuleId = Guid.NewGuid(),
RuleName = "默认复合规则",
Strategy = CoreCompositeJudgmentStrategy.WeightedAverage,
Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 },
MinimumPassConditions = 2
}
};
}
/// <summary>
/// 添加判定历史。
/// </summary>
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
}
/// <summary>
/// 确定判定质量。
/// </summary>
private JudgmentQuality DetermineJudgmentQuality(double confidence)
{
return confidence switch
{
>= 0.9 => JudgmentQuality.Excellent,
>= 0.8 => JudgmentQuality.Good,
>= 0.7 => JudgmentQuality.Fair,
_ => JudgmentQuality.Poor
};
}
/// <summary>
/// 创建默认期望面积。
/// </summary>
private IReadOnlyList<ExpectedArea> CreateDefaultExpectedAreas(IReadOnlyList<MappedPart> parts)
{
return parts.Select(part => new ExpectedArea
{
AreaId = Guid.NewGuid(),
ExpectedAreaValue = part.BoundingBox.Area,
ExpectedPartType = part.PartType,
ExpectedPartCode = part.PartCode,
Weight = 1.0
}).ToList();
}
/// <summary>
/// 创建默认期望位置。
/// </summary>
private IReadOnlyList<ExpectedPosition> CreateDefaultExpectedPositions(IReadOnlyList<MappedPart> 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();
}
/// <summary>
/// 创建默认稳定性规则。
/// </summary>
private IReadOnlyList<StabilityRule> CreateDefaultStabilityRules()
{
return new List<StabilityRule>
{
new()
{
RuleId = Guid.NewGuid(),
RuleName = "默认稳定性规则",
StabilityThreshold = 0.8,
WindowSize = 10,
PositionChangeThreshold = 5.0,
AreaChangeThreshold = 0.1,
ConfidenceChangeThreshold = 0.1
}
};
}
/// <summary>
/// 创建默认复合规则。
/// </summary>
private IReadOnlyList<CompositeJudgmentRule> CreateDefaultCompositeRules()
{
return new List<CompositeJudgmentRule>
{
new()
{
RuleId = Guid.NewGuid(),
RuleName = "默认复合规则",
Strategy = CoreCompositeJudgmentStrategy.WeightedAverage,
Weights = new JudgmentWeights { AreaWeight = 0.3, PositionWeight = 0.4, StabilityWeight = 0.3 },
MinimumPassConditions = 2
}
};
}
/// <summary>
/// 添加判定历史。
/// </summary>
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);
}
/// <summary>
/// 稳定性指标。
/// </summary>
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<PlacementJudgmentService> _logger;
public PlacementJudgmentService(ILogger<PlacementJudgmentService> logger, IOptions<RuntimeOptions> options)
{
_logger = logger;
}
public Task<Result<PlacementJudgmentResult>> JudgePlacementAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<PlacementJudgmentRule> judgmentRules, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<PlacementJudgmentResult>.Success(new PlacementJudgmentResult()));
}
public Task<Result<AreaJudgmentResult>> JudgeAreaAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ExpectedArea> expectedAreas, double tolerance, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<AreaJudgmentResult>.Success(new AreaJudgmentResult()));
}
public Task<Result<PositionDeviationJudgmentResult>> JudgePositionDeviationAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<ExpectedPosition> expectedPositions, double tolerance, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<PositionDeviationJudgmentResult>.Success(new PositionDeviationJudgmentResult()));
}
public Task<Result<StabilityJudgmentResult>> JudgeStabilityAsync(IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<StabilityRule> stabilityRules, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<StabilityJudgmentResult>.Success(new StabilityJudgmentResult()));
}
public Task<Result<CompositeJudgmentResult>> JudgeCompositeAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<CompositeJudgmentRule> compositeRules, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<CompositeJudgmentResult>.Success(new CompositeJudgmentResult()));
}
public Task<Result<BatchJudgmentResult>> BatchJudgeAsync(IReadOnlyList<MappedPart> parts, IReadOnlyList<InferenceResultDto> frameHistory, IReadOnlyList<PlacementJudgmentRule> judgmentRules, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<BatchJudgmentResult>.Success(new BatchJudgmentResult()));
}
public Task<Result<PlacementJudgmentStatistics>> GetJudgmentStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<PlacementJudgmentStatistics>.Success(new PlacementJudgmentStatistics()));
}
public Task<Result<PlacementJudgmentRule>> CreateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result<PlacementJudgmentRule>.Success(rule));
}
public Task<Result> UpdateJudgmentRuleAsync(PlacementJudgmentRule rule, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result.Success());
}
public Task<Result> DeleteJudgmentRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
{
return Task.FromResult(Result.Success());
}
public Task<Result<IReadOnlyList<PlacementJudgmentRule>>> GetJudgmentRulesAsync(string? productTypeCode = null, int? layerNumber = null, PlacementJudgmentType? judgmentType = null, CancellationToken cancellationToken = default)
{
IReadOnlyList<PlacementJudgmentRule> rules = Array.Empty<PlacementJudgmentRule>();
return Task.FromResult(Result<IReadOnlyList<PlacementJudgmentRule>>.Success(rules));
}
}