Metadata-Version: 2.4
Name: run-hy8
Version: 2026.10.18.1
Summary: Windows-only utilities for generating and executing HY-8 projects
Author: Codex scaffolding
License-Expression: SUL-1.0
Keywords: hydraulics,culvert,HY-8,engineering
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Operating System :: Microsoft :: Windows
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.13
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: loguru>=0.7
Requires-Dist: pandas>=2.0
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: pandas-stubs; extra == "dev"
Requires-Dist: pyright; extra == "dev"
Requires-Dist: ruff==0.16.6; extra == "dev"
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: mkdocs; extra == "dev"
Requires-Dist: mkdocs-material; extra == "dev"
Requires-Dist: mkdocstrings[python]; extra == "dev"
Dynamic: license-file

# run-hy8

run hy8 and parse outputs via python

Utilities for assembling HY-8 project files and running the HY-8 executable from Python. The focus is on a
pythonic, extensible object model that can be imported into other scripts, with Windows/Python 3.13 and 3.14 as the supported runtimes.

## Highlights

- Strongly typed dataclasses that describe flows, tailwater, roadway geometry, and culvert barrels.
- Separation between domain objects (`run_hy8.models`), on-disk serialization (`run_hy8.writer`), and process
  orchestration (`run_hy8.executor`).
- Friendly validation helpers that raise actionable errors before the HY-8 binary is invoked. At the moment this
  library intentionally supports a *subset* of HY-8 features (constant tailwater elevation, paved/gravel/user
  roadway surfaces, circle/box culverts). When a configuration requires more advanced HY-8 options we surface a
  clear error that instructs the caller to finish the edit in the HY-8 GUI.
- Small CLI scaffold (`python -m run_hy8`) that can emit a demo file or build projects from
  JSON configs, and a `validate-only` mode to lint configs without writing any files.

## Project Structure

The `run-hy8` project is organized into the following modules:

- **`run_hy8.models`**: Contains the core data classes that represent the HY-8 project structure, including `Hy8Project`, `CulvertCrossing`, `CulvertBarrel`, `FlowDefinition`, `TailwaterDefinition`, and `RoadwayProfile`.
- **`run_hy8.reader`**: Handles the parsing of existing HY-8 project files (`.hy8`) into the object model.
- **`run_hy8.writer`**: Serializes the object model back into HY-8 project files.
- **`run_hy8.executor`**: Provides a wrapper around the HY-8 command-line executable for running simulations.
- **`run_hy8.hydraulics`**: Contains helper functions for running hydraulic scenarios and parsing the results.
- **`run_hy8.results`**: Parses the HY-8 output files (`.rst` and `.rsql`) into a more usable format.
- **`run_hy8.cli`**: Implements the command-line interface for the `run-hy8` package.
- **`run_hy8.config`**: Handles the loading of project configurations from JSON files.
- **`run_hy8.classes_references`**: Contains core data classes and references for `run-hy8`.
- **`run_hy8.type_helpers`**: Contains enums and enum helpers shared between HY-8 domain models.
- **`run_hy8.units`**: Contains unit conversion helpers shared across the `run-hy8` domain.

## Quick Start

```powershell
python -m pip install --user -e .
```

```python
from pathlib import Path
from run_hy8 import (
    Hy8Project,
    CulvertCrossing,
    CulvertBarrel,
    FlowDefinition,
    Hy8FileWriter,
)

project = Hy8Project(title="Sample project", designer="River Engineer")
crossing = CulvertCrossing(name="Crossing 1")
crossing.flow = FlowDefinition(minimum=10.0, design=25.0, maximum=50.0)
crossing.tailwater.constant_elevation = 99.1
crossing.tailwater.invert_elevation = 99.0
crossing.culverts.append(CulvertBarrel(name="Barrel 1", span=1.2, rise=1.2))
project.crossings.append(crossing)

writer = Hy8FileWriter(project)
hy8_file = writer.write(Path("output/sample.hy8"))
print(f"Wrote {hy8_file}")
```

