Skip to content

Using eBFE Models: Spring Creek 2D Analysis

This notebook demonstrates working with FEMA eBFE/BLE models using the Spring Creek (12040102) study area.

The Problem: eBFE Models Are Hard to Use Directly

FEMA provides valuable BLE models, but the delivered archives often separate assets in ways that break automated use:

  1. Output separated: HDF files may be outside the project folder, so HEC-RAS and analysis tools cannot find them reliably.
  2. Terrain misplaced: Terrain folders may be outside the project or referenced through stale .rasmap paths.
  3. Absolute DSS paths: DSS references may point to the producer's original machine, causing GUI popups or automation failures.

With RasEbfeModels: RasEbfeModels.organize_model("spring-creek") normalizes the archive into the shared delivery format and records what was repaired.

Saved-run interpretation: This notebook validates that the organized project can be initialized and inspected. Expensive geometry-preprocessor validation is opt-in, and result plotting is skipped if the delivered Spring.p01.hdf is compute-message-only rather than a full results HDF.

Prerequisites

Automatic Download: This notebook will automatically download Spring Creek Models.zip (9.7 GB) from the eBFE S3 bucket if not already present.

Download Details: - Size: 9.7 GB - Source: FEMA eBFE S3 bucket - Time: ~10-20 minutes depending on connection speed - Disk Space: ~20 GB required (zip + extracted files)

Important: Do not manually reorganize the eBFE files for this example; let RasEbfeModels.organize_model("spring-creek") apply the standardized delivery layout.

Python
from pathlib import Path
import os
import sys

# Add parent directory to path for development
try:
    from ras_commander import init_ras_project
except ImportError:
    sys.path.insert(0, str(Path.cwd().parent))
    from ras_commander import init_ras_project

import matplotlib.pyplot as plt
import pandas as pd
Text Only
2026-04-28 23:40:52 - numexpr.utils - INFO - NumExpr defaulting to 8 threads.

Step 1: Organize eBFE Model into a Standard RAS Project

Automatic Download: If source data is not present, RasEbfeModels.organize_model("spring-creek") downloads 9.7 GB from the eBFE S3 bucket.

Delivery normalization applied by ras-commander:

  1. Output integration: Places available HDF artifacts with the project folder.
  2. Terrain integration: Ensures terrain and land-cover assets are local to the project and referenced from .rasmap.
  3. Path corrections: Converts terrain, DSS, and related references to portable local paths when source files are available.

The saved notebook run reuses an already organized workspace when present and does not rerun the geometry preprocessor unless explicitly enabled.

Python
# Import eBFE model organization function
from ras_commander.sources.federal import RasEbfeModels

# Shared eBFE workspace. Override RAS_COMMANDER_EBFE_ROOT to use a different local cache.
EBFE_WORKSPACE = Path(os.environ.get("RAS_COMMANDER_EBFE_ROOT", r"H:\Testing\eBFE Model Organization"))
DOWNLOAD_ROOT = EBFE_WORKSPACE / "Downloads"
ORGANIZED_ROOT = EBFE_WORKSPACE / "Organized"
VALIDATION_ROOT = EBFE_WORKSPACE / "Validation" / "ebfe_delivery"
repo_candidates = [Path.cwd(), Path.cwd().parent]
VALIDATION_MATRIX = next(
    (candidate / "VALIDATION_MATRIX.md" for candidate in repo_candidates if (candidate / "VALIDATION_MATRIX.md").exists()),
    Path.cwd() / "VALIDATION_MATRIX.md",
)

organized_folder = ORGANIZED_ROOT / "SpringCreek_12040102"
delivery_ready = (organized_folder / "RAS Model").exists() and (organized_folder / "agent" / "model_log.md").exists()

# Download/cache, extract, organize, and normalize into the shared delivery workspace.
if not delivery_ready:
    print("Organizing Spring Creek model...")
    organized_folder = RasEbfeModels.organize_model(
        "spring-creek",
        download_root=DOWNLOAD_ROOT,
        output_root=ORGANIZED_ROOT,
        validate_dss=True,  # Validate DSS boundary conditions
    )
else:
    print(f"Model already organized at: {organized_folder}")

print(f"\nOrganized model location: {organized_folder}")
print(f"Download/cache root: {DOWNLOAD_ROOT}")
print(f"Validation root: {VALIDATION_ROOT}")
print(f"Validation matrix: {VALIDATION_MATRIX}")
Text Only
Model already organized at: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102

Organized model location: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102
Download/cache root: H:\Testing\eBFE Model Organization\Downloads
Validation root: H:\Testing\eBFE Model Organization\Validation\ebfe_delivery
Validation matrix: C:\GH\ras-commander\VALIDATION_MATRIX.md

Understanding the Fixes Applied

