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.
102 lines
3.5 KiB
Python
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)
|