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));
}
}