/**
|
* 临时对拍脚本:用前端渲染/规则引擎跑同一份 fixture,产出期望产物,
|
* 供后端 Java 引擎的一致性测试逐字比对。用完即删,不入库。
|
*
|
* 用法:npx tsx .qc-conformance/render-ts.ts <fixtureDir> <outputDir>
|
*/
|
import { readFileSync, writeFileSync } from 'node:fs';
|
|
import { createContext } from '../src/components/quality/engine/context';
|
import { evaluateReport } from '../src/components/quality/engine/report-evaluator';
|
import { renderReport } from '../src/components/quality/engine/render';
|
import { evaluateRule } from '../src/components/quality/engine/rule-engine';
|
|
interface ExpressionCase {
|
expression: string;
|
scope: 'item2' | 'item3' | 'report';
|
}
|
|
const EXPRESSIONS: ExpressionCase[] = [
|
{ expression: 'report.reportNo', scope: 'report' },
|
{ expression: "report.inspectType IN ('IQC', 'IPQC')", scope: 'report' },
|
{ expression: "report.inspectType NOT IN ('OQC')", scope: 'report' },
|
{ expression: 'PASS_RATE(inspectionItems.result)', scope: 'report' },
|
{ expression: 'FAIL_RATE(inspectionItems.result)', scope: 'report' },
|
{ expression: 'COUNT(inspectionItems)', scope: 'report' },
|
{ expression: 'AVG(inspectionItems.actualValue)', scope: 'report' },
|
{ expression: 'ROUND(AVG(inspectionItems.actualValue), 1)', scope: 'report' },
|
{ expression: 'STDDEV(inspectionItems.actualValue)', scope: 'report' },
|
{ expression: 'SUM(inspectionItems.actualValue)', scope: 'report' },
|
{ expression: 'MAX(inspectionItems.actualValue)', scope: 'report' },
|
{ expression: 'MIN(inspectionItems.actualValue)', scope: 'report' },
|
{ expression: 'ABS(-3.5)', scope: 'report' },
|
{ expression: 'CP(inspectionItems.actualValue, 70, 30)', scope: 'report' },
|
{ expression: 'CPK(inspectionItems.actualValue, 70, 30)', scope: 'report' },
|
{ expression: 'ROUND(10 / 3, 4)', scope: 'report' },
|
{ expression: '3 * 2 - 1', scope: 'report' },
|
{ expression: "report.reportNo + '-' + report.spec", scope: 'report' },
|
{ expression: 'report.total = 3 AND report.passCount = 1', scope: 'report' },
|
{ expression: "report.passRate = '33.33%'", scope: 'report' },
|
{ expression: 'report.supplierCode = NULL', scope: 'report' },
|
{ expression: 'TRUE AND NOT FALSE', scope: 'report' },
|
{ expression: '!report.total', scope: 'report' },
|
{ expression: '1 / 0', scope: 'report' },
|
{ expression: 'FOO(1)', scope: 'report' },
|
{ expression: 'report.reportNo >= ', scope: 'report' },
|
{ expression: "AVG('abc')", scope: 'report' },
|
{ expression: 'item.actualValue >= item.lowerLimit AND item.actualValue <= item.upperLimit', scope: 'item2' },
|
{ expression: "item.itemName IN ('长度', '硬度')", scope: 'item2' },
|
{ expression: "item.itemName NOT IN ('外观')", scope: 'item2' },
|
{ expression: '-item.index', scope: 'item2' },
|
{ expression: 'item.actualValue = 10.2', scope: 'item2' },
|
{ expression: 'item.result', scope: 'item2' },
|
{ expression: 'item.actualValue BETWEEN item.lowerLimit AND item.upperLimit', scope: 'item3' },
|
{ expression: 'item.actualValue NOT BETWEEN item.lowerLimit AND item.upperLimit', scope: 'item3' },
|
// 真值口径:能当数字看的一律按 !== 0 判真,字符串 "0" 是假值(不是 JS Boolean() 的口径)
|
{ expression: '!0', scope: 'report' },
|
{ expression: '!1', scope: 'report' },
|
{ expression: '!"0"', scope: 'report' },
|
{ expression: '!"abc"', scope: 'report' },
|
{ expression: '!""', scope: 'report' },
|
{ expression: '-0', scope: 'report' },
|
];
|
|
const fixtureDir = process.argv[2]!;
|
const outputDir = process.argv[3]!;
|
|
const schema = JSON.parse(readFileSync(`${fixtureDir}/schema.json`, 'utf8'));
|
const rawContext = JSON.parse(readFileSync(`${fixtureDir}/context.json`, 'utf8'));
|
const rules = schema.rules ?? [];
|
const context = createContext(rawContext);
|
|
const outcome = renderReport({ context, rules, schema });
|
writeFileSync(`${outputDir}/expected-html.html`, outcome.html, 'utf8');
|
|
const evaluated = evaluateReport(context, rules).context;
|
const scopes: Record<string, unknown> = {
|
report: { ...evaluated, index: 0 },
|
item2: { ...evaluated, item: evaluated.inspectionItems[1], index: 2 },
|
item3: { ...evaluated, item: evaluated.inspectionItems[2], index: 3 },
|
};
|
|
const results = EXPRESSIONS.map(({ expression, scope }) => {
|
try {
|
const value = evaluateRule(expression, scopes[scope]);
|
const kind =
|
value === null || value === undefined ? 'nil' : typeof value === 'object' ? 'object' : typeof value;
|
return { expression, scope, kind, text: String(value) };
|
} catch (error) {
|
return { expression, scope, error: (error as Error).message };
|
}
|
});
|
|
writeFileSync(
|
`${outputDir}/expected-rules.json`,
|
`${JSON.stringify({ errors: outcome.errors, results }, null, 2)}\n`,
|
'utf8',
|
);
|
|
// eslint-disable-next-line no-console
|
console.log(
|
`判定后的报告字段:${JSON.stringify({
|
result: evaluated.report.result,
|
resultText: evaluated.report.resultText,
|
total: evaluated.report.total,
|
passCount: evaluated.report.passCount,
|
failCount: evaluated.report.failCount,
|
passRate: evaluated.report.passRate,
|
})}`,
|
);
|
// eslint-disable-next-line no-console
|
console.log(`各项判定:${evaluated.inspectionItems.map((item) => `${item.itemName}=${item.result}`).join(', ')}`);
|