"""Experience Library AI Enhancer (P3-M5). Uses AI to extract design knowledge, rules, and insights from historical simulation results, enhancing the experience library. """ import json from pathlib import Path from typing import Dict, List, Optional, Any from datetime import datetime from ..config import PROMPTS_DIR from ..services.ai_client import get_kimi_client class ExperienceEnhancer: """AI-powered experience library enhancer. Extracts: - Design rules and heuristics from successful cases - Failure patterns and constraint boundaries - Parameter sensitivity rankings - Optimal design regions - Comparative insights across topologies """ def __init__(self): self.ai_client = get_kimi_client() self._prompt_template = None def _load_prompt(self) -> str: if self._prompt_template is None: prompt_path = PROMPTS_DIR / "experience" / "extract.txt" if prompt_path.exists(): with open(prompt_path, "r", encoding="utf-8") as f: self._prompt_template = f.read() else: self._prompt_template = self._default_prompt() return self._prompt_template def _default_prompt(self) -> str: return """你是轴向磁通电机设计知识提取专家。从仿真结果数据中提取设计规则、失败模式、参数敏感性和最优设计区域。输出JSON格式。""" def extract_insights( self, results: List[Dict[str, Any]], project_context: Optional[Dict[str, Any]] = None, ) -> Dict[str, Any]: """Extract design insights from a batch of simulation results. Args: results: List of simulation result dicts project_context: Optional project context Returns: Structured insights including rules, patterns, and recommendations """ if not results: return {"error": "No results to analyze"} # Prepare condensed data condensed = self._condense_results(results) if project_context: condensed["project_context"] = project_context user_message = f"从以下轴向磁通电机仿真结果中提取设计知识:\n{json.dumps(condensed, ensure_ascii=False, indent=2)}" if not self.ai_client.is_configured: return self._fallback_insights(results) try: result = self.ai_client.chat_json( messages=[{"role": "user", "content": user_message}], system_prompt=self._load_prompt(), max_tokens=3000, ) insights = result.get("parsed_json", {}) if not insights: raw = result.get("raw_content", result.get("content", "")) try: start = raw.find("{") end = raw.rfind("}") + 1 if start >= 0 and end > start: insights = json.loads(raw[start:end]) except (json.JSONDecodeError, Exception): insights = {"summary": raw[:500]} insights["extracted_at"] = datetime.now().isoformat() insights["n_results_analyzed"] = len(results) insights["usage"] = result.get("usage", {}) return insights except Exception as e: return {"error": f"AI insight extraction failed: {str(e)}", **self._fallback_insights(results)} def _condense_results(self, results: List[Dict[str, Any]]) -> Dict[str, Any]: """Condense results for AI processing (avoid token overflow).""" # Extract key metrics metrics = [] for r in results: m = {} for key in ["tavg_nm", "efficiency_pct", "total_losses_w", "winding_temp_c", "airgap_mm", "magnet_thickness_mm", "current_a", "speed_rpm", "outer_diameter_mm", "inner_diameter_mm", "feasible"]: if key in r: m[key] = r[key] if m: metrics.append(m) # Sort by torque and take top/bottom 10 + random 10 sorted_by_torque = sorted(metrics, key=lambda x: x.get("tavg_nm", 0), reverse=True) top_10 = sorted_by_torque[:10] bottom_10 = sorted_by_torque[-10:] if len(sorted_by_torque) > 20 else [] # Calculate basic statistics all_torque = [m.get("tavg_nm", 0) for m in metrics if "tavg_nm" in m] all_efficiency = [m.get("efficiency_pct", 0) for m in metrics if "efficiency_pct" in m] stats = {} if all_torque: stats["torque"] = { "min": min(all_torque), "max": max(all_torque), "mean": sum(all_torque) / len(all_torque), } if all_efficiency: stats["efficiency"] = { "min": min(all_efficiency), "max": max(all_efficiency), "mean": sum(all_efficiency) / len(all_efficiency), } return { "total_points": len(results), "statistics": stats, "top_performers": top_10, "bottom_performers": bottom_10, } def _fallback_insights(self, results: List[Dict[str, Any]]) -> Dict[str, Any]: """Generate basic insights without AI.""" feasible = [r for r in results if r.get("feasible", True)] infeasible = [r for r in results if not r.get("feasible", True)] insights = { "summary": f"Analyzed {len(results)} points ({len(feasible)} feasible, {len(infeasible)} infeasible).", "feasibility_rate": len(feasible) / len(results) if results else 0, "design_rules": [], "failure_patterns": [], "extracted_at": datetime.now().isoformat(), "n_results_analyzed": len(results), "ai_used": False, } # Simple correlation analysis if len(feasible) >= 5: # Find parameters that correlate with high torque for param in ["airgap_mm", "magnet_thickness_mm", "current_a"]: values = [(r.get(param, 0), r.get("tavg_nm", 0)) for r in feasible if param in r] if len(values) >= 5: values.sort(key=lambda x: x[1], reverse=True) top_params = [v[0] for v in values[:len(values)//3]] avg_top = sum(top_params) / len(top_params) if top_params else 0 all_avg = sum(v[0] for v in values) / len(values) if abs(avg_top - all_avg) > 0.1 * abs(all_avg) if all_avg else False: direction = "higher" if avg_top > all_avg else "lower" insights["design_rules"].append( f"High torque designs tend to have {direction} {param} (avg top={avg_top:.2f} vs all={all_avg:.2f})" ) return insights def generate_experience_entry( self, insights: Dict[str, Any], project_name: str, topology: str, ) -> Dict[str, Any]: """Generate a structured experience library entry from insights. Args: insights: Extracted insights project_name: Project name topology: Motor topology Returns: Structured experience entry ready for database storage """ return { "title": f"{project_name} - {topology} Design Insights", "topology": topology, "project": project_name, "summary": insights.get("summary", ""), "design_rules": insights.get("design_rules", []), "failure_patterns": insights.get("failure_patterns", []), "parameter_sensitivity": insights.get("parameter_sensitivity", {}), "optimal_regions": insights.get("optimal_regions", []), "confidence": insights.get("confidence_grade", "C"), "source": "ai_extracted", "created_at": datetime.now().isoformat(), "tags": [topology, "ai-insights", project_name], } # Global singleton _enhancer: Optional[ExperienceEnhancer] = None def get_experience_enhancer() -> ExperienceEnhancer: """Get or create global ExperienceEnhancer singleton.""" global _enhancer if _enhancer is None: _enhancer = ExperienceEnhancer() return _enhancer