Files
cmm-report-analyzer/app/ai/summarizer.py
chrisryn 9abf9b4b58 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.
2026-02-19 10:38:51 -06:00

102 lines
3.5 KiB
Python

from __future__ import annotations
import logging
from openai import AsyncAzureOpenAI
from app.analysis.spc import SPCResult
from app.config import settings
from app.parsers.models import ParsedReport
logger = logging.getLogger(__name__)
SYSTEM_PROMPT = """\
You are a quality engineer reviewing CMM (Coordinate Measuring Machine) inspection data.
Provide a concise, actionable summary that includes:
1. Overall pass/fail assessment
2. Features of concern (low Cpk, out-of-tolerance, trends)
3. Root-cause hypotheses for any deviations
4. Recommended corrective actions
Use precise engineering language. Reference feature names and numeric values."""
async def summarize(
report: ParsedReport, spc_results: list[SPCResult]
) -> str:
"""Generate an AI-powered quality summary. Returns fallback text on failure."""
if not settings.azure_openai_endpoint or not settings.azure_openai_api_key:
return _fallback_summary(report, spc_results)
spc_text = _format_spc(spc_results)
user_msg = (
f"File: {report.filename}\n"
f"Measurements: {len(report.measurements)}, "
f"Out of tolerance: {len(report.out_of_tolerance)}\n\n"
f"SPC Results:\n{spc_text}\n\n"
f"Raw report excerpt:\n{report.raw_text[:3000]}"
)
try:
client = AsyncAzureOpenAI(
azure_endpoint=settings.azure_openai_endpoint,
api_key=settings.azure_openai_api_key,
api_version=settings.azure_openai_api_version,
)
response = await client.chat.completions.create(
model=settings.azure_openai_deployment,
messages=[
{"role": "system", "content": SYSTEM_PROMPT},
{"role": "user", "content": user_msg},
],
temperature=0.3,
max_tokens=1024,
)
return response.choices[0].message.content or _fallback_summary(
report, spc_results
)
except Exception:
logger.exception("Azure OpenAI call failed, using fallback summary")
return _fallback_summary(report, spc_results)
def _format_spc(results: list[SPCResult]) -> str:
lines: list[str] = []
for r in results:
cpk_str = f"{r.cpk:.3f}" if r.cpk is not None else "N/A"
ppk_str = f"{r.ppk:.3f}" if r.ppk is not None else "N/A"
lines.append(
f" {r.feature_name}: n={r.n}, mean={r.mean:.4f}, "
f"Cpk={cpk_str}, Ppk={ppk_str}, OOS={r.out_of_spec_count}"
)
return "\n".join(lines) if lines else " No SPC data available."
def _fallback_summary(report: ParsedReport, spc_results: list[SPCResult]) -> str:
total = len(report.measurements)
oot = len(report.out_of_tolerance)
status = "PASS" if oot == 0 else "FAIL"
lines = [
f"**Inspection Summary for {report.filename}**",
f"Status: **{status}** — {total} measurements, {oot} out of tolerance.",
"",
]
if oot > 0:
lines.append("Out-of-tolerance features:")
for m in report.out_of_tolerance:
lines.append(
f" - {m.feature_name}: actual={m.actual:.4f}, "
f"nominal={m.nominal:.4f}, tolerance=[{m.lsl:.4f}, {m.usl:.4f}]"
)
lines.append("")
for r in spc_results:
if r.cpk is not None and r.cpk < 1.0:
lines.append(
f" Warning: {r.feature_name} Cpk={r.cpk:.3f} (below 1.0)"
)
lines.append("")
lines.append("*(AI summary unavailable — configure Azure OpenAI for enhanced analysis)*")
return "\n".join(lines)