Skip to content

Case 12: Production line QC grading

This case corresponds to Scenario 12 in the JQuick-Java repository’s USE-CASES.md, targeting batch quality inspection of discrete manufacturing in the MES domain. For each sampling record, the system deducts points by counts of critical / major / minor defects, stacks additional deductions for appearance, critical-dimension tolerance, and equipment status, combines them with the defect rate computed in per-mille integer arithmetic, and determines a four-level conclusion: accept (ACCEPT), rework (REWORK), scrap (SCRAP), or hold (HOLD). It further decides the disposition destination, whether the batch may be released, and whether to trigger a CAPA corrective/preventive action. All judgment criteria are driven by JQuick-Java XML rule proxies.

Discrete manufacturing workshops organize production and sampling by batch. After a batch of precision shaft sleeves comes off the line, the QC station records the batch number, product information, sample size, and the counts of critical defects (safety / functional failure), major defects, and minor defects found in sampling. It also records the measured value, nominal value, and allowed deviation of the critical dimension, an appearance score from 0-100, the production equipment status (NORMAL / WARNING), and whether this is a first-article inspection.

The system first computes two base metrics: the defect rate uses integer arithmetic “defect count × 1000 / sample size”, outputting a per-mille value to avoid floating-point division precision issues; the critical dimension is checked with a two-sided tolerance against the nominal value — outside nominal ± tolerance means non-conforming. The quality score uses a deduction system starting from 100: each critical defect -40, major -15, minor -5 (with a floor of 0 inside the script); appearance below 95/85 deducts another 10/20; out-of-tolerance dimension deducts 30; equipment warning deducts 5. The Service aggregates these and then clamps to the 0-100 range.

Level grading follows the “one critical defect scraps the batch” principle: any critical defect or defect rate ≥200‰ means direct SCRAP; a quality score below 40 or defect rate ≥100‰ means REWORK; below 60 points or defect rate ≥30‰ means HOLD; otherwise ACCEPT. The four levels map to disposition destinations: scrap bin (SCRAP_BIN), rework line (REWORK_LINE), QA hold (QA_HOLD), and next process (NEXT_PROCESS).

Two parallel decisions are also produced: a CAPA corrective/preventive process is triggered when there is a critical defect, ≥3 major defects, or an equipment warning; only ACCEPT batches with a conforming critical dimension may be released. The decision’s notes record item by item: conforming flow-through, critical-defect isolation, dimension out-of-tolerance to rework, hold for tightened re-inspection, first-article confirmation, equipment check, and CAPA hints — forming a complete QC judgment explanation.

  • Defect deductions, extra deductions, and level thresholds scattered in hard-coded logic: critical -40 / major -15 / minor -5, the 200‰/100‰/30‰ defect-rate red lines, and the 40/60 score boundaries are spread across MES code, making adjustments difficult when switching production lines or materials.
  • Quality standards vary by customer/material and require frequent releases: different product families may have different tolerances, appearance tiers, and deduction values; traditionally every adjustment means modifying Java and redeploying the MES system.
  • Deduction systems can easily go negative or above 100: after stacking multiple deductions, the raw score can go negative (e.g., the mass-scrap case has a raw score of -55); without clamping on the Java side, illegal quality scores are produced.
  • Floating-point division in defect rate has precision risks: computing the ratio with double and comparing against thresholds can misjudge at boundaries like 30‰ and 100‰ due to rounding error; misjudging borderline batches is very costly on the production line.
  • Level, disposition, CAPA, and release are four coupled decisions: critical defects must scrap the batch regardless of score and block release; out-of-tolerance dimensions must route to the rework line. Getting these orderings and linkages wrong lets problem batches flow into the next process.
  • Legacy production-line systems are mostly JDK 8 and IT-innovation environments: workshop servers have limited resources and cannot host standalone rule platforms or database-backed middleware; an embeddable lightweight rule solution is needed, and results must be auditable for quality accountability.