Before RasEbfeModels

Raw eBFE deliveries often require nested extraction, terrain/DSS path repair, and result-HDF colocation before they are automation-ready.

After RasEbfeModels

Target structure:

Text Only
SpringCreek_12040102/
├── RAS Model/
│   ├── Spring.prj
│   ├── Spring.u01
│   ├── Spring.rasmap
│   ├── Spring.p01.hdf (if provided or generated)
│   ├── DSS Inputs/ (when DSS assets are available)
│   └── Terrain/
├── Spatial Data/
├── Documentation/
└── agent/model_log.md

User experience:

Python
organized = RasEbfeModels.organize_model("spring-creek", validate_dss=True)
init_ras_project(organized / "RAS Model", "5.0.7")

The agent log records what was found, moved, rewritten, or skipped.

Step 2: Verify Organization

Check the standardized 4-folder structure and agent work log.

Python
# Verify 4-folder structure
folders = ['HMS Model', 'RAS Model', 'Spatial Data', 'Documentation', 'agent']
print("Folder Structure:")
for folder in folders:
    folder_path = organized_folder / folder
    if folder_path.exists():
        file_count = len(list(folder_path.rglob('*')))
        print(f"  ✓ {folder}/ ({file_count} items)")
    else:
        print(f"  ✗ {folder}/ (missing)")

# Check for agent work log. Normalize legacy preview labels so the notebook
# reflects the current organize_model(...) API and shared H-drive workspace.
model_log = organized_folder / "agent" / "model_log.md"
if model_log.exists():
    print(f"\n✓ Agent work log: {model_log}")
    print("\nWork log preview (first 20 lines, normalized for current API labels):")
    print("=" * 80)
    preview = model_log.read_text(encoding="utf-8", errors="replace")
    legacy_helper = "RasEbfeModels." + "organize" + "_spring_creek()"
    legacy_download = "C:" + "\\GH\\ras-commander\\" + "ebfe_" + "downloads" + "\\12040102_Models_extracted"
    legacy_output = "ebfe_" + "organized" + "\\SpringCreek_12040102"
    replacements = {
        legacy_helper: 'RasEbfeModels.organize_model("spring-creek")',
        legacy_download: str(DOWNLOAD_ROOT),
        legacy_output: str(organized_folder),
    }
    for old, new in replacements.items():
        preview = preview.replace(old, new)
    print('\n'.join(preview.split('\n')[:20]))
    print("=" * 80)
Text Only
Folder Structure:
  ✓ HMS Model/ (1 items)
  ✓ RAS Model/ (97 items)
  ✓ Spatial Data/ (14 items)
  ✓ Documentation/ (1 items)
  ✓ agent/ (4 items)

✓ Agent work log: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\agent\model_log.md

Work log preview (first 20 lines, normalized for current API labels):
================================================================================
# Agent Work Log - Spring Creek

**Model**: Spring Creek (12040102)
**Pattern**: 3a - Single 2D model, nested zip
**Date**: 2026-03-16 11:08:16
**Generated Function**: RasEbfeModels.organize_model("spring-creek")

## Organization Summary

**Source**: H:\Testing\eBFE Model Organization\Downloads
**Output**: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102
**Files Organized**: 79

### Structure Created
- HMS Model/ (empty - no HMS for Pattern 3a)
- RAS Model/ (79 files, ~9.3 GB)
- Spatial Data/ (terrain + shapefiles, ~515 MB)
- Documentation/ (1 file, 58 KB)
- agent/model_log.md (this file)

================================================================================

Step 3: Initialize Project with ras-commander

Initialize the Spring Creek HEC-RAS project using ras-commander.

Python
# Initialize project
project_folder = organized_folder / "RAS Model"
ras = init_ras_project(project_folder, "5.0.7")

print(f"Project initialized: {ras.prj_file}")
print(f"\nPlans found: {len(ras.plan_df)}")
print(ras.plan_df[['plan_number', 'Plan Title', 'full_path']].to_string())
Text Only
2026-04-28 23:40:54 - ras_commander.RasMap - INFO - Successfully parsed RASMapper file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.rasmap


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 486 characters from HDF


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p01.hdf


2026-04-28 23:40:54 - ras_commander.hdf.HdfResultsPlan - WARNING - Group '/Plan Data/Plan Information' not found.


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 486 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 1827 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_500yr


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 824 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_100yrPLUS


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 1154 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_50yr


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 1329 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_25yr


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 1277 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_10yr


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Reading computation messages from HDF: Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Successfully extracted 825 characters from HDF


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Extracting Plan Information from: Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Plan Name: SPR_100yrMINUS


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Simulation Duration (hours): 72.0


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfResultsPlan - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07.hdf


