| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- """Boundary-condition (BC) field catalog - single source of truth.
- The BC key naming had drifted across three places:
- 1. the project boundary form / rule_engine -> current_a, speed_rpm, slots, ...
- 2. the fixed-param template (build_default_fixed_params)
- -> rated_current_a, rated_speed_rpm, ...
- 3. the plan-detail display template -> rated_* (a third set)
- This module is the single source for the canonical BC field keys, their
- Chinese labels, units, grouping, and the legacy/variant alias keys accepted on
- read. Canonical keys match the project boundary form and rule_engine (the
- data-entry and primary consumers). `aliases` keeps backward compatibility with
- the template-era rated_* names and other historical variants.
- All source is ASCII; Chinese labels are written as unicode escapes (project hard rule).
- """
- from __future__ import annotations
- from typing import Any, Dict, List
- # Category labels (unicode-escaped).
- _CAT_ELEC = "\u7535\u6c14"
- _CAT_GEOM = "\u51e0\u4f55\u5c3a\u5bf8"
- _CAT_SLOT = "\u69fd\u6781"
- _CAT_MAT = "\u6750\u6599\u5c5e\u6027"
- _CAT_GOAL = "\u6027\u80fd\u76ee\u6807"
- _CAT_THERM = "\u70ed\u4e0e\u73af\u5883"
- # Canonical BC field catalog. Order defines display order in the UI.
- BC_FIELD_CATALOG: List[Dict[str, Any]] = [
- # --- Electrical ---
- {"key": "power_w", "label": "\u989d\u5b9a\u529f\u7387", "unit": "W", "category": _CAT_ELEC,
- "aliases": ["rated_power_w"]},
- {"key": "voltage_v", "label": "\u989d\u5b9a\u7535\u538b", "unit": "V", "category": _CAT_ELEC,
- "aliases": ["dc_link_voltage_v"]},
- {"key": "phase_count", "label": "\u76f8\u6570", "unit": "", "category": _CAT_ELEC,
- "aliases": []},
- {"key": "speed_rpm", "label": "\u989d\u5b9a\u8f6c\u901f", "unit": "rpm", "category": _CAT_ELEC,
- "aliases": ["rated_speed_rpm"]},
- {"key": "max_speed_rpm", "label": "\u6700\u9ad8\u8f6c\u901f", "unit": "rpm", "category": _CAT_ELEC,
- "aliases": []},
- {"key": "current_a", "label": "\u989d\u5b9a\u7535\u6d41", "unit": "A", "category": _CAT_ELEC,
- "aliases": ["rated_current_a"]},
- # --- Geometry ---
- {"key": "outer_diameter_mm", "label": "\u5916\u5f84", "unit": "mm", "category": _CAT_GEOM,
- "aliases": []},
- {"key": "max_outer_diameter_mm", "label": "\u6700\u5927\u5916\u5f84", "unit": "mm", "category": _CAT_GEOM,
- "aliases": []},
- {"key": "inner_diameter_mm", "label": "\u5185\u5f84", "unit": "mm", "category": _CAT_GEOM,
- "aliases": []},
- {"key": "axial_length_mm", "label": "\u8f74\u5411\u957f\u5ea6", "unit": "mm", "category": _CAT_GEOM,
- "aliases": []},
- # --- Slots & poles ---
- {"key": "slots", "label": "\u69fd\u6570", "unit": "", "category": _CAT_SLOT,
- "aliases": ["slot_count"]},
- {"key": "poles", "label": "\u6781\u6570", "unit": "", "category": _CAT_SLOT,
- "aliases": []},
- {"key": "pole_pairs", "label": "\u6781\u5bf9\u6570", "unit": "", "category": _CAT_SLOT,
- "aliases": []},
- # --- Material ---
- {"key": "magnet_grade", "label": "\u78c1\u94a2\u724c\u53f7", "unit": "", "category": _CAT_MAT,
- "aliases": []},
- {"key": "magnet_temp_c", "label": "\u78c1\u94a2\u6e29\u5ea6", "unit": "\xb0C", "category": _CAT_MAT,
- "aliases": []},
- # --- Performance goals ---
- {"key": "target_torque_nm", "label": "\u76ee\u6807\u8f6c\u77e9", "unit": "Nm", "category": _CAT_GOAL,
- "aliases": ["rated_torque_nm"]},
- {"key": "target_efficiency_pct", "label": "\u76ee\u6807\u6548\u7387", "unit": "%", "category": _CAT_GOAL,
- "aliases": ["efficiency_min_pct"]},
- {"key": "max_losses_w", "label": "\u6700\u5927\u635f\u8017", "unit": "W", "category": _CAT_GOAL,
- "aliases": []},
- {"key": "target_ripple_pct", "label": "\u76ee\u6807\u8109\u52a8", "unit": "%", "category": _CAT_GOAL,
- "aliases": ["max_torque_ripple_pct"]},
- {"key": "max_axial_force_n", "label": "\u6700\u5927\u8f74\u5411\u529b", "unit": "N", "category": _CAT_GOAL,
- "aliases": []},
- {"key": "weight_kg", "label": "\u76ee\u6807\u91cd\u91cf", "unit": "kg", "category": _CAT_GOAL,
- "aliases": []},
- # --- Thermal & environment ---
- {"key": "cooling_type", "label": "\u51b7\u5374\u65b9\u5f0f", "unit": "", "category": _CAT_THERM,
- "aliases": ["cooling_method"]},
- {"key": "insulation_class", "label": "\u7edd\u7f18\u7b49\u7ea7", "unit": "", "category": _CAT_THERM,
- "aliases": []},
- {"key": "duty_cycle", "label": "\u5de5\u4f5c\u5236", "unit": "", "category": _CAT_THERM,
- "aliases": []},
- {"key": "ambient_temp_c", "label": "\u73af\u5883\u6e29\u5ea6", "unit": "\xb0C", "category": _CAT_THERM,
- "aliases": []},
- ]
- def get_bc_field_catalog() -> List[Dict[str, Any]]:
- """Return the BC field catalog as a list of dicts (for API / UI rendering).
- Each field is enriched with form-rendering metadata: `type` (number /
- int / enum) and `options` for enum fields, so the frontend can render the
- full catalog as a form without hard-coding any field list.
- """
- out = [dict(f) for f in BC_FIELD_CATALOG]
- for f in out:
- meta = _FIELD_FORM_META.get(f["key"], {"type": "number"})
- f["type"] = meta["type"]
- if meta.get("options"):
- f["options"] = list(meta["options"])
- return out
- # Form-rendering metadata keyed by canonical BC key. Enum options are
- # engineering-standard choices (IEC insulation classes / duty cycles, common
- # NdFeB grades; Cooling_Method verified against Motor-CAD via the MARS run).
- _FIELD_FORM_META: Dict[str, Dict[str, Any]] = {
- "cooling_type": {"type": "enum", "options": ["Natural", "Forced Air", "Water", "Oil"]},
- "insulation_class": {"type": "enum", "options": ["A", "E", "B", "F", "H"]},
- "duty_cycle": {"type": "enum", "options": ["S1", "S2", "S3", "S6"]},
- "magnet_grade": {"type": "enum", "options": ["N35", "N38", "N42", "N42SH", "N42UH", "N48", "N52"]},
- "phase_count": {"type": "int"},
- "slots": {"type": "int"},
- "poles": {"type": "int"},
- "pole_pairs": {"type": "int"},
- }
- def _alias_to_canonical() -> Dict[str, str]:
- """Build alias -> canonical key lookup from the catalog."""
- m: Dict[str, str] = {}
- for f in BC_FIELD_CATALOG:
- for a in f.get("aliases", []):
- m[a] = f["key"]
- return m
- def normalize_bc(data: Dict[str, Any]) -> Dict[str, Any]:
- """Normalize a boundary-conditions dict to canonical keys.
- Alias keys are mapped to their canonical key; a canonical key already
- present always wins over its aliases. Unknown keys are passed through
- unchanged so forward-compatible extras are preserved. Returns a new dict
- (the input is not mutated).
- """
- if not data:
- return {}
- amap = _alias_to_canonical()
- out: Dict[str, Any] = {}
- for k, v in data.items():
- canon = amap.get(k, k)
- # Canonical already set (explicitly or by an earlier alias): keep the
- # first non-empty value, preferring an explicit canonical entry.
- if canon in out and out[canon] not in (None, ""):
- if k == canon and v not in (None, ""):
- out[canon] = v # explicit canonical overrides alias-filled
- continue
- out[canon] = v
- return out
|