This case adopts the six-piece structure of “input model + decision result model + rule interface + XML rule script + Service orchestration + JUnit tests”. Tolerances, deduction criteria, level thresholds, and disposition mappings are all externalized in XML; the Service handles defect-rate/deduction aggregation, score clamping, and explanation assembly:

Layer Responsibility Files
Domain model Batch sampling record, QC decision (score / defect rate / level / disposition / CAPA / release / notes) InspectionBatch.java, InspectionDecision.java
Rule set & XML Defect rate / tolerance / deductions + level / disposition / CAPA / release QualityInspectionRules.java, quality-inspection.xml
Orchestration service Defect-rate and deduction aggregation, 0-100 clamp, level assembly, judgment note assembly QualityInspectionService.java
Tests 6 batch profiles (release, scrap, rework, hold, CAPA, high defect rate) QualityInspectionServiceTest.java

The rule interface has 10 methods in total; metric computation, deductions, level mapping, and parallel decisions are each independent:

Rule method Criterion Score / output
defectRatePermil Returns 0 when sample size ≤0; otherwise defect count × 1000 / sample size, integer arithmetic outputting per-mille Per-mille integer
dimensionPass Returns false when measured > nominal + tolerance or < nominal - tolerance; otherwise true boolean
scoreDefects 100 - critical×40 - major×15 - minor×5, floored at 0 0 ~ 100
scoreAppearance Appearance ≥95 no deduction; ≥85 deduct 10; otherwise deduct 20 0 / -10 / -20
scoreDimension Critical dimension out of tolerance deducts 30; conforming no deduction 0 / -30
scoreEquipment Equipment status WARNING deducts 5; NORMAL no deduction 0 / -5
qualityLevel Critical defects ≥1 or defect rate ≥200‰ → scrap; score <40 or ≥100‰ → rework; score <60 or ≥30‰ → hold; otherwise accept SCRAP/REWORK/HOLD/ACCEPT
disposition SCRAP→SCRAP_BIN; REWORK→REWORK_LINE; HOLD→QA_HOLD; ACCEPT→NEXT_PROCESS Disposition string
needCapa Returns true when critical defects ≥1, major defects ≥3, or equipment warning hits boolean
releaseAllowed Returns true only when level is ACCEPT and the critical dimension conforms boolean