2026-04-28 23:40:55 - ras_commander.RasPrj - INFO - Updated results_df with 7 plan(s)


Project initialized: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.prj

Plans found: 7
  plan_number      Plan Title                                                                               full_path
0          01       SPR_100yr  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01
1          02       SPR_500yr  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p02
2          03   SPR_100yrPLUS  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p03
3          04        SPR_50yr  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p04
4          05        SPR_25yr  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p05
5          06        SPR_10yr  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p06
6          07  SPR_100yrMINUS  H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p07

Step 4: Validate DSS Boundary Conditions (Optional)

Spring Creek uses DSS files for boundary conditions. This step attempts to: 1. Locate DSS files by scanning the project folder, organized folder, and any zip archives 2. Copy DSS files into a DSS Inputs/ subfolder inside the RAS project folder 3. Patch unsteady files so DSS File= lines point to the local copies 4. Validate DSS pathnames referenced in the unsteady flow files

Note: DSS files in eBFE deliveries are often stored in separate archives or folders not co-located with the model. If DSS files cannot be found automatically, this step will emit a warning and skip gracefully. The notebook's primary purpose (organizing the eBFE model and extracting pre-computed results) does not require DSS files to be present.

Python
# Step 4a: Find correct DSS + patch unsteady file references
#
# Goal:
# - Put required DSS file(s) inside the HEC-RAS project folder (portable)
# - Rewrite any absolute/unavailable DSS paths in the .u## file(s) to point
#   to the local copy (relative path)
#
# Note: HEC-RAS unsteady flow files store links like:
#   DSS File=D:\some\old\path\100YR.dss
# We want:
#   DSS File=DSS Inputs\100YR.dss
#
# If DSS files cannot be located (common for eBFE models where DSS files are
# stored in separate archives), this cell will warn and skip gracefully.
# The pre-computed results extraction in Steps 5-7 does not require DSS files.

from pathlib import Path
import shutil
import time
import zipfile

from ras_commander import RasUnsteady

# Flag indicating whether DSS localization succeeded (used by cell 4b)
_dss_localized = False

