Module Integration ================== This guide explains how to add new solver modules to pyhnhtools following the established architecture pattern. Architecture Pattern -------------------- All modules follow a three-layer integration pattern: .. code-block:: text qt_gui.py (Generic GUI) ↓ {module}_gui.py (Integration Layer) ↓ {module}_adapter.py (Data Adapter) ↓ {module}/ (Core Solver) Layer Responsibilities ---------------------- **Layer 1: qt_gui.py (Generic GUI Framework)** - Window layout and widget management - Tab organization - User interaction routing - Common visualization patterns **Does NOT contain**: Solver-specific logic **Layer 2: {module}_gui.py (Integration Interface)** - Module workflow orchestration - Converting GUI inputs to solver inputs - Converting solver outputs to GUI displays - Module-specific plotting and validation Example: ``std_step_1d_gui.py`` contains ``StandardStep1DInterface`` class **Layer 3: {module}_adapter.py (Data Adapter)** - Model serialization (JSON, CSV) - Data format transformation - File I/O operations **Does NOT contain**: GUI knowledge or solver details **Layer 4: {module}/ (Core Solver)** - Pure computation - Algorithm implementation - Physics/mathematics **Does NOT contain**: GUI or I/O logic Step-by-Step Implementation ---------------------------- **Step 1: Create the Integration Interface** Copy the template: .. code-block:: bash cp src/pyhnhtools/gui/std_step_1d_gui.py src/pyhnhtools/gui/culverts_gui.py Rename the class: .. code-block:: python # Change from: class StandardStep1DInterface: # To: class CulvertsInterface: Adapt the methods to your workflow: .. code-block:: python class CulvertsInterface: def __init__(self, gui_widget): self.gui = gui_widget def setup(self): """Initialize the culverts interface.""" try: from pyhnhtools.culverts import analyze_inlet self.analyze_inlet_func = analyze_inlet return True except ImportError: return False def create_config(self, diameter, length, manning_n): """Create configuration from parameters.""" config = CulvertConfig(diameter, length, manning_n) self.config = config return config def run_analysis(self): """Execute culvert analysis.""" results = self.analyze_inlet_func(self.config) self._display_results(results) return True **Step 2: Create or Adapt the Data Adapter** If your module doesn't have an adapter, create one: .. code-block:: python # File: src/pyhnhtools/culverts_adapter.py import json from pyhnhtools.culverts import CulvertConfig def create_config(params): """Create configuration from dict.""" return CulvertConfig(**params) def run_analysis(config): """Run analysis and return results.""" # Call your core solver results = config.analyze() return results def save_results(results, filepath): """Save results to JSON.""" with open(filepath, 'w') as f: json.dump(results.to_dict(), f) **Step 3: Register in qt_gui.py** Add import: .. code-block:: python from pyhnhtools.gui.culverts_gui import CulvertsInterface Instantiate in BackwaterWidget: .. code-block:: python class BackwaterWidget(QMainWindow): def __init__(self): # ... existing code ... # Initialize culverts interface self.culverts_interface = CulvertsInterface(self) self.culverts_interface.setup() Create tab: .. code-block:: python def create_culverts_tab(self): tab = QWidget() layout = QVBoxLayout() # Add culvert-specific controls # (diameter spinbox, length input, Manning's n, etc.) self.tabs.addTab(tab, "Culvert Analysis") Wire button clicks: .. code-block:: python def on_run_clicked(self): current_tab = self.tabs.currentIndex() if current_tab == 1: # Culverts tab self.culverts_interface.run_analysis() Template Interface Class ------------------------ Use this as a starting point: .. code-block:: python from typing import Optional, Dict, List import logging logger = logging.getLogger(__name__) class {Module}Interface: """Integration between GUI and {Module} solver.""" def __init__(self, gui_widget): self.gui = gui_widget self.model = None self.results = None def setup(self) -> bool: """Initialize interface. Call once at startup.""" try: # Import your module from pyhnhtools.{module} import YourClass self.YourClass = YourClass logger.info(f"{module} interface initialized") return True except ImportError as e: logger.error(f"Failed to initialize: {e}") self.gui.text_out.setText(f"Error: {e}") return False def load_model(self, filepath: str) -> bool: """Load model from file.""" try: # Load and parse self.model = load_from_file(filepath) self._update_display() return True except Exception as e: logger.error(f"Load failed: {e}") self.gui.text_out.setText(f"Error: {e}") return False def new_model(self) -> bool: """Create new empty model.""" try: self.model = self.YourClass() self.gui.text_out.setText("New model created") return True except Exception as e: logger.error(f"Create failed: {e}") return False def run_solver(self) -> bool: """Execute solver.""" if not self.validate_model(): return False try: self.results = self.model.solve() self._display_results() return True except Exception as e: logger.error(f"Solver failed: {e}") return False def validate_model(self) -> bool: """Check model validity.""" if not self.model: self.gui.text_out.setText("No model") return False # Add validation logic return True def save_model(self, filepath: str) -> bool: """Save model to file.""" try: save_to_file(self.model, filepath) return True except Exception as e: logger.error(f"Save failed: {e}") return False def _display_results(self): """Update GUI with results.""" if not self.results: return # Update GUI elements self.gui.text_out.setText("Results ready") Design Patterns --------------- **Pattern 1: Model Loading** .. code-block:: python def load_model(self, filepath): with open(filepath) as f: data = json.load(f) self.model = Model(**data) self._update_display() **Pattern 2: Error Handling** .. code-block:: python try: result = self.solver.compute() except ValueError as e: logger.error(f"Computation error: {e}") self.gui.text_out.setText(f"Error: {e}") return False **Pattern 3: Workflow State** .. code-block:: python self.model = None # Not loaded yet self.results = None # Not computed yet self.interface = None # Not initialized **Pattern 4: GUI Updates** .. code-block:: python # Access GUI through reference self.gui.table_results.setRowCount(10) self.gui.text_out.setText("Analysis complete") self.gui.plot_widget.update() Testing Your Module ------------------- **Unit Tests** .. code-block:: python # tests/test_culverts_gui.py def test_interface_setup(): widget = MockWidget() interface = CulvertsInterface(widget) assert interface.setup() == True def test_create_config(): widget = MockWidget() interface = CulvertsInterface(widget) config = interface.create_config(diameter=24, length=100) assert config.diameter == 24 **Integration Tests** .. code-block:: bash # Test loading GUI with new module python -m pyhnhtools.gui.app # Check that new tab appears # Check that buttons work # Check that solver runs Common Issues ------------- **ImportError: No module named {module}** Ensure module is installed: .. code-block:: bash pip install -e . **GUI doesn't show new tab** Check: - Tab creation called in BackwaterWidget.__init__() - Tab is added to self.tabs - Index correct in on_run_clicked() **Solver doesn't run** Check: - interface.setup() returned True - validate_model() passes - All required data set **Results don't display** Check: - _display_results() called after solver - GUI element references correct - Results object has expected fields File Checklist -------------- When adding a new module: - [ ] {module}_gui.py created - [ ] {module}_adapter.py created (if needed) - [ ] qt_gui.py updated with imports - [ ] qt_gui.py tab creation added - [ ] Button click routing updated - [ ] Tests added - [ ] Documentation updated - [ ] CHANGELOG.md updated - [ ] Git commit created Documentation Updates --------------------- Add documentation for your module: .. code-block:: rst # docs/user_guide/{module}.rst {Module} Analysis ================= .. automodule:: pyhnhtools.gui.{module}_gui :members: Usage ----- .. code-block:: python # Your usage example Update index.rst: .. code-block:: rst .. toctree:: :maxdepth: 2 user_guide/{module} See Also -------- - :doc:`templates` - Template reference files - :doc:`contributing` - Contributing guidelines - :doc:`../api/core` - API reference