"""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()