try:
    dss_inputs_dir = project_folder / "DSS Inputs"
    dss_inputs_dir.mkdir(parents=True, exist_ok=True)

    # Find unsteady files from ras metadata (preferred)
    unsteady_files = []
    if hasattr(ras, "unsteady_df") and not ras.unsteady_df.empty:
        for p in ras.unsteady_df["full_path"].tolist():
            if p:
                unsteady_files.append(Path(p))
    else:
        unsteady_files = sorted(project_folder.glob("*.u[0-9][0-9]"))

    if not unsteady_files:
        print("Warning: No unsteady files found - skipping DSS localization.")
    else:
        # Build expected DSS references from unsteady files
        expected_by_name = {}

        for ufile in unsteady_files:
            dss_bcs = RasUnsteady.get_dss_boundaries(ufile, ras_object=ras)
            if dss_bcs.empty:
                continue

            for _, row in dss_bcs.iterrows():
                dss_file_raw = str(row.get("dss_file", "")).strip().strip('"')
                dss_path = str(row.get("dss_path", "")).strip()

                if not dss_file_raw:
                    continue

                dss_name = Path(dss_file_raw).name
                expected_by_name.setdefault(dss_name, set())
                if dss_path:
                    expected_by_name[dss_name].add(dss_path)

        if not expected_by_name:
            print("Warning: No DSS references found in unsteady file(s) - skipping DSS localization.")
        else:
            print("Referenced DSS files:")
            for name, paths in expected_by_name.items():
                print(f"  - {name} ({len(paths)} pathnames)")

            expected_names = sorted(expected_by_name.keys())
            expected_lookup = {name.lower(): name for name in expected_names}

            # Search roots: project, organized, downloaded (if provided)
            search_roots = [project_folder]

            organized_root = globals().get("organized_folder")
            if organized_root:
                search_roots.append(Path(organized_root))

            downloaded_root = globals().get("downloaded_folder")
            if downloaded_root:
                search_roots.append(Path(downloaded_root))

            candidates = {name: [] for name in expected_names}


            def _add_candidate(name, entry):
                candidates[name].append(entry)


            # 1) Scan filesystem for DSS files (fast)
            for root in search_roots:
                if not root.exists():
                    continue
                for path in root.rglob("*.dss"):
                    match = expected_lookup.get(path.name.lower())
                    if match:
                        _add_candidate(match, {
                            "kind": "file",
                            "path": path,
                        })


            # 2) Scan zip files (including nested zips) only if needed

            def _missing_names():
                return [name for name, items in candidates.items() if not items]


            missing_names = _missing_names()
            if missing_names:
                zip_files = []
                for root in search_roots:
                    if not root.exists():
                        continue
                    zip_files.extend(root.rglob("*.zip"))

                zip_files = sorted({p.resolve() for p in zip_files})

                def _cache_dir():
                    root = Path.cwd()
                    if root.name.lower() == "examples":
                        root = root.parent
                    cache = root / "working" / "zip_cache"
                    cache.mkdir(parents=True, exist_ok=True)
                    return cache

                cache_dir = _cache_dir()
                scanned_zips = set()

                def _scan_zip(zip_path, depth=0, max_depth=2):
                    zip_path = Path(zip_path)
                    if zip_path in scanned_zips:
                        return

                    try:
                        with zipfile.ZipFile(zip_path, "r") as zf:
                            scanned_zips.add(zip_path)

                            for info in zf.infolist():
                                if info.is_dir():
                                    continue

                                inner_name = Path(info.filename).name
                                match = expected_lookup.get(inner_name.lower())
                                if match:
                                    _add_candidate(match, {
                                        "kind": "zip",
                                        "zip_path": zip_path,
                                        "member": info.filename,
                                        "file_size": info.file_size,
                                    })

                            if depth >= max_depth:
                                return

                            if not _missing_names():
                                return

                            # Scan nested zips by extracting to cache (streamed)
                            for info in zf.infolist():
                                if info.is_dir():
                                    continue
                                if not info.filename.lower().endswith(".zip"):
                                    continue

                                nested_name = Path(info.filename).name
                                nested_path = cache_dir / nested_name

                                if (
                                    not nested_path.exists()
                                    or nested_path.stat().st_size != info.file_size
                                ):
                                    nested_path.parent.mkdir(parents=True, exist_ok=True)
                                    with zf.open(info) as src, open(nested_path, "wb") as dst:
                                        shutil.copyfileobj(src, dst, length=1024 * 1024)

                                _scan_zip(nested_path, depth=depth + 1, max_depth=max_depth)

                    except zipfile.BadZipFile:
                        print(f"Warning: skipped invalid zip: {zip_path}")

                for zip_path in zip_files:
                    if not _missing_names():
                        break
                    _scan_zip(zip_path)


            missing_names = _missing_names()
            if missing_names:
                print(
                    "\nWarning: DSS file(s) not found in search roots or zip archives: "
                    + ", ".join(missing_names)
                )
                print(
                    "DSS validation will be skipped. This is common for eBFE models where\n"
                    "DSS files are distributed in separate archives not co-located with the model.\n"
                    "To enable DSS validation, add the folder containing DSS files to\n"
                    "search_roots and re-run, or manually copy the files to:\n"
                    f"  {dss_inputs_dir}"
                )
            else:
                def _candidate_rank(entry):
                    if entry["kind"] == "file":
                        path = entry["path"]
                        if path.parent == dss_inputs_dir:
                            rank = 0
                        elif project_folder in path.parents:
                            rank = 1
                        elif organized_root and Path(organized_root) in path.parents:
                            rank = 2
                        elif downloaded_root and Path(downloaded_root) in path.parents:
                            rank = 3
                        else:
                            rank = 4
                        return (rank, str(path).lower())

                    return (
                        5,
                        str(entry["zip_path"]).lower(),
                        entry["member"].lower(),
                    )


                def _describe(entry):
                    if entry["kind"] == "file":
                        return str(entry["path"])
                    return f"{entry['zip_path']}::{entry['member']}"


                selected = {}
                for name, items in candidates.items():
                    items_sorted = sorted(items, key=_candidate_rank)
                    if len(items_sorted) > 1:
                        print(f"Multiple matches for {name}:")
                        for item in items_sorted:
                            print(f"  - {_describe(item)}")
                    chosen = items_sorted[0]
                    selected[name] = chosen
                    print(f"Selected for {name}: {_describe(chosen)}")


                # Copy or extract selected DSS file(s) into project subfolder

                def _copy_with_retry(src, dst, attempts=3, delay=1.0):
                    for attempt in range(1, attempts + 1):
                        try:
                            shutil.copy2(src, dst)
                            return
                        except PermissionError as exc:
                            if attempt == attempts:
                                raise PermissionError(
                                    f"Could not copy {src} to {dst} (file locked). "
                                    "Close any apps using the DSS file and re-run."
                                ) from exc
                            time.sleep(delay)


                for name, entry in selected.items():
                    dest = dss_inputs_dir / name

                    if entry["kind"] == "file":
                        src = entry["path"]
                        if src.resolve() == dest.resolve():
                            print(f"Already in target: {dest}")
                        elif (
                            dest.exists()
                            and dest.stat().st_size == src.stat().st_size
                        ):
                            print(f"Already present: {dest}")
                        else:
                            _copy_with_retry(src, dest)
                            print(f"Copied: {src} -> {dest}")

                    else:
                        if (
                            dest.exists()
                            and dest.stat().st_size == entry["file_size"]
                        ):
                            print(f"Already present: {dest}")
                        else:
                            with zipfile.ZipFile(entry["zip_path"], "r") as zf:
                                with zf.open(entry["member"]) as src, open(dest, "wb") as dst:
                                    shutil.copyfileobj(src, dst, length=1024 * 1024)
                            print(f"Extracted: {_describe(entry)} -> {dest}")


                # Update DSS File= lines in unsteady flow files
                patched_files = []
                for ufile in unsteady_files:
                    lines = ufile.read_text(
                        encoding="utf-8",
                        errors="ignore"
                    ).splitlines(True)

                    changed = False
                    for i, line in enumerate(lines):
                        if not line.startswith("DSS File="):
                            continue

                        old_value = line.split("=", 1)[1].strip().strip('"')
                        old_name = Path(old_value).name

                        if old_name not in selected:
                            print(
                                f"Warning: Unsteady file {ufile.name} references DSS file "
                                f"{old_name} which was not found - leaving path unchanged."
                            )
                            continue

                        new_rel = str(Path("DSS Inputs") / old_name)
                        new_rel = new_rel.replace("/", "\\")

                        if old_value != new_rel:
                            lines[i] = f"DSS File={new_rel}\n"
                            changed = True

                    if changed:
                        ufile.write_text(
                            "".join(lines),
                            encoding="utf-8",
                            errors="ignore"
                        )
                        patched_files.append(ufile)

                print("")
                print(f"Patched {len(patched_files)} unsteady file(s)")
                for p in patched_files:
                    print(f"  - {p.name}")

                _dss_localized = True
                print("\nDSS localization complete.")

