test_topology_variable_map.py 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. """Tests for topology-aware Motor-CAD variable name mapping.
  2. Covers:
  3. - Topology normalization
  4. - RFM -> AFM alias resolution (the plan 23 bug)
  5. - Known variable validation
  6. - Parameter validation with suggestions
  7. - Boundary cases (empty, unknown topology, unknown variable)
  8. Note (2026-09-03): AFM target names were corrected to the MARS-verified
  9. names measured live via pymotorcad on MARS-12S10P_SSSR:
  10. Stator_Outer_Diameter -> Stator_Lam_Dia, Stator_Inner_Diameter -> Stator_Bore,
  11. Outer_Rotor_Diameter -> RotorOuterDiameter, Rotor_Back_Iron_Thickness ->
  12. Back_Iron_Thickness. Assertions below use the verified names.
  13. Run: python scripts/test_topology_variable_map.py
  14. Exit 0 = PASS, non-zero = FAIL.
  15. """
  16. import sys
  17. import os
  18. # Add backend to path
  19. sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "web", "backend"))
  20. from app.services.topology_variable_map import (
  21. TOPOLOGY_SSSR,
  22. TOPOLOGY_AFIR,
  23. TOPOLOGY_RFM,
  24. normalize_topology,
  25. resolve_variable,
  26. is_known_variable,
  27. validate_parameters,
  28. suggest_alternative,
  29. get_known_variables,
  30. )
  31. def test_normalize_topology():
  32. """Test topology string normalization."""
  33. assert normalize_topology("SSSR") == TOPOLOGY_SSSR
  34. assert normalize_topology("sssr") == TOPOLOGY_SSSR
  35. assert normalize_topology("AFIR") == TOPOLOGY_AFIR
  36. assert normalize_topology("RFM") == TOPOLOGY_RFM
  37. assert normalize_topology("AFM") == TOPOLOGY_SSSR
  38. assert normalize_topology("axial") == TOPOLOGY_SSSR
  39. assert normalize_topology("radial") == TOPOLOGY_RFM
  40. assert normalize_topology(None) == TOPOLOGY_SSSR
  41. assert normalize_topology("") == TOPOLOGY_SSSR
  42. assert normalize_topology("UNKNOWN") == TOPOLOGY_SSSR # default
  43. print("PASS: test_normalize_topology")
  44. def test_rfm_to_afm_alias_resolution():
  45. """Test that RFM variable names are mapped to AFM names on SSSR topology.
  46. This is the core fix for plan 23: Stator_Lam_Outer_Dia (RFM) must be
  47. resolved to Stator_Lam_Dia (MARS-verified AFM name) when topology is SSSR.
  48. """
  49. # RFM names on SSSR topology -> AFM names
  50. resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR)
  51. assert resolved == "Stator_Lam_Dia", f"Expected Stator_Lam_Dia, got {resolved}"
  52. assert was_alias is True
  53. resolved, was_alias = resolve_variable("Stator_Lam_Inner_Dia", TOPOLOGY_SSSR)
  54. assert resolved == "Stator_Bore"
  55. assert was_alias is True
  56. resolved, was_alias = resolve_variable("Stator_Yoke_Width", TOPOLOGY_SSSR)
  57. assert resolved == "Stator_Yoke_Thickness"
  58. assert was_alias is True
  59. # Same RFM names on RFM topology -> unchanged
  60. resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_RFM)
  61. assert resolved == "Stator_Lam_Outer_Dia"
  62. assert was_alias is False
  63. # AFIR topology also maps to AFM names
  64. resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_AFIR)
  65. assert resolved == "Stator_Lam_Dia"
  66. assert was_alias is True
  67. print("PASS: test_rfm_to_afm_alias_resolution")
  68. def test_template_logical_name_mapping():
  69. """Test that template logical names map to Motor-CAD actual names."""
  70. resolved, was_alias = resolve_variable("Number_of_Slots", TOPOLOGY_SSSR)
  71. assert resolved == "Slot_Number"
  72. assert was_alias is True
  73. resolved, was_alias = resolve_variable("Number_of_Poles", TOPOLOGY_SSSR)
  74. assert resolved == "Pole_Number"
  75. assert was_alias is True
  76. resolved, was_alias = resolve_variable("DC_Link_Voltage", TOPOLOGY_SSSR)
  77. assert resolved == "DCBusVoltage"
  78. assert was_alias is True
  79. resolved, was_alias = resolve_variable("Turns_per_Coil", TOPOLOGY_SSSR)
  80. assert resolved == "ConductorsPerSlot"
  81. assert was_alias is True
  82. print("PASS: test_template_logical_name_mapping")
  83. def test_known_variable_validation():
  84. """Test known variable detection per topology."""
  85. # AFM variables known on SSSR (MARS-verified names)
  86. assert is_known_variable("Stator_Lam_Dia", TOPOLOGY_SSSR) is True
  87. assert is_known_variable("Stator_Bore", TOPOLOGY_SSSR) is True
  88. assert is_known_variable("RotorOuterDiameter", TOPOLOGY_SSSR) is True
  89. assert is_known_variable("Airgap", TOPOLOGY_SSSR) is True
  90. assert is_known_variable("Slot_Number", TOPOLOGY_SSSR) is True
  91. assert is_known_variable("Magnet_Thickness", TOPOLOGY_SSSR) is True
  92. # Deprecated wrong names (radial-template naming) are now UNKNOWN
  93. assert is_known_variable("Stator_Outer_Diameter", TOPOLOGY_SSSR) is False
  94. assert is_known_variable("Stator_Inner_Diameter", TOPOLOGY_SSSR) is False
  95. assert is_known_variable("Outer_Rotor_Diameter", TOPOLOGY_SSSR) is False
  96. # RFM variable NOT known on SSSR
  97. assert is_known_variable("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR) is False
  98. # Unknown variable
  99. assert is_known_variable("NonExistent_Var", TOPOLOGY_SSSR) is False
  100. # Empty / None
  101. assert is_known_variable("", TOPOLOGY_SSSR) is False
  102. print("PASS: test_known_variable_validation")
  103. def test_validate_parameters():
  104. """Test full parameter validation with classification."""
  105. params = {
  106. "Stator_Lam_Dia": 100.0, # known AFM (MARS-verified)
  107. "Airgap": 1.0, # known
  108. "Stator_Lam_Outer_Dia": 80.0, # RFM alias - should be resolved
  109. "NonExistent": 42.0, # unknown
  110. }
  111. result = validate_parameters(params, TOPOLOGY_SSSR)
  112. # RFM alias should be resolved to the MARS-verified AFM name
  113. resolved_names = [r[0] for r in result["valid"]]
  114. assert "Stator_Lam_Dia" in resolved_names
  115. assert "Airgap" in resolved_names
  116. assert "Stator_Lam_Outer_Dia" not in resolved_names # should be resolved away
  117. # Unknown variable should be flagged
  118. unknown_names = [r[0] for r in result["unknown"]]
  119. assert "NonExistent" in unknown_names
  120. # resolved_params should have all parameters with resolved names
  121. assert "Stator_Lam_Dia" in result["resolved_params"]
  122. assert "NonExistent" in result["resolved_params"]
  123. print("PASS: test_validate_parameters")
  124. def test_suggest_alternative():
  125. """Test suggestion of close variable names."""
  126. suggestion = suggest_alternative("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR)
  127. assert suggestion is not None
  128. assert "Stator" in suggestion
  129. assert "Lam" in suggestion or "Dia" in suggestion
  130. # Completely unrelated name should return None
  131. suggestion = suggest_alternative("xyz_abc_123", TOPOLOGY_SSSR)
  132. # May or may not return something, just ensure no crash
  133. assert suggestion is None or isinstance(suggestion, str)
  134. print("PASS: test_suggest_alternative")
  135. def test_get_known_variables():
  136. """Test retrieval of known variable set."""
  137. sssr_vars = get_known_variables(TOPOLOGY_SSSR)
  138. assert isinstance(sssr_vars, set)
  139. assert len(sssr_vars) > 30 # should have substantial coverage
  140. assert "Stator_Lam_Dia" in sssr_vars
  141. assert "Airgap" in sssr_vars
  142. # AFIR should share SSSR variables
  143. afir_vars = get_known_variables(TOPOLOGY_AFIR)
  144. assert "Stator_Lam_Dia" in afir_vars
  145. print("PASS: test_get_known_variables")
  146. def test_plan23_regression():
  147. """Regression test for the exact plan 23 failure scenario.
  148. Plan 23 had scan variables:
  149. - Magnet_Thickness (valid)
  150. - Stator_Lam_Outer_Dia (RFM name -> should map to Stator_Lam_Dia)
  151. - Stator_Lam_Inner_Dia (RFM name -> should map to Stator_Bore)
  152. After expansion, NO Stator_Lam_* names should remain, and all variables
  153. should be known for SSSR topology.
  154. """
  155. from app.routers.plans import _expand_plan_to_parameters
  156. plan_data = {
  157. "topology": "SSSR",
  158. "fixed_params": [
  159. {"name": "Airgap", "value": 1, "source": "user"},
  160. {"name": "Number_of_Slots", "value": 12, "source": "user"},
  161. {"name": "Number_of_Poles", "value": 10, "source": "user"},
  162. ],
  163. "variables": [
  164. {"name": "Magnet_Thickness", "values": [2, 3, 4, 5]},
  165. {"name": "Stator_Lam_Outer_Dia", "values": [80, 85, 90, 95, 100]},
  166. {"name": "Stator_Lam_Inner_Dia", "values": [45, 50, 55, 60]},
  167. ],
  168. }
  169. parameters = _expand_plan_to_parameters(plan_data)
  170. assert len(parameters) == 80 # 4 * 5 * 4
  171. # Check first point
  172. point0 = parameters[0]
  173. # RFM names should NOT be present
  174. assert "Stator_Lam_Outer_Dia" not in point0, "RFM name Stator_Lam_Outer_Dia should be resolved"
  175. assert "Stator_Lam_Inner_Dia" not in point0, "RFM name Stator_Lam_Inner_Dia should be resolved"
  176. # MARS-verified AFM names SHOULD be present
  177. assert "Stator_Lam_Dia" in point0, "AFM name Stator_Lam_Dia should be present"
  178. assert "Stator_Bore" in point0, "AFM name Stator_Bore should be present"
  179. # Values should be correct
  180. assert point0["Stator_Lam_Dia"] == 80
  181. assert point0["Stator_Bore"] == 45
  182. assert point0["Magnet_Thickness"] == 2
  183. # All variables should be known for SSSR
  184. from app.services.topology_variable_map import is_known_variable
  185. unknown = [k for k in point0.keys() if not is_known_variable(k, "SSSR")]
  186. assert len(unknown) == 0, f"Unknown variables found: {unknown}"
  187. print("PASS: test_plan23_regression")
  188. def main():
  189. tests = [
  190. test_normalize_topology,
  191. test_rfm_to_afm_alias_resolution,
  192. test_template_logical_name_mapping,
  193. test_known_variable_validation,
  194. test_validate_parameters,
  195. test_suggest_alternative,
  196. test_get_known_variables,
  197. test_plan23_regression,
  198. ]
  199. passed = 0
  200. failed = 0
  201. for test in tests:
  202. try:
  203. test()
  204. passed += 1
  205. except Exception as e:
  206. print(f"FAIL: {test.__name__}: {e}")
  207. failed += 1
  208. print(f"\n{'='*50}")
  209. print(f"Results: {passed} passed, {failed} failed, {len(tests)} total")
  210. if failed > 0:
  211. sys.exit(1)
  212. else:
  213. print("ALL TESTS PASSED")
  214. sys.exit(0)
  215. if __name__ == "__main__":
  216. main()