zhangwencui
16 小时以前 ceeeb4f3315fbd4e4ca3e91efffcee0a54bc22ee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
/**
 * 临时对拍脚本:用前端渲染/规则引擎跑同一份 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(', ')}`);