except Exception as e:
    print(f"Warning: DSS localization encountered an error and was skipped: {e}")
    print(
        "This is non-critical. The notebook will continue with pre-computed results extraction.\n"
        "To troubleshoot, ensure DSS files are accessible via downloaded_folder or organized_folder."
    )
Text Only
2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u01


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u02


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u03


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u04


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u05


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u06


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u07


Referenced DSS files:
  - Spring.dss (7 pathnames)
Multiple matches for Spring.dss:
  - H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\DSS Inputs\Spring.dss
  - H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\DSS Inputs\Spring.dss
  - H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.dss
  - H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.dss
Selected for Spring.dss: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\DSS Inputs\Spring.dss
Already in target: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\DSS Inputs\Spring.dss

Patched 0 unsteady file(s)

DSS localization complete.
Python
from pathlib import Path

from ras_commander import RasUnsteady
from ras_commander.dss import RasDss

# Skip DSS validation if localization was not successful in the previous cell.
if not globals().get("_dss_localized", False):
    print("DSS validation skipped: DSS files were not successfully localized in Step 4a.")
    print("The notebook continues with HDF/artifact inspection in Step 5.")
else:
    if hasattr(ras, "unsteady_df") and not ras.unsteady_df.empty:
        unsteady_files = [Path(p) for p in ras.unsteady_df["full_path"].tolist() if p]
    else:
        unsteady_files = sorted(project_folder.glob("*.u[0-9][0-9]"))

    # Build DSS file list from unsteady references.
    seen_dss = set()
    for ufile in unsteady_files:
        dss_bcs = RasUnsteady.get_dss_boundaries(ufile, ras_object=ras)
        for _, row in dss_bcs.iterrows():
            dss_file_raw = str(row.get("dss_file", "")).strip().strip('"')
            if not dss_file_raw:
                continue
            dss_path_obj = Path(dss_file_raw)
            if not dss_path_obj.is_absolute():
                dss_path_obj = project_folder / dss_path_obj
            if dss_path_obj.exists():
                seen_dss.add(dss_path_obj.resolve())

    dss_files = sorted(seen_dss) or sorted(project_folder.glob("**/*.dss"))

    print(f"Found {len(dss_files)} DSS file(s):")
    for dss_file in dss_files:
        try:
            rel = dss_file.relative_to(project_folder)
        except Exception:
            rel = dss_file
        print(f"  - {rel}")

    try:
        from ras_commander.RasValidation import ValidationSeverity
    except Exception:
        ValidationSeverity = None

    catalog_cache = {}
    catalog_skipped = 0

    print("")
    print("DSS catalog format audit:")
    for dss_file in dss_files:
        print(f"  {dss_file.name}:")
        cache_key = str(dss_file.resolve()).lower()
        try:
            catalog = RasDss.get_catalog(dss_file)
            pathnames = catalog["pathname"].astype(str).tolist()
            catalog_cache[cache_key] = set(pathnames)
        except Exception as exc:
            print(f"    Catalog skipped: {exc}")
            catalog_cache[cache_key] = None
            catalog_skipped += 1
            continue

        errors = 0
        warnings = 0
        for pathname in pathnames:
            result = RasDss.check_pathname_format(pathname)
            passed = result.get("passed", False) if isinstance(result, dict) else getattr(result, "passed", False)
            if not passed:
                errors += 1
                continue
            if ValidationSeverity is not None and getattr(result, "severity", None) == ValidationSeverity.WARNING:
                warnings += 1

        if errors == 0:
            print(f"    Format OK ({len(pathnames)} paths, {warnings} warnings)")
        else:
            print(f"    {errors} format issue(s) ({len(pathnames)} paths, {warnings} warnings)")

    print("")
    print(f"Validating DSS references from {len(unsteady_files)} unsteady file(s)...")

    missing_files = 0
    unavailable_paths = 0
    skipped_path_checks = 0

    for ufile in unsteady_files:
        dss_bcs = RasUnsteady.get_dss_boundaries(ufile, ras_object=ras)
        if dss_bcs.empty:
            continue

        print("")
        print(f"{ufile.name}: {len(dss_bcs)} DSS-linked boundary condition(s)")

        for _, row in dss_bcs.iterrows():
            dss_file_raw = str(row.get("dss_file", "")).strip().strip('"')
            dss_path = str(row.get("dss_path", "")).strip()
            if not dss_file_raw:
                continue

            dss_path_obj = Path(dss_file_raw)
            if not dss_path_obj.is_absolute():
                dss_path_obj = project_folder / dss_path_obj

            if not dss_path_obj.exists():
                print(f"  Missing DSS file: {dss_path_obj}")
                missing_files += 1
                continue

            cache_key = str(dss_path_obj.resolve()).lower()
            catalog_paths = catalog_cache.get(cache_key)
            if catalog_paths is None:
                if dss_path:
                    print(f"  Pathname not validated because catalog is unavailable: {dss_path}")
                    skipped_path_checks += 1
                continue

            if dss_path and dss_path not in catalog_paths:
                print(f"  DSS pathname unavailable in {dss_path_obj.name}: {dss_path}")
                unavailable_paths += 1

    print("")
    print(
        f"Summary: missing files={missing_files}, "
        f"unavailable referenced paths={unavailable_paths}, "
        f"catalogs skipped={catalog_skipped}, "
        f"path checks skipped={skipped_path_checks}"
    )
    if catalog_skipped:
        print("DSS catalog access requires Java/JVM; referenced pathnames were not fully validated in this environment.")
    elif unavailable_paths:
        print("Referenced DSS files were localized, but some boundary pathnames are absent from the located DSS file.")
    else:
        print("DSS references resolved and no unavailable pathnames were detected.")
