Standard Acceptance Sampling
Executive Summary

Executive Summary

Whether the plan in force is telling good lots from bad ones

Lots Inspected
40
Units Inspected
3200
Defects Found
59
Observed Defect Rate
1.84 percent
Sampling Plan
n = 80, c = 2
Probability Model
binomial
Lots Accepted
31
Lots Rejected
9
Producer's Risk
0.0466
Consumer's Risk
0.2306
AOQL
0.0168
Average Sample Number
80
Discrimination
adequate
Verdict
Misses a risk target (alpha 4.66 percent, beta 23.06 percent)
31 of the 40 lots accept and 9 reject under this plan, a reject rate of 22.50 percent against the 4.66 percent the plan would produce if every lot were running exactly at the acceptable quality level. Lots are being returned more often than the agreement anticipates, which points at incoming quality worse than 1.00 percent rather than at the plan. At the acceptable level of 1.00 percent the plan accepts 95.34 percent of the time, so the producer's risk of having a good lot returned is 4.66 percent; at the rejectable level of 5.00 percent it still accepts 23.06 percent of the time, which is the consumer's risk of taking a bad one. Those two quality levels were not supplied with the request, so conventional placeholders were used; every risk figure here moves if your agreement names different ones. Sampling sorts lots, it does not improve them: with this plan running exactly as designed, the worst average defect rate that survives it over the long run is 1.68 percent, or 16,837 parts per million, reached when incoming quality sits at 2.81 percent. 12 lots found no defects at all, which is not the same as containing none: on the evidence of a clean sample that size, the true defect rate could still be as high as 3.68 percent with 95 percent confidence. Meeting the stated targets of 5.00 percent producer's risk and 10.00 percent consumer's risk instead needs n = 132 with c = 3, which is 52 more units inspected per lot.
Suggested Interpretation

The short answer

31 of 40 lots accept and 9 reject under this plan. The observed reject rate is 22.50 percent, but if incoming quality were exactly at the acceptable level of 1.00 percent, the plan would reject only 4.66 percent. The consumer's risk—accepting a bad lot at the rejectable level of 5.00 percent—is 23.06 percent, above the typical 10.00 percent target; the producer's risk is 4.66 percent, within the 5.00 percent target. This plan is tilted against the buyer.

The detail

Lots inspected: 40; units inspected: 3,200; defects found: 59; observed defect rate: 1.84 percent. Plan: n = 80, c = 2, binomial model. Lots accepted: 31; lots rejected: 9 (reject rate 22.50 percent). Producer's risk (alpha): 0.0466 (4.66 percent) at AQL of 1.00 percent. Consumer's risk (beta): 0.2306 (23.06 percent) at rejectable level of 5.00 percent. Average outgoing quality limit (AOQL): 0.0168 (1.68 percent, or 16,837 parts per million). 12 lots found zero defects; the upper 95 percent confidence bound on the true defect rate in a clean sample of 80 is 3.68 percent. Discrimination is adequate. Meeting targets of 5.00 percent producer's risk and 10.00 percent consumer's risk requires n = 132 with c = 3, adding 52 units per lot.

What this can't tell you

The 1.00 percent AQL and 5.00 percent rejectable level are placeholders, not your contractual agreement. If your thresholds differ, all risk figures change. The high reject rate (22.50 percent observed vs. 4.66 percent expected at the AQL) suggests incoming quality is worse than 1.00 percent, not that the plan is faulty. A single accept or reject is one draw from a probabilistic curve and does not prove the lot itself is good or bad.

Overview

Analysis Overview

A plan of n = 80, c = 2 judged against 40 lots of Lot ID.

N Lots40
Units Inspected3200
Plan N80
Plan C2
Modelbinomial
Suggested Interpretation

The short answer

This is a sampling plan that inspects 80 units per lot and accepts on 2 or fewer defects. It describes what happens to lots at two reference quality levels—1.00 percent and 5.00 percent defect rates—but those levels were not supplied with your request; they are placeholders. The plan's behavior is entirely contained in its operating characteristic curve, which also determines the producer's risk, the consumer's risk, and the average outgoing quality. The binomial distribution is appropriate here because the sample of 80 units is 1.6 percent of a lot of 5,000, well under the 10 percent rule.

The detail

The plan is n = 80, c = 2, applied to 40 lots with 3,200 units inspected in total. The operating characteristic gives the probability of acceptance at every possible true defect rate. At the conventional acceptable quality level of 1.00 percent, the plan accepts 95.34 percent of the time; at the rejectable level of 5.00 percent, it still accepts 23.06 percent of the time. Three distinct questions are answered here: whether the process is stable (control-chart question), whether output fits specification (capability question), and whether a completed lot should be accepted (acceptance-sampling question). This analysis addresses only the third.

What this can't tell you

The 1.00 percent and 5.00 percent quality levels are industry defaults, not your agreement. Every risk figure moves if your contract specifies different thresholds. This analysis does not establish whether the process is in statistical control or whether its output meets specification limits.

Data Preparation

Data Quality

How the inspection record was read into lots.

Initial Rows40
Final Rows40
Rows Removed0
Rows Dropped Missing0
Rows Merged Duplicate0
Units Inspected3200
Suggested Interpretation

The short answer

The data loaded cleanly: 40 distinct lots, 3,200 units inspected, 59 defects found (1.84 percent observed rate). Every lot was inspected at the plan's full sample size of 80 units, so a single operating-characteristic curve describes them all.

The detail

