using System.Globalization;
namespace Admin.NET.Plugin.AiDOP.Manufacturing;
/// 录入类型(快照冻结值)。
public static class S6ResultType
{
public const string Numeric = "NUMERIC";
public const string NonNumeric = "NON_NUMERIC";
}
/// 逐样本 / 单项 / 整单判定值。
public static class S6Judgement
{
public const string Pass = "PASS";
public const string Fail = "FAIL";
public const string Invalid = "INVALID"; // 配置无法判定 / 值非法
public const string Pending = "PENDING"; // 样本不足,未完成
}
/// 单项完成状态。
public static class S6ItemStatus
{
public const string NotStarted = "NOT_STARTED";
public const string InProgress = "IN_PROGRESS";
public const string Completed = "COMPLETED";
}
///
/// S6 过程检验 自动判定引擎(Phase 1C,纯函数,后端唯一判定真源)。
/// 只依据"快照冻结的规格"(ResultType/上下限/要求样本量)+ 实录样本值判定;绝不回查 S0 检规。
/// FQC 未做的两处在此补齐:① NON_NUMERIC 自动 OK→PASS/NG→FAIL;② 单项完成度 + 整单聚合后端计算。
///
public static class S6ProcessInspectionJudge
{
/// 解析上/下限字符串为 decimal;空/非法 → null(该边界不生效)。
public static decimal? ParseLimit(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return null;
return decimal.TryParse(raw.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : null;
}
/// 解析数值样本;空/非法 → null。
public static decimal? ParseNumeric(string? raw)
{
if (string.IsNullOrWhiteSpace(raw)) return null;
return decimal.TryParse(raw.Trim(), NumberStyles.Any, CultureInfo.InvariantCulture, out var d) ? d : null;
}
///
/// 逐样本判定。NUMERIC:闭区间(上下限可各自缺省,均缺=INVALID 不可判);值非法=INVALID。
/// NON_NUMERIC:OK→PASS / NG→FAIL / 其他=INVALID。
///
public static string JudgeSample(string? resultType, string? actualRaw, string? lowerRaw, string? upperRaw)
{
var rt = (resultType ?? string.Empty).Trim().ToUpperInvariant();
if (rt == S6ResultType.NonNumeric)
{
var v = (actualRaw ?? string.Empty).Trim().ToUpperInvariant();
return v switch { "OK" => S6Judgement.Pass, "NG" => S6Judgement.Fail, _ => S6Judgement.Invalid };
}
if (rt == S6ResultType.Numeric)
{
var lo = ParseLimit(lowerRaw);
var up = ParseLimit(upperRaw);
if (lo == null && up == null) return S6Judgement.Invalid; // 无任何界限 → 无法判定
var actual = ParseNumeric(actualRaw);
if (actual == null) return S6Judgement.Invalid; // 非法/空数值 → 不可默认 PASS
var ok = (lo == null || actual.Value >= lo.Value) && (up == null || actual.Value <= up.Value);
return ok ? S6Judgement.Pass : S6Judgement.Fail;
}
return S6Judgement.Invalid; // 未知录入类型
}
///
/// 单项聚合:由该项已录样本的判定 + 要求样本量 → (完成状态, 单项结果)。
/// 任一 INVALID→INVALID;任一 FAIL→FAIL;已录满且全 PASS→PASS;否则 PENDING。
///
public static (string Status, string Result) AggregateItem(int requiredCount, IReadOnlyList sampleJudgements)
{
var recorded = sampleJudgements?.Count ?? 0;
var required = requiredCount <= 0 ? 1 : requiredCount;
if (recorded == 0) return (S6ItemStatus.NotStarted, S6Judgement.Pending);
var status = recorded >= required ? S6ItemStatus.Completed : S6ItemStatus.InProgress;
if (sampleJudgements!.Any(j => j == S6Judgement.Invalid)) return (status, S6Judgement.Invalid);
if (sampleJudgements.Any(j => j == S6Judgement.Fail)) return (status, S6Judgement.Fail);
if (status == S6ItemStatus.Completed && sampleJudgements.All(j => j == S6Judgement.Pass))
return (status, S6Judgement.Pass);
return (status, S6Judgement.Pending); // 已录未满
}
///
/// 整单聚合:由各项 (完成状态, 单项结果) → 整单结果。
/// 任一 INVALID→INVALID;任一 FAIL→FAIL;全部 COMPLETED 且 PASS→PASS;否则 PENDING。
///
public static string AggregateOverall(IReadOnlyList<(string Status, string Result)> items)
{
if (items == null || items.Count == 0) return S6Judgement.Pending;
if (items.Any(i => i.Result == S6Judgement.Invalid)) return S6Judgement.Invalid;
if (items.Any(i => i.Result == S6Judgement.Fail)) return S6Judgement.Fail;
if (items.All(i => i.Status == S6ItemStatus.Completed && i.Result == S6Judgement.Pass)) return S6Judgement.Pass;
return S6Judgement.Pending;
}
/// 是否全部项目均已完成(可完成检验的前置)。
public static bool AllItemsCompleted(IReadOnlyList<(string Status, string Result)> items)
=> items != null && items.Count > 0 && items.All(i => i.Status == S6ItemStatus.Completed);
}