-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.js
More file actions
144 lines (136 loc) · 4.94 KB
/
index.js
File metadata and controls
144 lines (136 loc) · 4.94 KB
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
/**
* aql-sample-size — ISO 2859-1 sample size lookup
*
* Zero-dependency lookup library for AQL (Acceptance Quality Limit) sampling
* plans defined in ISO 2859-1:1999. Used for garment, electronics, medical
* device, and general manufacturing buyer-audit quality inspection.
*
* Tables verified against QIMA reference (https://www.qima.com/aql-acceptable-quality-limit).
*
* @module aql-sample-size
* @see https://scanerp.pro/aql/ — Interactive lookup tables
* @see https://www.iso.org/standard/1141.html — ISO 2859-1:1999 standard
*/
const codeLetters = require('./data/code-letters.json');
const plans = require('./data/plans.json');
const sampleSizes = require('./data/sample-sizes.json');
const VALID_LEVELS = ['GI', 'GII', 'GIII'];
const VALID_AQLS = [0.65, 1.0, 1.5, 2.5, 4.0, 6.5];
const LOT_SIZE_KEYS = [2, 9, 16, 26, 51, 91, 151, 281, 501, 1201, 3201, 10001, 35001, 150001, 500001];
/**
* Get the ISO 2859-1 sample size code letter for a lot size + inspection level.
*
* @param {number} lotSize — number of items in the lot
* @param {string} level — inspection level: 'GI', 'GII' (default for garment QC), or 'GIII'
* @returns {string} sample size code letter (A through R)
* @throws if lotSize < 2 or level is invalid
*
* @example
* getCodeLetter(800, 'GII') // → 'J'
*/
function getCodeLetter(lotSize, level = 'GII') {
if (typeof lotSize !== 'number' || lotSize < 2) {
throw new Error(`lotSize must be a number >= 2, got ${lotSize}`);
}
if (!VALID_LEVELS.includes(level)) {
throw new Error(`level must be one of ${VALID_LEVELS.join(', ')}, got ${level}`);
}
const lookupTable = codeLetters[level];
let chosenKey = LOT_SIZE_KEYS[0];
for (const key of LOT_SIZE_KEYS) {
if (lotSize >= key) chosenKey = key;
else break;
}
return lookupTable[String(chosenKey)];
}
/**
* Get the sample size (number of units to inspect) for a given code letter.
*
* @param {string} codeLetter — A through R
* @returns {number} number of units to randomly select for inspection
*
* @example
* getSampleSize('J') // → 80
*/
function getSampleSize(codeLetter) {
const size = sampleSizes[codeLetter];
if (size === undefined) {
throw new Error(`Invalid code letter "${codeLetter}". Must be one of A–R.`);
}
return size;
}
/**
* Get the full sampling plan for a lot size + level + AQL.
* Returns sample size + accept number + reject number.
*
* @param {number} lotSize — number of items in the lot
* @param {string} level — inspection level: 'GI', 'GII' (default), or 'GIII'
* @param {number} aql — AQL value: 0.65, 1.0, 1.5, 2.5 (common), 4.0, or 6.5
* @returns {{ codeLetter: string, sampleSize: number, accept: number, reject: number, lotSize: number, level: string, aql: number }}
*
* @example
* getPlan(800, 'GII', 2.5)
* // → { codeLetter: 'J', sampleSize: 80, accept: 5, reject: 6, lotSize: 800, level: 'GII', aql: 2.5 }
*/
function getPlan(lotSize, level = 'GII', aql = 2.5) {
if (!VALID_AQLS.includes(aql)) {
throw new Error(`aql must be one of ${VALID_AQLS.join(', ')}, got ${aql}`);
}
const codeLetter = getCodeLetter(lotSize, level);
const sampleSize = getSampleSize(codeLetter);
const aqlKey = String(parseFloat(aql));
const [accept, reject] = plans[codeLetter][aqlKey];
return { codeLetter, sampleSize, accept, reject, lotSize, level, aql };
}
/**
* Get all sampling plans (across all AQLs) for a given lot size + level.
*
* @param {number} lotSize — number of items in the lot
* @param {string} level — inspection level: 'GI', 'GII' (default), or 'GIII'
* @returns {Object<string, { sampleSize, accept, reject }>} keyed by AQL value
*
* @example
* getAllPlans(800, 'GII')
* // → { '0.65': { sampleSize: 80, accept: 1, reject: 2 }, '1.0': ..., '2.5': ..., '6.5': ... }
*/
function getAllPlans(lotSize, level = 'GII') {
const codeLetter = getCodeLetter(lotSize, level);
const sampleSize = getSampleSize(codeLetter);
const result = {};
for (const aql of VALID_AQLS) {
const [accept, reject] = plans[codeLetter][String(parseFloat(aql))];
result[aql] = { sampleSize, accept, reject };
}
return result;
}
/**
* Decide pass/fail for an inspection result.
*
* @param {number} defects — count of defects found in sample
* @param {Object} plan — plan object from getPlan()
* @returns {'ACCEPT' | 'REJECT'} — buyer decision
*
* @example
* const plan = getPlan(800, 'GII', 2.5); // accept 5, reject 6
* decide(4, plan); // → 'ACCEPT' (4 ≤ 5)
* decide(6, plan); // → 'REJECT' (6 ≥ 6)
*/
function decide(defects, plan) {
if (defects <= plan.accept) return 'ACCEPT';
if (defects >= plan.reject) return 'REJECT';
// ISO 2859-1 single-sampling plans have no gap between accept and reject —
// accept and reject are consecutive integers. This branch is unreachable.
return 'REJECT';
}
module.exports = {
getCodeLetter,
getSampleSize,
getPlan,
getAllPlans,
decide,
VALID_LEVELS,
VALID_AQLS,
codeLetters,
plans,
sampleSizes,
};