/**
|
* 探针:确认前端判定器把「无判定规则」和「待判定」分开,且前者不计入合格率与报告结论。
|
*
|
* 背景:报告里常有「试样质量 m」「称量瓶 m0」这类只供公式取数的过程参数,它们没有规格、
|
* 也没有规则,属于「无从判定」。若照常算进分母,一份本来全合格的报告会被这些行压成「待判定」。
|
*
|
* 只读,不改代码、不碰数据库。用法:npx tsx .qc-conformance/verify-undecidable.ts
|
*/
|
import type { ReportContextItem } from '../src/components/quality/engine/context';
|
|
import { createContext } from '../src/components/quality/engine/context';
|
import { evaluateReport } from '../src/components/quality/engine/report-evaluator';
|
|
type Item = Partial<ReportContextItem> & { itemName: string };
|
|
const base = { reportNo: 'QC-2026-0001', reportName: '来料检验报告' };
|
|
function evaluate(items: Item[], rules: unknown[] = []) {
|
const context = createContext({ report: base, inspectionItems: items as ReportContextItem[] });
|
return evaluateReport(context, rules as never);
|
}
|
|
/** 一条带规格上下限的项,用来当「正常可判定」的对照组 */
|
const withLimits = (name: string, actual: string, upper: number, lower: number): Item => ({
|
itemName: name,
|
actualValue: actual,
|
upperLimit: upper,
|
lowerLimit: lower,
|
});
|
|
/** 一条只供公式取数的过程参数:既没有上下限、也没有自带结果 */
|
const processParam = (name: string, actual: string): Item => ({ itemName: name, actualValue: actual });
|
|
const failures: string[] = [];
|
|
function expect(label: string, actual: unknown, wanted: unknown) {
|
const ok = actual === wanted;
|
if (!ok) {
|
failures.push(`${label}:期望 ${JSON.stringify(wanted)},实际 ${JSON.stringify(actual)}`);
|
}
|
// eslint-disable-next-line no-console
|
console.log(`${ok ? ' ok ' : ' FAIL '} ${label} = ${JSON.stringify(actual)}`);
|
}
|
|
// --------- 场景 1:过程参数 + 可判定项混排,报告仍然应当给出「合格」
|
{
|
// eslint-disable-next-line no-console
|
console.log('\n===== 场景 1:试样质量 m(过程参数)不该把全合格的报告压成待判定 =====');
|
const outcome = evaluate([withLimits('长度', '10.2', 10.5, 9.5), processParam('试样质量 m', '50.2')]);
|
const report = outcome.context.report;
|
const items = outcome.context.inspectionItems;
|
|
expect('长度.resultText', items[0].resultText, '合格');
|
expect('试样质量 m.resultText', items[1].resultText, '无判定规则');
|
expect('试样质量 m.result', items[1].result, '');
|
expect('report.total(真实项数)', report.total, 2);
|
expect('report.passCount', report.passCount, 1);
|
expect('report.failCount', report.failCount, 0);
|
expect('report.passRate(分母只算可判定项)', report.passRate, '100.00%');
|
expect('report.result', report.result, 'PASS');
|
expect('report.conclusion', report.conclusion, '合格');
|
// eslint-disable-next-line no-console
|
console.log(` 问题清单:${JSON.stringify(outcome.errors)}`);
|
}
|
|
// --------- 场景 2:一项都判不了时不硬给结论
|
{
|
// eslint-disable-next-line no-console
|
console.log('\n===== 场景 2:全部是无判定规则项,合格率留空、不给结论 =====');
|
const outcome = evaluate([processParam('试样质量 m', '50.2'), processParam('称量瓶 m0', '28.7')]);
|
const report = outcome.context.report;
|
|
expect('report.total', report.total, 2);
|
expect('report.passRate', report.passRate, '');
|
expect('report.result', report.result, '');
|
expect('report.conclusion', report.conclusion, '待判定');
|
}
|
|
// --------- 场景 3:待判定(规则跑挂)仍留在分母里,仍压住合格结论
|
{
|
// eslint-disable-next-line no-console
|
console.log('\n===== 场景 3:待判定(规则跑挂)仍计入分母、仍不给合格结论 =====');
|
const outcome = evaluate(
|
[withLimits('长度', '10.2', 10.5, 9.5), withLimits('宽度', '4.9', 5.0, 4.0), processParam('外观', '无划痕')],
|
// 「无划痕」不是数字,ABS 在它身上算不出来 → 该项待判定
|
[{ id: 'r1', name: '实测值可算', scope: 'item', expression: 'ABS(item.actualValue) >= 0' }],
|
);
|
const report = outcome.context.report;
|
const items = outcome.context.inspectionItems;
|
|
expect('长度.resultText', items[0].resultText, '合格');
|
expect('外观.resultText', items[2].resultText, '待判定');
|
expect('report.total', report.total, 3);
|
expect('report.passCount', report.passCount, 2);
|
expect('report.passRate(待判定项留在分母里)', report.passRate, '66.67%');
|
expect('report.result(没验过不给合格)', report.result, '');
|
expect('report.conclusion', report.conclusion, '待判定');
|
}
|
|
// eslint-disable-next-line no-console
|
console.log(
|
failures.length === 0
|
? '\n全部通过:无判定规则不计入分母与结论,待判定仍计入分母并压住合格结论。'
|
: `\n有 ${failures.length} 处不符:\n${failures.map((line) => ` - ${line}`).join('\n')}`,
|
);
|
process.exitCode = failures.length === 0 ? 0 : 1;
|