Skip to content

Fixit Module

Automated geometry repair for HEC-RAS models.

Overview

The fixit module provides automated fix capabilities for common HEC-RAS geometry issues. It complements the check module by providing repair functionality for detected issues.

Engineering Review Required

All fixes should be reviewed by a licensed professional engineer before accepting changes to production models. The visualization outputs provide an audit trail showing original and fixed configurations.

Supported Fixes

Fix Type Method Description
Blocked Obstruction Overlaps fix_blocked_obstructions() Resolves overlapping obstructions using max elevation envelope

Quick Start

Python
from ras_commander import RasFixit

# Detect overlaps (non-destructive)
results = RasFixit.detect_obstruction_overlaps("model.g04")
print(f"Found {results.total_xs_fixed} cross sections with overlaps")

# Fix with visualization for engineering review
results = RasFixit.fix_blocked_obstructions(
    "model.g04",
    backup=True,      # Create timestamped backup
    visualize=True    # Generate before/after PNGs
)
print(f"Fixed {results.total_xs_fixed} cross sections")
print(f"Visualizations: {results.visualization_folder}")

RasFixit

RasFixit

RasFixit - Automated Geometry Repair for HEC-RAS Models.

This module provides automated fix capabilities for common HEC-RAS geometry issues. All methods are static and follow ras-commander conventions.

Supported Fixes

General-purpose (any model version): - Blocked Obstruction Overlaps: Resolves overlapping obstructions using max elevation - HTAB Starting Elevation: Fixes starting_el < invert issues

Version upgrade (HEC-RAS 4.x → 6.x): - Bank Station Normalization: Re-writes sta/elev with normalized fixed-width formatting - Ineffective Flow Areas: Replaces right_station=0 sentinels with actual rightmost station - Manning's n Repair: Reconstructs missing data or snaps misaligned breakpoints to banks

Engineering Review Requirements

All fixes generated by RasFixit modify hydraulic model geometry. Results should be reviewed by a licensed professional engineer before accepting changes to production models.

The visualization output provides an audit trail showing: - Original obstruction configuration - Fixed obstruction configuration - Algorithm decisions made

Example

from ras_commander import RasFixit results = RasFixit.fix_blocked_obstructions("model.g01", visualize=True) print(f"Fixed {results.total_xs_fixed} cross sections")

Method Details

fix_blocked_obstructions

Main method for fixing overlapping blocked obstructions.

Parameters:

