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