40 rows loaded with 40 distinct Lot ID values. No rows were dropped for missing defect counts. Each lot appears once; no duplicate lot identifiers were merged. Every lot was inspected at exactly 80 units (the plan's sample size), so all 3,200 inspected units and 59 defects are at uniform sample size. Lot size of 5,000 was read from the 'Lot Size' column. The observed defect rate across all inspected units is 59 / 3,200 = 1.84 percent.

What this can't tell you

The data structure does not reveal whether the 80 units per lot represent a true random sample or a systematic draw. If sampling is non-random, acceptance probabilities may differ from the binomial model.

Visualization

Operating Characteristic Curve

How often the plan accepts, at every true defect rate a lot might have.

Suggested Interpretation

The short answer

The curve shows acceptance probability falling steeply from 95 percent at 1.03 percent defective to 10 percent at 6.52 percent defective. At the acceptable level (1.00 percent), the plan accepts 95.34 percent of the time; at the rejectable level (5.00 percent), it still accepts 23.06 percent of the time. The plan discriminates well between acceptable and rejectable lots, but the consumer's risk is too high.

The detail

Operating characteristic: Pa(p) = P(X ≤ 2) where X ~ Binomial(80, p). At p = 0.01, Pa = 0.9534 (producer's risk = 0.0466). At p = 0.05, Pa = 0.2306 (consumer's risk = 0.2306). The curve passes through 95 percent acceptance at 1.03 percent defective and 10 percent acceptance at 6.52 percent defective, a discrimination ratio of 6.33. The indifference quality (50 percent acceptance) is at 3.33 percent. The second curve shown (n = 132, c = 3) would reach 95.57 percent acceptance at the AQL and 9.92 percent at the rejectable level, meeting both targets.

What this can't tell you

The OC curve describes the plan's long-run behavior at each fixed defect rate. A single accept or reject decision is one draw from this curve and does not establish the true defect rate of that lot.

Data Table

Producer's and Consumer's Risk

The two error rates the plan trades against each other, read off the curve.

MeasureEstimateAs PercentMeaning
Acceptable quality level (AQL)0.011.00 percentThe quality the plan is meant to accept routinely
Probability of acceptance at the AQL0.953495.34 percentHow often a lot truly at the AQL passes under n = 80, c = 2
Producer's risk (alpha at the AQL)0.04664.66 percentHow often an acceptable lot is wrongly rejected — the supplier's exposure
Rejectable quality level (LTPD)0.055.00 percentThe quality the plan is meant to catch
Consumer's risk (beta at the LTPD)0.230623.06 percentHow often a rejectable lot is wrongly accepted — the buyer's exposure
Indifference quality (accepted half the time)0.03333.33 percentThe defect rate the plan is exactly undecided about
Defect rate accepted 95 percent of the time0.01031.03 percentGood lots this clean pass almost always
Defect rate accepted 10 percent of the time0.06526.52 percentBad lots this dirty are caught nine times in ten
Operating ratio (discrimination)6.3276.33 timesHow far apart two lots must be before the plan reliably tells them apart
Average outgoing quality limit (AOQL)0.01681.68 percentThe worst average defect rate that survives the plan over the long run
Suggested Interpretation

The short answer

The plan trades two errors: rejecting a good lot (producer's risk of 4.66 percent at the acceptable level) against accepting a bad one (consumer's risk of 23.06 percent at the rejectable level). The consumer's risk exceeds its typical 10.00 percent target while the producer's risk stays within 5.00 percent, so this plan favors the supplier. The indifference quality is 3.33 percent—lots at exactly that defect rate are accepted half the time, which is usually much worse than the acceptable level people assume the plan defends.

The detail

At the acceptable quality level of 1.00 percent, the plan accepts 95.34 percent of the time, so the producer's risk is 0.0466 (4.66 percent). At the rejectable level of 5.00 percent, the plan accepts 23.06 percent of the time (consumer's risk of 0.2306). The indifference quality is 0.0333 (3.33 percent). Good lots at 1.03 percent defect rate pass 95 percent of the time; bad lots at 6.52 percent are caught nine times in ten. The operating ratio (discrimination) is 6.3275 (6.33 times). The average outgoing quality limit is 0.0168 (1.68 percent). These are properties of the plan at named quality levels, not measurements of this supplier's actual performance.

What this can't tell you

These risk figures are fixed by the plan and the reference quality levels you adopt. They do not change based on this record's 1.84 percent observed defect rate. The high consumer's risk reflects a deliberate trade-off in the plan's design, not a flaw in execution.

Visualization

Average Outgoing Quality and the AOQL

What still gets through when the plan is working exactly as designed.

Suggested Interpretation

The short answer

This curve shows how much defective work actually reaches downstream after the plan accepts or rejects. The worst average outgoing defect rate is 1.68 percent (16,837 parts per million), reached when incoming quality is 2.81 percent. On a lot of 5,000 units, that translates to about 84.2 defective units per accepted lot's worth of product. Sampling sorts lots into taken and returned; it does not remove defects from the ones it takes.

The detail

At each incoming defect rate, the outgoing rate is the incoming rate multiplied by the probability the plan accepts. The curve rises from zero, peaks at 0.0168 (1.68 percent outgoing defect rate) when incoming is 2.81 percent, then falls as the plan rejects nearly everything past that point. The rejected lots are assumed screened in full with defectives replaced, so the correction (5,000 − 80) / 5,000 is applied. The average outgoing quality limit (AOQL) of 0.0168 is the worst long-run average this plan can produce regardless of what the supplier sends. This is the honest price of acceptance sampling: it identifies and returns bad lots but does not improve the ones it accepts.

What this can't tell you

The AOQL assumes rejected lots are 100 percent inspected and all defectives replaced. If rejected lots receive partial or no inspection, actual outgoing quality will be worse. The curve describes long-run average behavior, not the quality of any single accepted lot.

Visualization

Accept or Reject, Lot by Lot

Defectives found in each sample against the acceptance number of 2.

Suggested Interpretation

The short answer

Defect counts in the 40 samples range from 0 to 6. 9 lots were rejected (22.50 percent), all with 3 or more defects. 6 lots sit exactly on the acceptance number of 2—one more defective and they would have been returned. An accepted lot is not evidence the lot is good, and a rejected lot is not evidence it is bad; each is one draw from a curve that accepts an acceptable lot 95.34 percent of the time and a rejectable one 23.06 percent of the time.

The detail

Each bar represents one lot's sample result against the acceptance number c = 2. Lots with 0, 1, or 2 defects are accepted; 3 or more are rejected. Defect counts range from 0 to 6. Of 40 lots, 31 accept and 9 reject. The 6 lots at exactly 2 defects are accepted by a single unit—one additional defect would have triggered rejection. Lots rejected: LOT-006, LOT-012, LOT-017, LOT-021, LOT-027, LOT-031, LOT-033, LOT-036, LOT-039 (9 total). The observed reject rate of 22.50 percent is higher than the 4.66 percent the plan would produce at the acceptable level of 1.00 percent, indicating incoming quality worse than that threshold.

What this can't tell you

A single accept or reject is one draw from a probabilistic curve. Rejecting LOT-006 does not prove it was bad; it could have been acceptable quality that the plan happened to flag. Similarly, accepting LOT-001 does not prove it was good. The lot-by-lot decisions reflect sampling variability, not the true quality of each lot.

Data Table

Lot Decisions and What the Sample Actually Proves

Per-lot counts, decisions, and an upper confidence bound on each true defect rate.

LotUnits InspectedDefects FoundObserved Rate PCTUpper 95 Bound PCTDecision
LOT-0358067.514.27Reject
LOT-0218056.2512.69Reject
LOT-012804511.08Reject
LOT-039804511.08Reject
LOT-0068033.759.408Reject
LOT-0178033.759.408Reject
LOT-0278033.759.408Reject
LOT-0328033.759.408Reject
LOT-0408033.759.408Reject
LOT-0038022.57.661Accept
LOT-0108022.57.661Accept
LOT-0168022.57.661Accept
LOT-0248022.57.661Accept
LOT-0298022.57.661Accept
LOT-0368022.57.661Accept
LOT-0018011.255.793Accept
LOT-0048011.255.793Accept
LOT-0078011.255.793Accept
LOT-0098011.255.793Accept
LOT-0138011.255.793Accept
LOT-0148011.255.793Accept
LOT-0198011.255.793Accept
LOT-0228011.255.793Accept
LOT-0258011.255.793Accept
LOT-0288011.255.793Accept
Suggested Interpretation