`CulvertBarrel` defaults to a circular corrugated-steel pipe with a thin-edge-projecting inlet. For other
shape/material combinations, select a semantic HY-8 version 8 inlet configuration explicitly:

```python
from run_hy8 import ConcreteBoxInlet, CulvertBarrel, CulvertMaterial, CulvertShape

box = CulvertBarrel(
    name="Box 1",
    shape=CulvertShape.BOX,
    material=CulvertMaterial.CONCRETE,
    span=2.4,
    rise=1.2,
    inlet_configuration=ConcreteBoxInlet.SQUARE_EDGE_30_TO_75_DEG_WINGWALL,
)
```

Elliptical culverts use HY-8's version-pinned, material-specific size catalogues rather than
arbitrary mathematical ellipses. HY-8 8.0.1.2 provides 23 concrete sizes and 40
Steel-or-Aluminum sizes. A requested `span`/`rise` pair must match an entry for the selected
material; unsupported or reversed dimensions fail closed and are never replaced with a nearby size.

```python
from run_hy8 import (
    CulvertBarrel,
    CulvertMaterial,
    CulvertShape,
    EllipticalConcreteInlet,
    EllipticalSteelOrAluminumInlet,
    find_concrete_ellipse_catalogue_size,
    find_steel_or_aluminum_ellipse_catalogue_size,
)

concrete_size = find_concrete_ellipse_catalogue_size(1.524, 0.9652)
concrete_ellipse = CulvertBarrel(
    name="Concrete ellipse",
    shape=CulvertShape.ELLIPTICAL,
    material=CulvertMaterial.CONCRETE,
    span=concrete_size.span_m,
    rise=concrete_size.rise_m,
    outlet_invert_station=20.0,
    inlet_configuration=EllipticalConcreteInlet.SQUARE_EDGE_WITH_HEADWALL,
)

steel_size = find_steel_or_aluminum_ellipse_catalogue_size(
    241.0 * 0.0254,
    156.0 * 0.0254,
)
steel_ellipse = CulvertBarrel(
    name="Steel/aluminum ellipse",
    shape=CulvertShape.ELLIPTICAL,
    material=CulvertMaterial.STEEL_OR_ALUMINUM,
    span=steel_size.span_m,
    rise=steel_size.rise_m,
    outlet_invert_station=20.0,
    inlet_configuration=EllipticalSteelOrAluminumInlet.HEADWALL,
)
```

Catalogue selection supplies HY-8's required `BARRELGEOMETRY` parameters and the
size-specific Manning value. Steel-or-Aluminum catalogue roughness is not constant across all
40 sizes, so the selected catalogue row is authoritative when no explicit override is supplied.

Inlet configurations are separated by HY-8 shape and material:
`CircularConcreteInlet`, `CircularCorrugatedSteelInlet`, `CircularHdpeInlet`,
`ConcreteBoxInlet`, `EllipticalConcreteInlet`, and
`EllipticalSteelOrAluminumInlet`. The package supports HY-8 version 8 project files only.
The older context-free `InletEdgeType` and `InletEdgeType71` inputs are deprecated and emit a
`LegacyInletConfigurationWarning` when translated.

The empirical file-format findings and extension guidance are recorded in
[`docs/hy8_v8_inlet_configurations.md`](docs/hy8_v8_inlet_configurations.md).

Once an `.hy8` file exists you can run HY-8 with `run_hy8.executor.Hy8Executable`. Each high-level action returns
a `CompletedProcess` so scripting layers can inspect stdout/stderr or retry with different parameters.

## TUFLOW integration ownership