Parameter Type Default Description
geom_path str, Path required Path to HEC-RAS geometry file (.g##)
backup bool True Create timestamped backup before modification
visualize bool False Generate before/after PNG comparisons
dry_run bool False Detect issues without modifying file

Returns: FixResults object containing:

  • total_xs_checked: Number of cross sections examined
  • total_xs_fixed: Number of cross sections modified
  • backup_path: Path to backup file (if created)
  • visualization_folder: Path to PNG visualizations (if generated)
  • messages: List of FixMessage objects with detailed fix information

Example:

Python
from ras_commander import RasFixit

# Fix with all options
results = RasFixit.fix_blocked_obstructions(
    "BaldEagle.g01",
    backup=True,
    visualize=True
)

# Examine results
for msg in results.messages:
    print(f"RS {msg.station}: {msg.original_count} -> {msg.fixed_count} obstructions")
    print(f"  Visualization: {msg.visualization_path}")

# Export to DataFrame
df = results.to_dataframe()
df.to_csv("fix_report.csv")

Result Classes

FixResults

FixResults dataclass

Container for all fix operation results.

Provides aggregate statistics and detailed messages for each fixed cross section.

Attributes:

Name Type Description
messages List[FixMessage]

List of FixMessage objects for each processed cross section

total_xs_checked int

Total number of cross sections examined

total_xs_fixed int

Number of cross sections that were modified

backup_path Optional[Path]

Path to backup file (if created)

visualization_folder Optional[Path]

Path to folder containing PNG visualizations

statistics Dict[str, Any]

Additional statistics dictionary

Example

results = RasFixit.fix_blocked_obstructions("model.g01") print(f"Fixed {results.total_xs_fixed} of {results.total_xs_checked} cross sections") df = results.to_dataframe()

Source code in ras_commander/fixit/results.py
Python
@dataclass
class FixResults:
    """
    Container for all fix operation results.

    Provides aggregate statistics and detailed messages for each fixed cross section.

    Attributes:
        messages: List of FixMessage objects for each processed cross section
        total_xs_checked: Total number of cross sections examined
        total_xs_fixed: Number of cross sections that were modified
        backup_path: Path to backup file (if created)
        visualization_folder: Path to folder containing PNG visualizations
        statistics: Additional statistics dictionary

    Example:
        >>> results = RasFixit.fix_blocked_obstructions("model.g01")
        >>> print(f"Fixed {results.total_xs_fixed} of {results.total_xs_checked} cross sections")
        >>> df = results.to_dataframe()
    """
    messages: List[FixMessage] = field(default_factory=list)
    total_xs_checked: int = 0
    total_xs_fixed: int = 0
    backup_path: Optional[Path] = None
    visualization_folder: Optional[Path] = None
    statistics: Dict[str, Any] = field(default_factory=dict)

    def to_dataframe(self) -> 'pd.DataFrame':
        """
        Convert all messages to a pandas DataFrame.

        Returns:
            DataFrame with one row per fix message.
        """
        import pandas as pd
        if not self.messages:
            return pd.DataFrame()
        return pd.DataFrame([m.to_dict() for m in self.messages])

    def get_fixed_count(self) -> int:
        """
        Count cross sections that were actually fixed.

        Returns:
            Number of messages with action other than NO_ACTION.
        """
        return len([m for m in self.messages if m.action != FixAction.NO_ACTION])

    def get_messages_by_action(self, action: FixAction) -> List[FixMessage]:
        """
        Filter messages by action type.

        Args:
            action: FixAction enum value to filter by.

        Returns:
            List of FixMessage objects matching the action.
        """
        return [m for m in self.messages if m.action == action]

    def __repr__(self) -> str:
        return (f"FixResults(checked={self.total_xs_checked}, "
                f"fixed={self.total_xs_fixed}, "
                f"messages={len(self.messages)})")

to_dataframe

Python
to_dataframe() -> pd.DataFrame

Convert all messages to a pandas DataFrame.

Returns:

Type Description
DataFrame

DataFrame with one row per fix message.

Source code in ras_commander/fixit/results.py
Python
def to_dataframe(self) -> 'pd.DataFrame':
    """
    Convert all messages to a pandas DataFrame.

    Returns:
        DataFrame with one row per fix message.
    """
    import pandas as pd
    if not self.messages:
        return pd.DataFrame()
    return pd.DataFrame([m.to_dict() for m in self.messages])

get_fixed_count

Python
get_fixed_count() -> int

Count cross sections that were actually fixed.

Returns:

Type Description
int

Number of messages with action other than NO_ACTION.

Source code in ras_commander/fixit/results.py
Python
def get_fixed_count(self) -> int:
    """
    Count cross sections that were actually fixed.

    Returns:
        Number of messages with action other than NO_ACTION.
    """
    return len([m for m in self.messages if m.action != FixAction.NO_ACTION])

get_messages_by_action

Python
get_messages_by_action(action: FixAction) -> List[FixMessage]

Filter messages by action type.

Parameters:

Name Type Description Default
action FixAction

FixAction enum value to filter by.

required

Returns:

Type Description
List[FixMessage]

List of FixMessage objects matching the action.

Source code in ras_commander/fixit/results.py
Python
def get_messages_by_action(self, action: FixAction) -> List[FixMessage]:
    """
    Filter messages by action type.

    Args:
        action: FixAction enum value to filter by.

    Returns:
        List of FixMessage objects matching the action.
    """
    return [m for m in self.messages if m.action == action]

FixMessage

FixMessage dataclass

A single fix action message for a cross section.

Attributes:

Name Type Description
message_id str

Unique identifier for the fix type (e.g., "FX_BO_01")

fix_type str

Category of fix (e.g., "OBSTRUCTION", "INEFFECTIVE", "BANK")

river str

River name (if available)

reach str

Reach name (if available)

station str

River station identifier

action FixAction

Type of fix action taken

message str

Human-readable description of the fix

original_count int

Number of obstructions before fix

fixed_count int

Number of obstructions after fix

original_data List[Tuple[float, float, float]]

List of (start_sta, end_sta, elevation) tuples before fix

fixed_data List[Tuple[float, float, float]]

List of (start_sta, end_sta, elevation) tuples after fix

visualization_path Optional[Path]

Path to before/after PNG visualization (if generated)

Source code in ras_commander/fixit/results.py
Python
@dataclass
class FixMessage:
    """
    A single fix action message for a cross section.

    Attributes:
        message_id: Unique identifier for the fix type (e.g., "FX_BO_01")
        fix_type: Category of fix (e.g., "OBSTRUCTION", "INEFFECTIVE", "BANK")
        river: River name (if available)
        reach: Reach name (if available)
        station: River station identifier
        action: Type of fix action taken
        message: Human-readable description of the fix
        original_count: Number of obstructions before fix
        fixed_count: Number of obstructions after fix
        original_data: List of (start_sta, end_sta, elevation) tuples before fix
        fixed_data: List of (start_sta, end_sta, elevation) tuples after fix
        visualization_path: Path to before/after PNG visualization (if generated)
    """
    message_id: str
    fix_type: str
    river: str = ""
    reach: str = ""
    station: str = ""
    action: FixAction = FixAction.NO_ACTION
    message: str = ""
    original_count: int = 0
    fixed_count: int = 0
    original_data: List[Tuple[float, float, float]] = field(default_factory=list)
    fixed_data: List[Tuple[float, float, float]] = field(default_factory=list)
    visualization_path: Optional[Path] = None

    def to_dict(self) -> Dict[str, Any]:
        """
        Convert to dictionary for DataFrame creation.

        Returns:
            Dictionary representation of the fix message.
        """
        return {
            'message_id': self.message_id,
            'fix_type': self.fix_type,
            'river': self.river,
            'reach': self.reach,
            'station': self.station,
            'action': self.action.value,
            'message': self.message,
            'original_count': self.original_count,
            'fixed_count': self.fixed_count,
            'visualization': str(self.visualization_path) if self.visualization_path else None
        }

to_dict

Python
to_dict() -> Dict[str, Any]

Convert to dictionary for DataFrame creation.

Returns:

Type Description
Dict[str, Any]

Dictionary representation of the fix message.

Source code in ras_commander/fixit/results.py
Python
def to_dict(self) -> Dict[str, Any]:
    """
    Convert to dictionary for DataFrame creation.

    Returns:
        Dictionary representation of the fix message.
    """
    return {
        'message_id': self.message_id,
        'fix_type': self.fix_type,
        'river': self.river,
        'reach': self.reach,
        'station': self.station,
        'action': self.action.value,
        'message': self.message,
        'original_count': self.original_count,
        'fixed_count': self.fixed_count,
        'visualization': str(self.visualization_path) if self.visualization_path else None
    }

FixAction

FixAction

Bases: Enum

Type of fix action taken.

Source code in ras_commander/fixit/results.py
Python
class FixAction(Enum):
    """Type of fix action taken."""
    OVERLAP_RESOLVED = "OVERLAP_RESOLVED"      # Overlapping obstructions merged via elevation envelope
    GAP_INSERTED = "GAP_INSERTED"              # 0.02-unit gap added for adjacency compliance
    SEGMENT_MERGED = "SEGMENT_MERGED"          # Same-elevation segments combined
    NO_ACTION = "NO_ACTION"                    # No fix needed (no issues detected)
    # HTAB (Hydraulic Table) fix actions
    HTAB_STARTING_EL_FIXED = "HTAB_STARTING_EL_FIXED"  # Starting elevation raised to >= invert
    HTAB_PARAMS_SET = "HTAB_PARAMS_SET"        # HTAB parameters written/updated
    # Bank station fix actions (version upgrade)
    BANK_STATION_REWRITTEN = "BANK_STATION_REWRITTEN"  # Sta/elev rewritten with bank interpolation
    # Manning's n fix actions (version upgrade)
    MANNINGS_N_REWRITTEN = "MANNINGS_N_REWRITTEN"      # Manning's n data renormalized
    # Ineffective flow area fix actions (version upgrade)
    INEFFECTIVE_FIXED = "INEFFECTIVE_FIXED"            # Malformed ineffective area corrected

Log Parser

The log_parser module provides utilities for detecting obstruction errors from HEC-RAS compute logs.

Python
from ras_commander.fixit import log_parser

# Parse log for errors
errors = log_parser.detect_obstruction_errors(log_content)

# Find geometry files
geom_files = log_parser.find_geometry_files_in_directory(project_dir)

# Generate report
print(log_parser.generate_error_report(errors))

Functions

Function Description
detect_obstruction_errors(log_content) Parse log text, return list of error dicts
extract_geometry_files(log_content, project_dir) Find geometry file references in log
find_geometry_files_in_directory(directory) Scan directory for .g## files
has_obstruction_errors(log_file_path) Quick boolean check for errors
generate_error_report(errors) Format errors for human review
extract_cross_section_ids(log_content) Get list of affected river stations

Automated Workflow Example

Python
from ras_commander import RasFixit
from ras_commander.fixit import log_parser

# Step 1: Check log for errors
if log_parser.has_obstruction_errors("compute.log"):

    # Step 2: Parse errors
    with open("compute.log", "r") as f:
        errors = log_parser.detect_obstruction_errors(f.read())

    print(f"Found {len(errors)} obstruction errors")

    # Step 3: Find and fix geometry files
    geom_files = log_parser.find_geometry_files_in_directory(project_dir)

    for geom_path in geom_files:
        results = RasFixit.fix_blocked_obstructions(
            geom_path,
            visualize=True
        )
        print(f"Fixed {results.total_xs_fixed} cross sections in {geom_path}")

Algorithm Details

Elevation Envelope

The core algorithm for fixing overlapping obstructions:

  1. Collect Critical Stations: Extract all start/end stations from obstructions
  2. Find Max Elevation: For each segment between stations, use the maximum elevation from all overlapping obstructions (hydraulically conservative)
  3. Merge Segments: Combine adjacent segments with identical elevations
  4. Insert Gaps: Add 0.02-unit gaps where different elevations meet (HEC-RAS requirement)
Text Only
Original:  [100-120@elev5, 110-130@elev3]  (overlap 110-120)

Step 1 - Critical stations: [100, 110, 120, 130]

Step 2 - Max elevation per segment:
  100-110: elev 5 (only covered by first obstruction)
  110-120: elev 5 (max of 5 and 3)
  120-130: elev 3 (only covered by second obstruction)

Step 3 - Merge same-elevation:
  100-120: elev 5 (merged)
  120-130: elev 3

Step 4 - Insert gap:
  100-120: elev 5
  120.02-130: elev 3  (0.02 gap added)

Result: [100-120@elev5, 120.02-130@elev3]

Why Max Elevation?

Using maximum elevation in overlap zones is hydraulically conservative:

  • Blocked obstructions represent areas where flow is completely blocked up to the specified elevation
  • Using the maximum ensures we preserve the most restrictive flow condition
  • This prevents underestimating flood impacts

Gap Insertion

HEC-RAS requires a minimum separation between adjacent obstructions:

  • Touching obstructions (e.g., end=100.0, start=100.0) cause errors
  • The algorithm inserts 0.02-unit gaps where obstructions would otherwise touch
  • This is the minimum safe separation that preserves hydraulic behavior

Integration with Check Module

The fixit module complements the check module:

Python
from ras_commander import RasCheck, RasFixit

# Detect issues with RasCheck (operates on HDF files)
check_results = RasCheck.check_xs(geom_hdf)
obstruction_issues = [m for m in check_results.messages
                      if m.message_id.startswith('XS_BO')]

if obstruction_issues:
    print(f"Found {len(obstruction_issues)} obstruction issues")

    # Fix with RasFixit (operates on plain text geometry files)
    fix_results = RasFixit.fix_blocked_obstructions(
        "model.g01",
        visualize=True
    )
    print(f"Fixed {fix_results.total_xs_fixed} cross sections")
Module Input Purpose
RasCheck HDF files (.p##.hdf) Detect issues during results review
RasFixit Geometry files (.g##) Repair issues in source geometry

Visualization Output

When visualize=True, the module generates PNG files showing:

  • Top Panel: Original obstructions (with overlaps) using 'viridis' colormap
  • Bottom Panel: Fixed obstructions (elevation envelope) using 'plasma' colormap

Files are saved to: {ProjectName}_g{##}_Obstructions_Fixed/RS_{station}.png

These visualizations are critical for engineering review - they show exactly what changes were made and why.

Example Project

The repository includes the HCFCD M3 Model A120-00-00 (Harris County Flood Control District) as a real-world example with blocked obstruction issues:

  • Location: examples/example_projects/A120-00-00/
  • Geometry files: A120_00_00.g01, A120_00_00.g02
  • Cross sections: 91 total, 15 with overlapping obstructions
  • Source: examples/data/A120-00-00.zip

An example notebook for blocked obstructions will be added in a future release; the API above covers the current workflow end-to-end.

CLB Engineering Corporation  ·  LLM Forward Engineering
RAS Commander is a free and open-source project maintained by CLB Engineering Corporation. For agencies and firms seeking to modernize H&H workflows with LLM Forward approaches, contact CLB to partner with the engineers who wrote the automation.