The short answer

12 lots found zero defects, but a clean sample of 80 units does not prove a lot is defect-free—it bounds the rate. For the cleanest lots, the true defect rate could still be as high as 3.68 percent with 95 percent confidence. The lots with the highest observed rates (up to 7.5 percent) all exceed the acceptance number and are rejected.

The detail

Upper confidence bound: one-sided 95 percent Clopper-Pearson limit on each lot's true defect rate. For 12 lots with zero defects found, the upper bound is 3.68 percent—real defective work the sample missed. For lots with 2 defects (the acceptance boundary), the upper bound is 7.6611 percent. The highest observed rate is 7.5 percent (LOT-035, 6 defects in 80 units), with upper bound 14.2665 percent. The table shows the 25 highest observed rates of the 40 lots. Every lot was inspected at 80 units. The Clopper-Pearson limit is the highest true defect rate that would still make the observed count unsurprising at the 95 percent confidence level.

What this can't tell you

The confidence bounds are one-sided upper limits, not two-sided intervals. They do not establish the true defect rate; they bound how high it could be. A wide bound reflects the coarse resolution of a sample of 80 units.

Data Table

Designing the Plan Backwards

The smallest (n, c) that meets the stated risks at the stated quality levels.

ItemValueDetail
Sample size in force (n)80every lot inspected the same 80 units, so that is the plan's sample size
Acceptance number in force (c)2not supplied, so it was set to the smallest acceptance number that keeps the producer's risk at or below 5.00 percent at the acceptable quality level
Producer's risk of the plan in force4.66 percenttarget was at most 5.00 percent
Consumer's risk of the plan in force23.06 percenttarget was at most 10.00 percent
Smallest sample size meeting both targets132found by searching sample sizes upward, taking the smallest acceptance number that holds the producer's risk at the AQL
Acceptance number of that plan3acceptance probability 95.57 percent at the AQL and 9.92 percent at the rejectable level
Change in inspection per lot+ 52more units inspected units per lot versus the plan in force
Suggested Interpretation

The short answer

The plan in force does not meet both targets: producer's risk is 4.66 percent (within the 5.00 percent target) but consumer's risk is 23.06 percent (above the 10.00 percent target). Meeting both requires n = 132 with c = 3, an increase of 52 units per lot, or 2,080 additional units across the 40 lots in this record.

The detail

Design targets: producer's risk ≤ 5.00 percent at AQL = 1.00 percent; consumer's risk ≤ 10.00 percent at LTPD = 5.00 percent. Plan in force: n = 80, c = 2. Producer's risk: 4.66 percent (within target). Consumer's risk: 23.06 percent (exceeds 10.00 percent target). Smallest plan meeting both targets: n = 132, c = 3. That plan reaches 95.57 percent acceptance at the AQL and 9.92 percent at the rejectable level. Inspection cost: +52 units per lot, or 2,080 units total across 40 lots. The search was conducted by testing sample sizes upward from 1, taking the smallest acceptance number that holds producer's risk at the AQL, then stopping at the first plan whose consumer's risk at the rejectable level meets the target.

What this can't tell you

Published schemes such as ANSI/ASQ Z1.4 tabulate the same trade-off by lot size and AQL, with switching rules for tightening or reducing inspection based on supplier history. This plan is derived from your stated targets, not a certification against a published standard.

Data Table

Methods and Disclosure

Every formula used, and what this analysis does not claim.

ItemDetail
Probability modelthe sample of 80 units is 1.6 percent of a lot of 5,000 (read from the column 'Lot Size'), well under the 10 percent rule, so the binomial distribution is an accurate model and is used
Operating characteristicPa(p) = P(X <= 2) where X is Binomial(80, p) — the chance that a sample of 80 units from a lot running at defect rate p contains no more than 2 defectives.
Producer's riskalpha = 1 - Pa(AQL) = 1 - 0.953447 = 0.046553, evaluated at an AQL of 1.00 percent.
Consumer's riskbeta = Pa(LTPD) = 0.230621, evaluated at a rejectable level of 5.00 percent.
Average outgoing qualityAOQ(p) = p x Pa(p) x (5,000 - 80) / 5,000, maximised over p to give the AOQL of 1.68 percent at an incoming rate of 2.81 percent. Rejected lots are assumed to be screened in full and their defectives replaced, so the (5,000 - 80) / 5,000 correction for the units already inspected is applied.
Average sample numberA single sampling plan inspects the same 80 units whatever it finds, so its average sample number is exactly 80 — a constant, not an average. Double and sequential plans are where an average sample number varies; this record was analysed as single sampling.
Average total inspectionATI = n + (1 - Pa(p)) x (N - n), the units inspected per lot once rejected lots are screened in full: 983.7 at the observed rate of 1.84 percent, and 309 at the AQL.
Upper confidence bound per lotOne-sided 95 percent Clopper-Pearson upper limit on the lot's true defect rate given the defectives found in its sample. For a sample of 80 units that found none, that limit is 3.68 percent — zero found is not zero present.
Plan design searchSample sizes were searched from 1 upward to 5,000; for each, the acceptance number was the smallest one holding acceptance at the AQL at or above 95.00 percent, and the first plan whose acceptance at the rejectable level fell to 10.00 percent or below was taken.
Standard schemesPublished schemes such as ANSI/ASQ Z1.4 are tables of (n, c) pairs indexed by lot size and AQL, together with switching rules between normal, tightened and reduced inspection. They are widely used conventions; this analysis computes a plan's properties directly from its own numbers and does not certify conformance to any published scheme.
What sampling does not doAcceptance sampling sorts lots into taken and returned. It does not change what the supplier produced, and it does not detect an unstable process — accepting at the AQL still passes defective units, at an average of up to 1.68 percent of everything that gets through. Control charts answer whether the process is stable; a capability study answers whether its output fits the specification; this answers only whether to take the lot.
Suggested Interpretation

The short answer

The binomial model applies because 80 units is 1.6 percent of a 5,000-unit lot, well under the 10 percent rule. The operating characteristic, both risks, and the AOQL are derived from that model. Sampling sorts lots; it does not improve them. A clean sample bounds the defect rate; it does not prove the rate is zero.

The detail

