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

86
app/analysis/charts.py Normal file
View File

@@ -0,0 +1,86 @@
from __future__ import annotations
import plotly.graph_objects as go
from app.analysis.spc import SPCResult
def histogram(result: SPCResult) -> dict:
"""Distribution histogram with spec limits overlay."""
fig = go.Figure()
fig.add_trace(go.Histogram(x=result.values, name="Measurements", nbinsx=20))
fig.add_vline(x=result.usl, line_dash="dash", line_color="red",
annotation_text="USL")
fig.add_vline(x=result.lsl, line_dash="dash", line_color="red",
annotation_text="LSL")
fig.add_vline(x=result.nominal, line_dash="dot", line_color="green",
annotation_text="Nominal")
fig.update_layout(
title=f"Distribution {result.feature_name}",
xaxis_title="Value", yaxis_title="Count",
template="plotly_white", height=350,
)
return fig.to_plotly_json()
def control_chart(result: SPCResult) -> dict:
"""Individual values control chart (I-chart)."""
x_axis = list(range(1, result.n + 1))
fig = go.Figure()
fig.add_trace(go.Scatter(
x=x_axis, y=result.values, mode="lines+markers", name="Value",
))
fig.add_hline(y=result.mean, line_color="green", annotation_text="Mean")
fig.add_hline(y=result.ucl, line_dash="dash", line_color="red",
annotation_text="UCL")
fig.add_hline(y=result.lcl, line_dash="dash", line_color="red",
annotation_text="LCL")
fig.add_hline(y=result.usl, line_dash="dot", line_color="orange",
annotation_text="USL")
fig.add_hline(y=result.lsl, line_dash="dot", line_color="orange",
annotation_text="LSL")
fig.update_layout(
title=f"Control Chart {result.feature_name}",
xaxis_title="Sample #", yaxis_title="Value",
template="plotly_white", height=350,
)
return fig.to_plotly_json()
def capability_bar(results: list[SPCResult]) -> dict:
"""Capability index bar chart comparing all features."""
names = [r.feature_name for r in results]
cpk_vals = [r.cpk if r.cpk is not None else 0.0 for r in results]
ppk_vals = [r.ppk if r.ppk is not None else 0.0 for r in results]
colors = ["#2ecc71" if v >= 1.33 else "#f39c12" if v >= 1.0 else "#e74c3c"
for v in cpk_vals]
fig = go.Figure()
fig.add_trace(go.Bar(x=names, y=cpk_vals, name="Cpk", marker_color=colors))
fig.add_trace(go.Bar(x=names, y=ppk_vals, name="Ppk", marker_color="rgba(52,152,219,0.6)"))
fig.add_hline(y=1.33, line_dash="dash", line_color="green",
annotation_text="Cpk=1.33")
fig.add_hline(y=1.0, line_dash="dot", line_color="orange",
annotation_text="Cpk=1.0")
fig.update_layout(
title="Process Capability Summary",
xaxis_title="Feature", yaxis_title="Index",
barmode="group", template="plotly_white", height=400,
)
return fig.to_plotly_json()
def generate_charts(results: list[SPCResult]) -> dict:
"""Generate all charts for a set of SPC results."""
charts: dict[str, list[dict] | dict] = {
"histograms": [],
"control_charts": [],
}
for r in results:
if r.n >= 2:
charts["histograms"].append(histogram(r))
charts["control_charts"].append(control_chart(r))
if results:
charts["capability_bar"] = capability_bar(results)
return charts