Initial commit: CMM Report Analyzer
FastAPI app that parses CMM inspection reports (PDF/Excel/CSV), computes SPC metrics (Cp/Cpk/Pp/Ppk, control limits, Shapiro-Wilk), generates interactive Plotly charts, and provides AI-powered quality summaries via Azure OpenAI with graceful fallback. Includes 21 passing tests covering parsers, SPC calculations, and API endpoints.
This commit is contained in:
84
tests/test_spc.py
Normal file
84
tests/test_spc.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from app.analysis.spc import SPCResult, calculate_spc
|
||||
from app.parsers.models import MeasurementRecord
|
||||
|
||||
|
||||
def _make_records(name: str, nominal: float, tol: float, actuals: list[float]):
|
||||
return [
|
||||
MeasurementRecord(
|
||||
feature_name=name,
|
||||
nominal=nominal,
|
||||
tolerance_plus=tol,
|
||||
tolerance_minus=-tol,
|
||||
actual=a,
|
||||
deviation=a - nominal,
|
||||
)
|
||||
for a in actuals
|
||||
]
|
||||
|
||||
|
||||
def test_single_measurement_returns_none_indices():
|
||||
records = _make_records("D1", 10.0, 0.05, [10.01])
|
||||
results = calculate_spc(records)
|
||||
assert len(results) == 1
|
||||
r = results[0]
|
||||
assert r.cp is None
|
||||
assert r.cpk is None
|
||||
assert r.pp is None
|
||||
assert r.ppk is None
|
||||
|
||||
|
||||
def test_basic_spc_calculation():
|
||||
actuals = [10.01, 10.02, 9.99, 10.00, 10.03, 9.98, 10.01, 10.02, 9.99, 10.00]
|
||||
records = _make_records("D1", 10.0, 0.05, actuals)
|
||||
results = calculate_spc(records)
|
||||
|
||||
assert len(results) == 1
|
||||
r = results[0]
|
||||
assert r.n == 10
|
||||
assert r.pp is not None
|
||||
assert r.ppk is not None
|
||||
assert r.cp is not None
|
||||
assert r.cpk is not None
|
||||
assert r.pp > 0
|
||||
assert r.cpk > 0
|
||||
assert r.out_of_spec_count == 0
|
||||
|
||||
|
||||
def test_out_of_spec_count():
|
||||
actuals = [10.0, 10.06, 9.94, 10.0, 10.0] # 10.06 and 9.94 outside ±0.05
|
||||
records = _make_records("D1", 10.0, 0.05, actuals)
|
||||
results = calculate_spc(records)
|
||||
assert results[0].out_of_spec_count == 2
|
||||
|
||||
|
||||
def test_multiple_features():
|
||||
records = (
|
||||
_make_records("D1", 10.0, 0.05, [10.01, 10.02, 9.99])
|
||||
+ _make_records("D2", 20.0, 0.10, [20.05, 19.95, 20.01])
|
||||
)
|
||||
results = calculate_spc(records)
|
||||
assert len(results) == 2
|
||||
names = {r.feature_name for r in results}
|
||||
assert names == {"D1", "D2"}
|
||||
|
||||
|
||||
def test_shapiro_not_computed_for_small_n():
|
||||
records = _make_records("D1", 10.0, 0.05, [10.01, 10.02])
|
||||
results = calculate_spc(records)
|
||||
assert results[0].shapiro_p is None
|
||||
|
||||
|
||||
def test_shapiro_computed_for_n_ge_3():
|
||||
records = _make_records("D1", 10.0, 0.05, [10.01, 10.02, 9.99])
|
||||
results = calculate_spc(records)
|
||||
assert results[0].shapiro_p is not None
|
||||
|
||||
|
||||
def test_to_dict():
|
||||
records = _make_records("D1", 10.0, 0.05, [10.01, 10.02, 9.99, 10.00, 10.03])
|
||||
results = calculate_spc(records)
|
||||
d = results[0].to_dict()
|
||||
assert "feature_name" in d
|
||||
assert "cpk" in d
|
||||
assert "values" in d
|
||||
assert isinstance(d["values"], list)
|
||||
Reference in New Issue
Block a user