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)