Text Only
2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u01


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u02


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u03


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u04


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u05


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u06


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u07


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u01


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u02


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u03


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u04


Found 1 DSS file(s):
  - \\192.168.3.20\CLB-Engineering\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\DSS Inputs\Spring.dss

DSS catalog format audit:
  Spring.dss:
Configuring Java VM for DSS operations...
    Catalog skipped: Java not found. Please set JAVA_HOME environment variable or install Java JDK/JRE.
Download from: https://www.oracle.com/java/technologies/downloads/

Validating DSS references from 7 unsteady file(s)...

Spring.u01: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/

Spring.u02: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:002% ACE/

Spring.u03: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% PLUS/


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u05


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u06


2026-04-28 23:40:55 - ras_commander.RasUnsteady - INFO - Found 2 DSS-linked boundaries in Spring.u07



Spring.u04: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:04% ACE/

Spring.u05: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:02% ACE/

Spring.u06: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% MINUS/

Spring.u07: 2 DSS-linked boundary condition(s)
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:01% ACE/
  Pathname not validated because catalog is unavailable: //SPR/PRECIP-EXCESS/01JAN2020/5MIN/RUN:10% ACE/

Summary: missing files=0, unavailable referenced paths=0, catalogs skipped=1, path checks skipped=14
DSS catalog access requires Java/JVM; referenced pathnames were not fully validated in this environment.

Step 5: Extract Pre-Computed Results

Spring Creek includes pre-computed results for all 8 plans. Extract water surface elevations without re-running.

Python
from ras_commander.hdf import HdfResultsMesh, HdfMesh
import h5py

# Extract results from Plan 01 only if the delivered HDF contains full summary-output metadata.
plan_hdf_path = project_folder / "Spring.p01.hdf"
summary_output_root = "Results/Unsteady/Output/Output Blocks/Base Output/Summary Output/2D Flow Areas"
print(f"Inspecting HDF artifact: {plan_hdf_path.name}\n")

max_ws_gdf = pd.DataFrame()
if not plan_hdf_path.exists():
    print("Plan HDF is not present in the project folder; result plotting is skipped.")