Place the following six files into the JQuick-Java test project using the original package names from the source: the two models go into com.github.paohaijiao.demo.model, the rule interface and test class go into com.github.paohaijiao.demo, the orchestration service goes into com.github.paohaijiao.demo.service, and the XML script goes into src/test/resources/demo/. Keeping directories and package names unchanged, run mvn test -Dtest=QualityInspectionServiceTest at the project root.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 产线批次质检记录(抽检指标与实测数据快照)
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class InspectionBatch {
/**
* 批次号
*/
private String batchNo;
/**
* 产品编码
*/
private String productCode;
/**
* 产品名称
*/
private String productName;
/**
* 车间
*/
private String workshop;
/**
* 抽检数量
*/
private int sampleSize;
/**
* 致命缺陷数(安全/功能丧失)
*/
private int criticalDefects;
/**
* 严重缺陷数
*/
private int majorDefects;
/**
* 轻微缺陷数
*/
private int minorDefects;
/**
* 关键尺寸实测值
*/
private double measuredSize;
/**
* 关键尺寸标称值
*/
private double targetSize;
/**
* 允许偏差(绝对值)
*/
private double tolerance;
/**
* 外观评分(0-100)
*/
private int surfaceScore;
/**
* 设备状态:NORMAL / WARNING
*/
private String equipmentStatus;
/**
* 是否首件检验
*/
private boolean firstArticle;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 批次质检决策结果
*/
@Data
public class InspectionDecision {
/**
* 批次号
*/
private String batchNo;
/**
* 综合质量分(0-100,扣分制)
*/
private int qualityScore;
/**
* 不良率(千分比)
*/
private int defectRatePermil;
/**
* 关键尺寸是否合格
*/
private boolean dimensionPass;
/**
* 质量等级:ACCEPT-接收 / REWORK-返工 / SCRAP-报废 / HOLD-挂起待检
*/
private String qualityLevel;
/**
* 处置去向:NEXT_PROCESS / REWORK_LINE / SCRAP_BIN / QA_HOLD
*/
private String disposition;
/**
* 是否启动纠正预防措施(CAPA)
*/
private boolean needCapa;
/**
* 是否允许批次放行
*/
private boolean releaseAllowed;
/**
* 判定说明
*/
private List<String> notes = new ArrayList<String>();
public void addNote(String note) {
this.notes.add(note);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 12:产线质检多指标判定规则集
* <p>
* 规则按「缺陷扣分 + 加扣项修正 → 等级判定 → 处置与 CAPA」拆分:
* <ul>
* <li>扣分类:致命/严重/轻微缺陷、外观、关键尺寸、设备状态</li>
* <li>决策类:质量等级、处置去向、纠正措施、放行许可</li>
* </ul>
* 规则脚本见 {@code src/test/resources/demo/quality-inspection.xml}。
*/
public interface QualityInspectionRules {
int defectRatePermil(@Param("totalDefects") int totalDefects,
@Param("sampleSize") int sampleSize);
boolean dimensionPass(@Param("measuredSize") double measuredSize,
@Param("targetSize") double targetSize,
@Param("tolerance") double tolerance);
int scoreDefects(@Param("criticalDefects") int criticalDefects,
@Param("majorDefects") int majorDefects,
@Param("minorDefects") int minorDefects);
int scoreAppearance(@Param("surfaceScore") int surfaceScore);
int scoreDimension(@Param("dimensionPass") boolean dimensionPass);
int scoreEquipment(@Param("equipmentStatus") String equipmentStatus);
String qualityLevel(@Param("qualityScore") int qualityScore,
@Param("criticalDefects") int criticalDefects,
@Param("defectRatePermil") int defectRatePermil);
String disposition(@Param("qualityLevel") String qualityLevel);
boolean needCapa(@Param("criticalDefects") int criticalDefects,
@Param("majorDefects") int majorDefects,
@Param("equipmentWarning") boolean equipmentWarning);
boolean releaseAllowed(@Param("qualityLevel") String qualityLevel,
@Param("dimensionPass") boolean dimensionPass);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-java.dtd">
<javas namespace="com.github.paohaijiao.demo.QualityInspectionRules">
<!-- 不良率(千分比,整数运算避免精度问题) -->
<java name="defectRatePermil" returnClass="int">
<![CDATA[
int def defectRatePermil(int:totalDefects, int:sampleSize) {
if (sampleSize <= 0) {
return 0;
}
return totalDefects * 1000 / sampleSize;
}
]]>
</java>
<!-- 关键尺寸双向公差判定 -->
<java name="dimensionPass" returnClass="boolean">
<![CDATA[
boolean def dimensionPass(double:measuredSize, double:targetSize, double:tolerance) {
if (measuredSize > targetSize + tolerance) {
return false;
} else if (measuredSize < targetSize - tolerance) {
return false;
} else {
return true;
}
}
]]>
</java>
<!-- 缺陷扣分:致命 40 / 严重 15 / 轻微 5,下限 0 -->
<java name="scoreDefects" returnClass="int">
<![CDATA[
int def scoreDefects(int:criticalDefects, int:majorDefects, int:minorDefects) {
int score = 100 - criticalDefects * 40 - majorDefects * 15 - minorDefects * 5;
if (score < 0) {
score = 0;
}
return score;
}
]]>
</java>
<!-- 外观加扣项 -->
<java name="scoreAppearance" returnClass="int">
<![CDATA[
int def scoreAppearance(int:surfaceScore) {
if (surfaceScore >= 95) {
return 0;
} else if (surfaceScore >= 85) {
return -10;
} else {
return -20;
}
}
]]>
</java>
<!-- 尺寸加扣项 -->
<java name="scoreDimension" returnClass="int">
<![CDATA[
int def scoreDimension(boolean:dimensionPass) {
if (dimensionPass) {
return 0;
} else {
return -30;
}
}
]]>
</java>
<!-- 设备状态加扣项 -->
<java name="scoreEquipment" returnClass="int">
<![CDATA[
int def scoreEquipment(str:equipmentStatus) {
if (equipmentStatus == "WARNING") {
return -5;
} else {
return 0;
}
}
]]>
</java>
<!-- 质量等级:致命缺陷或高不良率直接报废,其次返工、挂起、接收 -->
<java name="qualityLevel" returnClass="java.lang.String">
<![CDATA[
str def qualityLevel(int:qualityScore, int:criticalDefects, int:defectRatePermil) {
if (criticalDefects >= 1 || defectRatePermil >= 200) {
return "SCRAP";
} else if (qualityScore < 40 || defectRatePermil >= 100) {
return "REWORK";
} else if (qualityScore < 60 || defectRatePermil >= 30) {
return "HOLD";
} else {
return "ACCEPT";
}
}
]]>
</java>
<!-- 处置去向 -->
<java name="disposition" returnClass="java.lang.String">
<![CDATA[
str def disposition(str:qualityLevel) {
if (qualityLevel == "SCRAP") {
return "SCRAP_BIN";
} else if (qualityLevel == "REWORK") {
return "REWORK_LINE";
} else if (qualityLevel == "HOLD") {
return "QA_HOLD";
} else {
return "NEXT_PROCESS";
}
}
]]>
</java>
<!-- 纠正预防措施:致命缺陷、严重缺陷成片、设备告警时触发 -->
<java name="needCapa" returnClass="boolean">
<![CDATA[
boolean def needCapa(int:criticalDefects, int:majorDefects, boolean:equipmentWarning) {
if (criticalDefects >= 1 || majorDefects >= 3 || equipmentWarning) {
return true;
} else {
return false;
}
}
]]>
</java>
<!-- 放行许可:必须接收且关键尺寸合格 -->
<java name="releaseAllowed" returnClass="boolean">
<![CDATA[
boolean def releaseAllowed(str:qualityLevel, boolean:dimensionPass) {
if (qualityLevel == "ACCEPT" && dimensionPass) {
return true;
} else {
return false;
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.QualityInspectionRules;
import com.github.paohaijiao.demo.model.InspectionBatch;
import com.github.paohaijiao.demo.model.InspectionDecision;
/**
* 场景 12:产线批次质检分级服务
* <p>
* 职责划分:
* <ul>
* <li><b>Java 侧</b>:不良率与扣分汇总、等级装配、处置说明与 CAPA 提示组装</li>
* <li><b>规则侧</b>:公差判定、缺陷扣分口径、等级阈值与报废/返工界限,全部外置在 XML 中</li>
* </ul>
*/
public class QualityInspectionService {
private static final int SCORE_FLOOR = 0;
private static final int SCORE_CEIL = 100;
private final QualityInspectionRules rules;
public QualityInspectionService(QualityInspectionRules rules) {
this.rules = rules;
}
/**
* 执行批次质检判定
*
* @param batch 批次抽检记录
* @return 质检决策
*/
public InspectionDecision inspect(InspectionBatch batch) {
InspectionDecision decision = new InspectionDecision();
decision.setBatchNo(batch.getBatchNo());
int totalDefects = batch.getCriticalDefects() + batch.getMajorDefects() + batch.getMinorDefects();
int ratePermil = rules.defectRatePermil(totalDefects, batch.getSampleSize());
boolean dimensionPass = rules.dimensionPass(batch.getMeasuredSize(),
batch.getTargetSize(), batch.getTolerance());
int rawScore = rules.scoreDefects(batch.getCriticalDefects(),
batch.getMajorDefects(), batch.getMinorDefects())
+ rules.scoreAppearance(batch.getSurfaceScore())
+ rules.scoreDimension(dimensionPass)
+ rules.scoreEquipment(batch.getEquipmentStatus());
int qualityScore = clamp(rawScore);
String level = rules.qualityLevel(qualityScore, batch.getCriticalDefects(), ratePermil);
boolean equipmentWarning = "WARNING".equals(batch.getEquipmentStatus());
decision.setQualityScore(qualityScore);
decision.setDefectRatePermil(ratePermil);
decision.setDimensionPass(dimensionPass);
decision.setQualityLevel(level);
decision.setDisposition(rules.disposition(level));
decision.setNeedCapa(rules.needCapa(batch.getCriticalDefects(),
batch.getMajorDefects(), equipmentWarning));
decision.setReleaseAllowed(rules.releaseAllowed(level, dimensionPass));
buildNotes(batch, decision, totalDefects);
return decision;
}
private int clamp(int score) {
if (score < SCORE_FLOOR) {
return SCORE_FLOOR;
}
if (score > SCORE_CEIL) {
return SCORE_CEIL;
}
return score;
}
private void buildNotes(InspectionBatch batch, InspectionDecision decision, int totalDefects) {
if ("ACCEPT".equals(decision.getQualityLevel())) {
decision.addNote("抽检 " + batch.getSampleSize() + " 件、不良 " + totalDefects
+ " 件,批次合格,流转下一工序");
}
if (batch.getCriticalDefects() >= 1) {
decision.addNote("发现致命缺陷 " + batch.getCriticalDefects() + " 件,整批判废并隔离");
}
if (!decision.isDimensionPass()) {
decision.addNote("关键尺寸实测 " + batch.getMeasuredSize()
+ " 超出公差 " + batch.getTolerance() + ",转返工线");
}
if ("HOLD".equals(decision.getQualityLevel())) {
decision.addNote("不良率或质量分处于临界区间,挂起等待加严复检");
}
if (batch.isFirstArticle()) {
decision.addNote("首件检验,需质量工程师确认后再批量投产");
}
if ("WARNING".equals(batch.getEquipmentStatus())) {
decision.addNote("生产设备处于告警状态,建议同步安排点检");
}
if (decision.isNeedCapa()) {
decision.addNote("已触发纠正预防措施(CAPA)流程");
}
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.InspectionBatch;
import com.github.paohaijiao.demo.model.InspectionDecision;
import com.github.paohaijiao.demo.service.QualityInspectionService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 12 生产级样板:产线质检多指标判定
* <p>
* 覆盖 6 组批次形态:合格放行、致命缺陷报废、尺寸超差返工、
* 不良率临界挂起、设备告警成片缺陷、大批量报废。
*/
public class QualityInspectionServiceTest {
private QualityInspectionService service;
@Before
public void setUp() {
QualityInspectionRules rules = JQuickJava.create()
.createApi(QualityInspectionRules.class, "demo/quality-inspection.xml");
this.service = new QualityInspectionService(rules);
}
/**
* 优质批次:200 抽 1 轻微不良、尺寸合格、外观 97 → 95 分接收放行
*/
@Test
public void shouldReleaseConformingBatch() {
InspectionBatch batch = batch("BAT-1201", 200, 0, 0, 1, 10.01, 10.0, 0.05, 97, "NORMAL", false);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(95, decision.getQualityScore());
Assert.assertEquals(5, decision.getDefectRatePermil());
Assert.assertTrue(decision.isDimensionPass());
Assert.assertEquals("ACCEPT", decision.getQualityLevel());
Assert.assertEquals("NEXT_PROCESS", decision.getDisposition());
Assert.assertTrue(decision.isReleaseAllowed());
Assert.assertFalse(decision.isNeedCapa());
Assert.assertTrue(decision.getNotes().contains("抽检 200 件、不良 1 件,批次合格,流转下一工序"));
}
/**
* 致命缺陷:1 件致命缺陷 → 不看分数直接整批报废
*/
@Test
public void shouldScrapBatchWithCriticalDefect() {
InspectionBatch batch = batch("BAT-1202", 100, 1, 0, 2, 10.02, 10.0, 0.05, 80, "NORMAL", false);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(30, decision.getQualityScore());
Assert.assertEquals("SCRAP", decision.getQualityLevel());
Assert.assertEquals("SCRAP_BIN", decision.getDisposition());
Assert.assertFalse(decision.isReleaseAllowed());
Assert.assertTrue(decision.isNeedCapa());
Assert.assertTrue(decision.getNotes().contains("发现致命缺陷 1 件,整批判废并隔离"));
}
/**
* 尺寸超差:2 严重 + 2 轻微、关键尺寸超差 → 20 分返工
*/
@Test
public void shouldReworkDimensionOutOfToleranceBatch() {
InspectionBatch batch = batch("BAT-1203", 100, 0, 2, 2, 10.12, 10.0, 0.05, 90, "NORMAL", false);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(20, decision.getQualityScore());
Assert.assertEquals(40, decision.getDefectRatePermil());
Assert.assertFalse(decision.isDimensionPass());
Assert.assertEquals("REWORK", decision.getQualityLevel());
Assert.assertEquals("REWORK_LINE", decision.getDisposition());
Assert.assertFalse(decision.isReleaseAllowed());
Assert.assertFalse(decision.isNeedCapa());
Assert.assertTrue(decision.getNotes().contains("关键尺寸实测 10.12 超出公差 0.05,转返工线"));
}
/**
* 不良率临界:4 件轻微不良、千分之 40 → 挂起待加严复检
*/
@Test
public void shouldHoldBorderlineDefectRateBatch() {
InspectionBatch batch = batch("BAT-1204", 100, 0, 0, 4, 10.00, 10.0, 0.05, 97, "NORMAL", false);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(80, decision.getQualityScore());
Assert.assertEquals(40, decision.getDefectRatePermil());
Assert.assertEquals("HOLD", decision.getQualityLevel());
Assert.assertEquals("QA_HOLD", decision.getDisposition());
Assert.assertFalse(decision.isReleaseAllowed());
Assert.assertFalse(decision.isNeedCapa());
Assert.assertTrue(decision.getNotes().contains("不良率或质量分处于临界区间,挂起等待加严复检"));
}
/**
* 设备告警 + 3 件严重缺陷 → 40 分挂起,触发 CAPA
*/
@Test
public void shouldTriggerCapaForEquipmentWarningAndMajorDefects() {
InspectionBatch batch = batch("BAT-1205", 200, 0, 3, 0, 10.01, 10.0, 0.05, 90, "WARNING", true);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(40, decision.getQualityScore());
Assert.assertEquals(15, decision.getDefectRatePermil());
Assert.assertEquals("HOLD", decision.getQualityLevel());
Assert.assertTrue(decision.isNeedCapa());
Assert.assertTrue(decision.getNotes().contains("生产设备处于告警状态,建议同步安排点检"));
Assert.assertTrue(decision.getNotes().contains("首件检验,需质量工程师确认后再批量投产"));
Assert.assertTrue(decision.getNotes().contains("已触发纠正预防措施(CAPA)流程"));
}
/**
* 大批量报废:50 抽 16 件不良含 2 件致命、不良率千分之 320
*/
@Test
public void shouldScrapHighDefectRateBatch() {
InspectionBatch batch = batch("BAT-1206", 50, 2, 4, 10, 10.20, 10.0, 0.05, 70, "WARNING", false);
InspectionDecision decision = service.inspect(batch);
Assert.assertEquals(0, decision.getQualityScore());
Assert.assertEquals(320, decision.getDefectRatePermil());
Assert.assertEquals("SCRAP", decision.getQualityLevel());
Assert.assertEquals("SCRAP_BIN", decision.getDisposition());
Assert.assertFalse(decision.isReleaseAllowed());
Assert.assertTrue(decision.isNeedCapa());
}
private InspectionBatch batch(String batchNo, int sampleSize, int critical, int major, int minor,
double measured, double target, double tolerance,
int surfaceScore, String equipmentStatus, boolean firstArticle) {
InspectionBatch b = new InspectionBatch();
b.setBatchNo(batchNo);
b.setProductCode("P-001");
b.setProductName("精密轴套");
b.setWorkshop("一号车间");
b.setSampleSize(sampleSize);
b.setCriticalDefects(critical);
b.setMajorDefects(major);
b.setMinorDefects(minor);
b.setMeasuredSize(measured);
b.setTargetSize(target);
b.setTolerance(tolerance);
b.setSurfaceScore(surfaceScore);
b.setEquipmentStatus(equipmentStatus);
b.setFirstArticle(firstArticle);
return b;
}
}

Run at the project root:

Terminal window
mvn test -Dtest=QualityInspectionServiceTest
Test method Constructed business profile Key assertions / expected decision
shouldReleaseConformingBatch 200 sampled with 1 minor defect, dimension conforming, appearance 97, equipment normal (BAT-1201) 95 points, defect rate 5‰, ACCEPT, NEXT_PROCESS, release true, no CAPA
shouldScrapBatchWithCriticalDefect 100 sampled with 1 critical + 2 minor, appearance 80 (BAT-1202) Still SCRAP at 30 points, SCRAP_BIN, no release, CAPA true, contains the critical-defect isolation note
shouldReworkDimensionOutOfToleranceBatch 2 major + 2 minor, measured 10.12 exceeding tolerance 0.05 (BAT-1203) 20 points, defect rate 40‰, dimension non-conforming, REWORK, REWORK_LINE, no release
shouldHoldBorderlineDefectRateBatch 100 sampled with 4 minor, dimension conforming, appearance 97 (BAT-1204) 80 points but defect rate 40‰ → HOLD, QA_HOLD, no release, contains tightened re-inspection note
shouldTriggerCapaForEquipmentWarningAndMajorDefects 200 sampled with 3 major, equipment WARNING, first-article inspection (BAT-1205) 40 points, 15‰, HOLD, CAPA true, with equipment-check / first-article-confirmation / CAPA notes
shouldScrapHighDefectRateBatch 50 sampled with 16 defects (2 critical + 4 major + 10 minor), dimension out of tolerance (BAT-1206) Raw negative score clamped to 0, defect rate 320‰, SCRAP, SCRAP_BIN, CAPA true

High-quality batch (200 sampled with 1 minor defect, dimension conforming, appearance 97):

质量分: 95 不良率: 5‰ 等级: ACCEPT 处置: NEXT_PROCESS 放行: true

Critical-defect batch (1 critical defect, score ignored):

质量分: 30 等级: SCRAP 处置: SCRAP_BIN CAPA: true
说明: 发现致命缺陷 1 件,整批判废并隔离
  • Defect rate uses cross-multiplication in integer arithmetic: defectRatePermil computes the per-mille value with totalDefects * 1000 / sampleSize, staying in integers throughout — avoiding floating-point division rounding error and unifying comparison thresholds as integers, so the 30‰/100‰/200‰ boundary judgments are stable and reproducible.
  • Two-layer backstop for the deduction system: the scoreDefects script internally floors defect deductions at 0, and the Service clamps the four-deduction aggregate to 0-100; the mass-scrap case with a raw score of -55 finally lands at 0, keeping illegal scores out of level grading.
  • One-critical-defect scrap priority hard-coded in rule order: the first branch of qualityLevel is criticalDefects >= 1 || defectRatePermil >= 200; a 30-point critical-defect batch is SCRAP just like a 0-point batch, without relying on incidental score outcomes.
  • All boolean judgments use positive returns, no negation: dimensionPass returns false for “above upper tolerance” and “below lower tolerance” separately, and true only when conforming — matching the script’s lack of a ! negation operator, intuitive and auditable branch by branch.
  • Parallel decisions are independent, assembled uniformly by the Service: level, disposition, CAPA, and release are four independent rule methods; needCapa can be triggered by major defects ≥3 or equipment warning independently of the level, and releaseAllowed constrains both level and dimension — clear, testable linkages.
  • Thresholds externalized in XML + lightweight Java 8 embedding: deduction values, tolerance checks, and defect-rate red lines all live in quality-inspection.xml; with the ASM call chain and JDK 8 compatibility, the solution embeds directly into legacy workshop MES and IT-innovation production-line systems without a standalone rule server.