Probability model: Binomial(80, p), because sample size 80 is 1.6 percent of lot size 5,000, under the 10 percent rule. Operating characteristic: Pa(p) = P(X ≤ 2) where X ~ Binomial(80, p). Producer's risk: alpha = 1 − Pa(0.01) = 1 − 0.953447 = 0.046553. Consumer's risk: beta = Pa(0.05) = 0.230621. Average outgoing quality: AOQ(p) = p × Pa(p) × (5,000 − 80) / 5,000, maximized to give AOQL = 1.68 percent at p = 2.81 percent. Average total inspection: ATI = n + (1 − Pa(p)) × (N − n) = 983.7 at the observed rate of 1.84 percent, and 309 at the AQL. Upper confidence bound: one-sided 95 percent Clopper-Pearson limit on each lot's true defect rate. For zero defects in 80 units, that limit is 3.68 percent. Plan design: sample sizes searched from 1 to 5,000; for each, the acceptance number was the smallest holding acceptance at the AQL ≥ 95.00 percent, and the first plan with acceptance at the rejectable level ≤ 10.00 percent was taken.

What this can't tell you

Sampling sorts lots into taken and returned; it does not remove defects from accepted lots. The AOQL of 1.68 percent is how much defective work the plan lets through when running as designed. A sample finding zero defects bounds the defect rate at 3.68 percent; it does not establish the rate is zero. The risks computed here are properties of the plan at named quality levels (1.00 percent and 5.00 percent), not measurements of this supplier's actual performance.

Methodology

Methodology

Statistical methodology and diagnostics for Acceptance Sampling Plans

Statistical Method

Acceptance Sampling Plans

Standard-library analysis: should I accept this lot? Map an inspection record — the lot identifier, how many units were inspected, and how many were found defective — and get the operating-characteristic curve of the sampling plan in force, the producer's risk (alpha at the AQL) and the consumer's risk (beta at the LTPD), the average outgoing quality curve with its worst point (the AOQL), the average sample number and average total inspection, the accept/reject decision for every lot with a one-sided upper confidence bound on each lot's true defect rate, and the inverse problem solved by search — the smallest (n, c) plan that meets a target AQL and LTPD at the two risks you name. Binomial or hypergeometric probabilities are selected automatically and the choice is stated.

Data
N = 40 observations
Assumptions
  • Each lot is inspected once, on an attribute basis: every inspected unit is either conforming or defective
  • The units inspected are a random sample of the lot, not the first or the most convenient ones
  • Every lot in the record is judged against the same plan; lots inspected at a different sample size are reported as deviations
  • For the hypergeometric model, the lot size is the number of units the sample was drawn from
  • The AOQ curve assumes rectifying inspection — rejected lots are screened in full and their defectives replaced
Limitations
  • Acceptance sampling sorts lots; it does not improve them. A plan accepting at the AQL still passes defective units, and the AOQL is exactly how many
  • A sample that finds no defects does not show a lot contains none — the reported upper confidence bound is the honest statement of what a clean sample proves
  • Small samples give weak discrimination; where the operating-characteristic curve barely separates good lots from bad, the analysis says so rather than reporting a decision as reliable
  • The producer's and consumer's risks are properties of the plan at the quality levels named, not measurements of this supplier's actual quality
Software & Citation
MCP Analytics · mcpanalytics.ai
Code Appendix

Analysis Code

Complete R source code for this analysis

Acceptance Sampling Plans — Should I Accept This Lot?

Attribute acceptance sampling. Given inspection records (a lot identifier, how many units were inspected, and how many were found defective), the analysis evaluates the sampling plan in force and the accept/reject decision for every lot: the operating-characteristic curve, the producer's risk at the AQL, the consumer's risk at the LTPD, the average outgoing quality curve and its worst point (the AOQL), the average sample number and average total inspection, and a one-sided upper confidence bound on each lot's true defect rate. It also solves the inverse problem — given a target AQL, LTPD and the two risks, it searches over sample size and acceptance number for the smallest plan that meets them.

Why This Method?

A sampling plan is two numbers — inspect n, accept if at most c defectives — and the operating-characteristic curve is the complete description of what those two numbers do. It says, for every possible true defect rate, how often the plan will accept. Every other quantity here is read off that curve: the producer's risk is one minus its height at the AQL, the consumer's risk is its height at the rejectable level, and the AOQ curve is the same curve multiplied by the defect rate itself.

What This Analysis Covers

  • The OC curve of the plan in force, with the AQL and rejectable level marked
  • Producer's risk (alpha at the AQL) and consumer's risk (beta at the LTPD)
  • The AOQ curve and the AOQL — the worst average quality the plan lets through
  • Average sample number, and average total inspection under rectification
  • Per-lot accept/reject decisions with an upper confidence bound on each rate
  • The inverse problem: the smallest (n, c) plan meeting a target AQL/LTPD

What This Analysis Does NOT Do

Acceptance sampling sorts lots. It does not improve them, and it is not process control: a plan that accepts at the AQL still passes defective units, and the AOQL says how many. Whether the process is stable is a control-chart question; whether its output fits the specification window is a capability question; this tool answers only whether a delivered lot should be taken.

Standard Library

Platform standard-library module (LAT-1441): runs on ANY dataset via the semantic mapping {lot_id, sample_size, defects, lot_size}. All narrative is derived from the user's own column names and computed values.

suppressPackageStartupMessages(library(DT))
suppressPackageStartupMessages(library(htmlwidgets))
suppressPackageStartupMessages(library(arrow))
suppressPackageStartupMessages(library(knitr))
suppressPackageStartupMessages(library(rmarkdown))
suppressPackageStartupMessages(library(dplyr))
suppressPackageStartupMessages(library(tidyr))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(stringr))
suppressPackageStartupMessages(library(lubridate))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(Matrix))
suppressPackageStartupMessages(library(cluster))
suppressPackageStartupMessages(library(data.table))

Helpers

Core Analysis Pipeline

