1491 lines
60 KiB
C#
1491 lines
60 KiB
C#
using Microsoft.Extensions.Logging;
|
||
using Microsoft.Extensions.Options;
|
||
using OrpaonVision.Core.FinalJudgment;
|
||
using OrpaonVision.Core.LayerCompletion;
|
||
using OrpaonVision.Core.Results;
|
||
using OrpaonVision.SiteApp.Runtime.Options;
|
||
using System.Collections.Concurrent;
|
||
using System.Text.Json;
|
||
|
||
namespace OrpaonVision.SiteApp.Runtime.Services;
|
||
|
||
/// <summary>
|
||
/// 整机完成判定服务实现。
|
||
/// </summary>
|
||
#if false
|
||
public sealed class FinalJudgmentService : IFinalJudgmentService
|
||
{
|
||
private readonly ILogger<FinalJudgmentService> _logger;
|
||
private readonly RuntimeOptions _options;
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentRule> _finalRules = new();
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentResult> _judgmentHistory = new();
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentHistory> _judgmentRecords = new();
|
||
private readonly object _lock = new();
|
||
|
||
/// <summary>
|
||
/// 构造函数。
|
||
/// </summary>
|
||
public FinalJudgmentService(ILogger<FinalJudgmentService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_options = options.Value;
|
||
InitializeDefaultFinalRules();
|
||
}
|
||
|
||
#endif
|
||
|
||
/// <summary>
|
||
/// 整机完成判定服务最小实现。
|
||
/// </summary>
|
||
public sealed class FinalJudgmentService : IFinalJudgmentService
|
||
{
|
||
private readonly ILogger<FinalJudgmentService> _logger;
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentRule> _rules = new();
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentResult> _results = new();
|
||
private readonly ConcurrentDictionary<Guid, FinalJudgmentHistory> _history = new();
|
||
|
||
public FinalJudgmentService(ILogger<FinalJudgmentService> logger, IOptions<RuntimeOptions> options)
|
||
{
|
||
_logger = logger;
|
||
_ = options;
|
||
|
||
var now = DateTime.UtcNow;
|
||
var defaultRule = new FinalJudgmentRule
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "默认终判规则",
|
||
RuleDescription = "最小可运行默认规则",
|
||
ProductTypeCode = "default",
|
||
RuleType = FinalJudgmentRuleType.CompletionRate,
|
||
RuleWeight = 1.0,
|
||
Threshold = 90,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsMandatory = true,
|
||
IsEnabled = true,
|
||
Priority = 1,
|
||
CreatedAtUtc = now,
|
||
UpdatedAtUtc = now,
|
||
Version = "1.0"
|
||
};
|
||
|
||
_rules[defaultRule.RuleId] = defaultRule;
|
||
}
|
||
|
||
public Task<Result<FinalJudgmentResult>> ExecuteFinalJudgmentAsync(FinalJudgmentContext judgmentContext, IReadOnlyList<FinalJudgmentRule> finalRules, CancellationToken cancellationToken = default)
|
||
{
|
||
var start = DateTime.UtcNow;
|
||
var score = judgmentContext.OverallCompletionPercentage;
|
||
var rules = finalRules.Where(r => r.IsEnabled).ToList();
|
||
|
||
var passedRules = rules.Select(r => new RuleJudgmentResult
|
||
{
|
||
RuleId = r.RuleId,
|
||
RuleName = r.RuleName,
|
||
RuleType = r.RuleType,
|
||
IsPassed = true,
|
||
RuleScore = score,
|
||
RuleWeight = r.RuleWeight,
|
||
ActualValue = score,
|
||
Threshold = r.Threshold,
|
||
Details = "最小实现:默认通过"
|
||
}).ToList();
|
||
|
||
var result = new FinalJudgmentResult
|
||
{
|
||
JudgmentId = Guid.NewGuid(),
|
||
ProductTypeCode = judgmentContext.ProductTypeCode,
|
||
SessionId = judgmentContext.SessionId,
|
||
IsPassed = true,
|
||
JudgmentType = FinalJudgmentType.Automatic,
|
||
JudgmentTimeUtc = start,
|
||
JudgmentElapsedMs = 0,
|
||
OverallScore = score,
|
||
JudgmentQuality = JudgmentQuality.Good,
|
||
PassedRules = passedRules,
|
||
FailedRules = Array.Empty<RuleJudgmentResult>(),
|
||
NGCauses = Array.Empty<NGCause>(),
|
||
EvidenceChain = new EvidenceChain
|
||
{
|
||
ChainId = Guid.NewGuid(),
|
||
CreatedAtUtc = start,
|
||
EvidenceNodes = Array.Empty<EvidenceNode>(),
|
||
CompletenessScore = 100,
|
||
CredibilityScore = 100
|
||
},
|
||
Details = "最小实现:终判完成",
|
||
Recommendation = null
|
||
};
|
||
|
||
_results[result.JudgmentId] = result;
|
||
_history[result.JudgmentId] = new FinalJudgmentHistory
|
||
{
|
||
JudgmentId = result.JudgmentId,
|
||
ProductTypeCode = result.ProductTypeCode,
|
||
SessionId = result.SessionId,
|
||
JudgmentType = result.JudgmentType,
|
||
JudgmentTimeUtc = result.JudgmentTimeUtc,
|
||
IsPassed = result.IsPassed,
|
||
OverallScore = result.OverallScore,
|
||
JudgmentQuality = result.JudgmentQuality,
|
||
OperatorUser = "system",
|
||
NGCauseCount = 0
|
||
};
|
||
|
||
_logger.LogInformation("最小终判完成:{JudgmentId}", result.JudgmentId);
|
||
return Task.FromResult(Result<FinalJudgmentResult>.Success(result));
|
||
}
|
||
|
||
public async Task<Result<BatchFinalJudgmentResult>> BatchExecuteFinalJudgmentAsync(IReadOnlyList<FinalJudgmentContext> judgmentContexts, IReadOnlyList<FinalJudgmentRule> finalRules, CancellationToken cancellationToken = default)
|
||
{
|
||
var list = new List<FinalJudgmentResult>();
|
||
foreach (var context in judgmentContexts)
|
||
{
|
||
var one = await ExecuteFinalJudgmentAsync(context, finalRules, cancellationToken);
|
||
if (one.Data != null)
|
||
{
|
||
list.Add(one.Data);
|
||
}
|
||
}
|
||
|
||
var batch = new BatchFinalJudgmentResult
|
||
{
|
||
JudgmentResults = list,
|
||
TotalJudgments = judgmentContexts.Count,
|
||
PassedCount = list.Count(r => r.IsPassed),
|
||
FailedCount = list.Count(r => !r.IsPassed),
|
||
BatchProcessingTimeUtc = DateTime.UtcNow,
|
||
TotalElapsedMs = 0,
|
||
Details = "最小实现:批量终判完成"
|
||
};
|
||
|
||
return Result<BatchFinalJudgmentResult>.Success(batch);
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<FinalJudgmentHistory>>> GetJudgmentHistoryAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
IReadOnlyList<FinalJudgmentHistory> records = _history.Values
|
||
.Where(x => x.JudgmentTimeUtc >= startTime && x.JudgmentTimeUtc <= endTime)
|
||
.OrderByDescending(x => x.JudgmentTimeUtc)
|
||
.ToList();
|
||
return Task.FromResult(Result<IReadOnlyList<FinalJudgmentHistory>>.Success(records));
|
||
}
|
||
|
||
public Task<Result<FinalJudgmentStatistics>> GetJudgmentStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
var records = _history.Values
|
||
.Where(x => x.JudgmentTimeUtc >= startTime && x.JudgmentTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var statistics = new FinalJudgmentStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalJudgments = records.Count,
|
||
PassedCount = records.Count(x => x.IsPassed),
|
||
FailedCount = records.Count(x => !x.IsPassed),
|
||
ByJudgmentType = records
|
||
.GroupBy(x => x.JudgmentType)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new JudgmentTypeStatistics
|
||
{
|
||
JudgmentType = g.Key,
|
||
JudgmentCount = g.Count(),
|
||
PassCount = g.Count(x => x.IsPassed),
|
||
AverageScore = g.Average(x => x.OverallScore),
|
||
AverageElapsedMs = 0
|
||
}),
|
||
ByProductType = records
|
||
.GroupBy(x => x.ProductTypeCode)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new ProductTypeJudgmentStatistics
|
||
{
|
||
ProductTypeCode = g.Key,
|
||
JudgmentCount = g.Count(),
|
||
PassCount = g.Count(x => x.IsPassed),
|
||
AverageScore = g.Average(x => x.OverallScore),
|
||
AverageElapsedMs = 0
|
||
}),
|
||
ByQuality = records
|
||
.GroupBy(x => x.JudgmentQuality)
|
||
.ToDictionary(
|
||
g => g.Key,
|
||
g => new QualityStatistics
|
||
{
|
||
Quality = g.Key,
|
||
JudgmentCount = g.Count(),
|
||
AverageScore = g.Average(x => x.OverallScore)
|
||
}),
|
||
AverageScore = records.Any() ? records.Average(x => x.OverallScore) : 0,
|
||
AverageElapsedMs = 0
|
||
};
|
||
|
||
return Task.FromResult(Result<FinalJudgmentStatistics>.Success(statistics));
|
||
}
|
||
|
||
public Task<Result<NGCauseAnalysis>> GetNGCauseAnalysisAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
var analysis = new NGCauseAnalysis
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalNGCount = 0,
|
||
ByCauseType = new Dictionary<NGCauseType, CauseTypeStatistics>(),
|
||
ByCauseLevel = new Dictionary<NGCauseLevel, CauseLevelStatistics>(),
|
||
ByImpactLevel = new Dictionary<ImpactLevel, ImpactStatistics>(),
|
||
ByLayer = new Dictionary<int, LayerNGStatistics>(),
|
||
HotCauses = Array.Empty<HotNGCause>(),
|
||
Trend = new NGCauseTrend
|
||
{
|
||
Direction = NGCauseTrendDirection.Stable,
|
||
ChangeRate = 0,
|
||
TrendDescription = "最小实现:无趋势"
|
||
}
|
||
};
|
||
|
||
return Task.FromResult(Result<NGCauseAnalysis>.Success(analysis));
|
||
}
|
||
|
||
public Task<Result<FinalJudgmentRule>> CreateFinalRuleAsync(FinalJudgmentRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
var now = DateTime.UtcNow;
|
||
var created = new FinalJudgmentRule
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = rule.RuleName,
|
||
RuleDescription = rule.RuleDescription,
|
||
ProductTypeCode = rule.ProductTypeCode,
|
||
RuleType = rule.RuleType,
|
||
RuleWeight = rule.RuleWeight,
|
||
Threshold = rule.Threshold,
|
||
ComparisonOperator = rule.ComparisonOperator,
|
||
IsMandatory = rule.IsMandatory,
|
||
IsEnabled = rule.IsEnabled,
|
||
Priority = rule.Priority,
|
||
CreatedAtUtc = now,
|
||
UpdatedAtUtc = now,
|
||
Version = "1.0",
|
||
ExtendedProperties = new Dictionary<string, object>(rule.ExtendedProperties)
|
||
};
|
||
|
||
_rules[created.RuleId] = created;
|
||
return Task.FromResult(Result<FinalJudgmentRule>.Success(created));
|
||
}
|
||
|
||
public Task<Result> UpdateFinalRuleAsync(FinalJudgmentRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
if (!_rules.TryGetValue(rule.RuleId, out var existing))
|
||
{
|
||
return Task.FromResult(Result.Fail("RULE_NOT_FOUND", $"未找到规则:{rule.RuleId}"));
|
||
}
|
||
|
||
var updated = new FinalJudgmentRule
|
||
{
|
||
RuleId = existing.RuleId,
|
||
RuleName = rule.RuleName,
|
||
RuleDescription = rule.RuleDescription,
|
||
ProductTypeCode = rule.ProductTypeCode,
|
||
RuleType = rule.RuleType,
|
||
RuleWeight = rule.RuleWeight,
|
||
Threshold = rule.Threshold,
|
||
ComparisonOperator = rule.ComparisonOperator,
|
||
IsMandatory = rule.IsMandatory,
|
||
IsEnabled = rule.IsEnabled,
|
||
Priority = rule.Priority,
|
||
CreatedAtUtc = existing.CreatedAtUtc,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = existing.Version,
|
||
ExtendedProperties = new Dictionary<string, object>(rule.ExtendedProperties)
|
||
};
|
||
|
||
_rules[updated.RuleId] = updated;
|
||
return Task.FromResult(Result.Success());
|
||
}
|
||
|
||
public Task<Result> DeleteFinalRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
return Task.FromResult(_rules.TryRemove(ruleId, out _)
|
||
? Result.Success()
|
||
: Result.Fail("RULE_NOT_FOUND", $"未找到规则:{ruleId}"));
|
||
}
|
||
|
||
public Task<Result<IReadOnlyList<FinalJudgmentRule>>> GetFinalRulesAsync(string? productTypeCode = null, FinalJudgmentRuleType? ruleType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
IEnumerable<FinalJudgmentRule> query = _rules.Values;
|
||
|
||
if (!string.IsNullOrWhiteSpace(productTypeCode))
|
||
{
|
||
query = query.Where(x => string.Equals(x.ProductTypeCode, productTypeCode, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
if (ruleType.HasValue)
|
||
{
|
||
query = query.Where(x => x.RuleType == ruleType.Value);
|
||
}
|
||
|
||
IReadOnlyList<FinalJudgmentRule> rules = query.OrderBy(x => x.Priority).ThenBy(x => x.RuleName).ToList();
|
||
return Task.FromResult(Result<IReadOnlyList<FinalJudgmentRule>>.Success(rules));
|
||
}
|
||
|
||
public Task<Result<FinalJudgmentReport>> GenerateJudgmentReportAsync(Guid judgmentId, CancellationToken cancellationToken = default)
|
||
{
|
||
if (!_results.TryGetValue(judgmentId, out var result))
|
||
{
|
||
return Task.FromResult(Result<FinalJudgmentReport>.FailWithTrace("JUDGMENT_NOT_FOUND", $"未找到判定记录:{judgmentId}", string.Empty));
|
||
}
|
||
|
||
var report = new FinalJudgmentReport
|
||
{
|
||
ReportId = Guid.NewGuid(),
|
||
JudgmentId = judgmentId,
|
||
GeneratedAtUtc = DateTime.UtcNow,
|
||
ReportType = ReportType.Standard,
|
||
ReportContent = $"最小实现报告: Judgment={result.JudgmentId}, Score={result.OverallScore:F2}",
|
||
ReportFormat = ReportFormat.Text
|
||
};
|
||
|
||
return Task.FromResult(Result<FinalJudgmentReport>.Success(report));
|
||
}
|
||
}
|
||
|
||
#if false
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<FinalJudgmentResult>> ExecuteFinalJudgmentAsync(FinalJudgmentContext judgmentContext, IReadOnlyList<FinalJudgmentRule> finalRules, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("开始整机完成判定:产品类型={ProductTypeCode},会话={SessionId},总层级={TotalLayers},完成层级={CompletedLayers}",
|
||
judgmentContext.ProductTypeCode, judgmentContext.SessionId, judgmentContext.TotalLayers, judgmentContext.CompletedLayers);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var judgmentId = Guid.NewGuid();
|
||
|
||
// 执行规则判定
|
||
var ruleResults = new List<RuleJudgmentResult>();
|
||
var passedRules = new List<RuleJudgmentResult>();
|
||
var failedRules = new List<RuleJudgmentResult>();
|
||
var ngCauses = new List<NGCause>();
|
||
|
||
foreach (var rule in finalRules.Where(r => r.IsEnabled))
|
||
{
|
||
var ruleResult = await ExecuteRuleJudgmentAsync(judgmentContext, rule, cancellationToken);
|
||
ruleResults.Add(ruleResult);
|
||
|
||
if (ruleResult.IsPassed)
|
||
{
|
||
passedRules.Add(ruleResult);
|
||
}
|
||
else
|
||
{
|
||
failedRules.Add(ruleResult);
|
||
|
||
// 生成NG原因
|
||
var ngCause = GenerateNGCause(ruleResult, judgmentContext);
|
||
ngCauses.Add(ngCause);
|
||
}
|
||
}
|
||
|
||
// 计算总体评分
|
||
var overallScore = CalculateOverallScore(ruleResults, finalRules);
|
||
|
||
// 判定是否通过
|
||
var mandatoryRules = finalRules.Where(r => r.IsMandatory && r.IsEnabled).ToList();
|
||
var failedMandatoryRules = failedRules.Where(r => mandatoryRules.Any(m => m.RuleId == r.RuleId)).ToList();
|
||
var isPassed = failedMandatoryRules.Count == 0 && overallScore >= _options.FinalJudgmentPassThreshold;
|
||
|
||
// 生成证据链
|
||
var evidenceChain = await GenerateEvidenceChainAsync(judgmentContext, ruleResults, cancellationToken);
|
||
|
||
// 确定判定质量
|
||
var judgmentQuality = DetermineJudgmentQuality(overallScore, failedRules.Count, ruleResults.Count);
|
||
|
||
var result = new FinalJudgmentResult
|
||
{
|
||
JudgmentId = judgmentId,
|
||
ProductTypeCode = judgmentContext.ProductTypeCode,
|
||
SessionId = judgmentContext.SessionId,
|
||
IsPassed = isPassed,
|
||
JudgmentType = FinalJudgmentType.Automatic,
|
||
JudgmentTimeUtc = startTime,
|
||
JudgmentElapsedMs = (long)(DateTime.UtcNow - startTime).TotalMilliseconds,
|
||
OverallScore = overallScore,
|
||
JudgmentQuality = judgmentQuality,
|
||
PassedRules = passedRules,
|
||
FailedRules = failedRules,
|
||
NGCauses = ngCauses,
|
||
EvidenceChain = evidenceChain,
|
||
Details = isPassed
|
||
? $"整机完成判定通过:总体评分={overallScore:F2},通过规则={passedRules.Count}/{ruleResults.Count}"
|
||
: $"整机完成判定失败:总体评分={overallScore:F2},失败规则={failedRules.Count}/{ruleResults.Count},NG原因={ngCauses.Count}个",
|
||
Recommendation = GenerateRecommendation(isPassed, failedRules, ngCauses),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_type"] = "final_judgment",
|
||
["product_type_code"] = judgmentContext.ProductTypeCode,
|
||
["session_id"] = judgmentContext.SessionId,
|
||
["total_layers"] = judgmentContext.TotalLayers,
|
||
["completed_layers"] = judgmentContext.CompletedLayers,
|
||
["failed_layers"] = judgmentContext.FailedLayers,
|
||
["overall_completion_percentage"] = judgmentContext.OverallCompletionPercentage,
|
||
["mandatory_rules_count"] = mandatoryRules.Count,
|
||
["failed_mandatory_rules_count"] = failedMandatoryRules.Count
|
||
}
|
||
};
|
||
|
||
// 记录判定历史
|
||
await AddJudgmentHistoryAsync(result, cancellationToken);
|
||
|
||
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
|
||
_logger.LogInformation("整机完成判定完成:判定ID={JudgmentId},结果={IsPassed},评分={Score:F2},耗时={ElapsedMs:F1}ms",
|
||
judgmentId, result.IsPassed, result.OverallScore, 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, "FINAL_JUDGMENT_FAILED", "整机完成判定失败", traceId);
|
||
return Result.FailWithTrace<FinalJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<BatchFinalJudgmentResult>> BatchExecuteFinalJudgmentAsync(IReadOnlyList<FinalJudgmentContext> judgmentContexts, IReadOnlyList<FinalJudgmentRule> finalRules, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("开始批量整机完成判定:判定数量={JudgmentCount},规则数量={RuleCount}",
|
||
judgmentContexts.Count, finalRules.Count);
|
||
|
||
var startTime = DateTime.UtcNow;
|
||
var judgmentResults = new List<FinalJudgmentResult>();
|
||
var passedCount = 0;
|
||
var failedCount = 0;
|
||
|
||
foreach (var context in judgmentContexts)
|
||
{
|
||
var judgmentResult = await ExecuteFinalJudgmentAsync(context, finalRules, cancellationToken);
|
||
judgmentResults.Add(judgmentResult.Data);
|
||
|
||
if (judgmentResult.Data.IsPassed)
|
||
{
|
||
passedCount++;
|
||
}
|
||
else
|
||
{
|
||
failedCount++;
|
||
}
|
||
}
|
||
|
||
var elapsed = DateTime.UtcNow - startTime;
|
||
|
||
var result = new BatchFinalJudgmentResult
|
||
{
|
||
JudgmentResults = judgmentResults,
|
||
TotalJudgments = judgmentContexts.Count,
|
||
PassedCount = passedCount,
|
||
FailedCount = failedCount,
|
||
BatchProcessingTimeUtc = startTime,
|
||
TotalElapsedMs = (long)elapsed.TotalMilliseconds,
|
||
Details = $"批量整机完成判定完成:总数={judgmentContexts.Count},通过={passedCount},失败={failedCount},通过率={result.OverallPassRate:F2}%",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["throughput_per_second"] = judgmentContexts.Count / Math.Max(elapsed.TotalSeconds, 0.001)
|
||
}
|
||
};
|
||
|
||
_logger.LogInformation("批量整机完成判定完成:总数={TotalCount},通过={PassedCount},失败={FailedCount},通过率={PassRate:F2}%,耗时={ElapsedMs}ms",
|
||
result.TotalJudgments, result.PassedCount, result.FailedCount, 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_FINAL_JUDGMENT_FAILED", "批量整机完成判定失败", traceId);
|
||
return Result.FailWithTrace<BatchFinalJudgmentResult>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<FinalJudgmentHistory>>> GetJudgmentHistoryAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取整机完成判定历史:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _judgmentRecords.Values
|
||
.Where(h => h.JudgmentTimeUtc >= startTime && h.JudgmentTimeUtc <= endTime)
|
||
.OrderByDescending(h => h.JudgmentTimeUtc)
|
||
.ToList();
|
||
|
||
_logger.LogDebug("获取整机完成判定历史完成:记录数量={Count}", history.Count);
|
||
return Result.Success<IReadOnlyList<FinalJudgmentHistory>>(history);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取整机完成判定历史失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_JUDGMENT_HISTORY_FAILED", "获取整机完成判定历史失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<FinalJudgmentHistory>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<FinalJudgmentStatistics>> GetJudgmentStatisticsAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取整机完成判定统计信息:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _judgmentRecords.Values
|
||
.Where(h => h.JudgmentTimeUtc >= startTime && h.JudgmentTimeUtc <= endTime)
|
||
.ToList();
|
||
|
||
var statistics = new FinalJudgmentStatistics
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalJudgments = history.Count,
|
||
PassedCount = history.Count(h => h.IsPassed),
|
||
FailedCount = history.Count(h => !h.IsPassed)
|
||
};
|
||
|
||
// 按判定类型分组统计
|
||
var typeGroups = history.GroupBy(h => h.JudgmentType);
|
||
foreach (var group in typeGroups)
|
||
{
|
||
statistics.ByJudgmentType[group.Key] = new JudgmentTypeStatistics
|
||
{
|
||
JudgmentType = group.Key,
|
||
JudgmentCount = group.Count(),
|
||
PassCount = group.Count(h => h.IsPassed),
|
||
AverageScore = group.Average(h => h.OverallScore),
|
||
AverageElapsedMs = group.Average(h => Convert.ToDouble(h.ExtendedProperties.GetValueOrDefault("judgment_elapsed_ms", 0)))
|
||
};
|
||
}
|
||
|
||
// 按产品类型分组统计
|
||
var productTypeGroups = history.GroupBy(h => h.ProductTypeCode);
|
||
foreach (var group in productTypeGroups)
|
||
{
|
||
statistics.ByProductType[group.Key] = new ProductTypeJudgmentStatistics
|
||
{
|
||
ProductTypeCode = group.Key,
|
||
JudgmentCount = group.Count(),
|
||
PassCount = group.Count(h => h.IsPassed),
|
||
AverageScore = group.Average(h => h.OverallScore),
|
||
AverageElapsedMs = group.Average(h => Convert.ToDouble(h.ExtendedProperties.GetValueOrDefault("judgment_elapsed_ms", 0)))
|
||
};
|
||
}
|
||
|
||
// 按判定质量分组统计
|
||
var qualityGroups = history.GroupBy(h => h.JudgmentQuality);
|
||
foreach (var group in qualityGroups)
|
||
{
|
||
statistics.ByQuality[group.Key] = new QualityStatistics
|
||
{
|
||
Quality = group.Key,
|
||
JudgmentCount = group.Count(),
|
||
AverageScore = group.Average(h => h.OverallScore)
|
||
};
|
||
}
|
||
|
||
statistics.AverageScore = history.Any() ? history.Average(h => h.OverallScore) : 0.0;
|
||
statistics.AverageElapsedMs = history.Any()
|
||
? history.Average(h => Convert.ToDouble(h.ExtendedProperties.GetValueOrDefault("judgment_elapsed_ms", 0)))
|
||
: 0.0;
|
||
|
||
_logger.LogInformation("整机完成判定统计信息获取完成:总判定次数={Total},通过率={PassRate:F2}%,平均评分={AvgScore:F2},平均耗时={AvgElapsedMs:F1}ms",
|
||
statistics.TotalJudgments, statistics.OverallPassRate, statistics.AverageScore, statistics.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<FinalJudgmentStatistics>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<NGCauseAnalysis>> GetNGCauseAnalysisAsync(DateTime startTime, DateTime endTime, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取NG原因分析:开始时间={StartTime},结束时间={EndTime}", startTime, endTime);
|
||
|
||
var history = _judgmentHistory.Values
|
||
.Where(h => h.JudgmentTimeUtc >= startTime && h.JudgmentTimeUtc <= endTime && !h.IsPassed)
|
||
.ToList();
|
||
|
||
var allNGCauses = history.SelectMany(h => h.NGCauses).ToList();
|
||
|
||
var analysis = new NGCauseAnalysis
|
||
{
|
||
StartTimeUtc = startTime,
|
||
EndTimeUtc = endTime,
|
||
TotalNGCount = allNGCauses.Count
|
||
};
|
||
|
||
// 按原因类型分组统计
|
||
var causeTypeGroups = allNGCauses.GroupBy(c => c.CauseType);
|
||
foreach (var group in causeTypeGroups)
|
||
{
|
||
analysis.ByCauseType[group.Key] = new CauseTypeStatistics
|
||
{
|
||
CauseType = group.Key,
|
||
OccurrenceCount = group.Count(),
|
||
AverageImpactLevel = group.Average(c => (double)c.ImpactLevel)
|
||
};
|
||
}
|
||
|
||
// 按原因级别分组统计
|
||
var causeLevelGroups = allNGCauses.GroupBy(c => c.CauseLevel);
|
||
foreach (var group in causeLevelGroups)
|
||
{
|
||
analysis.ByCauseLevel[group.Key] = new CauseLevelStatistics
|
||
{
|
||
CauseLevel = group.Key,
|
||
OccurrenceCount = group.Count()
|
||
};
|
||
}
|
||
|
||
// 按影响程度分组统计
|
||
var impactGroups = allNGCauses.GroupBy(c => c.ImpactLevel);
|
||
foreach (var group in impactGroups)
|
||
{
|
||
analysis.ByImpactLevel[group.Key] = new ImpactStatistics
|
||
{
|
||
ImpactLevel = group.Key,
|
||
OccurrenceCount = group.Count()
|
||
};
|
||
}
|
||
|
||
// 按层级分组统计
|
||
var layerGroups = allNGCauses.Where(c => c.RelatedLayer.HasValue).GroupBy(c => c.RelatedLayer!.Value);
|
||
foreach (var group in layerGroups)
|
||
{
|
||
analysis.ByLayer[group.Key] = new LayerNGStatistics
|
||
{
|
||
LayerNumber = group.Key,
|
||
NGCount = group.Count()
|
||
};
|
||
}
|
||
|
||
// 生成热点NG原因
|
||
analysis.HotCauses = GenerateHotNGCauses(allNGCauses);
|
||
|
||
// 生成趋势分析
|
||
analysis.Trend = GenerateNGCauseTrend(allNGCauses);
|
||
|
||
_logger.LogInformation("NG原因分析完成:总NG数量={TotalNG},热点原因数量={HotCauses},趋势方向={Trend}",
|
||
analysis.TotalNGCount, analysis.HotCauses.Count, analysis.Trend.Direction);
|
||
|
||
return Result.Success(analysis);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取NG原因分析失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_NG_CAUSE_ANALYSIS_FAILED", "获取NG原因分析失败", traceId);
|
||
return Result.FailWithTrace<NGCauseAnalysis>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<FinalJudgmentRule>> CreateFinalRuleAsync(FinalJudgmentRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("创建终判规则:规则名称={RuleName},规则类型={RuleType},产品类型={ProductTypeCode}",
|
||
rule.RuleName, rule.RuleType, rule.ProductTypeCode);
|
||
|
||
lock (_lock)
|
||
{
|
||
rule.RuleId = Guid.NewGuid();
|
||
rule.CreatedAtUtc = DateTime.UtcNow;
|
||
rule.UpdatedAtUtc = DateTime.UtcNow;
|
||
rule.Version = "1.0";
|
||
|
||
_finalRules[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_FINAL_RULE_FAILED", "创建终判规则失败", traceId);
|
||
return Result.FailWithTrace<FinalJudgmentRule>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> UpdateFinalRuleAsync(FinalJudgmentRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("更新终判规则:规则ID={RuleId},规则名称={RuleName}",
|
||
rule.RuleId, rule.RuleName);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_finalRules.ContainsKey(rule.RuleId))
|
||
{
|
||
rule.UpdatedAtUtc = DateTime.UtcNow;
|
||
_finalRules[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_FINAL_RULE_FAILED", "更新终判规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result> DeleteFinalRuleAsync(Guid ruleId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogInformation("删除终判规则:规则ID={RuleId}", ruleId);
|
||
|
||
lock (_lock)
|
||
{
|
||
if (_finalRules.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_FINAL_RULE_FAILED", "删除终判规则失败", traceId);
|
||
return Result.FailWithTrace(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<IReadOnlyList<FinalJudgmentRule>>> GetFinalRulesAsync(string? productTypeCode = null, FinalJudgmentRuleType? ruleType = null, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("获取终判规则列表:产品类型={ProductTypeCode},规则类型={RuleType}",
|
||
productTypeCode, ruleType);
|
||
|
||
var rules = _finalRules.Values.AsEnumerable();
|
||
|
||
if (!string.IsNullOrEmpty(productTypeCode))
|
||
{
|
||
rules = rules.Where(r => r.ProductTypeCode.Equals(productTypeCode, StringComparison.OrdinalIgnoreCase));
|
||
}
|
||
|
||
if (ruleType.HasValue)
|
||
{
|
||
rules = rules.Where(r => r.RuleType == ruleType.Value);
|
||
}
|
||
|
||
var result = rules.OrderBy(r => r.Priority).ThenBy(r => r.RuleName).ToList();
|
||
|
||
_logger.LogDebug("获取终判规则列表完成:规则数量={RuleCount}", result.Count);
|
||
return Result.Success<IReadOnlyList<FinalJudgmentRule>>(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "获取终判规则列表失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GET_FINAL_RULES_FAILED", "获取终判规则列表失败", traceId);
|
||
return Result.FailWithTrace<IReadOnlyList<FinalJudgmentRule>>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
/// <inheritdoc />
|
||
public async Task<Result<FinalJudgmentReport>> GenerateJudgmentReportAsync(Guid judgmentId, CancellationToken cancellationToken = default)
|
||
{
|
||
try
|
||
{
|
||
_logger.LogDebug("生成整机完成判定报告:判定ID={JudgmentId}", judgmentId);
|
||
|
||
var judgmentResult = _judgmentHistory.GetValueOrDefault(judgmentId);
|
||
if (judgmentResult == null)
|
||
{
|
||
return Result.FailWithTrace<FinalJudgmentReport>("JUDGMENT_NOT_FOUND", $"未找到判定记录:{judgmentId}", string.Empty);
|
||
}
|
||
|
||
var report = await GenerateReportContentAsync(judgmentResult, cancellationToken);
|
||
|
||
_logger.LogInformation("整机完成判定报告生成完成:判定ID={JudgmentId},报告ID={ReportId}",
|
||
judgmentId, report.ReportId);
|
||
|
||
return Result.Success(report);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
var traceId = Guid.NewGuid().ToString("N");
|
||
_logger.LogError(ex, "生成整机完成判定报告失败。TraceId: {TraceId}", traceId);
|
||
var result = Result.FromException(ex, "GENERATE_JUDGMENT_REPORT_FAILED", "生成整机完成判定报告失败", traceId);
|
||
return Result.FailWithTrace<FinalJudgmentReport>(result.Code, result.Message, result.TraceId ?? traceId, result.Errors.ToArray());
|
||
}
|
||
}
|
||
|
||
#region 私有方法
|
||
|
||
/// <summary>
|
||
/// 初始化默认终判规则。
|
||
/// </summary>
|
||
private void InitializeDefaultFinalRules()
|
||
{
|
||
var defaultRules = new List<FinalJudgmentRule>
|
||
{
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "完成度规则",
|
||
RuleDescription = "整机完成度必须达到要求",
|
||
ProductTypeCode = "default",
|
||
RuleType = FinalJudgmentRuleType.CompletionRate,
|
||
RuleWeight = 0.4,
|
||
Threshold = 95.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsMandatory = true,
|
||
IsEnabled = true,
|
||
Priority = 1,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "质量评分规则",
|
||
RuleDescription = "整机质量评分必须达到要求",
|
||
ProductTypeCode = "default",
|
||
RuleType = FinalJudgmentRuleType.QualityScore,
|
||
RuleWeight = 0.3,
|
||
Threshold = 85.0,
|
||
ComparisonOperator = ComparisonOperator.GreaterThanOrEqual,
|
||
IsMandatory = true,
|
||
IsEnabled = true,
|
||
Priority = 2,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "错误率规则",
|
||
RuleDescription = "整机错误率不能超过阈值",
|
||
ProductTypeCode = "default",
|
||
RuleType = FinalJudgmentRuleType.ErrorRate,
|
||
RuleWeight = 0.2,
|
||
Threshold = 5.0,
|
||
ComparisonOperator = ComparisonOperator.LessThanOrEqual,
|
||
IsMandatory = false,
|
||
IsEnabled = true,
|
||
Priority = 3,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
},
|
||
new()
|
||
{
|
||
RuleId = Guid.NewGuid(),
|
||
RuleName = "时间约束规则",
|
||
RuleDescription = "整机处理时间不能超过限制",
|
||
ProductTypeCode = "default",
|
||
RuleType = FinalJudgmentRuleType.TimeConstraint,
|
||
RuleWeight = 0.1,
|
||
Threshold = 300000.0, // 5分钟
|
||
ComparisonOperator = ComparisonOperator.LessThanOrEqual,
|
||
IsMandatory = false,
|
||
IsEnabled = true,
|
||
Priority = 4,
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
UpdatedAtUtc = DateTime.UtcNow,
|
||
Version = "1.0"
|
||
}
|
||
};
|
||
|
||
foreach (var rule in defaultRules)
|
||
{
|
||
_finalRules[rule.RuleId] = rule;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 执行规则判定。
|
||
/// </summary>
|
||
private async Task<RuleJudgmentResult> ExecuteRuleJudgmentAsync(FinalJudgmentContext context, FinalJudgmentRule rule, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var actualValue = CalculateRuleValue(context, rule);
|
||
var isPassed = EvaluateRuleCondition(actualValue, rule.Threshold, rule.ComparisonOperator);
|
||
|
||
return new RuleJudgmentResult
|
||
{
|
||
RuleId = rule.RuleId,
|
||
RuleName = rule.RuleName,
|
||
RuleType = rule.RuleType,
|
||
IsPassed = isPassed,
|
||
RuleScore = isPassed ? 100.0 : Math.Max(0.0, 100.0 - Math.Abs(actualValue - rule.Threshold)),
|
||
RuleWeight = rule.RuleWeight,
|
||
ActualValue = actualValue,
|
||
Threshold = rule.Threshold,
|
||
Details = isPassed
|
||
? $"规则通过:实际值{actualValue:F2}满足条件{GetComparisonDescription(rule.ComparisonOperator)}{rule.Threshold:F2}"
|
||
: $"规则失败:实际值{actualValue:F2}不满足条件{GetComparisonDescription(rule.ComparisonOperator)}{rule.Threshold:F2}",
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["rule_type"] = rule.RuleType.ToString(),
|
||
["is_mandatory"] = rule.IsMandatory
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算规则值。
|
||
/// </summary>
|
||
private double CalculateRuleValue(FinalJudgmentContext context, FinalJudgmentRule rule)
|
||
{
|
||
return rule.RuleType switch
|
||
{
|
||
FinalJudgmentRuleType.CompletionRate => context.OverallCompletionPercentage,
|
||
FinalJudgmentRuleType.QualityScore => CalculateQualityScore(context),
|
||
FinalJudgmentRuleType.ErrorRate => CalculateErrorRate(context),
|
||
FinalJudgmentRuleType.TimeConstraint => context.TotalElapsedMs,
|
||
_ => 0.0
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算质量评分。
|
||
/// </summary>
|
||
private double CalculateQualityScore(FinalJudgmentContext context)
|
||
{
|
||
if (context.LayerCompletionResults.Count == 0) return 0.0;
|
||
|
||
var totalScore = context.LayerCompletionResults.Sum(r => r.OverallConfidence * 100);
|
||
return totalScore / context.LayerCompletionResults.Count;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算错误率。
|
||
/// </summary>
|
||
private double CalculateErrorRate(FinalJudgmentContext context)
|
||
{
|
||
var totalLayers = context.TotalLayers;
|
||
var failedLayers = context.FailedLayers;
|
||
|
||
return totalLayers > 0 ? (double)failedLayers / totalLayers * 100 : 0.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 评估规则条件。
|
||
/// </summary>
|
||
private bool EvaluateRuleCondition(double actualValue, double threshold, ComparisonOperator comparisonOperator)
|
||
{
|
||
return comparisonOperator switch
|
||
{
|
||
ComparisonOperator.Equal => Math.Abs(actualValue - threshold) < 0.001,
|
||
ComparisonOperator.NotEqual => Math.Abs(actualValue - threshold) >= 0.001,
|
||
ComparisonOperator.GreaterThan => actualValue > threshold,
|
||
ComparisonOperator.GreaterThanOrEqual => actualValue >= threshold,
|
||
ComparisonOperator.LessThan => actualValue < threshold,
|
||
ComparisonOperator.LessThanOrEqual => actualValue <= threshold,
|
||
_ => false
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取比较描述。
|
||
/// </summary>
|
||
private string GetComparisonDescription(ComparisonOperator comparisonOperator)
|
||
{
|
||
return comparisonOperator switch
|
||
{
|
||
ComparisonOperator.Equal => "==",
|
||
ComparisonOperator.NotEqual => "!=",
|
||
ComparisonOperator.GreaterThan => ">",
|
||
ComparisonOperator.GreaterThanOrEqual => ">=",
|
||
ComparisonOperator.LessThan => "<",
|
||
ComparisonOperator.LessThanOrEqual => "<=",
|
||
_ => "?"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算总体评分。
|
||
/// </summary>
|
||
private double CalculateOverallScore(IReadOnlyList<RuleJudgmentResult> ruleResults, IReadOnlyList<FinalJudgmentRule> finalRules)
|
||
{
|
||
if (ruleResults.Count == 0) return 0.0;
|
||
|
||
var totalWeightedScore = 0.0;
|
||
var totalWeight = 0.0;
|
||
|
||
foreach (var result in ruleResults)
|
||
{
|
||
var rule = finalRules.FirstOrDefault(r => r.RuleId == result.RuleId);
|
||
if (rule != null && rule.IsEnabled)
|
||
{
|
||
totalWeightedScore += result.RuleScore * rule.RuleWeight;
|
||
totalWeight += rule.RuleWeight;
|
||
}
|
||
}
|
||
|
||
return totalWeight > 0 ? totalWeightedScore / totalWeight : 0.0;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定判定质量。
|
||
/// </summary>
|
||
private JudgmentQuality DetermineJudgmentQuality(double overallScore, int failedRulesCount, int totalRulesCount)
|
||
{
|
||
var failRate = totalRulesCount > 0 ? (double)failedRulesCount / totalRulesCount : 0.0;
|
||
|
||
return (overallScore, failRate) switch
|
||
{
|
||
(>= 95.0, <= 0.05) => JudgmentQuality.Excellent,
|
||
(>= 85.0, <= 0.15) => JudgmentQuality.Good,
|
||
(>= 70.0, <= 0.30) => JudgmentQuality.Fair,
|
||
(>= 50.0, <= 0.50) => JudgmentQuality.Poor,
|
||
_ => JudgmentQuality.Unqualified
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成NG原因。
|
||
/// </summary>
|
||
private NGCause GenerateNGCause(RuleJudgmentResult ruleResult, FinalJudgmentContext context)
|
||
{
|
||
var causeType = DetermineNGCauseType(ruleResult.RuleType);
|
||
var causeLevel = DetermineNGCauseLevel(ruleResult.RuleWeight, ruleResult.IsMandatory);
|
||
|
||
return new NGCause
|
||
{
|
||
CauseId = Guid.NewGuid(),
|
||
CauseType = causeType,
|
||
CauseLevel = causeLevel,
|
||
Description = $"{ruleResult.RuleName}:{ruleResult.Details}",
|
||
RelatedLayer = null, // 整机判定不关联特定层级
|
||
RelatedPart = null,
|
||
OccurrenceTimeUtc = DateTime.UtcNow,
|
||
FixSuggestion = GenerateFixSuggestion(ruleResult.RuleType, causeType),
|
||
ImpactLevel = DetermineImpactLevel(causeLevel),
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["rule_id"] = ruleResult.RuleId,
|
||
["rule_name"] = ruleResult.RuleName,
|
||
["actual_value"] = ruleResult.ActualValue,
|
||
["threshold"] = ruleResult.Threshold
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定NG原因类型。
|
||
/// </summary>
|
||
private NGCauseType DetermineNGCauseType(FinalJudgmentRuleType ruleType)
|
||
{
|
||
return ruleType switch
|
||
{
|
||
FinalJudgmentRuleType.CompletionRate => NGCauseType.ProcessIssue,
|
||
FinalJudgmentRuleType.QualityScore => NGCauseType.QualityIssue,
|
||
FinalJudgmentRuleType.ErrorRate => NGCauseType.ProcessIssue,
|
||
FinalJudgmentRuleType.TimeConstraint => NGCauseType.EquipmentIssue,
|
||
_ => NGCauseType.Other
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定NG原因级别。
|
||
/// </summary>
|
||
private NGCauseLevel DetermineNGCauseLevel(double ruleWeight, bool isMandatory)
|
||
{
|
||
if (isMandatory) return NGCauseLevel.Critical;
|
||
|
||
return ruleWeight switch
|
||
{
|
||
>= 0.3 => NGCauseLevel.Serious,
|
||
>= 0.1 => NGCauseLevel.Normal,
|
||
_ => NGCauseLevel.Minor
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成修复建议。
|
||
/// </summary>
|
||
private string? GenerateFixSuggestion(FinalJudgmentRuleType ruleType, NGCauseType causeType)
|
||
{
|
||
return (ruleType, causeType) switch
|
||
{
|
||
(FinalJudgmentRuleType.CompletionRate, NGCauseType.ProcessIssue) => "检查各层级完成情况,确保所有必要步骤都已完成",
|
||
(FinalJudgmentRuleType.QualityScore, NGCauseType.QualityIssue) => "提高各层级检测质量,确保检测精度和稳定性",
|
||
(FinalJudgmentRuleType.ErrorRate, NGCauseType.ProcessIssue) => "减少错误发生,优化工艺流程和操作规范",
|
||
(FinalJudgmentRuleType.TimeConstraint, NGCauseType.EquipmentIssue) => "优化设备性能,提高处理效率",
|
||
_ => "请根据具体情况进行针对性改进"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 确定影响程度。
|
||
/// </summary>
|
||
private ImpactLevel DetermineImpactLevel(NGCauseLevel causeLevel)
|
||
{
|
||
return causeLevel switch
|
||
{
|
||
NGCauseLevel.Critical => ImpactLevel.Critical,
|
||
NGCauseLevel.Serious => ImpactLevel.High,
|
||
NGCauseLevel.Normal => ImpactLevel.Medium,
|
||
NGCauseLevel.Minor => ImpactLevel.Low,
|
||
_ => ImpactLevel.Low
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成证据链。
|
||
/// </summary>
|
||
private async Task<EvidenceChain> GenerateEvidenceChainAsync(FinalJudgmentContext context, IReadOnlyList<RuleJudgmentResult> ruleResults, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var evidenceNodes = new List<EvidenceNode>();
|
||
|
||
// 添加判定上下文证据
|
||
evidenceNodes.Add(new EvidenceNode
|
||
{
|
||
NodeId = Guid.NewGuid(),
|
||
NodeType = EvidenceNodeType.Data,
|
||
NodeName = "判定上下文",
|
||
NodeDescription = "整机完成判定的上下文信息",
|
||
TimestampUtc = DateTime.UtcNow,
|
||
DataContent = new
|
||
{
|
||
ProductTypeCode = context.ProductTypeCode,
|
||
SessionId = context.SessionId,
|
||
TotalLayers = context.TotalLayers,
|
||
CompletedLayers = context.CompletedLayers,
|
||
FailedLayers = context.FailedLayers,
|
||
OverallCompletionPercentage = context.OverallCompletionPercentage,
|
||
TotalElapsedMs = context.TotalElapsedMs
|
||
},
|
||
EvidenceWeight = 1.0
|
||
});
|
||
|
||
// 添加层级完成结果证据
|
||
foreach (var layerResult in context.LayerCompletionResults)
|
||
{
|
||
evidenceNodes.Add(new EvidenceNode
|
||
{
|
||
NodeId = Guid.NewGuid(),
|
||
NodeType = EvidenceNodeType.Data,
|
||
NodeName = $"层级{layerResult.ExtendedProperties.GetValueOrDefault("current_layer", 0)}完成结果",
|
||
NodeDescription = "层级完成判定的详细结果",
|
||
TimestampUtc = layerResult.JudgmentTimeUtc,
|
||
RelatedLayer = Convert.ToInt32(layerResult.ExtendedProperties.GetValueOrDefault("current_layer", 0)),
|
||
DataContent = new
|
||
{
|
||
IsCompleted = layerResult.IsCompleted,
|
||
CompletionPercentage = layerResult.CompletionPercentage,
|
||
OverallConfidence = layerResult.OverallConfidence,
|
||
CompletionQuality = layerResult.CompletionQuality
|
||
},
|
||
EvidenceWeight = 0.8
|
||
});
|
||
}
|
||
|
||
// 添加规则判定结果证据
|
||
foreach (var ruleResult in ruleResults)
|
||
{
|
||
evidenceNodes.Add(new EvidenceNode
|
||
{
|
||
NodeId = Guid.NewGuid(),
|
||
NodeType = EvidenceNodeType.Data,
|
||
NodeName = ruleResult.RuleName,
|
||
NodeDescription = "终判规则的判定结果",
|
||
TimestampUtc = DateTime.UtcNow,
|
||
DataContent = new
|
||
{
|
||
RuleType = ruleResult.RuleType,
|
||
IsPassed = ruleResult.IsPassed,
|
||
RuleScore = ruleResult.RuleScore,
|
||
ActualValue = ruleResult.ActualValue,
|
||
Threshold = ruleResult.Threshold,
|
||
Details = ruleResult.Details
|
||
},
|
||
EvidenceWeight = ruleResult.RuleWeight
|
||
});
|
||
}
|
||
|
||
return new EvidenceChain
|
||
{
|
||
ChainId = Guid.NewGuid(),
|
||
CreatedAtUtc = DateTime.UtcNow,
|
||
EvidenceNodes = evidenceNodes,
|
||
CompletenessScore = 95.0, // 简化处理
|
||
CredibilityScore = 90.0, // 简化处理
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["evidence_node_count"] = evidenceNodes.Count,
|
||
["generation_time_ms"] = 100
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成建议。
|
||
/// </summary>
|
||
private string? GenerateRecommendation(bool isPassed, IReadOnlyList<RuleJudgmentResult> failedRules, IReadOnlyList<NGCause> ngCauses)
|
||
{
|
||
if (isPassed)
|
||
{
|
||
return "整机完成判定通过,产品可以进入下一阶段";
|
||
}
|
||
|
||
var recommendations = new List<string>();
|
||
|
||
if (failedRules.Any(r => r.RuleType == FinalJudgmentRuleType.CompletionRate))
|
||
{
|
||
recommendations.Add("建议检查各层级完成情况,确保所有必要步骤都已完成");
|
||
}
|
||
|
||
if (failedRules.Any(r => r.RuleType == FinalJudgmentRuleType.QualityScore))
|
||
{
|
||
recommendations.Add("建议提高各层级检测质量,确保检测精度和稳定性");
|
||
}
|
||
|
||
if (failedRules.Any(r => r.RuleType == FinalJudgmentRuleType.ErrorRate))
|
||
{
|
||
recommendations.Add("建议减少错误发生,优化工艺流程和操作规范");
|
||
}
|
||
|
||
if (failedRules.Any(r => r.RuleType == FinalJudgmentRuleType.TimeConstraint))
|
||
{
|
||
recommendations.Add("建议优化设备性能,提高处理效率");
|
||
}
|
||
|
||
return recommendations.Any() ? string.Join(";", recommendations) : "请根据具体情况进行针对性改进";
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加判定历史。
|
||
/// </summary>
|
||
private async Task AddJudgmentHistoryAsync(FinalJudgmentResult result, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Run(() =>
|
||
{
|
||
_judgmentHistory.TryAdd(result.JudgmentId, result);
|
||
|
||
var history = new FinalJudgmentHistory
|
||
{
|
||
JudgmentId = result.JudgmentId,
|
||
ProductTypeCode = result.ProductTypeCode,
|
||
SessionId = result.SessionId,
|
||
JudgmentType = result.JudgmentType,
|
||
JudgmentTimeUtc = result.JudgmentTimeUtc,
|
||
IsPassed = result.IsPassed,
|
||
OverallScore = result.OverallScore,
|
||
JudgmentQuality = result.JudgmentQuality,
|
||
OperatorUser = "system", // 简化处理
|
||
NGCauseCount = result.NGCauses.Count,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["judgment_elapsed_ms"] = result.JudgmentElapsedMs,
|
||
["passed_rules_count"] = result.PassedRules.Count,
|
||
["failed_rules_count"] = result.FailedRules.Count
|
||
}
|
||
};
|
||
|
||
_judgmentRecords.TryAdd(result.JudgmentId, history);
|
||
|
||
// 保持历史记录数量在合理范围内
|
||
if (_judgmentRecords.Count > _options.MaxFinalJudgmentHistoryCount)
|
||
{
|
||
var oldestRecords = _judgmentRecords.Values
|
||
.OrderBy(h => h.JudgmentTimeUtc)
|
||
.Take(_judgmentRecords.Count - _options.MaxFinalJudgmentHistoryCount)
|
||
.Select(h => h.JudgmentId)
|
||
.ToList();
|
||
|
||
foreach (var id in oldestRecords)
|
||
{
|
||
_judgmentRecords.TryRemove(id, out _);
|
||
_judgmentHistory.TryRemove(id, out _);
|
||
}
|
||
}
|
||
}, cancellationToken);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成热点NG原因。
|
||
/// </summary>
|
||
private IReadOnlyList<HotNGCause> GenerateHotNGCauses(IReadOnlyList<NGCause> ngCauses)
|
||
{
|
||
var groupedCauses = ngCauses
|
||
.GroupBy(c => c.Description)
|
||
.Select(g => new HotNGCause
|
||
{
|
||
Description = g.Key,
|
||
OccurrenceCount = g.Count(),
|
||
Rank = 0, // 将在排序后设置
|
||
Trend = NGCauseTrendDirection.Stable // 简化处理
|
||
})
|
||
.OrderByDescending(c => c.OccurrenceCount)
|
||
.Take(10)
|
||
.ToList();
|
||
|
||
// 设置排名
|
||
for (int i = 0; i < groupedCauses.Count; i++)
|
||
{
|
||
groupedCauses[i] = groupedCauses[i] with { Rank = i + 1 };
|
||
}
|
||
|
||
return groupedCauses;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成NG原因趋势。
|
||
/// </summary>
|
||
private NGCauseTrend GenerateNGCauseTrend(IReadOnlyList<NGCause> ngCauses)
|
||
{
|
||
// 简化处理,基于时间分布计算趋势
|
||
var recentCount = ngCauses.Count(c => c.OccurrenceTimeUtc >= DateTime.UtcNow.AddDays(-7));
|
||
var previousCount = ngCauses.Count(c => c.OccurrenceTimeUtc >= DateTime.UtcNow.AddDays(-14) && c.OccurrenceTimeUtc < DateTime.UtcNow.AddDays(-7));
|
||
|
||
var changeRate = previousCount > 0 ? (double)(recentCount - previousCount) / previousCount : 0.0;
|
||
var direction = changeRate switch
|
||
{
|
||
> 0.1 => NGCauseTrendDirection.Increasing,
|
||
< -0.1 => NGCauseTrendDirection.Decreasing,
|
||
_ => NGCauseTrendDirection.Stable
|
||
};
|
||
|
||
return new NGCauseTrend
|
||
{
|
||
Direction = direction,
|
||
ChangeRate = changeRate,
|
||
TrendDescription = $"NG原因趋势{direction},变化率{changeRate:P1}"
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报告内容。
|
||
/// </summary>
|
||
private async Task<FinalJudgmentReport> GenerateReportContentAsync(FinalJudgmentResult judgmentResult, CancellationToken cancellationToken = default)
|
||
{
|
||
await Task.Delay(1, cancellationToken);
|
||
|
||
var reportContent = GenerateReportText(judgmentResult);
|
||
|
||
return new FinalJudgmentReport
|
||
{
|
||
ReportId = Guid.NewGuid(),
|
||
JudgmentId = judgmentResult.JudgmentId,
|
||
GeneratedAtUtc = DateTime.UtcNow,
|
||
ReportType = ReportType.Standard,
|
||
ReportContent = reportContent,
|
||
ReportFormat = ReportFormat.Text,
|
||
ExtendedProperties = new Dictionary<string, object>
|
||
{
|
||
["generation_time_ms"] = 50
|
||
}
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成报告文本。
|
||
/// </summary>
|
||
private string GenerateReportText(FinalJudgmentResult judgmentResult)
|
||
{
|
||
var report = new System.Text.StringBuilder();
|
||
|
||
report.AppendLine("=== 整机完成判定报告 ===");
|
||
report.AppendLine($"判定ID: {judgmentResult.JudgmentId}");
|
||
report.AppendLine($"产品类型: {judgmentResult.ProductTypeCode}");
|
||
report.AppendLine($"会话ID: {judgmentResult.SessionId}");
|
||
report.AppendLine($"判定时间: {judgmentResult.JudgmentTimeUtc:yyyy-MM-dd HH:mm:ss}");
|
||
report.AppendLine($"判定结果: {(judgmentResult.IsPassed ? "通过" : "失败")}");
|
||
report.AppendLine($"总体评分: {judgmentResult.OverallScore:F2}");
|
||
report.AppendLine($"判定质量: {judgmentResult.JudgmentQuality}");
|
||
report.AppendLine($"判定耗时: {judgmentResult.JudgmentElapsedMs}ms");
|
||
report.AppendLine();
|
||
|
||
report.AppendLine("=== 规则判定结果 ===");
|
||
report.AppendLine($"通过规则数量: {judgmentResult.PassedRules.Count}");
|
||
report.AppendLine($"失败规则数量: {judgmentResult.FailedRules.Count}");
|
||
report.AppendLine();
|
||
|
||
if (judgmentResult.FailedRules.Any())
|
||
{
|
||
report.AppendLine("失败规则详情:");
|
||
foreach (var failedRule in judgmentResult.FailedRules)
|
||
{
|
||
report.AppendLine($"- {failedRule.RuleName}: {failedRule.Details}");
|
||
}
|
||
report.AppendLine();
|
||
}
|
||
|
||
if (judgmentResult.NGCauses.Any())
|
||
{
|
||
report.AppendLine("=== NG原因分析 ===");
|
||
foreach (var ngCause in judgmentResult.NGCauses)
|
||
{
|
||
report.AppendLine($"- {ngCause.Description} (级别: {ngCause.CauseLevel}, 影响: {ngCause.ImpactLevel})");
|
||
if (!string.IsNullOrEmpty(ngCause.FixSuggestion))
|
||
{
|
||
report.AppendLine($" 修复建议: {ngCause.FixSuggestion}");
|
||
}
|
||
}
|
||
report.AppendLine();
|
||
}
|
||
|
||
if (!string.IsNullOrEmpty(judgmentResult.Recommendation))
|
||
{
|
||
report.AppendLine("=== 改进建议 ===");
|
||
report.AppendLine(judgmentResult.Recommendation);
|
||
report.AppendLine();
|
||
}
|
||
|
||
report.AppendLine("=== 证据链信息 ===");
|
||
report.AppendLine($"证据节点数量: {judgmentResult.EvidenceChain.EvidenceNodes.Count}");
|
||
report.AppendLine($"完整性评分: {judgmentResult.EvidenceChain.CompletenessScore:F1}");
|
||
report.AppendLine($"可信度评分: {judgmentResult.EvidenceChain.CredibilityScore:F1}");
|
||
|
||
return report.ToString();
|
||
}
|
||
|
||
#endregion
|
||
}
|
||
|
||
#endif
|