Files
Atomizer/tests/mcp_server/tools/test_model_discovery.py
Claude 96ed53e3d7 feat: Implement Option A - MCP Model Discovery tool
This commit implements the first phase of the MCP server as outlined
in PROJECT_SUMMARY.md Option A: Model Discovery.

New Features:
- Complete .sim file parser (XML-based)
- Expression extraction from .sim and .prt files
- Solution, FEM, materials, loads, constraints extraction
- Structured JSON output for LLM consumption
- Markdown formatting for human-readable output

Implementation Details:
- mcp_server/tools/model_discovery.py: Core parser and discovery logic
  - SimFileParser class: Handles XML parsing of .sim files
  - discover_fea_model(): Main MCP tool function
  - format_discovery_result_for_llm(): Markdown formatter
- mcp_server/tools/__init__.py: Updated to export new functions
- mcp_server/tools/README.md: Complete documentation for MCP tools

Testing & Examples:
- examples/test_bracket.sim: Sample .sim file for testing
- tests/mcp_server/tools/test_model_discovery.py: Comprehensive unit tests
- Manual testing verified: Successfully extracts 4 expressions, solution
  info, mesh data, materials, loads, and constraints

Validation:
- Command-line tool works: python mcp_server/tools/model_discovery.py examples/test_bracket.sim
- Output includes both Markdown and JSON formats
- Error handling for missing files and invalid formats

Next Steps (Phase 2):
- Port optimization engine from P04 Atomizer
- Implement build_optimization_config tool
- Create pluggable result extractor system

References:
- PROJECT_SUMMARY.md: Option A (lines 339-350)
- mcp_server/prompts/system_prompt.md: Model Discovery workflow
2025-11-15 13:23:05 +00:00

212 lines
6.9 KiB
Python