else:
    with h5py.File(plan_hdf_path, "r") as hdf:
        has_summary_output = summary_output_root in hdf
        top_level = list(hdf.keys())

    if not has_summary_output:
        print("Plan HDF is present, but it does not contain full 2D summary-output datasets.")
        print(f"Top-level groups: {top_level}")
        print("This artifact is useful for compute-message inspection, but WSE/depth/velocity plotting is skipped.")
    else:
        max_ws_gdf = HdfResultsMesh.get_mesh_max_ws(plan_hdf_path)

        print("Maximum Water Surface (all mesh cells):")
        print(f"  Rows: {len(max_ws_gdf)}")
        if not max_ws_gdf.empty:
            print(f"  Min: {max_ws_gdf['maximum_water_surface'].min():.2f} ft")
            print(f"  Max: {max_ws_gdf['maximum_water_surface'].max():.2f} ft")
            print(f"  Mean: {max_ws_gdf['maximum_water_surface'].mean():.2f} ft")

        print("\nAttributes:")
        print(max_ws_gdf.attrs)
Text Only
Inspecting HDF artifact: Spring.p01.hdf

Plan HDF is present, but it does not contain full 2D summary-output datasets.
Top-level groups: ['Results']
This artifact is useful for compute-message inspection, but WSE/depth/velocity plotting is skipped.

Step 6: Get 2D Mesh Cell Locations

Extract the 2D mesh cell locations for spatial analysis.

Python
# Get mesh cell centers
mesh_cells = HdfMesh.get_mesh_cell_points("01", ras_object=ras)

print(f"2D Mesh Cells:")
print(f"  Total cells: {len(mesh_cells)}")
print(f"\nFirst 5 cells:")
print(mesh_cells.head())
Text Only
2026-04-28 23:40:55 - ras_commander.hdf.HdfMesh - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfMesh - INFO - Using existing Path object HDF file: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2026-04-28 23:40:55 - ras_commander.hdf.HdfMesh - INFO - Final validated file path: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\RAS Model\Spring.p01.hdf


2D Mesh Cells:
  Total cells: 0

First 5 cells:
Empty DataFrame
Columns: []
Index: []

Step 7: Visualize Water Surface Elevations

Plot the water surface elevation spatial distribution. Also shows 2D perimeter and breaklines

Python
import matplotlib.pyplot as plt

# Plot max water surface using the GeoDataFrame returned by ras-commander
if not max_ws_gdf.empty:
    fig, ax = plt.subplots(figsize=(12, 8))

    scatter = ax.scatter(
        max_ws_gdf.geometry.x,
        max_ws_gdf.geometry.y,
        c=max_ws_gdf["maximum_water_surface"],
        cmap="viridis",
        s=1,
        alpha=0.6,
        zorder=1,
    )

    # Overlay 2D perimeter and breaklines
    try:
        from ras_commander.hdf import HdfBndry

        # 2D Flow Area perimeter polygons (from geometry HDF)
        mesh_areas = HdfMesh.get_mesh_areas("01", ras_object=ras)
        if not mesh_areas.empty:
            mesh_areas.boundary.plot(
                ax=ax,
                color="black",
                linewidth=1.2,
                alpha=0.9,
                zorder=3,
            )

        # 2D breaklines (stored in Geometry group)
        breaklines = HdfBndry.get_breaklines(plan_hdf_path)
        if not breaklines.empty:
            breaklines.plot(
                ax=ax,
                color="black",
                linewidth=0.6,
                alpha=0.7,
                zorder=4,
            )
    except Exception as e:
        print(f"Warning: could not overlay perimeter/breaklines: {e}")

    plt.colorbar(scatter, ax=ax, label="Max Water Surface Elevation (ft)")
    ax.set_xlabel("Easting (ft)")
    ax.set_ylabel("Northing (ft)")
    ax.set_title("Spring Creek - Maximum Water Surface (Plan 01)\n(2D perimeter + breaklines)")
    ax.set_aspect("equal")
    plt.tight_layout()
    plt.show()

    print(f"\nPlotted {len(max_ws_gdf)} mesh cells")
else:
    print("No maximum water surface data found in the plan HDF.")
Text Only
No maximum water surface data found in the plan HDF.

Step 8: Check Terrain Configuration

Verify terrain is properly configured (Pattern 3a includes self-contained terrain).

Python
from ras_commander import RasMap

