simulation_result.py 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. """Simulation result model."""
  2. import json
  3. from datetime import datetime
  4. from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Float
  5. from ..database import Base
  6. class SimulationResult(Base):
  7. """A single simulation result row (from scan_results.csv).
  8. P3-M1: Extended with fidelity level, confidence grade,
  9. constraint margins, surrogate prediction, and cross-validation
  10. per third-party review.
  11. """
  12. __tablename__ = "simulation_results"
  13. id = Column(Integer, primary_key=True, index=True)
  14. plan_id = Column(Integer, ForeignKey("simulation_plans.id"), nullable=False, index=True)
  15. run_index = Column(Integer, default=0)
  16. status = Column(String(20), default="OK") # OK / FAILED / UNCERTAIN
  17. solve_time_s = Column(Float, default=0.0)
  18. params_json = Column(Text, default="{}") # JSON: parameter values
  19. metrics_json = Column(Text, default="{}") # JSON: metric values
  20. # Lossless Motor-CAD export archive: JSON list of
  21. # {section, name, value, unit} covering every exported EM+thermal field.
  22. raw_json = Column(Text, default="[]")
  23. error_message = Column(Text, default="")
  24. created_at = Column(DateTime, default=datetime.utcnow)
  25. # P3: Multi-fidelity and confidence fields
  26. fidelity_level = Column(String(30), default="L1_motorcad_emag", index=True) # L0-L4
  27. confidence_grade = Column(String(2), default="C", index=True) # A/B/C/D
  28. model_template_version = Column(String(100), default="") # Motor-CAD template / PCB model version
  29. solver_settings_hash = Column(String(64), default="") # Hash of solver config
  30. constraint_margins_json = Column(Text, default="{}") # JSON: hard constraint margins
  31. surrogate_prediction_json = Column(Text, default="{}") # JSON: surrogate prediction + uncertainty
  32. cross_validation_json = Column(Text, default="{}") # JSON: Motor-CAD vs Maxwell/JMAG deviation
  33. convergence_status_json = Column(Text, default="{}") # JSON: six-type convergence status
  34. def get_params(self) -> dict:
  35. try:
  36. return json.loads(self.params_json) if self.params_json else {}
  37. except (json.JSONDecodeError, TypeError):
  38. return {}
  39. def set_params(self, data: dict) -> None:
  40. self.params_json = json.dumps(data, ensure_ascii=False)
  41. def get_metrics(self) -> dict:
  42. try:
  43. return json.loads(self.metrics_json) if self.metrics_json else {}
  44. except (json.JSONDecodeError, TypeError):
  45. return {}
  46. def set_metrics(self, data: dict) -> None:
  47. self.metrics_json = json.dumps(data, ensure_ascii=False)
  48. def get_raw(self) -> list:
  49. """Full-fidelity export rows: [{section, name, value, unit}, ...]."""
  50. try:
  51. data = json.loads(self.raw_json) if self.raw_json else []
  52. return data if isinstance(data, list) else []
  53. except (json.JSONDecodeError, TypeError):
  54. return []
  55. def set_raw(self, data: list) -> None:
  56. self.raw_json = json.dumps(data or [], ensure_ascii=False)
  57. # P3: Helper methods for extended fields
  58. def get_constraint_margins(self) -> dict:
  59. try:
  60. return json.loads(self.constraint_margins_json) if self.constraint_margins_json else {}
  61. except (json.JSONDecodeError, TypeError):
  62. return {}
  63. def set_constraint_margins(self, data: dict) -> None:
  64. self.constraint_margins_json = json.dumps(data, ensure_ascii=False)
  65. def get_surrogate_prediction(self) -> dict:
  66. try:
  67. return json.loads(self.surrogate_prediction_json) if self.surrogate_prediction_json else {}
  68. except (json.JSONDecodeError, TypeError):
  69. return {}
  70. def set_surrogate_prediction(self, data: dict) -> None:
  71. self.surrogate_prediction_json = json.dumps(data, ensure_ascii=False)
  72. def get_cross_validation(self) -> dict:
  73. try:
  74. return json.loads(self.cross_validation_json) if self.cross_validation_json else {}
  75. except (json.JSONDecodeError, TypeError):
  76. return {}
  77. def set_cross_validation(self, data: dict) -> None:
  78. self.cross_validation_json = json.dumps(data, ensure_ascii=False)
  79. def get_convergence_status(self) -> dict:
  80. try:
  81. return json.loads(self.convergence_status_json) if self.convergence_status_json else {}
  82. except (json.JSONDecodeError, TypeError):
  83. return {}
  84. def set_convergence_status(self, data: dict) -> None:
  85. self.convergence_status_json = json.dumps(data, ensure_ascii=False)