compute_shared <- function(df, params, col_map = list()) {
  # === SHARED EXPORTS ===
  #   initial_rows/final_rows/rows_removed  $ row accounting
  #   lot_name/size_name/defect_name/lotsize_name  $ humanized user names
  #   n_lots / total_inspected / total_defects / p_hat  $ the inspection record
  #   plan_n / plan_c / plan_source                     $ the plan in force
  #   model / model_reason / lot_N / sample_fraction    $ binomial vs hypergeom
  #   aql / ltpd / alpha_target / beta_target / level_source
  #   pa_aql / producer_risk / pa_ltpd / consumer_risk
  #   p95 / p50 / p10 / operating_ratio / weak_discrimination / weak_reasons
  #   aoql / p_at_aoql / aoq_note
  #   asn / observed_mean_n / ati_phat / ati_aql
  #   n_accept / n_reject / observed_reject_rate / expected_reject_rate_aql
  #   n_zero_lots / zero_bound / worst_bound_lot
  #   design_n / design_c / design_pa_aql / design_pa_ltpd / design_found
  #   oc_df / aoq_df / risk_df / lot_chart_df / lot_table_df / design_df /
  #   methods_df
  #   metrics / json_output
  # === /SHARED EXPORTS ===

  initial_rows <- nrow(df)
  lot_name     <- humanize_semantic("lot_id", col_map)[1]
  size_name    <- humanize_semantic("sample_size", col_map)[1]
  defect_name  <- humanize_semantic("defects", col_map)[1]
  lotsize_name <- humanize_semantic("lot_size", col_map)[1]

Step 1: The two columns the analysis cannot proceed without

if (!("lot_id" %in% names(df))) {
    stop(sprintf("A lot identifier column(&#x27;%s') must be mapped — acceptance sampling decides one lot at a time, so the analysis needs to know which rows belong to which lot.",
                 lot_name))
  }
  if (!("defects" %in% names(df))) {
    stop(sprintf("A defect-count column(&#x27;%s') must be mapped — it is the number of defective units found in each inspected sample.",
                 defect_name))
  }

  lot_raw <- as.character(df$lot_id)
  lot_raw[is.na(lot_raw) | !nzchar(trimws(lot_raw))] <- "Unlabelled"
  lot_raw <- trimws(lot_raw)

  dv <- coerce_numeric_95(df$defects)
  defect_is_text <- is.null(dv)

  has_size <- "sample_size" %in% names(df)
  sv <- if (has_size) coerce_numeric_95(df$sample_size) else NULL
  if (has_size && is.null(sv)) {
    stop(sprintf("The sample-size column &#x27;%s' is not numeric — it must hold the number of units inspected in each sample as a number.",
                 size_name))
  }

Step 2: Shape the record — lot-level counts, or one row per unit

A lot-level file carries the sample size and the defect count on one row per lot. A unit-level file carries one row per inspected unit and a pass/fail flag; then the sample size is the row count per lot.

unit_level <- FALSE
  if (!has_size) {
    if (!defect_is_text && all(dv[!is.na(dv)] %in% c(0, 1))) {
      unit_level <- TRUE
    } else if (defect_is_text) {
      levs <- unique(trimws(as.character(df$defects)))
      levs <- levs[nzchar(levs) & !is.na(levs)]
      bad_tokens <- c("defect", "defective", "defectives", "fail", "failed",
                      "failure", "reject", "rejected", "bad", "nonconforming",
                      "non-conforming", "yes", "y", "true", "1")
      is_bad <- tolower(levs) %in% bad_tokens
      if (length(levs) == 2 && sum(is_bad) == 1) {
        unit_level <- TRUE
        raw_txt <- trimws(as.character(df$defects))
        dv <- as.numeric(tolower(raw_txt) == tolower(levs[is_bad]))
        dv[is.na(raw_txt) | !nzchar(raw_txt)] <- NA_real_
        defect_is_text <- FALSE
      } else {
        stop(sprintf("The column &#x27;%s' holds %d distinct value(s) that are neither counts nor a clear pass/fail flag (%s). Map a numeric defect count together with the number of units inspected, or a two-level pass/fail column with one row per unit.",
                     defect_name, length(levs),
                     paste(utils::head(levs, 4), collapse = ", ")))
      }
    } else {
      stop(sprintf("No sample-size column was mapped, and &#x27;%s' is not a 0/1 flag, so the analysis cannot tell how many units each count came out of. Map the column holding the number of units inspected per lot.",
                   defect_name))
    }
  } else if (defect_is_text) {
    levs <- unique(trimws(as.character(df$defects)))
    levs <- levs[nzchar(levs) & !is.na(levs)]
    stop(sprintf("The defect-count column &#x27;%s' is not numeric — it holds %d distinct non-numeric value(s) (%s). It must be the count of defective units found in each sample.",
                 defect_name, length(levs),
                 paste(utils::head(levs, 4), collapse = ", ")))
  }

  keep <- !is.na(dv)
  if (has_size) keep <- keep & !is.na(sv) & is.finite(sv) & sv > 0
  n_dropped <- sum(!keep)
  if (sum(keep) == 0) {
    stop(sprintf("Every row is missing either &#x27;%s' or '%s', so there is nothing to inspect.",
                 defect_name, if (has_size) size_name else lot_name))
  }

  lot_v <- lot_raw[keep]
  d_v   <- dv[keep]
  n_v   <- if (has_size) sv[keep] else rep(1, sum(keep))

  if (any(d_v < 0)) {
    stop(sprintf("The defect-count column &#x27;%s' contains negative values — a count of defective units cannot be below zero.",
                 defect_name))
  }

Aggregate to one row per lot. Repeated lot identifiers are summed, which is the only reading that preserves the total inspected and the total found.

lev <- unique(lot_v)
  n_by  <- vapply(lev, function(l) sum(n_v[lot_v == l]), numeric(1))
  d_by  <- vapply(lev, function(l) sum(d_v[lot_v == l]), numeric(1))
  rows_by <- vapply(lev, function(l) sum(lot_v == l), numeric(1))
  n_duplicate_rows <- sum(keep) - length(lev)

  bad_lot <- which(d_by > n_by)
  if (length(bad_lot) > 0) {
    i <- bad_lot[1]
    stop(sprintf("Lot &#x27;%s' reports %s defective %s from %s %s inspected — more defects than units, which cannot happen. Check '%s' against '%s'.",
                 lev[i], fmt_num(d_by[i], 0), units_word(d_by[i]),
                 fmt_num(n_by[i], 0), units_word(n_by[i]),
                 defect_name, if (has_size) size_name else lot_name))
  }

  n_lots <- length(lev)
  if (n_lots < 5) {
    stop(sprintf("Only %d %s of &#x27;%s' remained after cleaning — a sampling record needs at least 5 lots before the accept/reject pattern says anything about the plan or the supplier.",
                 n_lots, lots_word(n_lots), lot_name))
  }

  total_inspected <- sum(n_by)
  total_defects   <- sum(d_by)
  p_hat <- total_defects / total_inspected
  final_rows <- n_lots
  rows_removed <- initial_rows - final_rows

Step 3: The lot size, and therefore which probability model applies

Sampling is done without replacement. When the sample is a material fraction of the lot, the hypergeometric distribution is the exact model and the binomial is an approximation to it.

lot_N <- NA_real_
  lotN_source <- NA_character_
  param_N <- param_num(params$lot_size %||% params$N %||% params$population,
                       "lot_size")
  if (is.finite(param_N) && param_N > 0) {
    lot_N <- param_N
    lotN_source <- "supplied as the lot_size parameter"
  } else if ("lot_size" %in% names(df)) {
    lnv <- coerce_numeric_95(df$lot_size)
    if (is.null(lnv)) {
      stop(sprintf("The lot-size column &#x27;%s' is not numeric — it must hold the number of units in the whole lot.",
                   lotsize_name))
    }
    lnv <- lnv[keep]
    lnv <- lnv[is.finite(lnv) & lnv > 0]
    if (length(lnv) > 0) {
      lot_N <- stats::median(lnv)
      n_distinct_N <- length(unique(lnv))
      lotN_source <- if (n_distinct_N == 1) {
        sprintf("read from the column &#x27;%s'", lotsize_name)
      } else {
        sprintf("the median of the %d different lot sizes in the column &#x27;%s'",
                n_distinct_N, lotsize_name)
      }
    }
  }

Step 4: The plan in force — n and c

plan_n <- param_num(params$n %||% params$plan_n %||% params$sample_size_plan,
                      "n")
  n_src <- if (is.finite(plan_n)) "supplied as the n parameter" else NA_character_
  if (!is.finite(plan_n)) {
    tab <- table(n_by)
    plan_n <- as.numeric(names(tab)[which.max(as.numeric(tab))])
    n_src <- if (length(unique(n_by)) == 1) {
      sprintf("every lot inspected the same %s %s, so that is the plan&#x27;s sample size",
              fmt_num(plan_n, 0), units_word(plan_n))
    } else {
      sprintf("the most common sample size across the %d lots(%d of them used it)",
              n_lots, as.integer(max(as.numeric(tab))))
    }
  }
  if (!is.finite(plan_n) || plan_n < 1) {
    stop(sprintf("No usable sample size could be read from &#x27;%s'.",
                 if (has_size) size_name else lot_name))
  }
  plan_n <- round(plan_n)
  n_off_plan <- sum(n_by != plan_n)

Quality levels. When the user does not state them, the analysis uses the conventional 1 percent AQL and 5 percent rejectable level and says so — every risk figure below moves if the agreement uses different numbers.

aql_in  <- as_rate(param_num(params$aql %||% params$AQL %||%
                                 params$acceptable_quality_level, "aql"))
  ltpd_in <- as_rate(param_num(params$ltpd %||% params$LTPD %||% params$rql %||%
                                 params$RQL %||% params$lqa %||%
                                 params$rejectable_quality_level, "ltpd"))
  alpha_in <- as_rate(param_num(params$alpha %||% params$producer_risk, "alpha"))
  beta_in  <- as_rate(param_num(params$beta %||% params$consumer_risk, "beta"))

  aql          <- if (is.finite(aql_in))  aql_in  else 0.01
  ltpd         <- if (is.finite(ltpd_in)) ltpd_in else 0.05
  alpha_target <- if (is.finite(alpha_in)) alpha_in else 0.05
  beta_target  <- if (is.finite(beta_in))  beta_in  else 0.10
  if (!(ltpd > aql)) {
    stop(sprintf("The rejectable quality level(%s percent) must be worse than the acceptable quality level(%s percent) — the plan is being asked to tell apart two levels that are the same or the wrong way round.",
                 fmt_pct(ltpd), fmt_pct(aql)))
  }
  levels_supplied <- is.finite(aql_in) && is.finite(ltpd_in)
  level_source <- if (levels_supplied) {
    sprintf("an acceptable quality level of %s percent and a rejectable level of %s percent, both supplied with the request",
            fmt_pct(aql), fmt_pct(ltpd))
  } else if (is.finite(aql_in)) {
    sprintf("an acceptable quality level of %s percent supplied with the request, and the conventional %s percent rejectable level because none was supplied",
            fmt_pct(aql), fmt_pct(ltpd))
  } else if (is.finite(ltpd_in)) {
    sprintf("the conventional %s percent acceptable quality level because none was supplied, and a rejectable level of %s percent supplied with the request",
            fmt_pct(aql), fmt_pct(ltpd))
  } else {
    sprintf("the conventional %s percent acceptable quality level and %s percent rejectable level, neither of which was supplied with the request",
            fmt_pct(aql), fmt_pct(ltpd))
  }

Model choice, now that both n and the lot size are known.

sample_fraction <- if (is.finite(lot_N) && lot_N > 0) plan_n / lot_N else NA_real_
  model <- "binomial"
  if (is.finite(sample_fraction) && sample_fraction > 0.10) model <- "hypergeometric"
  if (model == "hypergeometric" && plan_n > lot_N) {
    stop(sprintf("The plan inspects %s %s but the lot holds only %s — a sample cannot be larger than the lot it comes from. Check &#x27;%s'.",
                 fmt_num(plan_n, 0), units_word(plan_n), fmt_num(lot_N, 0),
                 lotsize_name))
  }
  model_reason <- if (model == "hypergeometric") {
    sprintf("the sample of %s %s is %s percent of a lot of %s(%s), which is above the usual 10 percent rule, so sampling without replacement matters and the exact hypergeometric distribution is used rather than the binomial approximation",
            fmt_num(plan_n, 0), units_word(plan_n), fmt_pct(sample_fraction, 1),
            fmt_num(lot_N, 0), lotN_source)
  } else if (is.finite(sample_fraction)) {
    sprintf("the sample of %s %s is %s percent of a lot of %s(%s), well under the 10 percent rule, so the binomial distribution is an accurate model and is used",
            fmt_num(plan_n, 0), units_word(plan_n), fmt_pct(sample_fraction, 1),
            fmt_num(lot_N, 0), lotN_source)
  } else {
    sprintf("no lot size was supplied, so the binomial distribution is used; if the sample of %s %s is more than about a tenth of the lot, map the lot-size column and the exact hypergeometric model will be used instead",
            fmt_num(plan_n, 0), units_word(plan_n))
  }

Step 5: The operating characteristic — Pa(p) for the plan in force

Everything downstream is a reading of this one function.

make_pa <- function(nn, cc) {
    if (model == "hypergeometric") {
      function(p) {
        D <- round(pmin(pmax(p, 0), 1) * lot_N)
        stats::phyper(cc, D, lot_N - D, nn)
      }
    } else {
      function(p) stats::pbinom(cc, nn, pmin(pmax(p, 0), 1))
    }
  }

Smallest acceptance number whose acceptance probability at the AQL clears the producer's-risk target — the standard way c is chosen for a given n.

c_for <- function(nn) {
    if (model == "hypergeometric") {
      D <- round(aql * lot_N)
      as.numeric(stats::qhyper(1 - alpha_target, D, lot_N - D, nn))
    } else {
      as.numeric(stats::qbinom(1 - alpha_target, nn, aql))
    }
  }

  plan_c <- param_num(params$c %||% params$acceptance_number %||% params$plan_c,
                      "c")
  c_src <- if (is.finite(plan_c)) "supplied as the c parameter" else NA_character_
  if (!is.finite(plan_c)) {
    plan_c <- c_for(plan_n)
    c_src <- sprintf("not supplied, so it was set to the smallest acceptance number that keeps the producer&#x27;s risk at or below %s percent at the acceptable quality level",
                     fmt_pct(alpha_target))
  }
  plan_c <- round(plan_c)
  if (plan_c < 0 || plan_c > plan_n) {
    stop(sprintf("The acceptance number must lie between 0 and the sample size of %s; %s was resolved instead.",
                 fmt_num(plan_n, 0), fmt_num(plan_c, 0)))
  }
  plan_source <- sprintf("inspect %s %s and accept the lot on at most %s defective %s. The sample size was %s; the acceptance number was %s.",
                         fmt_num(plan_n, 0), units_word(plan_n),
                         fmt_num(plan_c, 0), units_word(plan_c),
                         n_src, c_src)

  pa <- make_pa(plan_n, plan_c)
  pa_aql  <- pa(aql)
  pa_ltpd <- pa(ltpd)
  producer_risk <- 1 - pa_aql
  consumer_risk <- pa_ltpd

Where the curve crosses given heights. Pa is non-increasing in p, so a bisection is exact to machine precision for the binomial and lands on the correct step for the hypergeometric.

invert_pa <- function(target) {
    lo <- 0; hi <- 1
    if (pa(0) <= target) return(0)
    if (pa(1) >= target) return(1)
    for (i in seq_len(200)) {
      mid <- (lo + hi) / 2
      if (pa(mid) > target) lo <- mid else hi <- mid
    }
    (lo + hi) / 2
  }
  p95 <- invert_pa(0.95)
  p50 <- invert_pa(0.50)
  p10 <- invert_pa(0.10)
  operating_ratio <- if (is.finite(p95) && p95 > 0) p10 / p95 else NA_real_

Step 6: Average outgoing quality, and its worst point

Under rectifying inspection a rejected lot is screened 100 percent and its defectives replaced, so what leaves is the defects the accepted lots carry.

rect_factor <- if (is.finite(lot_N) && lot_N > plan_n) (lot_N - plan_n) / lot_N else 1
  aoq_fn <- function(p) p * pa(p) * rect_factor
  grid_full <- seq(0, 1, length.out = 2001)
  aoq_full <- aoq_fn(grid_full)
  aoq_full[!is.finite(aoq_full)] <- -Inf
  if (all(!is.finite(aoq_full) | aoq_full == -Inf)) {
    aoql <- NA_real_; p_at_aoql <- NA_real_
  } else {
    i_star <- which.max(aoq_full)
    lo_b <- grid_full[max(1, i_star - 1)]
    hi_b <- grid_full[min(length(grid_full), i_star + 1)]
    ref <- tryCatch(stats::optimize(aoq_fn, c(lo_b, hi_b), maximum = TRUE,
                                    tol = .Machine$double.eps^0.5),
                    error = function(e) NULL)
    if (!is.null(ref) && is.finite(ref$objective) &&
        ref$objective >= aoq_full[i_star]) {
      aoql <- ref$objective; p_at_aoql <- ref$maximum
    } else {
      aoql <- aoq_full[i_star]; p_at_aoql <- grid_full[i_star]
    }
  }
  aoq_note <- if (rect_factor < 1) {
    sprintf("Rejected lots are assumed to be screened in full and their defectives replaced, so the(%s - %s) / %s correction for the units already inspected is applied.",
            fmt_num(lot_N, 0), fmt_num(plan_n, 0), fmt_num(lot_N, 0))
  } else {
    "No lot size was available, so the average outgoing quality is computed without the correction for the units already inspected; with a lot size mapped it would be slightly lower."
  }

Step 7: How much inspection this costs

A single sampling plan has no stopping rule, so its average sample number is the sample size itself — a constant, not an average over anything.

asn <- plan_n
  observed_mean_n <- mean(n_by)
  ati_at <- function(p) {
    if (!is.finite(lot_N) || lot_N <= plan_n) return(NA_real_)
    plan_n + (1 - pa(p)) * (lot_N - plan_n)
  }
  ati_phat <- ati_at(p_hat)
  ati_aql  <- ati_at(aql)

Step 8: The decisions actually taken on this record

decision <- ifelse(d_by <= plan_c, "Accept", "Reject")
  n_accept <- sum(decision == "Accept")
  n_reject <- n_lots - n_accept
  observed_reject_rate <- n_reject / n_lots
  expected_reject_rate_aql <- 1 - pa_aql
  lot_rate <- d_by / n_by
  ub <- vapply(seq_len(n_lots), function(i) upper_bound_p(d_by[i], n_by[i]),
               numeric(1))
  n_zero_lots <- sum(d_by == 0)
  zero_bound <- if (n_zero_lots > 0) {

The weakest claim a zero-defect sample supports: the largest upper bound among the lots that found nothing.

max(ub[d_by == 0], na.rm = TRUE)
  } else NA_real_
  finite_ub <- which(is.finite(ub))
  worst_bound_lot <- if (length(finite_ub) > 0) {
    lev[finite_ub[which.max(ub[finite_ub])]]
  } else NA_character_

Step 9: Discrimination — can this plan tell a good lot from a bad one?

weak_reasons <- character(0)
  if (is.finite(consumer_risk) && consumer_risk > 0.5) {
    weak_reasons <- c(weak_reasons, sprintf(
      "a lot running at the %s percent rejectable level is still accepted %s percent of the time",
      fmt_pct(ltpd), fmt_pct(consumer_risk)))
  }
  if (is.finite(p10) && p10 > 0.20) {
    weak_reasons <- c(weak_reasons, sprintf(
      "a lot would have to be %s percent defective before this plan rejected it nine times in ten",
      fmt_pct(p10)))
  }
  if (is.finite(operating_ratio) && operating_ratio > 10) {
    weak_reasons <- c(weak_reasons, sprintf(
      "the defect rate has to rise by a factor of %s to move the plan from almost always accepting to almost always rejecting",
      fmt_num(operating_ratio, 1)))
  }
  weak_discrimination <- length(weak_reasons) > 0
  discrimination_text <- if (weak_discrimination) {
    paste0("The operating-characteristic curve turns slowly over the range that matters: ",
           paste(weak_reasons, collapse = "; "),
           ". A plan of this shape separates good lots from bad ones only weakly, so an individual accept or reject from it is not strong evidence about the lot behind it. Only a larger sample steepens the curve.")
  } else {
    sprintf("The curve falls from %s percent acceptance at a defect rate of %s percent to %s percent acceptance at %s percent, so the plan separates those two quality levels by a factor of %s in defect rate. The steeper this fall, the more a single accept or reject actually tells you.",
            fmt_pct(0.95, 0), fmt_pct(p95), fmt_pct(0.10, 0), fmt_pct(p10),
            fmt_num(operating_ratio, 1))
  }

Step 10: The inverse problem — design the smallest plan that qualifies

Search sample sizes upward; for each, take the smallest acceptance number meeting the producer's-risk target and test it against the consumer's.

n_max <- if (model == "hypergeometric") min(lot_N, 5000) else 5000
  design_n <- NA_real_; design_c <- NA_real_
  design_pa_aql <- NA_real_; design_pa_ltpd <- NA_real_
  for (nn in seq_len(as.integer(n_max))) {
    cc <- c_for(nn)
    if (!is.finite(cc)) next
    pa_try <- make_pa(nn, cc)
    if (pa_try(aql) >= 1 - alpha_target && pa_try(ltpd) <= beta_target) {
      design_n <- nn; design_c <- cc
      design_pa_aql <- pa_try(aql); design_pa_ltpd <- pa_try(ltpd)
      break
    }
  }
  design_found <- is.finite(design_n)
  plan_meets_targets <- is.finite(producer_risk) && is.finite(consumer_risk) &&
    producer_risk <= alpha_target && consumer_risk <= beta_target

Step 12: Headline metrics

verdict <- if (plan_meets_targets) {
    sprintf("Meets both risk targets(alpha %s percent, beta %s percent)",
            fmt_pct(producer_risk), fmt_pct(consumer_risk))
  } else if (weak_discrimination) {
    sprintf("Weak discrimination(consumer&#x27;s risk %s percent)", fmt_pct(consumer_risk))
  } else {
    sprintf("Misses a risk target(alpha %s percent, beta %s percent)",
            fmt_pct(producer_risk), fmt_pct(consumer_risk))
  }

  metrics <- list()
  metrics[["Lots Inspected"]]        <- n_lots
  metrics[["Units Inspected"]]       <- total_inspected
  metrics[["Defects Found"]]         <- total_defects
  metrics[["Observed Defect Rate"]]  <- paste0(fmt_pct(p_hat), " percent")
  metrics[["Sampling Plan"]]         <- sprintf("n = %s, c = %s",
                                                fmt_num(plan_n, 0), fmt_num(plan_c, 0))
  metrics[["Probability Model"]]     <- model
  metrics[["Lots Accepted"]]         <- n_accept
  metrics[["Lots Rejected"]]         <- n_reject
  metrics[["Producer&#x27;s Risk"]]       <- round(producer_risk, 6)
  metrics[["Consumer&#x27;s Risk"]]       <- round(consumer_risk, 6)
  metrics[["AOQL"]]                  <- round(aoql, 6)
  metrics[["Average Sample Number"]] <- asn
  metrics[["Discrimination"]]        <- if (weak_discrimination) "weak" else "adequate"
  metrics[["Verdict"]]               <- verdict

  json_output <- list(
    answer = paste0(
      "Attribute acceptance sampling of ", format(n_lots, big.mark = ","), " ",
      lots_word(n_lots), " covering ", format(total_inspected, big.mark = ","),
      " inspected ", units_word(total_inspected), " of ", lot_name,
      ", against a plan that inspects ", fmt_num(plan_n, 0), " and accepts on at most ",
      fmt_num(plan_c, 0), " defective ", units_word(plan_c), ". ",
      format(n_accept, big.mark = ","), " ", lots_word(n_accept), " accept and ",
      format(n_reject, big.mark = ","), " reject on this record, against an observed defect rate of ",
      fmt_pct(p_hat), " percent. Using ", level_source, ", the producer&#x27;s risk is ",
      fmt_pct(producer_risk), " percent and the consumer&#x27;s risk is ",
      fmt_pct(consumer_risk), " percent, computed from the ", model,
      " operating-characteristic curve. The average outgoing quality limit is ",
      fmt_pct(aoql), " percent(", fmt_ppm(1e6 * aoql),
      " parts per million) at an incoming rate of ", fmt_pct(p_at_aoql),
      " percent, which is the point worth keeping in view: acceptance sampling sorts lots, it does not improve them, and a plan operating exactly as intended still passes that much defective work. ",
      if (weak_discrimination) {
        "The operating-characteristic curve turns slowly over the relevant range, so an individual accept or reject from this plan is weak evidence about the lot behind it."
      } else {
        sprintf("A lot at %s percent defective is accepted 95 percent of the time and one at %s percent only 10 percent of the time, so the plan does separate those levels.",
                fmt_pct(p95), fmt_pct(p10))
      }
    ),
    cards = lapply(
      c("tldr", "overview", "preprocessing", "oc_curve", "risk_table",
        "aoq_curve", "lot_decisions", "lot_table", "plan_design", "methods"),
      function(cid) list(id = cid, metrics = metrics)
    )
  )

  list(
    initial_rows = initial_rows, final_rows = final_rows, rows_removed = rows_removed,
    n_dropped = n_dropped, n_duplicate_rows = n_duplicate_rows,
    unit_level = unit_level, has_size = has_size,
    lot_name = lot_name, size_name = size_name, defect_name = defect_name,
    lotsize_name = lotsize_name,
    n_lots = n_lots, total_inspected = total_inspected,
    total_defects = total_defects, p_hat = p_hat,
    lot_ids = lev, lot_n = n_by, lot_d = d_by, lot_rate = lot_rate,
    lot_upper = ub, decision = decision, rows_per_lot = rows_by,
    plan_n = plan_n, plan_c = plan_c, plan_source = plan_source,
    n_src = n_src, c_src = c_src, n_off_plan = n_off_plan,
    model = model, model_reason = model_reason,
    lot_N = lot_N, lotN_source = lotN_source, sample_fraction = sample_fraction,
    aql = aql, ltpd = ltpd, alpha_target = alpha_target, beta_target = beta_target,
    levels_supplied = levels_supplied, level_source = level_source,
    pa_aql = pa_aql, pa_ltpd = pa_ltpd,
    producer_risk = producer_risk, consumer_risk = consumer_risk,
    p95 = p95, p50 = p50, p10 = p10, operating_ratio = operating_ratio,
    weak_discrimination = weak_discrimination, weak_reasons = weak_reasons,
    discrimination_text = discrimination_text,
    aoql = aoql, p_at_aoql = p_at_aoql, aoq_note = aoq_note,
    rect_factor = rect_factor,
    asn = asn, observed_mean_n = observed_mean_n,
    ati_phat = ati_phat, ati_aql = ati_aql,
    n_accept = n_accept, n_reject = n_reject,
    observed_reject_rate = observed_reject_rate,
    expected_reject_rate_aql = expected_reject_rate_aql,
    n_zero_lots = n_zero_lots, zero_bound = zero_bound,
    worst_bound_lot = worst_bound_lot,
    design_n = design_n, design_c = design_c, design_found = design_found,
    design_pa_aql = design_pa_aql, design_pa_ltpd = design_pa_ltpd,
    plan_meets_targets = plan_meets_targets, n_max = n_max,
    chart_truncated = chart_truncated, table_truncated = table_truncated,
    oc_df = oc_df, aoq_df = aoq_df, risk_df = risk_df,
    lot_chart_df = lot_chart_df, lot_table_df = lot_table_df,
    design_df = design_df, methods_df = methods_df,
    metrics = metrics, json_output = json_output
  )
}
Your data has more stories to tell. Run any analysis on your own data — validated R modules, interactive reports, AI insights, and PDF export. 500 free credits on signup.
Try Free — No Signup Sign Up Free

Report an Issue

Tell us what's wrong. You'll get a free re-run of this analysis so you can try again with different parameters. If the re-run still doesn't meet your expectations, we'll refund your credits.

Want to run this analysis on your own data? Upload CSV — Free Analysis See Pricing