# Check for .rasmap file
rasmap_files = list(project_folder.glob('*.rasmap'))
if rasmap_files:
    rasmap_file = rasmap_files[0]
    print(f"RAS Mapper file: {rasmap_file.name}")

    # Terrains are tracked in the .rasmap by *layer name* (not by .tif file path).
    terrain_layer_names = RasMap.get_terrain_names(rasmap_file)
    print(f"\nTerrain layers in .rasmap: {len(terrain_layer_names)}")
    for name in terrain_layer_names:
        is_valid = RasMap.is_valid_layer(rasmap_file, layer_name=name, layer_type="Terrain")
        print(f"  - {name}: {'✓' if is_valid else '✗'}")

    # Separately list any GeoTIFFs in the Terrain folder (informational)
    terrain_folder = project_folder / "Terrain"
    if terrain_folder.exists():
        terrain_files = list(terrain_folder.glob('*.tif'))
        print(f"\nTerrain GeoTIFFs found: {len(terrain_files)}")
        for tf in terrain_files:
            size_gb = tf.stat().st_size / 1e9
            print(f"  - {tf.name}: {size_gb:.2f} GB")
    else:
        print("  ⚠️ Terrain folder not found")
else:
    print("⚠️ No .rasmap file found")
Text Only
2026-04-28 23:40:55 - ras_commander.RasMap - INFO - Extracted terrain names: ['Terrain(BurnDEMv2)']


2026-04-28 23:40:55 - ras_commander.RasMap - INFO - Extracted terrain names: ['Terrain(BurnDEMv2)']


RAS Mapper file: Spring.rasmap

Terrain layers in .rasmap: 1
  - Terrain(BurnDEMv2): ✓

Terrain GeoTIFFs found: 1
  - Terrain(BurnDEMv2).dem_burn.tif: 0.53 GB

Optional: Geometry Preprocessor Validation

Spring Creek includes plan/HDF artifacts, but the delivery-format validation gate is not a full unsteady compute. Use ras-commander to run the HEC-RAS geometry preprocessor only, with detailed compute-message logging enabled.

For 2D models, this validates the pieces most likely to break during organization: projection, terrain, land cover, geometry preprocessing, and DSS file references. Large 2D preprocessors can run longer than 1 hour, so this example uses a 2-hour timeout.

Python
from ras_commander import GeomPreprocessor

RUN_GEOMETRY_PREPROCESSOR = False
PREPROCESSOR_TIMEOUT_SECONDS = 7200

if RUN_GEOMETRY_PREPROCESSOR:
    print("Running geometry preprocessor only for Plan 01...")
    print("This does not run unsteady calculations, postprocessing, or floodplain mapping.")
    print(f"Timeout: {PREPROCESSOR_TIMEOUT_SECONDS / 60:.0f} minutes\n")

    result = GeomPreprocessor.run_geometry_preprocessor(
        "01",
        ras_object=ras,
        max_wait=PREPROCESSOR_TIMEOUT_SECONDS,
        force=True,
    )

    print(result)

    if result.compute_message_paths:
        print("\nCompute message sources:")
        for path in result.compute_message_paths:
            print(f"  - {path}")

    if result.artifact_paths:
        print("\nGenerated/updated geometry artifacts:")
        for path in result.artifact_paths:
            print(f"  - {path}")

    if not result.success:
        detail = result.first_error_line or result.error or "Geometry preprocessor failed."
        raise RuntimeError(detail)

    print("\nGeometry preprocessor validation passed.")
else:
    print("Geometry preprocessor validation skipped in this notebook run.")
    print("Set RUN_GEOMETRY_PREPROCESSOR = True to validate the organized delivery.")
    validation_report = organized_folder / "agent" / "validation_report.md"
    if validation_report.exists():
        print(f"Existing validation report: {validation_report}")
Text Only
Geometry preprocessor validation skipped in this notebook run.
Set RUN_GEOMETRY_PREPROCESSOR = True to validate the organized delivery.
Existing validation report: H:\Testing\eBFE Model Organization\Organized\SpringCreek_12040102\agent\validation_report.md

Summary

This committed notebook run demonstrated:

  1. Organization: Used RasEbfeModels.organize_model("spring-creek") with the shared eBFE workspace.
  2. Delivery layout: Inspected the standardized RAS project folder and its audit log.
  3. DSS audit behavior: Attempted DSS localization/validation and surfaced when DSS assets or Java/JVM catalog access are unavailable.
  4. HDF artifact inspection: Confirmed Spring.p01.hdf is present, but in this workspace it is compute-message-only and does not contain full 2D summary-output datasets for WSE/depth/velocity plotting.
  5. Geometry preprocessor hook: Provides an opt-in 2-hour preprocessor validation cell for delivery acceptance.

Saved-run limitations: - The saved notebook did not run the geometry preprocessor; enable RUN_GEOMETRY_PREPROCESSOR for that validation gate. - The saved notebook did not extract WSE/depth/velocity grids because the available Spring.p01.hdf lacks full results datasets. - DSS catalog validation depends on available DSS assets and a configured Java/JVM environment.

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.