TUFLOW-specific culvert ingestion and batch evaluation live in
[ryan-tools](https://github.com/Chain-Frost/ryan-tools), not in this package.

The former `scripts/culvert_demo-from-tuflow.py` and
`scripts/culvert_demo-from-1d_nwk.py` workflows have been migrated downstream in
[ryan-tools PR #100](https://github.com/Chain-Frost/ryan-tools/pull/100), where they
support explicit `hy8` or `ryan-culverts` engine selection. `run-hy8` remains
responsible for generic HY-8 project construction, execution and result parsing.

## Reading existing HY-8 projects

Existing HY-8 files can be parsed back into the same object model via `run_hy8.reader.load_project_from_hy8`. The
[`scripts/gen-hy8-example.py`](scripts/gen-hy8-example.py) helper demonstrates the flow in a standalone script; inline
usage looks like:

```python
from pathlib import Path
from run_hy8 import Hy8FileWriter, load_project_from_hy8

source_path = Path("tests/example_crossings.hy8")
project = load_project_from_hy8(source_path)
project.title = "Round-tripped project"

round_tripped = Hy8FileWriter(project).write(Path("output/round_trip.hy8"))
print(f"Serialized {round_tripped}")
```

This is the same pipeline used in `tests/test_reader.py` to guarantee that we can faithfully read and re-write reference
projects supplied by HY-8.

## Parsing HY-8 outputs

When HY-8 finishes a run it emits `.rst` (culvert summary table) and `.rsql` (flow profile) files next to the project.
`run_hy8.results` contains parsers for both formats plus a `Hy8Results` helper that merges them into easier-to-query
rows:

```python
from pathlib import Path
from run_hy8 import Hy8Results, parse_rst, parse_rsql

crossing = "Crossing 1"
rst_data = parse_rst(Path("output/sample.rst"))
rsql_profiles = parse_rsql(Path("output/sample.rsql"))

series = rst_data.get(crossing, {})
profiles = rsql_profiles.get(crossing, [])
results = Hy8Results(series, profiles)
best_design = results.nearest(target=50.0)
print(f"Design headwater: {best_design.headwater_elevation}")
```

Each result row also retains diagnostics for every culvert in the crossing. The
culvert index is the zero-based value written by HY-8:

```python
for culvert in best_design.culverts:
    print(
        culvert.name,
        culvert.inlet_control_depth,
        culvert.outlet_control_depth,
        culvert.full_length,
        culvert.free_length,
    )
```

HY-8 occasionally appends `*` or `**` to a reported control depth. The numeric
portion remains available as a float and the marker is retained separately in
`inlet_control_depth_qualifier` or `outlet_control_depth_qualifier`. Numeric
values remain in the unit system used by the HY-8 report. See the
[HY-8 control-depth qualifier legend](docs/headwater_helpers.md#hy-8-control-depth-qualifiers)
for the meaning and limitations of `*` and `**`.

These utilities power `scripts/batch_hy8_compare.py` and let you automate regression checks without opening the HY-8 GUI.

## HY-8 executable location

The repository stores its default HY-8 path inside [`HY8_PATH.txt`](HY8_PATH.txt). Update that file if HY-8 is installed
somewhere else—the helpers automatically pick up the new value. `Hy8Executable()` falls back to the path recorded in the
file (or the `HY8_EXE` / `HY8_EXECUTABLE` environment variables) so scripts can simply do `Hy8Executable()` without
passing a path every time. Call `Hy8Executable.configure_default_path(Path(...))` to override the default for the
current process, or `Hy8Executable.persist_default_path(Path(...))` to update both the in-memory default and the
`HY8_PATH.txt` file. All command-line tooling also respects these settings; pass `--run-exe` to override on a
per-invocation basis if needed.

## Configuration via JSON

Intentional roadway studies can select `RoadwayOvertoppingPolicy.WARN` or `ALLOW`
in the hydraulic helpers, or `--roadway-overtopping warn|allow` with CLI execution.
The default `ERROR` checks actual reported roadway discharge after execution.
See [roadway coefficient, profile, policy and validation contracts](docs/roadway_overtopping.md).

For quick scripting, describe your project in JSON and let the CLI write the `.hy8` file. The same schema is used
by the checked-in `sample_project.json`:

```json
{
  "project": {
    "title": "Culvert Replacement",
    "designer": "River Engineer",
    "units": "EN"
  },
  "crossings": [
    {
      "name": "Crossing A",
      "flow": {
        "minimum": 10,
        "design": 25,
        "maximum": 40
      },
      "tailwater": {
        "constant_elevation": 100.5,
        "invert_elevation": 99.0
      },
      "roadway": {
        "shape": "IRREGULAR",
        "width": 40,
        "surface": "paved",
        "stations": [-20, 0, 20],
        "elevations": [102, 101.5, 102]
      },
      "culverts": [
        {
          "name": "Barrel 1",
          "shape": "circle",
          "material": "concrete",
          "inlet_configuration": "square-edge-with-headwall",
          "span": 4.0,
          "rise": 4.0
        }
      ]
    }
  ]
}
```

```powershell
python -m run_hy8 build --config project.json --output output/sample.hy8 --overwrite
```

If you pass `--run-exe path\to\HY864.exe` the CLI will immediately call `-OpenRunSave` after writing the project.

To lint a configuration without touching the filesystem:

```powershell
python -m run_hy8 build --config sample_project.json --output ignored.hy8 --validate-only
```

## Tests

Targeted unit tests live under `tests`. Run them with:

```powershell
python -m pytest tests
```

## Packaging & automation

The repository includes Windows batch helpers so packaging can happen without remembering long commands.

1. `build_package.bat` installs/updates the [`build`](https://pypi.org/project/build/) backend and then runs `python -m build --wheel`, replacing the current wheel under `dist\`.
2. `install_package.bat` installs the most recently built artifact (wheel if present, otherwise the source distribution) via `pip install --force-reinstall`.
3. `run_tests.bat` runs `python -m pytest`. Pass any additional pytest arguments after the script name (for example `run_tests.bat -k culvert`).

Before running tests locally, install the development extras once per virtual environment:

```powershell
pip install -e .[dev]
```

For PyPI release setup and automation, see [publishing](docs/pypi_publishing.md).

## License

This project is licensed under the [Sustainable Use License v1.0](LICENSE)
(`SUL-1.0`).

The software may be used and modified for personal, non-commercial, and internal
business purposes, including commercial professional and consulting work where the
software itself is not provided as a commercial product or service.

Commercial distribution, incorporation into software supplied commercially to third
parties, or provision of the software or its functionality as a paid hosted service,
SaaS, or API is not permitted under the free licence. See the `LICENSE` file for the
governing terms.

## Contributing

Contributions are welcome! If you would like to contribute to the project, please follow the guidelines in [`docs/agents.md`](docs/agents.md). It is recommended to first open an issue to discuss any planned changes.

Before submitting a pull request, please ensure your changes are `pyright`-clean and that all tests pass. To get started with the development environment:

```powershell
pip install -e .[dev]
```

## Development Notes

Coding agents and contributors should follow the conventions in [`docs/agents.md`](docs/agents.md), including the
project-wide expectation for explicit type hints and `pyright`-clean changes.

## Current Limitations

- Constant tailwater elevation only: other HY-8 tailwater definitions are flagged so the user can finish in the GUI.
- Roadway crest protection: if the constant tailwater elevation reaches the roadway elevation we abort with a clear error.
- The JSON loader currently supports HY-8 fundamentals (flow ranges, roadway geometry, culvert barrels). More exotic
  features (rating curves, irregular channels, etc.) are intentionally deferred until the new structure solidifies.

Once these constraints are proven in downstream workflows we can extend the parser/CLI and add regression tests around
validation and serialization behaviors.

## Legacy hy8runner

A separate `hy8runner` implementation lives under `tests/hy8runner` so we can use it for regression comparisons.
It is not published or installed; the supported entry points are the automated regression test
(`tests/test_legacy_regression.py`) and the comparison workflow in `scripts/sample_crossing_compare.py`, both of which
import it via `from .hy8runner.hy8_runner import Hy8Runner`.