"""
Unit tests for MCP Model Discovery Tool
Tests the .sim file parser and FEA model discovery functionality.
"""
import pytest
from pathlib import Path
import sys
# Add project root to path
project_root = Path(__file__).parent.parent.parent.parent
sys.path.insert(0, str(project_root))
from mcp_server.tools.model_discovery import (
discover_fea_model,
format_discovery_result_for_llm,
SimFileParser
)
class TestSimFileParser:
"""Test the SimFileParser class"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return project_root / "examples" / "test_bracket.sim"
def test_parser_initialization(self, example_sim_path):
"""Test that parser initializes correctly"""
parser = SimFileParser(example_sim_path)
assert parser.sim_path.exists()
assert parser.tree is not None
assert parser.root is not None
def test_parser_file_not_found(self):
"""Test error handling for missing file"""
with pytest.raises(FileNotFoundError):
SimFileParser("/nonexistent/path/file.sim")
def test_parser_invalid_extension(self):
"""Test error handling for non-.sim file"""
with pytest.raises(ValueError):
SimFileParser(project_root / "README.md")
def test_extract_solutions(self, example_sim_path):
"""Test solution extraction"""
parser = SimFileParser(example_sim_path)
solutions = parser.extract_solutions()
assert len(solutions) > 0
assert solutions[0]['name'] == 'Structural Analysis 1'
assert solutions[0]['type'] == 'Static Structural'
assert solutions[0]['solver'] == 'NX Nastran'
def test_extract_expressions(self, example_sim_path):
"""Test expression extraction"""
parser = SimFileParser(example_sim_path)
expressions = parser.extract_expressions()
assert len(expressions) > 0
# Check for expected expressions
expr_names = [e['name'] for e in expressions]
assert 'wall_thickness' in expr_names
assert 'hole_diameter' in expr_names
assert 'rib_spacing' in expr_names
# Check expression values
wall_thickness = next(e for e in expressions if e['name'] == 'wall_thickness')
assert wall_thickness['value'] == '5.0'
assert wall_thickness['units'] == 'mm'
def test_extract_fem_info(self, example_sim_path):
"""Test FEM information extraction"""
parser = SimFileParser(example_sim_path)
fem_info = parser.extract_fem_info()
# Check mesh info
assert 'mesh' in fem_info
assert fem_info['mesh']['name'] == 'Bracket Mesh'
assert fem_info['mesh']['node_count'] == '8234'
assert fem_info['mesh']['element_count'] == '4521'
# Check materials
assert len(fem_info['materials']) > 0
assert fem_info['materials'][0]['name'] == 'Aluminum 6061-T6'
# Check loads
assert len(fem_info['loads']) > 0
assert fem_info['loads'][0]['name'] == 'Applied Force'
# Check constraints
assert len(fem_info['constraints']) > 0
assert fem_info['constraints'][0]['name'] == 'Fixed Support'
class TestDiscoverFEAModel:
"""Test the main discover_fea_model function"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return str(project_root / "examples" / "test_bracket.sim")
def test_successful_discovery(self, example_sim_path):
"""Test successful model discovery"""
result = discover_fea_model(example_sim_path)
assert result['status'] == 'success'
assert result['file_exists'] is True
assert 'solutions' in result
assert 'expressions' in result
assert 'fem_info' in result
assert 'summary' in result
# Check summary statistics
assert result['summary']['solution_count'] >= 1
assert result['summary']['expression_count'] >= 3
def test_file_not_found_error(self):
"""Test error handling for missing file"""
result = discover_fea_model("/nonexistent/file.sim")
assert result['status'] == 'error'
assert result['error_type'] == 'file_not_found'
assert 'message' in result
assert 'suggestion' in result
def test_result_structure(self, example_sim_path):
"""Test that result has expected structure"""
result = discover_fea_model(example_sim_path)
# Check top-level keys
expected_keys = ['status', 'sim_file', 'file_exists', 'solutions',
'expressions', 'fem_info', 'linked_files', 'metadata', 'summary']
for key in expected_keys:
assert key in result, f"Missing key: {key}"
# Check summary keys
expected_summary_keys = ['solution_count', 'expression_count',
'material_count', 'load_count', 'constraint_count']
for key in expected_summary_keys:
assert key in result['summary'], f"Missing summary key: {key}"
class TestFormatDiscoveryResult:
"""Test the Markdown formatting function"""
@pytest.fixture
def example_sim_path(self):
"""Path to example .sim file"""
return str(project_root / "examples" / "test_bracket.sim")
def test_format_success_result(self, example_sim_path):
"""Test formatting of successful discovery"""
result = discover_fea_model(example_sim_path)
formatted = format_discovery_result_for_llm(result)
assert isinstance(formatted, str)
assert '# FEA Model Analysis' in formatted
assert 'Solutions' in formatted
assert 'Expressions' in formatted
assert 'wall_thickness' in formatted
def test_format_error_result(self):
"""Test formatting of error result"""
result = discover_fea_model("/nonexistent/file.sim")
formatted = format_discovery_result_for_llm(result)
assert isinstance(formatted, str)
assert '' in formatted or 'Error' in formatted
assert result['message'] in formatted
# Integration test
def test_end_to_end_workflow():
"""
Test the complete workflow:
1. Discover model
2. Format for LLM
3. Verify output is useful
"""
example_sim = str(project_root / "examples" / "test_bracket.sim")
# Step 1: Discover
result = discover_fea_model(example_sim)
assert result['status'] == 'success'
# Step 2: Format
formatted = format_discovery_result_for_llm(result)
assert len(formatted) > 100 # Should be substantial output
# Step 3: Verify key information is present
assert 'wall_thickness' in formatted
assert 'Aluminum' in formatted
assert 'Static Structural' in formatted
print("\n" + "="*60)
print("INTEGRATION TEST OUTPUT:")
print("="*60)
print(formatted)
print("="*60)
if __name__ == "__main__":
# Run tests with pytest
pytest.main([__file__, "-v", "-s"])