| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- """Tests for topology-aware Motor-CAD variable name mapping.
- Covers:
- - Topology normalization
- - RFM -> AFM alias resolution (the plan 23 bug)
- - Known variable validation
- - Parameter validation with suggestions
- - Boundary cases (empty, unknown topology, unknown variable)
- Note (2026-09-03): AFM target names were corrected to the MARS-verified
- names measured live via pymotorcad on MARS-12S10P_SSSR:
- Stator_Outer_Diameter -> Stator_Lam_Dia, Stator_Inner_Diameter -> Stator_Bore,
- Outer_Rotor_Diameter -> RotorOuterDiameter, Rotor_Back_Iron_Thickness ->
- Back_Iron_Thickness. Assertions below use the verified names.
- Run: python scripts/test_topology_variable_map.py
- Exit 0 = PASS, non-zero = FAIL.
- """
- import sys
- import os
- # Add backend to path
- sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "web", "backend"))
- from app.services.topology_variable_map import (
- TOPOLOGY_SSSR,
- TOPOLOGY_AFIR,
- TOPOLOGY_RFM,
- normalize_topology,
- resolve_variable,
- is_known_variable,
- validate_parameters,
- suggest_alternative,
- get_known_variables,
- )
- def test_normalize_topology():
- """Test topology string normalization."""
- assert normalize_topology("SSSR") == TOPOLOGY_SSSR
- assert normalize_topology("sssr") == TOPOLOGY_SSSR
- assert normalize_topology("AFIR") == TOPOLOGY_AFIR
- assert normalize_topology("RFM") == TOPOLOGY_RFM
- assert normalize_topology("AFM") == TOPOLOGY_SSSR
- assert normalize_topology("axial") == TOPOLOGY_SSSR
- assert normalize_topology("radial") == TOPOLOGY_RFM
- assert normalize_topology(None) == TOPOLOGY_SSSR
- assert normalize_topology("") == TOPOLOGY_SSSR
- assert normalize_topology("UNKNOWN") == TOPOLOGY_SSSR # default
- print("PASS: test_normalize_topology")
- def test_rfm_to_afm_alias_resolution():
- """Test that RFM variable names are mapped to AFM names on SSSR topology.
- This is the core fix for plan 23: Stator_Lam_Outer_Dia (RFM) must be
- resolved to Stator_Lam_Dia (MARS-verified AFM name) when topology is SSSR.
- """
- # RFM names on SSSR topology -> AFM names
- resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR)
- assert resolved == "Stator_Lam_Dia", f"Expected Stator_Lam_Dia, got {resolved}"
- assert was_alias is True
- resolved, was_alias = resolve_variable("Stator_Lam_Inner_Dia", TOPOLOGY_SSSR)
- assert resolved == "Stator_Bore"
- assert was_alias is True
- resolved, was_alias = resolve_variable("Stator_Yoke_Width", TOPOLOGY_SSSR)
- assert resolved == "Stator_Yoke_Thickness"
- assert was_alias is True
- # Same RFM names on RFM topology -> unchanged
- resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_RFM)
- assert resolved == "Stator_Lam_Outer_Dia"
- assert was_alias is False
- # AFIR topology also maps to AFM names
- resolved, was_alias = resolve_variable("Stator_Lam_Outer_Dia", TOPOLOGY_AFIR)
- assert resolved == "Stator_Lam_Dia"
- assert was_alias is True
- print("PASS: test_rfm_to_afm_alias_resolution")
- def test_template_logical_name_mapping():
- """Test that template logical names map to Motor-CAD actual names."""
- resolved, was_alias = resolve_variable("Number_of_Slots", TOPOLOGY_SSSR)
- assert resolved == "Slot_Number"
- assert was_alias is True
- resolved, was_alias = resolve_variable("Number_of_Poles", TOPOLOGY_SSSR)
- assert resolved == "Pole_Number"
- assert was_alias is True
- resolved, was_alias = resolve_variable("DC_Link_Voltage", TOPOLOGY_SSSR)
- assert resolved == "DCBusVoltage"
- assert was_alias is True
- resolved, was_alias = resolve_variable("Turns_per_Coil", TOPOLOGY_SSSR)
- assert resolved == "ConductorsPerSlot"
- assert was_alias is True
- print("PASS: test_template_logical_name_mapping")
- def test_known_variable_validation():
- """Test known variable detection per topology."""
- # AFM variables known on SSSR (MARS-verified names)
- assert is_known_variable("Stator_Lam_Dia", TOPOLOGY_SSSR) is True
- assert is_known_variable("Stator_Bore", TOPOLOGY_SSSR) is True
- assert is_known_variable("RotorOuterDiameter", TOPOLOGY_SSSR) is True
- assert is_known_variable("Airgap", TOPOLOGY_SSSR) is True
- assert is_known_variable("Slot_Number", TOPOLOGY_SSSR) is True
- assert is_known_variable("Magnet_Thickness", TOPOLOGY_SSSR) is True
- # Deprecated wrong names (radial-template naming) are now UNKNOWN
- assert is_known_variable("Stator_Outer_Diameter", TOPOLOGY_SSSR) is False
- assert is_known_variable("Stator_Inner_Diameter", TOPOLOGY_SSSR) is False
- assert is_known_variable("Outer_Rotor_Diameter", TOPOLOGY_SSSR) is False
- # RFM variable NOT known on SSSR
- assert is_known_variable("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR) is False
- # Unknown variable
- assert is_known_variable("NonExistent_Var", TOPOLOGY_SSSR) is False
- # Empty / None
- assert is_known_variable("", TOPOLOGY_SSSR) is False
- print("PASS: test_known_variable_validation")
- def test_validate_parameters():
- """Test full parameter validation with classification."""
- params = {
- "Stator_Lam_Dia": 100.0, # known AFM (MARS-verified)
- "Airgap": 1.0, # known
- "Stator_Lam_Outer_Dia": 80.0, # RFM alias - should be resolved
- "NonExistent": 42.0, # unknown
- }
- result = validate_parameters(params, TOPOLOGY_SSSR)
- # RFM alias should be resolved to the MARS-verified AFM name
- resolved_names = [r[0] for r in result["valid"]]
- assert "Stator_Lam_Dia" in resolved_names
- assert "Airgap" in resolved_names
- assert "Stator_Lam_Outer_Dia" not in resolved_names # should be resolved away
- # Unknown variable should be flagged
- unknown_names = [r[0] for r in result["unknown"]]
- assert "NonExistent" in unknown_names
- # resolved_params should have all parameters with resolved names
- assert "Stator_Lam_Dia" in result["resolved_params"]
- assert "NonExistent" in result["resolved_params"]
- print("PASS: test_validate_parameters")
- def test_suggest_alternative():
- """Test suggestion of close variable names."""
- suggestion = suggest_alternative("Stator_Lam_Outer_Dia", TOPOLOGY_SSSR)
- assert suggestion is not None
- assert "Stator" in suggestion
- assert "Lam" in suggestion or "Dia" in suggestion
- # Completely unrelated name should return None
- suggestion = suggest_alternative("xyz_abc_123", TOPOLOGY_SSSR)
- # May or may not return something, just ensure no crash
- assert suggestion is None or isinstance(suggestion, str)
- print("PASS: test_suggest_alternative")
- def test_get_known_variables():
- """Test retrieval of known variable set."""
- sssr_vars = get_known_variables(TOPOLOGY_SSSR)
- assert isinstance(sssr_vars, set)
- assert len(sssr_vars) > 30 # should have substantial coverage
- assert "Stator_Lam_Dia" in sssr_vars
- assert "Airgap" in sssr_vars
- # AFIR should share SSSR variables
- afir_vars = get_known_variables(TOPOLOGY_AFIR)
- assert "Stator_Lam_Dia" in afir_vars
- print("PASS: test_get_known_variables")
- def test_plan23_regression():
- """Regression test for the exact plan 23 failure scenario.
- Plan 23 had scan variables:
- - Magnet_Thickness (valid)
- - Stator_Lam_Outer_Dia (RFM name -> should map to Stator_Lam_Dia)
- - Stator_Lam_Inner_Dia (RFM name -> should map to Stator_Bore)
- After expansion, NO Stator_Lam_* names should remain, and all variables
- should be known for SSSR topology.
- """
- from app.routers.plans import _expand_plan_to_parameters
- plan_data = {
- "topology": "SSSR",
- "fixed_params": [
- {"name": "Airgap", "value": 1, "source": "user"},
- {"name": "Number_of_Slots", "value": 12, "source": "user"},
- {"name": "Number_of_Poles", "value": 10, "source": "user"},
- ],
- "variables": [
- {"name": "Magnet_Thickness", "values": [2, 3, 4, 5]},
- {"name": "Stator_Lam_Outer_Dia", "values": [80, 85, 90, 95, 100]},
- {"name": "Stator_Lam_Inner_Dia", "values": [45, 50, 55, 60]},
- ],
- }
- parameters = _expand_plan_to_parameters(plan_data)
- assert len(parameters) == 80 # 4 * 5 * 4
- # Check first point
- point0 = parameters[0]
- # RFM names should NOT be present
- assert "Stator_Lam_Outer_Dia" not in point0, "RFM name Stator_Lam_Outer_Dia should be resolved"
- assert "Stator_Lam_Inner_Dia" not in point0, "RFM name Stator_Lam_Inner_Dia should be resolved"
- # MARS-verified AFM names SHOULD be present
- assert "Stator_Lam_Dia" in point0, "AFM name Stator_Lam_Dia should be present"
- assert "Stator_Bore" in point0, "AFM name Stator_Bore should be present"
- # Values should be correct
- assert point0["Stator_Lam_Dia"] == 80
- assert point0["Stator_Bore"] == 45
- assert point0["Magnet_Thickness"] == 2
- # All variables should be known for SSSR
- from app.services.topology_variable_map import is_known_variable
- unknown = [k for k in point0.keys() if not is_known_variable(k, "SSSR")]
- assert len(unknown) == 0, f"Unknown variables found: {unknown}"
- print("PASS: test_plan23_regression")
- def main():
- tests = [
- test_normalize_topology,
- test_rfm_to_afm_alias_resolution,
- test_template_logical_name_mapping,
- test_known_variable_validation,
- test_validate_parameters,
- test_suggest_alternative,
- test_get_known_variables,
- test_plan23_regression,
- ]
- passed = 0
- failed = 0
- for test in tests:
- try:
- test()
- passed += 1
- except Exception as e:
- print(f"FAIL: {test.__name__}: {e}")
- failed += 1
- print(f"\n{'='*50}")
- print(f"Results: {passed} passed, {failed} failed, {len(tests)} total")
- if failed > 0:
- sys.exit(1)
- else:
- print("ALL TESTS PASSED")
- sys.exit(0)
- if __name__ == "__main__":
- main()
|