bc_fields.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. """Boundary-condition (BC) field catalog - single source of truth.
  2. The BC key naming had drifted across three places:
  3. 1. the project boundary form / rule_engine -> current_a, speed_rpm, slots, ...
  4. 2. the fixed-param template (build_default_fixed_params)
  5. -> rated_current_a, rated_speed_rpm, ...
  6. 3. the plan-detail display template -> rated_* (a third set)
  7. This module is the single source for the canonical BC field keys, their
  8. Chinese labels, units, grouping, and the legacy/variant alias keys accepted on
  9. read. Canonical keys match the project boundary form and rule_engine (the
  10. data-entry and primary consumers). `aliases` keeps backward compatibility with
  11. the template-era rated_* names and other historical variants.
  12. All source is ASCII; Chinese labels are written as unicode escapes (project hard rule).
  13. """
  14. from __future__ import annotations
  15. from typing import Any, Dict, List
  16. # Category labels (unicode-escaped).
  17. _CAT_ELEC = "\u7535\u6c14"
  18. _CAT_GEOM = "\u51e0\u4f55\u5c3a\u5bf8"
  19. _CAT_SLOT = "\u69fd\u6781"
  20. _CAT_MAT = "\u6750\u6599\u5c5e\u6027"
  21. _CAT_GOAL = "\u6027\u80fd\u76ee\u6807"
  22. _CAT_THERM = "\u70ed\u4e0e\u73af\u5883"
  23. # Canonical BC field catalog. Order defines display order in the UI.
  24. BC_FIELD_CATALOG: List[Dict[str, Any]] = [
  25. # --- Electrical ---
  26. {"key": "power_w", "label": "\u989d\u5b9a\u529f\u7387", "unit": "W", "category": _CAT_ELEC,
  27. "aliases": ["rated_power_w"]},
  28. {"key": "voltage_v", "label": "\u989d\u5b9a\u7535\u538b", "unit": "V", "category": _CAT_ELEC,
  29. "aliases": ["dc_link_voltage_v"]},
  30. {"key": "phase_count", "label": "\u76f8\u6570", "unit": "", "category": _CAT_ELEC,
  31. "aliases": []},
  32. {"key": "speed_rpm", "label": "\u989d\u5b9a\u8f6c\u901f", "unit": "rpm", "category": _CAT_ELEC,
  33. "aliases": ["rated_speed_rpm"]},
  34. {"key": "max_speed_rpm", "label": "\u6700\u9ad8\u8f6c\u901f", "unit": "rpm", "category": _CAT_ELEC,
  35. "aliases": []},
  36. {"key": "current_a", "label": "\u989d\u5b9a\u7535\u6d41", "unit": "A", "category": _CAT_ELEC,
  37. "aliases": ["rated_current_a"]},
  38. # --- Geometry ---
  39. {"key": "outer_diameter_mm", "label": "\u5916\u5f84", "unit": "mm", "category": _CAT_GEOM,
  40. "aliases": []},
  41. {"key": "max_outer_diameter_mm", "label": "\u6700\u5927\u5916\u5f84", "unit": "mm", "category": _CAT_GEOM,
  42. "aliases": []},
  43. {"key": "inner_diameter_mm", "label": "\u5185\u5f84", "unit": "mm", "category": _CAT_GEOM,
  44. "aliases": []},
  45. {"key": "axial_length_mm", "label": "\u8f74\u5411\u957f\u5ea6", "unit": "mm", "category": _CAT_GEOM,
  46. "aliases": []},
  47. # --- Slots & poles ---
  48. {"key": "slots", "label": "\u69fd\u6570", "unit": "", "category": _CAT_SLOT,
  49. "aliases": ["slot_count"]},
  50. {"key": "poles", "label": "\u6781\u6570", "unit": "", "category": _CAT_SLOT,
  51. "aliases": []},
  52. {"key": "pole_pairs", "label": "\u6781\u5bf9\u6570", "unit": "", "category": _CAT_SLOT,
  53. "aliases": []},
  54. # --- Material ---
  55. {"key": "magnet_grade", "label": "\u78c1\u94a2\u724c\u53f7", "unit": "", "category": _CAT_MAT,
  56. "aliases": []},
  57. {"key": "magnet_temp_c", "label": "\u78c1\u94a2\u6e29\u5ea6", "unit": "\xb0C", "category": _CAT_MAT,
  58. "aliases": []},
  59. # --- Performance goals ---
  60. {"key": "target_torque_nm", "label": "\u76ee\u6807\u8f6c\u77e9", "unit": "Nm", "category": _CAT_GOAL,
  61. "aliases": ["rated_torque_nm"]},
  62. {"key": "target_efficiency_pct", "label": "\u76ee\u6807\u6548\u7387", "unit": "%", "category": _CAT_GOAL,
  63. "aliases": ["efficiency_min_pct"]},
  64. {"key": "max_losses_w", "label": "\u6700\u5927\u635f\u8017", "unit": "W", "category": _CAT_GOAL,
  65. "aliases": []},
  66. {"key": "target_ripple_pct", "label": "\u76ee\u6807\u8109\u52a8", "unit": "%", "category": _CAT_GOAL,
  67. "aliases": ["max_torque_ripple_pct"]},
  68. {"key": "max_axial_force_n", "label": "\u6700\u5927\u8f74\u5411\u529b", "unit": "N", "category": _CAT_GOAL,
  69. "aliases": []},
  70. {"key": "weight_kg", "label": "\u76ee\u6807\u91cd\u91cf", "unit": "kg", "category": _CAT_GOAL,
  71. "aliases": []},
  72. # --- Thermal & environment ---
  73. {"key": "cooling_type", "label": "\u51b7\u5374\u65b9\u5f0f", "unit": "", "category": _CAT_THERM,
  74. "aliases": ["cooling_method"]},
  75. {"key": "insulation_class", "label": "\u7edd\u7f18\u7b49\u7ea7", "unit": "", "category": _CAT_THERM,
  76. "aliases": []},
  77. {"key": "duty_cycle", "label": "\u5de5\u4f5c\u5236", "unit": "", "category": _CAT_THERM,
  78. "aliases": []},
  79. {"key": "ambient_temp_c", "label": "\u73af\u5883\u6e29\u5ea6", "unit": "\xb0C", "category": _CAT_THERM,
  80. "aliases": []},
  81. ]
  82. def get_bc_field_catalog() -> List[Dict[str, Any]]:
  83. """Return the BC field catalog as a list of dicts (for API / UI rendering).
  84. Each field is enriched with form-rendering metadata: `type` (number /
  85. int / enum) and `options` for enum fields, so the frontend can render the
  86. full catalog as a form without hard-coding any field list.
  87. """
  88. out = [dict(f) for f in BC_FIELD_CATALOG]
  89. for f in out:
  90. meta = _FIELD_FORM_META.get(f["key"], {"type": "number"})
  91. f["type"] = meta["type"]
  92. if meta.get("options"):
  93. f["options"] = list(meta["options"])
  94. return out
  95. # Form-rendering metadata keyed by canonical BC key. Enum options are
  96. # engineering-standard choices (IEC insulation classes / duty cycles, common
  97. # NdFeB grades; Cooling_Method verified against Motor-CAD via the MARS run).
  98. _FIELD_FORM_META: Dict[str, Dict[str, Any]] = {
  99. "cooling_type": {"type": "enum", "options": ["Natural", "Forced Air", "Water", "Oil"]},
  100. "insulation_class": {"type": "enum", "options": ["A", "E", "B", "F", "H"]},
  101. "duty_cycle": {"type": "enum", "options": ["S1", "S2", "S3", "S6"]},
  102. "magnet_grade": {"type": "enum", "options": ["N35", "N38", "N42", "N42SH", "N42UH", "N48", "N52"]},
  103. "phase_count": {"type": "int"},
  104. "slots": {"type": "int"},
  105. "poles": {"type": "int"},
  106. "pole_pairs": {"type": "int"},
  107. }
  108. def _alias_to_canonical() -> Dict[str, str]:
  109. """Build alias -> canonical key lookup from the catalog."""
  110. m: Dict[str, str] = {}
  111. for f in BC_FIELD_CATALOG:
  112. for a in f.get("aliases", []):
  113. m[a] = f["key"]
  114. return m
  115. def normalize_bc(data: Dict[str, Any]) -> Dict[str, Any]:
  116. """Normalize a boundary-conditions dict to canonical keys.
  117. Alias keys are mapped to their canonical key; a canonical key already
  118. present always wins over its aliases. Unknown keys are passed through
  119. unchanged so forward-compatible extras are preserved. Returns a new dict
  120. (the input is not mutated).
  121. """
  122. if not data:
  123. return {}
  124. amap = _alias_to_canonical()
  125. out: Dict[str, Any] = {}
  126. for k, v in data.items():
  127. canon = amap.get(k, k)
  128. # Canonical already set (explicitly or by an earlier alias): keep the
  129. # first non-empty value, preferring an explicit canonical entry.
  130. if canon in out and out[canon] not in (None, ""):
  131. if k == canon and v not in (None, ""):
  132. out[canon] = v # explicit canonical overrides alias-filled
  133. continue
  134. out[canon] = v
  135. return out