Metadata-Version: 2.4
Name: run-hy8
Version: 2026.10.18.2
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, and circular, box, and catalogue-based elliptical 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

For a normal installation from PyPI:

```powershell
python -m pip install --upgrade run-hy8
```

For local development, install the source checkout with development dependencies:

```powershell
python -m pip install -e ".[dev]"
```

Local builds use `build_package.bat` or `python scripts/build_library.py`, with
wheel verification described in [verified package builds](docs/packaging.md).
`run_tests.bat` runs pytest and accepts additional pytest arguments.

Eligible, successfully validated merges to `main` are published through the
[automated PyPI workflow](docs/pypi_publishing.md). The release process uses a
tag-scoped verified wheel rather than committing distribution artifacts.

## 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.
- Intentional roadway overtopping is supported for constant and irregular roadway profiles. The high-level
  hydraulic helpers default to `RoadwayOvertoppingPolicy.ERROR`, which rejects actual parsed roadway discharge
  unless the caller selects `WARN` or `ALLOW`. Tailwater above a roadway crest alone does not imply actual flow.
- The exact HY-8 submergence law for a user-defined roadway discharge coefficient has not been independently
  reconstructed. The adapter has been compared against HY-8, not independently calibrated as a hydraulic solver.
- Elliptical geometry is limited to version-pinned HY-8 catalogued sizes for supported materials.
- The JSON loader supports fundamental flows, roadway geometry and culvert barrels; more specialised
  HY-8 features such as rating curves and irregular channels remain deferred.

## 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`.
