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:
chrisryn
2026-02-19 10:38:51 -06:00
commit 9abf9b4b58
28 changed files with 1727 additions and 0 deletions

72
tests/test_api.py Normal file
View File

@@ -0,0 +1,72 @@
import io
import tempfile
from pathlib import Path
import pandas as pd
import pytest
from httpx import ASGITransport, AsyncClient
from app.main import app
@pytest.fixture
def sample_excel() -> bytes:
df = pd.DataFrame({
"Feature Name": ["D1", "D1", "D1", "D2", "D2", "D2"],
"Nominal": [10.0, 10.0, 10.0, 20.0, 20.0, 20.0],
"Actual": [10.02, 9.99, 10.01, 20.05, 19.97, 20.02],
"Tol+": [0.05, 0.05, 0.05, 0.10, 0.10, 0.10],
"Tol-": [-0.05, -0.05, -0.05, -0.10, -0.10, -0.10],
})
buf = io.BytesIO()
df.to_excel(buf, index=False)
buf.seek(0)
return buf.read()
@pytest.mark.asyncio
async def test_upload_and_results(sample_excel):
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.post(
"/api/upload",
files=[("files", ("test.xlsx", sample_excel, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"))],
)
assert resp.status_code == 200
data = resp.json()
assert "batch_id" in data
resp2 = await client.get(f"/api/results/{data['batch_id']}")
assert resp2.status_code == 200
result = resp2.json()
assert result["status"] == "complete"
assert len(result["files"]) == 1
assert result["files"][0]["filename"] == "test.xlsx"
assert len(result["files"][0]["spc"]) == 2 # D1 and D2
@pytest.mark.asyncio
async def test_upload_no_files():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.post("/api/upload", files=[])
assert resp.status_code in (400, 422)
@pytest.mark.asyncio
async def test_upload_unsupported_type():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.post(
"/api/upload",
files=[("files", ("test.png", b"fake", "image/png"))],
)
assert resp.status_code == 400
@pytest.mark.asyncio
async def test_results_not_found():
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
resp = await client.get("/api/results/nonexistent")
assert resp.status_code == 404